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('
'),i=e.Node.create('').set("innerHTML",M.util.get_string("edittitleinstructions","moodle")),o=e.Node.create('').setAttrs({value:c,autocomplete:"off","aria-describedby":"id_editinstructions",maxLength:"12",size:parseInt(this.get("config").questiondecimalpoints,10)+2});r.appendChild(o),r.setData("anchor",p),a.insert(i,"before"),p.replace(r);var u="left";window.right_to_left()&&(u="right"),s.addClass(n.EDITINGMAXMARK),o.focus().select(),h=o.on("blur",this.edit_maxmark_cancel,this,s,!1),this.editmaxmarkevents.push(h),h=o.on("key",this.edit_maxmark_cancel,"esc",this,s,!0),this.editmaxmarkevents.push(h),h=r.on("submit",this.edit_maxmark_submit,this,s,l),this.editmaxmarkevents.push(h)})},edit_maxmark_submit:function(t,n,i){t.preventDefault();var s=e.Lang.trim(n.one(r.ACTIVITYFORM+" "+r.ACTIVITYMAXMARK).get("value")),o=this.add_spinner(n);this.edit_maxmark_clear(n),n.one(r.INSTANCEMAXMARK).setContent(s);if(s!==null&&s!==""&&s!==i){var u={"class":"resource",field:"updatemaxmark",maxmark:s,id:e.Moodle.mod_quiz.util.slot.getId(n)};this.send_request(u,o,function(e){e.instancemaxmark&&n.one(r.INSTANCEMAXMARK).setContent(e.instancemaxmark)})}},edit_maxmark_cancel:function(e,t,n){n&&e.preventDefault(),this.edit_maxmark_clear(t)},edit_maxmark_clear:function(t){(new e.EventHandle(this.editmaxmarkevents)).detach();var i=t.one(r.ACTIVITYFORM),s=t.one("#id_editinstructions");i&&i.replace(i.getData("anchor")),s&&s.remove(),t.removeClass(n.EDITINGMAXMARK),e.later(100,this,function(){t.one(r.EDITMAXMARK -).focus()}),e.one("input[name=maxmark")||e.one("body").append('')},update_page_break:function(t,n,r,i){t.preventDefault();var s=r.next("li.activity.slot"),o=this.add_spinner(s),u=0,a=i==="removepagebreak"?1:2,f={"class":"resource",field:"updatepagebreak",id:u,value:a};return u=e.Moodle.mod_quiz.util.slot.getId(s),u&&(f.id=Number(u)),this.send_request(f,o,function(t){if(t.slots){if(i==="addpagebreak")e.Moodle.mod_quiz.util.page.add(r);else{var n=r.next(e.Moodle.mod_quiz.util.page.SELECTORS.PAGE);e.Moodle.mod_quiz.util.page.remove(n,!0)}this.reorganise_edit_page()}else window.location.reload(!0)}),this},reorganise_edit_page:function(){e.Moodle.mod_quiz.util.slot.reorderSlots(),e.Moodle.mod_quiz.util.slot.reorderPageBreaks(),e.Moodle.mod_quiz.util.page.reorderPages()},NAME:"mod_quiz-resource-toolbox",ATTRS:{courseid:{value:0},quizid:{value:0}}}),M.mod_quiz.resource_toolbox=null,M.mod_quiz.init_resource_toolbox=function(e){return M.mod_quiz.resource_toolbox=new o(e),M.mod_quiz.resource_toolbox};var u=function(){u.superclass.constructor.apply(this,arguments)};e.extend(u,s,{initializer:function(){M.mod_quiz.quizbase.register_module(this),e.delegate("click",this.toggle_highlight,r.PAGECONTENT,r.SECTIONLI+" "+r.HIGHLIGHT,this),e.delegate("click",this.toggle_hide_section,r.PAGECONTENT,r.SECTIONLI+" "+r.SHOWHIDE,this)},toggle_hide_section:function(t){t.preventDefault();var i=t.target.ancestor(M.mod_quiz.format.get_section_selector(e)),s=t.target.ancestor("a",!0),o=s.one("img"),u,a,f;i.hasClass(n.SECTIONHIDDENCLASS)?(i.removeClass(n.SECTIONHIDDENCLASS),u=1,a="show",f="hide"):(i.addClass(n.SECTIONHIDDENCLASS),u=0,a="hide",f="show");var l=M.util.get_string(f+"fromothers","format_"+this.get("format"));o.setAttrs({alt:l,src:M.util.image_url("i/"+f)}),s.set("title",l);var c={"class":"section",field:"visible",id:e.Moodle.core_course.util.section.getId(i.ancestor(M.mod_quiz.edit.get_section_wrapper(e),!0)),value:u},h=M.util.add_lightbox(e,i);h.show(),this.send_request(c,h,function(t){var n=i.all(r.ACTIVITYLI);n.each(function(n){var i;n.one(r.SHOW)?i=n.one(r.SHOW):i=n.one(r.HIDE);var s=e.Moodle.mod_quiz.util.slot.getId(n);e.Array.indexOf(t.resourcestotoggle,""+s)!==-1&&M.mod_quiz.resource_toolbox.handle_resource_dim(i,n,a)},this)})},toggle_highlight:function(t){t.preventDefault();var n=t.target.ancestor(M.mod_quiz.edit.get_section_selector(e)),i=t.target.ancestor("a",!0),s=i.one("img"),o=n.hasClass("current"),u=0,a=M.util.get_string("markthistopic","moodle");e.one(r.PAGECONTENT).all(M.mod_quiz.edit.get_section_selector(e)+".current "+r.HIGHLIGHT).set("title",a),e.one(r.PAGECONTENT).all(M.mod_quiz.edit.get_section_selector(e)+".current "+r.HIGHLIGHT+" img").set("alt",a).set("src",M.util.image_url("i/marker")),e.one(r.PAGECONTENT).all(M.mod_quiz.edit.get_section_selector(e)).removeClass("current");if(!o){n.addClass("current"),u=e.Moodle.core_course.util.section.getId(n.ancestor(M.mod_quiz.edit.get_section_wrapper(e),!0));var f=M.util.get_string("markedthistopic","moodle");i.set("title",f),s.set("alt",f).set("src",M.util.image_url("i/marked"))}var l={"class":"course",field:"marker",value:u},c=M.util.add_lightbox(e,n);c.show(),this.send_request(l,c)}},{NAME:"mod_quiz-section-toolbox",ATTRS:{courseid:{value:0},quizid:{value:0},format:{value:"topics"}}}),M.mod_quiz.init_section_toolbox=function(e){return new u(e)}},"@VERSION@",{requires:["base","node","event","event-key","io","moodle-mod_quiz-quizbase","moodle-mod_quiz-util-slot","moodle-core-notification-ajaxexception"]}); +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",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",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,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),e.delegate("click",this.handle_data_action,i,r.ACTIVITYACTION,this),e.delegate("click",this.handle_data_action,i,r.DEPENDENCY_LINK,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;case"adddependency":case"removedependency":this.update_dependency(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())})},this),this},edit_maxmark:function(t,i,s){var o=s.one(r.INSTANCEMAXMARK),u=s.one(r.ACTIVITYINSTANCE),a=o.get("firstChild"),f=a.get("data"),l=f,c,h=o,p={"class":"resource",field:"getmaxmark",id:e.Moodle.mod_quiz.util.slot.getId(s)};t.preventDefault(),this.send_request(p,null,function(t){M.core.actionmenu&&M.core.actionmenu.instance&&M.core.actionmenu.instance.hideMenu(),t.instancemaxmark&&(l=t.instancemaxmark);var r=e.Node.create(''),i=e.Node.create('').set("innerHTML",M.util.get_string("edittitleinstructions","moodle")),o=e.Node.create('').setAttrs({value:l,autocomplete:"off","aria-describedby":"id_editinstructions",maxLength:"12",size:parseInt(this.get("config").questiondecimalpoints,10)+2});r.appendChild(o),r.setData("anchor",h),u.insert(i,"before"),h.replace(r);var a="left";window.right_to_left()&&(a="right"),s.addClass(n.EDITINGMAXMARK),o.focus().select(),c=o.on("blur",this.edit_maxmark_cancel,this,s,!1),this.editmaxmarkevents.push(c),c=o.on("key",this.edit_maxmark_cancel,"esc",this,s,!0),this.editmaxmarkevents.push(c),c=r.on("submit",this.edit_maxmark_submit,this,s,f),this.editmaxmarkevents.push(c)})},edit_maxmark_submit:function(t,n,i){t.preventDefault();var s=e.Lang.trim(n.one(r.ACTIVITYFORM+" "+r.ACTIVITYMAXMARK).get("value")),o=this.add_spinner(n);this.edit_maxmark_clear(n),n.one(r.INSTANCEMAXMARK).setContent(s);if(s!==null&&s!==""&&s!==i){var u={"class":"resource",field:"updatemaxmark",maxmark:s,id:e.Moodle.mod_quiz.util.slot.getId(n)};this.send_request(u,o,function(e){e.instancemaxmark&&n.one(r.INSTANCEMAXMARK).setContent(e.instancemaxmark)})}},edit_maxmark_cancel:function(e,t,n){n&&e.preventDefault(),this.edit_maxmark_clear(t)},edit_maxmark_clear:function(t){(new e.EventHandle(this.editmaxmarkevents)).detach();var i=t.one(r.ACTIVITYFORM),s=t.one("#id_editinstructions");i&&i.replace(i.getData("anchor")),s&&s.remove(),t.removeClass(n.EDITINGMAXMARK),e.later(100,this,function(){t.one(r.EDITMAXMARK).focus()}),e.one("input[name=maxmark" +)||e.one("body").append('')},update_page_break:function(t,n,r,i){t.preventDefault();var s=r.next("li.activity.slot"),o=this.add_spinner(s),u=i==="removepagebreak"?1:2,a={"class":"resource",field:"updatepagebreak",id:e.Moodle.mod_quiz.util.slot.getId(s),value:u};return this.send_request(a,o,function(t){if(t.slots){if(i==="addpagebreak")e.Moodle.mod_quiz.util.page.add(r);else{var n=r.next(e.Moodle.mod_quiz.util.page.SELECTORS.PAGE);e.Moodle.mod_quiz.util.page.remove(n,!0)}this.reorganise_edit_page()}}),this},update_dependency:function(t,n,r,i){t.preventDefault();var s=this.add_spinner(r),o={"class":"resource",field:"updatedependency",id:e.Moodle.mod_quiz.util.slot.getId(r),value:i==="adddependency"?1:0};return this.send_request(o,s,function(t){t.hasOwnProperty("requireprevious")&&e.Moodle.mod_quiz.util.slot.updateDependencyIcon(r,t.requireprevious)}),this},reorganise_edit_page:function(){e.Moodle.mod_quiz.util.slot.reorderSlots(),e.Moodle.mod_quiz.util.slot.reorderPageBreaks(),e.Moodle.mod_quiz.util.page.reorderPages(),e.Moodle.mod_quiz.util.slot.updateAllDependencyIcons()},NAME:"mod_quiz-resource-toolbox",ATTRS:{courseid:{value:0},quizid:{value:0}}}),M.mod_quiz.resource_toolbox=null,M.mod_quiz.init_resource_toolbox=function(e){return M.mod_quiz.resource_toolbox=new o(e),M.mod_quiz.resource_toolbox};var u=function(){u.superclass.constructor.apply(this,arguments)};e.extend(u,s,{initializer:function(){M.mod_quiz.quizbase.register_module(this)}},{NAME:"mod_quiz-section-toolbox",ATTRS:{courseid:{value:0},quizid:{value:0}}}),M.mod_quiz.init_section_toolbox=function(e){return new u(e)}},"@VERSION@",{requires:["base","node","event","event-key","io","moodle-mod_quiz-quizbase","moodle-mod_quiz-util-slot","moodle-core-notification-ajaxexception"]}); diff --git a/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes.js b/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes.js index 4bb67ed08bc..df0a58317c3 100644 --- a/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes.js +++ b/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes.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-util-slot/moodle-mod_quiz-util-slot-debug.js b/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot-debug.js index 68436fa94a1..2a35d50b8f6 100644 --- a/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot-debug.js +++ b/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot-debug.js @@ -18,7 +18,8 @@ Y.namespace('Moodle.mod_quiz.util.slot'); Y.Moodle.mod_quiz.util.slot = { CSS: { SLOT : 'slot', - QUESTIONTYPEDESCRIPTION : 'qtype_description' + QUESTIONTYPEDESCRIPTION : 'qtype_description', + CANNOT_DEPEND: 'question_dependency_cannot_depend' }, CONSTANTS: { SLOTIDPREFIX : 'slot-', @@ -32,7 +33,10 @@ Y.Moodle.mod_quiz.util.slot = { PAGEBREAK : 'span.page_split_join_wrapper', ICON : 'img.smallicon', QUESTIONTYPEDESCRIPTION : '.qtype_description', - SECTIONUL : 'ul.section' + SECTIONUL : 'ul.section', + DEPENDENCY_WRAPPER : '.question_dependency_wrapper', + DEPENDENCY_LINK : '.question_dependency_wrapper .cm-edit-action', + DEPENDENCY_ICON : '.question_dependency_wrapper img' }, /** @@ -333,6 +337,65 @@ Y.Moodle.mod_quiz.util.slot = { // Update the anchor. pagebreaklink.set('href', newurl); }, this); + }, + + /** + * Update the dependency icons. + * + * @method updateAllDependencyIcons + * @return void + */ + updateAllDependencyIcons: function() { + // Get list of slot nodes. + var slots = this.getSlots(), + slotnumber = 0, + previousslot = null; + // Loop through slots incrementing the number each time. + slots.each (function(slot) { + slotnumber++; + + if (slotnumber == 1 || previousslot.getData('canfinish') === '0') { + slot.one(this.SELECTORS.DEPENDENCY_WRAPPER).addClass(this.CSS.CANNOT_DEPEND); + } else { + slot.one(this.SELECTORS.DEPENDENCY_WRAPPER).removeClass(this.CSS.CANNOT_DEPEND); + } + this.updateDependencyIcon(slot, null); + + previousslot = slot; + }, this); + }, + + /** + * Update the slot icon to indicate the new requiresprevious state. + * + * @method slot Slot node + * @method requiresprevious Whether this node now requires the previous one. + * @return void + */ + updateDependencyIcon: function(slot, requiresprevious) { + var link = slot.one(this.SELECTORS.DEPENDENCY_LINK); + var icon = slot.one(this.SELECTORS.DEPENDENCY_ICON); + var previousSlot = this.getPrevious(slot); + var a = {thisq: this.getNumber(slot)}; + if (previousSlot) { + a.previousq = this.getNumber(previousSlot); + } + + if (requiresprevious === null) { + requiresprevious = link.getData('action') === 'removedependency'; + } + + if (requiresprevious) { + link.set('title', M.util.get_string('questiondependencyremove', 'quiz', a)); + link.setData('action', 'removedependency'); + icon.set('alt', M.util.get_string('questiondependsonprevious', 'quiz')); + icon.set('src', M.util.image_url('t/locked', 'moodle')); + } else { + link.set('title', M.util.get_string('questiondependencyadd', 'quiz', a)); + link.setData('action', 'adddependency'); + icon.set('alt', M.util.get_string('questiondependencyfree', 'quiz')); + icon.set('src', M.util.image_url('t/unlocked', 'moodle')); + } } }; diff --git a/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot-min.js b/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot-min.js index a090900ce84..c5936edc450 100644 --- a/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot-min.js +++ b/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot-min.js @@ -1 +1 @@ -YUI.add("moodle-mod_quiz-util-slot",function(e,t){e.namespace("Moodle.mod_quiz.util.slot"),e.Moodle.mod_quiz.util.slot={CSS:{SLOT:"slot",QUESTIONTYPEDESCRIPTION:"qtype_description"},CONSTANTS:{SLOTIDPREFIX:"slot-",QUESTION:M.util.get_string("question","moodle")},SELECTORS:{SLOT:"li.slot",INSTANCENAME:".instancename",NUMBER:"span.slotnumber",PAGECONTENT:"div#page-content",PAGEBREAK:"span.page_split_join_wrapper",ICON:"img.smallicon",QUESTIONTYPEDESCRIPTION:".qtype_description",SECTIONUL:"ul.section"},getSlotFromComponent:function(t){return e.one(t).ancestor(this.SELECTORS.SLOT,!0)},getId:function(e){var t=e.get("id").replace(this.CONSTANTS.SLOTIDPREFIX,"");return t=parseInt(t,10),typeof t=="number"&&isFinite(t)?t:!1},getName:function(e){var t=e.one(this.SELECTORS.INSTANCENAME);return t?t.get("firstChild").get("data"):null},getNumber:function(e){if(!e)return!1;var t=e.one(this.SELECTORS.NUMBER).get("text").replace(this.CONSTANTS.QUESTION,"");return t=parseInt(t,10),typeof t=="number"&&isFinite(t)?t:!1},setNumber:function(e,t){var n=e.one(this.SELECTORS.NUMBER);n.setHTML(''+this.CONSTANTS.QUESTION+" "+t)},getSlots:function(){return e.all(this.SELECTORS.PAGECONTENT+" "+this.SELECTORS.SECTIONUL+" "+this.SELECTORS.SLOT)},getNumberedSlots:function(){var t=this.SELECTORS.PAGECONTENT+" "+this.SELECTORS.SECTIONUL;return t+=" "+this.SELECTORS.SLOT+":not("+this.SELECTORS.QUESTIONTYPEDESCRIPTION+")",e.all(t)},getPrevious:function(e){return e.previous(this.SELECTORS.SLOT)},getPreviousNumbered:function(e){return e.previous(this.SELECTORS.SLOT+":not("+this.SELECTORS.QUESTIONTYPEDESCRIPTION+")")},reorderSlots:function(){var t=this.getSlots();t.each(function(t){if(!e.Moodle.mod_quiz.util.page.getPageFromSlot(t)){var n=t.next(e.Moodle.mod_quiz.util.page.SELECTORS.PAGE);t.swap(n)}var r=this.getPreviousNumbered(t),i=0;if(t.hasClass(this.CSS.QUESTIONTYPEDESCRIPTION))return;r&&(i=this.getNumber(r)),this.setNumber(t,i+1)},this)},remove:function(t){var n=e.Moodle.mod_quiz.util.page.getPageFromSlot(t);t.remove();if(!e.Moodle.mod_quiz.util.page.isEmpty(n))return;e.Moodle.mod_quiz.util.page.remove(n)},getPageBreaks:function(){var t=this.SELECTORS.PAGECONTENT+" "+this.SELECTORS.SECTIONUL;return t+=" "+this.SELECTORS.SLOT+this.SELECTORS.PAGEBREAK,e.all(t)},getPageBreak:function(t){return e.one(t).one(this.SELECTORS.PAGEBREAK)},addPageBreak:function(t){var n=M.mod_quiz.resource_toolbox.get("config").addpageiconhtml;n=n.replace("%%SLOT%%",this.getNumber(t));var r=e.Node.create(n);return t.one("div").insert(r,"after"),r},removePageBreak:function(e){var t=this.getPageBreak(e);return t?(t.remove(),!0):!1},reorderPageBreaks:function(){var t=this.getSlots(),n=0;t.each(function(r,i){n++;var s=this.getPageBreak(r);if(!s&&i===t.size()-1)return;!s&&i!==t.size()-1&&(s=this.addPageBreak(r)),s&&i===t.size()-1&&this.removePageBreak(r);var o=s.get("childNodes").item(0),u="",a="";e.Moodle.mod_quiz.util.page.isPage(r.next("li.activity"))?(u="removepagebreak",a="e/remove_page_break"):(u="addpagebreak",a="e/insert_page_break"),o.set("title",M.util.get_string(u,"quiz")),o.setData("action",u);var f=o.one(this.SELECTORS.ICON);f.set("title",M.util.get_string(u,"quiz")),f.set("alt",M.util.get_string(u,"quiz")),f.set("src",M.util.image_url(a));var l=e.QueryString.parse(o.get("href"));l.slot=n;var c="";for(var h in l)c.length&&(c+="&"),c+=h+"="+l[h];o.set("href",c)},this)}}},"@VERSION@",{requires:["node","moodle-mod_quiz-util-base"]}); +YUI.add("moodle-mod_quiz-util-slot",function(e,t){e.namespace("Moodle.mod_quiz.util.slot"),e.Moodle.mod_quiz.util.slot={CSS:{SLOT:"slot",QUESTIONTYPEDESCRIPTION:"qtype_description",CANNOT_DEPEND:"question_dependency_cannot_depend"},CONSTANTS:{SLOTIDPREFIX:"slot-",QUESTION:M.util.get_string("question","moodle")},SELECTORS:{SLOT:"li.slot",INSTANCENAME:".instancename",NUMBER:"span.slotnumber",PAGECONTENT:"div#page-content",PAGEBREAK:"span.page_split_join_wrapper",ICON:"img.smallicon",QUESTIONTYPEDESCRIPTION:".qtype_description",SECTIONUL:"ul.section",DEPENDENCY_WRAPPER:".question_dependency_wrapper",DEPENDENCY_LINK:".question_dependency_wrapper .cm-edit-action",DEPENDENCY_ICON:".question_dependency_wrapper img"},getSlotFromComponent:function(t){return e.one(t).ancestor(this.SELECTORS.SLOT,!0)},getId:function(e){var t=e.get("id").replace(this.CONSTANTS.SLOTIDPREFIX,"");return t=parseInt(t,10),typeof t=="number"&&isFinite(t)?t:!1},getName:function(e){var t=e.one(this.SELECTORS.INSTANCENAME);return t?t.get("firstChild").get("data"):null},getNumber:function(e){if(!e)return!1;var t=e.one(this.SELECTORS.NUMBER).get("text").replace(this.CONSTANTS.QUESTION,"");return t=parseInt(t,10),typeof t=="number"&&isFinite(t)?t:!1},setNumber:function(e,t){var n=e.one(this.SELECTORS.NUMBER);n.setHTML(''+this.CONSTANTS.QUESTION+" "+t)},getSlots:function(){return e.all(this.SELECTORS.PAGECONTENT+" "+this.SELECTORS.SECTIONUL+" "+this.SELECTORS.SLOT)},getNumberedSlots:function(){var t=this.SELECTORS.PAGECONTENT+" "+this.SELECTORS.SECTIONUL;return t+=" "+this.SELECTORS.SLOT+":not("+this.SELECTORS.QUESTIONTYPEDESCRIPTION+")",e.all(t)},getPrevious:function(e){return e.previous(this.SELECTORS.SLOT)},getPreviousNumbered:function(e){return e.previous(this.SELECTORS.SLOT+":not("+this.SELECTORS.QUESTIONTYPEDESCRIPTION+")")},reorderSlots:function(){var t=this.getSlots();t.each(function(t){if(!e.Moodle.mod_quiz.util.page.getPageFromSlot(t)){var n=t.next(e.Moodle.mod_quiz.util.page.SELECTORS.PAGE);t.swap(n)}var r=this.getPreviousNumbered(t),i=0;if(t.hasClass(this.CSS.QUESTIONTYPEDESCRIPTION))return;r&&(i=this.getNumber(r)),this.setNumber(t,i+1)},this)},remove:function(t){var n=e.Moodle.mod_quiz.util.page.getPageFromSlot(t);t.remove();if(!e.Moodle.mod_quiz.util.page.isEmpty(n))return;e.Moodle.mod_quiz.util.page.remove(n)},getPageBreaks:function(){var t=this.SELECTORS.PAGECONTENT+" "+this.SELECTORS.SECTIONUL;return t+=" "+this.SELECTORS.SLOT+this.SELECTORS.PAGEBREAK,e.all(t)},getPageBreak:function(t){return e.one(t).one(this.SELECTORS.PAGEBREAK)},addPageBreak:function(t){var n=M.mod_quiz.resource_toolbox.get("config").addpageiconhtml;n=n.replace("%%SLOT%%",this.getNumber(t));var r=e.Node.create(n);return t.one("div").insert(r,"after"),r},removePageBreak:function(e){var t=this.getPageBreak(e);return t?(t.remove(),!0):!1},reorderPageBreaks:function(){var t=this.getSlots(),n=0;t.each(function(r,i){n++;var s=this.getPageBreak(r);if(!s&&i===t.size()-1)return;!s&&i!==t.size()-1&&(s=this.addPageBreak(r)),s&&i===t.size()-1&&this.removePageBreak(r);var o=s.get("childNodes").item(0),u="",a="";e.Moodle.mod_quiz.util.page.isPage(r.next("li.activity"))?(u="removepagebreak",a="e/remove_page_break"):(u="addpagebreak",a="e/insert_page_break"),o.set("title",M.util.get_string(u,"quiz")),o.setData("action",u);var f=o.one(this.SELECTORS.ICON);f.set("title",M.util.get_string(u,"quiz")),f.set("alt",M.util.get_string(u,"quiz")),f.set("src",M.util.image_url(a));var l=e.QueryString.parse(o.get("href"));l.slot=n;var c="";for(var h in l)c.length&&(c+="&"),c+=h+"="+l[h];o.set("href",c)},this)},updateAllDependencyIcons:function(){var e=this.getSlots(),t=0,n=null;e.each(function(e){t++,t==1||n.getData("canfinish")==="0"?e.one(this.SELECTORS.DEPENDENCY_WRAPPER).addClass(this.CSS.CANNOT_DEPEND):e.one(this.SELECTORS.DEPENDENCY_WRAPPER).removeClass(this.CSS.CANNOT_DEPEND),this.updateDependencyIcon(e,null),n=e},this)},updateDependencyIcon:function(e,t){var n=e.one(this.SELECTORS.DEPENDENCY_LINK),r=e.one(this.SELECTORS.DEPENDENCY_ICON),i=this.getPrevious(e),s={thisq:this.getNumber(e)};i&&(s.previousq=this.getNumber(i)),t===null&&(t=n.getData("action")==="removedependency"),t?(n.set("title",M.util.get_string("questiondependencyremove","quiz",s)),n.setData("action","removedependency"),r.set("alt",M.util.get_string("questiondependsonprevious","quiz")),r.set("src",M.util.image_url("t/locked","moodle"))):(n.set("title",M.util.get_string("questiondependencyadd","quiz",s)),n.setData("action","adddependency"),r.set("alt",M.util.get_string("questiondependencyfree","quiz")),r.set("src",M.util.image_url("t/unlocked","moodle")))}}},"@VERSION@",{requires:["node","moodle-mod_quiz-util-base"]}); diff --git a/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot.js b/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot.js index 68436fa94a1..2a35d50b8f6 100644 --- a/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot.js +++ b/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot.js @@ -18,7 +18,8 @@ Y.namespace('Moodle.mod_quiz.util.slot'); Y.Moodle.mod_quiz.util.slot = { CSS: { SLOT : 'slot', - QUESTIONTYPEDESCRIPTION : 'qtype_description' + QUESTIONTYPEDESCRIPTION : 'qtype_description', + CANNOT_DEPEND: 'question_dependency_cannot_depend' }, CONSTANTS: { SLOTIDPREFIX : 'slot-', @@ -32,7 +33,10 @@ Y.Moodle.mod_quiz.util.slot = { PAGEBREAK : 'span.page_split_join_wrapper', ICON : 'img.smallicon', QUESTIONTYPEDESCRIPTION : '.qtype_description', - SECTIONUL : 'ul.section' + SECTIONUL : 'ul.section', + DEPENDENCY_WRAPPER : '.question_dependency_wrapper', + DEPENDENCY_LINK : '.question_dependency_wrapper .cm-edit-action', + DEPENDENCY_ICON : '.question_dependency_wrapper img' }, /** @@ -333,6 +337,65 @@ Y.Moodle.mod_quiz.util.slot = { // Update the anchor. pagebreaklink.set('href', newurl); }, this); + }, + + /** + * Update the dependency icons. + * + * @method updateAllDependencyIcons + * @return void + */ + updateAllDependencyIcons: function() { + // Get list of slot nodes. + var slots = this.getSlots(), + slotnumber = 0, + previousslot = null; + // Loop through slots incrementing the number each time. + slots.each (function(slot) { + slotnumber++; + + if (slotnumber == 1 || previousslot.getData('canfinish') === '0') { + slot.one(this.SELECTORS.DEPENDENCY_WRAPPER).addClass(this.CSS.CANNOT_DEPEND); + } else { + slot.one(this.SELECTORS.DEPENDENCY_WRAPPER).removeClass(this.CSS.CANNOT_DEPEND); + } + this.updateDependencyIcon(slot, null); + + previousslot = slot; + }, this); + }, + + /** + * Update the slot icon to indicate the new requiresprevious state. + * + * @method slot Slot node + * @method requiresprevious Whether this node now requires the previous one. + * @return void + */ + updateDependencyIcon: function(slot, requiresprevious) { + var link = slot.one(this.SELECTORS.DEPENDENCY_LINK); + var icon = slot.one(this.SELECTORS.DEPENDENCY_ICON); + var previousSlot = this.getPrevious(slot); + var a = {thisq: this.getNumber(slot)}; + if (previousSlot) { + a.previousq = this.getNumber(previousSlot); + } + + if (requiresprevious === null) { + requiresprevious = link.getData('action') === 'removedependency'; + } + + if (requiresprevious) { + link.set('title', M.util.get_string('questiondependencyremove', 'quiz', a)); + link.setData('action', 'removedependency'); + icon.set('alt', M.util.get_string('questiondependsonprevious', 'quiz')); + icon.set('src', M.util.image_url('t/locked', 'moodle')); + } else { + link.set('title', M.util.get_string('questiondependencyadd', 'quiz', a)); + link.setData('action', 'adddependency'); + icon.set('alt', M.util.get_string('questiondependencyfree', 'quiz')); + icon.set('src', M.util.image_url('t/unlocked', 'moodle')); + } } }; diff --git a/mod/quiz/yui/src/toolboxes/js/resource.js b/mod/quiz/yui/src/toolboxes/js/resource.js index bca3e890299..a455a4d21f2 100644 --- a/mod/quiz/yui/src/toolboxes/js/resource.js +++ b/mod/quiz/yui/src/toolboxes/js/resource.js @@ -60,8 +60,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); }, /** @@ -106,6 +106,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; @@ -174,8 +179,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, { if (M.core.actionmenu && M.core.actionmenu.instance) { M.core.actionmenu.instance.hideMenu(); } - } else { - window.location.reload(true); } }); @@ -198,8 +201,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'), @@ -209,7 +211,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. @@ -359,6 +361,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) { @@ -366,21 +369,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') { @@ -390,8 +388,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); } }); @@ -408,6 +437,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', diff --git a/mod/quiz/yui/src/toolboxes/js/section.js b/mod/quiz/yui/src/toolboxes/js/section.js index 19051ebd2c7..0d0bd5cf20b 100644 --- a/mod/quiz/yui/src/toolboxes/js/section.js +++ b/mod/quiz/yui/src/toolboxes/js/section.js @@ -33,134 +33,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', @@ -170,9 +42,6 @@ Y.extend(SECTIONTOOLBOX, TOOLBOX, { }, quizid : { 'value' : 0 - }, - format : { - 'value' : 'topics' } } }); diff --git a/mod/quiz/yui/src/toolboxes/js/toolbox.js b/mod/quiz/yui/src/toolboxes/js/toolbox.js index 7929788e97a..be5eed1482a 100644 --- a/mod/quiz/yui/src/toolboxes/js/toolbox.js +++ b/mod/quiz/yui/src/toolboxes/js/toolbox.js @@ -9,7 +9,7 @@ */ // The CSS classes we use. - var CSS = { +var CSS = { ACTIVITYINSTANCE : 'activityinstance', AVAILABILITYINFODIV : 'div.availabilityinfo', CONTENTWITHOUTLINK : 'contentwithoutlink', @@ -22,7 +22,6 @@ JOIN: 'page_join', MODINDENTCOUNT : 'mod-indent-', MODINDENTHUGE : 'mod-indent-huge', - MODULEIDPREFIX : 'slot-', PAGE: 'page', SECTIONHIDDENCLASS : 'hidden', SECTIONIDPREFIX : 'section-', @@ -36,7 +35,6 @@ 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', @@ -56,7 +54,6 @@ PAGELI : 'li.page', SECTIONUL : 'ul.section', SHOW : 'a.' + CSS.SHOW, - SHOWHIDE : 'a.editing_showhide', SLOTLI : 'li.slot', SUMMARKS : '.mod_quiz_summarks' }, diff --git a/mod/quiz/yui/src/util/js/slot.js b/mod/quiz/yui/src/util/js/slot.js index c969f5d1864..70a5790d0c1 100644 --- a/mod/quiz/yui/src/util/js/slot.js +++ b/mod/quiz/yui/src/util/js/slot.js @@ -16,7 +16,8 @@ Y.namespace('Moodle.mod_quiz.util.slot'); Y.Moodle.mod_quiz.util.slot = { CSS: { SLOT : 'slot', - QUESTIONTYPEDESCRIPTION : 'qtype_description' + QUESTIONTYPEDESCRIPTION : 'qtype_description', + CANNOT_DEPEND: 'question_dependency_cannot_depend' }, CONSTANTS: { SLOTIDPREFIX : 'slot-', @@ -30,7 +31,10 @@ Y.Moodle.mod_quiz.util.slot = { PAGEBREAK : 'span.page_split_join_wrapper', ICON : 'img.smallicon', QUESTIONTYPEDESCRIPTION : '.qtype_description', - SECTIONUL : 'ul.section' + SECTIONUL : 'ul.section', + DEPENDENCY_WRAPPER : '.question_dependency_wrapper', + DEPENDENCY_LINK : '.question_dependency_wrapper .cm-edit-action', + DEPENDENCY_ICON : '.question_dependency_wrapper img' }, /** @@ -331,5 +335,64 @@ Y.Moodle.mod_quiz.util.slot = { // Update the anchor. pagebreaklink.set('href', newurl); }, this); + }, + + /** + * Update the dependency icons. + * + * @method updateAllDependencyIcons + * @return void + */ + updateAllDependencyIcons: function() { + // Get list of slot nodes. + var slots = this.getSlots(), + slotnumber = 0, + previousslot = null; + // Loop through slots incrementing the number each time. + slots.each (function(slot) { + slotnumber++; + + if (slotnumber == 1 || previousslot.getData('canfinish') === '0') { + slot.one(this.SELECTORS.DEPENDENCY_WRAPPER).addClass(this.CSS.CANNOT_DEPEND); + } else { + slot.one(this.SELECTORS.DEPENDENCY_WRAPPER).removeClass(this.CSS.CANNOT_DEPEND); + } + this.updateDependencyIcon(slot, null); + + previousslot = slot; + }, this); + }, + + /** + * Update the slot icon to indicate the new requiresprevious state. + * + * @method slot Slot node + * @method requiresprevious Whether this node now requires the previous one. + * @return void + */ + updateDependencyIcon: function(slot, requiresprevious) { + var link = slot.one(this.SELECTORS.DEPENDENCY_LINK); + var icon = slot.one(this.SELECTORS.DEPENDENCY_ICON); + var previousSlot = this.getPrevious(slot); + var a = {thisq: this.getNumber(slot)}; + if (previousSlot) { + a.previousq = this.getNumber(previousSlot); + } + + if (requiresprevious === null) { + requiresprevious = link.getData('action') === 'removedependency'; + } + + if (requiresprevious) { + link.set('title', M.util.get_string('questiondependencyremove', 'quiz', a)); + link.setData('action', 'removedependency'); + icon.set('alt', M.util.get_string('questiondependsonprevious', 'quiz')); + icon.set('src', M.util.image_url('t/locked', 'moodle')); + } else { + link.set('title', M.util.get_string('questiondependencyadd', 'quiz', a)); + link.setData('action', 'adddependency'); + icon.set('alt', M.util.get_string('questiondependencyfree', 'quiz')); + icon.set('src', M.util.image_url('t/unlocked', 'moodle')); + } } }; diff --git a/question/engine/questionusage.php b/question/engine/questionusage.php index 005a69079fe..c4ce6e90489 100644 --- a/question/engine/questionusage.php +++ b/question/engine/questionusage.php @@ -821,35 +821,6 @@ class question_usage_by_activity { $this->observer->notify_attempt_modified($newqa); } - /** - * Replace a question with a dummy description question in this usage. - * - * @param object $slot - */ - public function replace_question_with_a_description_qtye($slot) { - global $OUTPUT; - // Create a description qtye for the message. - question_bank::load_question_definition_classes('description'); - $q = new qtype_description_question(); - $q->id = $slot->questionid; - $q->name = 'Description'; - $q->questiontext = get_string('questiondependsonprevious', 'quiz'); - $q->generalfeedback = ''; - $q->qtype = question_bank::get_qtype('description'); - $q->options = new question_display_options(); - $q->options->flags = 0; - - $oldqa = $this->get_question_attempt($slot->slot); - $newqa = new question_attempt($q, $oldqa->get_usage_id(), $this->observer, $slot->maxmark); - $newqa->get_question()->options->flags = 1; - - $newqa->set_database_id($oldqa->get_database_id()); - $newqa->set_slot($slot->slot); - $this->questionattempts[$slot->slot] = $newqa; - $this->start_question($slot->slot); - $this->render_question($slot->slot, $q->options); - } - /** * Regrade all the questions in this usage (without changing their max mark). * @param bool $finished whether each question should be forced to be finished