Merge branch 'MDL-40990' of git://github.com/timhunt/moodle
This commit is contained in:
+79
-1
@@ -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'.
|
||||
@@ -1255,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.
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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,9 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
$contexts, $pagevars, $pageurl) {
|
||||
|
||||
$output = '';
|
||||
foreach ($structure->get_questions_in_section($section->id) as $question) {
|
||||
$output .= $this->question_row($structure, $question, $contexts, $pagevars, $pageurl);
|
||||
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'));
|
||||
}
|
||||
|
||||
@@ -353,30 +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 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, $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, $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;
|
||||
}
|
||||
@@ -385,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;
|
||||
@@ -542,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 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, \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.
|
||||
@@ -577,12 +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_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');
|
||||
@@ -594,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')
|
||||
@@ -611,8 +615,7 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
*/
|
||||
public function question_number($number) {
|
||||
if (is_numeric($number)) {
|
||||
$number = html_writer::span(get_string('question'), 'accesshide') .
|
||||
' ' . $number;
|
||||
$number = html_writer::span(get_string('question'), 'accesshide') . ' ' . $number;
|
||||
}
|
||||
return html_writer::tag('span', $number, array('class' => 'slotnumber'));
|
||||
}
|
||||
@@ -648,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);
|
||||
@@ -665,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');
|
||||
@@ -687,14 +690,53 @@ 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)),
|
||||
'page_split_join_wrapper');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the icon for whether this question can only be seen if the previous
|
||||
* one has been answered.
|
||||
*
|
||||
* @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($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('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 (!$structure->can_be_edited()) {
|
||||
$disabled = 'disabled';
|
||||
}
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders html to display a name with the link to the question on a quiz edit page
|
||||
*
|
||||
@@ -702,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));
|
||||
@@ -739,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));
|
||||
@@ -777,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' => '')),
|
||||
@@ -792,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(
|
||||
@@ -858,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,
|
||||
))
|
||||
@@ -892,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,
|
||||
))
|
||||
@@ -902,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);
|
||||
@@ -945,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) {
|
||||
@@ -969,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') . ' ';
|
||||
@@ -995,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);
|
||||
}
|
||||
|
||||
|
||||
+161
-11
@@ -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.
|
||||
@@ -359,7 +498,7 @@ class structure {
|
||||
|
||||
$slots = $DB->get_records_sql("
|
||||
SELECT slot.id AS slotid, slot.slot, slot.questionid, slot.page, slot.maxmark,
|
||||
q.*, qc.contextid
|
||||
slot.requireprevious, q.*, qc.contextid
|
||||
FROM {quiz_slots} slot
|
||||
LEFT JOIN {question} q ON q.id = slot.questionid
|
||||
LEFT JOIN {question_categories} qc ON qc.id = q.category
|
||||
@@ -381,6 +520,7 @@ class structure {
|
||||
$slot->page = $slotdata->page;
|
||||
$slot->questionid = $slotdata->questionid;
|
||||
$slot->maxmark = $slotdata->maxmark;
|
||||
$slot->requireprevious = $slotdata->requireprevious;
|
||||
|
||||
$this->slots[$slot->id] = $slot;
|
||||
$this->slotsinorder[$slot->slot] = $slot;
|
||||
@@ -414,6 +554,7 @@ class structure {
|
||||
$slot->name = get_string('missingquestion', 'quiz');
|
||||
$slot->slot = $slot->slot;
|
||||
$slot->maxmark = 0;
|
||||
$slot->requireprevious = 0;
|
||||
$slot->questiontext = ' ';
|
||||
$slot->questiontextformat = FORMAT_HTML;
|
||||
$slot->length = 1;
|
||||
@@ -451,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;
|
||||
}
|
||||
}
|
||||
@@ -658,6 +798,16 @@ class structure {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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($slotid, $requireprevious) {
|
||||
global $DB;
|
||||
$DB->set_field('quiz_slots', 'requireprevious', $requireprevious, array('id' => $slotid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/Remove a pagebreak.
|
||||
*
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
<FIELD NAME="slot" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Where this question comes in order in the list of questions in this quiz. Like question_attempts.slot."/>
|
||||
<FIELD NAME="quizid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Foreign key references quiz.id."/>
|
||||
<FIELD NAME="page" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The page number that this questions appears on. If the question in slot n appears on page p, then the question in slot n+1 must appear on page p or p+1. Well, except that when a quiz is being created, there may be empty pages, which would cause the page number to jump here."/>
|
||||
<FIELD NAME="requireprevious" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Set to 1 when current question requires previous one to be answered first."/>
|
||||
<FIELD NAME="questionid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Foreign key references question.id."/>
|
||||
<FIELD NAME="maxmark" TYPE="number" LENGTH="12" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="7" COMMENT="How many marks this question contributes to quiz.sumgrades."/>
|
||||
</FIELDS>
|
||||
|
||||
+14
-1
@@ -807,6 +807,19 @@ function xmldb_quiz_upgrade($oldversion) {
|
||||
// Moodle v2.8.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
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, XMLDB_NOTNULL, null, 0, 'page');
|
||||
|
||||
// Conditionally launch add field page.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Quiz savepoint reached.
|
||||
upgrade_mod_savepoint(true, 2015022600, 'quiz');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -615,6 +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['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: <br />{$a->quiznames}<br />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';
|
||||
|
||||
@@ -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++;
|
||||
|
||||
+46
-25
@@ -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,6 +686,31 @@ table.quizreviewsummary td.cell {
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
#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 {
|
||||
display: block;
|
||||
min-height: 1.7em;
|
||||
|
||||
@@ -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."
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+46
-150
@@ -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'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+2
-2
File diff suppressed because one or more lines are too long
+46
-150
@@ -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'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+65
-2
@@ -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'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -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('<span class="accesshide">'+this.CONSTANTS.QUESTION+"</span> "+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('<span class="accesshide">'+this.CONSTANTS.QUESTION+"</span> "+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"]});
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+45
-15
@@ -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',
|
||||
|
||||
-131
@@ -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'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+1
-4
@@ -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'
|
||||
},
|
||||
|
||||
Vendored
+65
-2
@@ -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'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,6 +84,16 @@ abstract class question_behaviour {
|
||||
return substr(get_class($this), 11);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current attempt at this question could be completed just by the
|
||||
* student interacting with the question, before $qa->finish() is called.
|
||||
*
|
||||
* @return boolean whether the attempt can finish naturally.
|
||||
*/
|
||||
public function can_finish_during_attempt() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cause the question to be renderered. This gets the appropriate behaviour
|
||||
* renderer using {@link get_renderer()}, and adjusts the display
|
||||
|
||||
@@ -46,6 +46,10 @@ class qbehaviour_immediatefeedback extends question_behaviour_with_save {
|
||||
return $question instanceof question_automatically_gradable;
|
||||
}
|
||||
|
||||
public function can_finish_during_attempt() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function get_min_fraction() {
|
||||
return $this->question->get_min_fraction();
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ class qbehaviour_interactive extends question_behaviour_with_multiple_tries {
|
||||
return $question instanceof question_automatically_gradable;
|
||||
}
|
||||
|
||||
public function can_finish_during_attempt() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function get_right_answer_summary() {
|
||||
return $this->question->get_right_answer_summary();
|
||||
}
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
This files describes API changes for question behaviour plugins.
|
||||
|
||||
=== 2.9 ===
|
||||
|
||||
1) New method question_behaviour::can_finish_during_attempt. This returns false
|
||||
by default. You should override it if, with your behaviour, questions may
|
||||
finish just through the student interacting with them (e.g. by clicking the
|
||||
Check button within the question.)
|
||||
|
||||
|
||||
=== 2.7 ===
|
||||
|
||||
1) question_behaviour_type has a new method allows_multiple_submitted_responses which defaults to false but should return
|
||||
true if this question behaviour accepts multiple submissions of responses within one attempt eg. multiple tries for the
|
||||
interactive or adaptive question behaviours.
|
||||
question_behaviour has a new method step_has_a_submitted_response($step). For question behaviours where it is not only the
|
||||
final response that is submitted by the student, you need to override this method to return true for other steps where a
|
||||
student has submitted a response. See question_behaviour_with_multiple_tries::step_has_a_submitted_response($step) for
|
||||
example. This method only needs to be overriden if you are returning true from allows_multiple_response_submissions
|
||||
1) question_behaviour_type has a new method allows_multiple_submitted_responses
|
||||
which defaults to false but should return true if this question behaviour
|
||||
accepts multiple submissions of responses within one attempt eg. multiple
|
||||
tries for the interactive or adaptive question behaviours.
|
||||
question_behaviour has a new method step_has_a_submitted_response($step). For
|
||||
question behaviours where it is not only the final response that is
|
||||
submitted by the student, you need to override this method to return true
|
||||
for other steps where a student has submitted a response. See
|
||||
question_behaviour_with_multiple_tries::step_has_a_submitted_response($step)
|
||||
for example. This method only needs to be overriden if you are returning
|
||||
true from allows_multiple_response_submissions.
|
||||
|
||||
|
||||
=== 2.6 ===
|
||||
|
||||
@@ -1163,6 +1163,16 @@ class question_attempt {
|
||||
return $this->rightanswer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this attempt at this question could be completed just by the
|
||||
* student interacting with the question, before {@link finish()} is called.
|
||||
*
|
||||
* @return boolean whether this attempt can finish naturally.
|
||||
*/
|
||||
public function can_finish_during_attempt() {
|
||||
return $this->behaviour->can_finish_during_attempt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the action described by $submitteddata.
|
||||
* @param array $submitteddata the submitted data the determines the action.
|
||||
|
||||
@@ -271,6 +271,17 @@ class question_usage_by_activity {
|
||||
return $this->get_question_attempt($slot)->get_state_class($showcorrectness);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this attempt at a given question could be completed just by the
|
||||
* student interacting with the question, before {@link finish_question()} is called.
|
||||
*
|
||||
* @param int $slot the number used to identify this question within this usage.
|
||||
* @return boolean whether the attempt at the given question can finish naturally.
|
||||
*/
|
||||
public function can_question_finish_during_attempt($slot) {
|
||||
return $this->get_question_attempt($slot)->can_finish_during_attempt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time of the most recent action performed on a question.
|
||||
* @param int $slot the number used to identify this question within this usage.
|
||||
|
||||
Reference in New Issue
Block a user