diff --git a/question/format/xml/tests/xmlformat_test.php b/question/format/xml/tests/xmlformat_test.php index acd73bbfd36..e7c180d7083 100644 --- a/question/format/xml/tests/xmlformat_test.php +++ b/question/format/xml/tests/xmlformat_test.php @@ -1423,7 +1423,7 @@ END; $mc->layout = 0; $mc->single = 1; - $mc->shuffleanswers = 1; + $mc->shuffleanswers = 0; $mc->correctfeedback = array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null); $mc->partiallycorrectfeedback = array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null); $mc->incorrectfeedback = array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null); diff --git a/question/type/multianswer/backup/moodle2/restore_qtype_multianswer_plugin.class.php b/question/type/multianswer/backup/moodle2/restore_qtype_multianswer_plugin.class.php index b8daa034d25..e63b7773194 100644 --- a/question/type/multianswer/backup/moodle2/restore_qtype_multianswer_plugin.class.php +++ b/question/type/multianswer/backup/moodle2/restore_qtype_multianswer_plugin.class.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); - +require_once($CFG->dirroot . '/question/type/multianswer/questiontype.php'); /** * restore plugin class that provides the necessary information * needed to restore one multianswer qtype plugin @@ -109,6 +109,24 @@ class restore_qtype_multianswer_plugin extends restore_qtype_plugin { $sequence = implode(',', $sequencearr); $DB->set_field('question_multianswer', 'sequence', $sequence, array('id' => $rec->id)); + if (!empty($sequence)) { + // Get relevant data indexed by positionkey from the multianswers table. + $wrappedquestions = $DB->get_records_list('question', 'id', + explode(',', $sequence), 'id ASC'); + foreach ($wrappedquestions as $wrapped) { + if ($wrapped->qtype == 'multichoice') { + question_bank::get_qtype($wrapped->qtype)->get_question_options($wrapped); + if (isset($wrapped->options->shuffleanswers)) { + preg_match('/'.ANSWER_REGEX.'/s', $wrapped->questiontext, $answerregs); + if ($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE] != '' ) { + $wrapped->options->shuffleanswers = 0; + $DB->set_field_select('qtype_multichoice_options', 'shuffleanswers', '0', "id =:select", + array('select' => $wrapped->options->id) ); + } + } + } + } + } } $rs->close(); } @@ -163,7 +181,7 @@ class restore_qtype_multianswer_plugin extends restore_qtype_plugin { $subanswer = $pairarr[1]; // Calculate the questionid based on sequenceid. // Note it is already one *new* questionid that doesn't need mapping. - $questionid = $sequencearr[$sequenceid-1]; + $questionid = $sequencearr[$sequenceid - 1]; // Fetch qtype of the question (needed for delegation). $questionqtype = $DB->get_field('question', 'qtype', array('id' => $questionid)); // Delegate subanswer recode to proper qtype, faking one question_states record. diff --git a/question/type/multianswer/db/upgrade.php b/question/type/multianswer/db/upgrade.php index 3dfc2a0b3e1..1b58ec70e0d 100644 --- a/question/type/multianswer/db/upgrade.php +++ b/question/type/multianswer/db/upgrade.php @@ -26,6 +26,7 @@ defined('MOODLE_INTERNAL') || die(); +require_once($CFG->dirroot . '/question/type/multianswer/questiontype.php'); /** * Upgrade code for the multi-answer question type. @@ -61,5 +62,40 @@ function xmldb_qtype_multianswer_upgrade($oldversion) { // Moodle v2.9.0 release upgrade line. // Put any upgrade step following this. + if ($oldversion < 2015100201) { + $rs = $DB->get_recordset_sql("SELECT q.id, q.category, qma.sequence + FROM {question} q + JOIN {question_multianswer} qma ON q.id = qma.question"); + foreach ($rs as $q) { + if (!empty($q->sequence)) { + // Get relevant data indexed by positionkey from the multianswers table. + $wrappedquestions = $DB->get_records_list('question', 'id', + explode(',', $q->sequence), 'id ASC'); + foreach ($wrappedquestions as $wrapped) { + if ($wrapped->qtype == 'multichoice') { + $options = $DB->get_record('qtype_multichoice_options', array('questionid' => $wrapped->id), '*'); + if (isset($options->shuffleanswers)) { + preg_match('/'.ANSWER_REGEX.'/s', $wrapped->questiontext, $answerregs); + if ($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE] != '' ) { + $DB->set_field('qtype_multichoice_options', 'shuffleanswers', '0', + array('id' => $options->id) ); + } + } else { + $newrecord = new stdClass(); + $newrecord->questionid = $wrapped->id; + $newrecord->correctfeedback = ''; + $newrecord->partiallycorrectfeedback = ''; + $newrecord->incorrectfeedback = ''; + $DB->insert_record('qtype_multichoice_options', $newrecord); + } + } + } + } + } + $rs->close(); + // Multianswer savepoint reached. + upgrade_plugin_savepoint(true, 2015100201, 'qtype', 'multianswer'); + } + return true; } diff --git a/question/type/multianswer/edit_multianswer_form.php b/question/type/multianswer/edit_multianswer_form.php index 8f206dfcf1a..4aa28b2029f 100644 --- a/question/type/multianswer/edit_multianswer_form.php +++ b/question/type/multianswer/edit_multianswer_form.php @@ -187,6 +187,8 @@ class qtype_multianswer_edit_form extends question_edit_form { if ($this->questiondisplay->options->questions[$sub]->qtype == 'multichoice') { $mform->addElement('static', 'sub_'.$sub.'_layout', get_string('layout', 'qtype_multianswer')); + $mform->addElement('static', 'sub_'.$sub.'_shuffleanswers', + get_string('shuffleanswers', 'qtype_multichoice')); } foreach ($this->questiondisplay->options->questions[$sub]->answer as $key => $ans) { @@ -362,6 +364,11 @@ class qtype_multianswer_edit_form extends question_edit_form { $defaultvalues[$prefix.'layout'] = get_string('layoutundefined', 'qtype_multianswer'); } + if ($subquestion->shuffleanswers ) { + $defaultvalues[$prefix.'shuffleanswers'] = get_string('yes', 'moodle'); + } else { + $defaultvalues[$prefix.'shuffleanswers'] = get_string('no', 'moodle'); + } } foreach ($subquestion->answer as $key => $answer) { if ($subquestion->qtype == 'numerical' && $key == 0) { @@ -458,12 +465,7 @@ class qtype_multianswer_edit_form extends question_edit_form { $answercount = 0; $maxgrade = false; $maxfraction = -1; - if (isset($this->savedquestiondisplay->options->questions[$sub]->qtype) && - $this->savedquestiondisplay->options->questions[$sub]->qtype != - $questiondisplay->options->questions[$sub]->qtype) { - $storemess = " STORED QTYPE ".question_bank::get_qtype_name( - $this->savedquestiondisplay->options->questions[$sub]->qtype); - } + foreach ($subquestion->answer as $key => $answer) { if (is_array($answer)) { $answer = $answer['text']; diff --git a/question/type/multianswer/questiontype.php b/question/type/multianswer/questiontype.php index 3924ff2463e..697c0f65217 100644 --- a/question/type/multianswer/questiontype.php +++ b/question/type/multianswer/questiontype.php @@ -71,7 +71,6 @@ class qtype_multianswer extends question_type { $wrapped->maxmark = $wrapped->defaultmark; $question->options->questions[$sequence[$wrapped->id]] = $wrapped; } - $question->hints = $DB->get_records('question_hints', array('questionid' => $question->id), 'id ASC'); @@ -206,11 +205,15 @@ class qtype_multianswer extends question_type { $question->textfragments[$i] = array_shift($bits); $i += 1; } - foreach ($questiondata->options->questions as $key => $subqdata) { $subqdata->contextid = $questiondata->contextid; - $subqdata->options->shuffleanswers = !isset($questiondata->options->shuffleanswers) || - $questiondata->options->shuffleanswers; + if ($subqdata->qtype == 'multichoice') { + $answerregs = array(); + if ($subqdata->options->shuffleanswers == 1 && isset($questiondata->options->shuffleanswers) + && $questiondata->options->shuffleanswers == 0 ) { + $subqdata->options->shuffleanswers = 0; + } + } $question->subquestions[$key] = question_bank::make_question($subqdata); $question->subquestions[$key]->maxmark = $subqdata->defaultmark; if (isset($subqdata->options->layout)) { @@ -275,7 +278,8 @@ define('NUMERICAL_ABS_ERROR_MARGIN', 6); // Remaining ANSWER regexes. define('ANSWER_TYPE_DEF_REGEX', '(NUMERICAL|NM)|(MULTICHOICE|MC)|(MULTICHOICE_V|MCV)|(MULTICHOICE_H|MCH)|' . - '(SHORTANSWER|SA|MW)|(SHORTANSWER_C|SAC|MWC)'); + '(SHORTANSWER|SA|MW)|(SHORTANSWER_C|SAC|MWC)|' . + '(MULTICHOICE_S|MCS)|(MULTICHOICE_VS|MCVS)|(MULTICHOICE_HS|MCHS)'); define('ANSWER_START_REGEX', '\{([0-9]*):(' . ANSWER_TYPE_DEF_REGEX . '):'); @@ -294,7 +298,32 @@ define('ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_REGULAR', 5); define('ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_HORIZONTAL', 6); define('ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER', 7); define('ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER_C', 8); -define('ANSWER_REGEX_ALTERNATIVES', 9); +define('ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_SHUFFLED', 9); +define('ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_REGULAR_SHUFFLED', 10); +define('ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_HORIZONTAL_SHUFFLED', 11); +define('ANSWER_REGEX_ALTERNATIVES', 12); + +/** + * Initialise subquestion fields that are constant across all MULTICHOICE + * types. + * + * @param objet $wrapped The subquestion to initialise + * + */ +function qtype_multianswer_initialise_multichoice_subquestion($wrapped) { + $wrapped->qtype = 'multichoice'; + $wrapped->single = 1; + $wrapped->answernumbering = 0; + $wrapped->correctfeedback['text'] = ''; + $wrapped->correctfeedback['format'] = FORMAT_HTML; + $wrapped->correctfeedback['itemid'] = ''; + $wrapped->partiallycorrectfeedback['text'] = ''; + $wrapped->partiallycorrectfeedback['format'] = FORMAT_HTML; + $wrapped->partiallycorrectfeedback['itemid'] = ''; + $wrapped->incorrectfeedback['text'] = ''; + $wrapped->incorrectfeedback['format'] = FORMAT_HTML; + $wrapped->incorrectfeedback['itemid'] = ''; +} function qtype_multianswer_extract_question($text) { // Variable $text is an array [text][format][itemid]. @@ -335,49 +364,28 @@ function qtype_multianswer_extract_question($text) { $wrapped->qtype = 'shortanswer'; $wrapped->usecase = 1; } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE])) { - $wrapped->qtype = 'multichoice'; - $wrapped->single = 1; + qtype_multianswer_initialise_multichoice_subquestion($wrapped); + $wrapped->shuffleanswers = 0; + $wrapped->layout = qtype_multichoice_base::LAYOUT_DROPDOWN; + } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_SHUFFLED])) { + qtype_multianswer_initialise_multichoice_subquestion($wrapped); $wrapped->shuffleanswers = 1; - $wrapped->answernumbering = 0; - $wrapped->correctfeedback['text'] = ''; - $wrapped->correctfeedback['format'] = FORMAT_HTML; - $wrapped->correctfeedback['itemid'] = ''; - $wrapped->partiallycorrectfeedback['text'] = ''; - $wrapped->partiallycorrectfeedback['format'] = FORMAT_HTML; - $wrapped->partiallycorrectfeedback['itemid'] = ''; - $wrapped->incorrectfeedback['text'] = ''; - $wrapped->incorrectfeedback['format'] = FORMAT_HTML; - $wrapped->incorrectfeedback['itemid'] = ''; $wrapped->layout = qtype_multichoice_base::LAYOUT_DROPDOWN; } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_REGULAR])) { - $wrapped->qtype = 'multichoice'; - $wrapped->single = 1; + qtype_multianswer_initialise_multichoice_subquestion($wrapped); $wrapped->shuffleanswers = 0; - $wrapped->answernumbering = 0; - $wrapped->correctfeedback['text'] = ''; - $wrapped->correctfeedback['format'] = FORMAT_HTML; - $wrapped->correctfeedback['itemid'] = ''; - $wrapped->partiallycorrectfeedback['text'] = ''; - $wrapped->partiallycorrectfeedback['format'] = FORMAT_HTML; - $wrapped->partiallycorrectfeedback['itemid'] = ''; - $wrapped->incorrectfeedback['text'] = ''; - $wrapped->incorrectfeedback['format'] = FORMAT_HTML; - $wrapped->incorrectfeedback['itemid'] = ''; + $wrapped->layout = qtype_multichoice_base::LAYOUT_VERTICAL; + } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_REGULAR_SHUFFLED])) { + qtype_multianswer_initialise_multichoice_subquestion($wrapped); + $wrapped->shuffleanswers = 1; $wrapped->layout = qtype_multichoice_base::LAYOUT_VERTICAL; } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_HORIZONTAL])) { - $wrapped->qtype = 'multichoice'; - $wrapped->single = 1; + qtype_multianswer_initialise_multichoice_subquestion($wrapped); $wrapped->shuffleanswers = 0; - $wrapped->answernumbering = 0; - $wrapped->correctfeedback['text'] = ''; - $wrapped->correctfeedback['format'] = FORMAT_HTML; - $wrapped->correctfeedback['itemid'] = ''; - $wrapped->partiallycorrectfeedback['text'] = ''; - $wrapped->partiallycorrectfeedback['format'] = FORMAT_HTML; - $wrapped->partiallycorrectfeedback['itemid'] = ''; - $wrapped->incorrectfeedback['text'] = ''; - $wrapped->incorrectfeedback['format'] = FORMAT_HTML; - $wrapped->incorrectfeedback['itemid'] = ''; + $wrapped->layout = qtype_multichoice_base::LAYOUT_HORIZONTAL; + } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_HORIZONTAL_SHUFFLED])) { + qtype_multianswer_initialise_multichoice_subquestion($wrapped); + $wrapped->shuffleanswers = 1; $wrapped->layout = qtype_multichoice_base::LAYOUT_HORIZONTAL; } else { print_error('unknownquestiontype', 'question', '', $answerregs[2]); diff --git a/question/type/multianswer/renderer.php b/question/type/multianswer/renderer.php index 1e1639813a6..baa76cee1da 100644 --- a/question/type/multianswer/renderer.php +++ b/question/type/multianswer/renderer.php @@ -153,7 +153,7 @@ abstract class qtype_multianswer_subq_renderer_base extends qtype_renderer { && (!is_null($fraction) || $feedback)) { $a = new stdClass(); $a->mark = format_float($fraction * $subq->maxmark, $options->markdp); - $a->max = format_float($subq->maxmark, $options->markdp); + $a->max = format_float($subq->maxmark, $options->markdp); $feedback[] = get_string('markoutofmax', 'question', $a); } @@ -205,7 +205,7 @@ class qtype_multianswer_textfield_renderer extends qtype_multianswer_subq_render foreach ($subq->answers as $ans) { $size = max($size, core_text::strlen(trim($ans->answer))); } - $size = min(60, round($size + rand(0, $size*0.15))); + $size = min(60, round($size + rand(0, $size * 0.15))); // The rand bit is to make guessing harder. $inputattributes = array( @@ -389,7 +389,7 @@ class qtype_multianswer_multichoice_vertical_renderer extends qtype_multianswer_ $subq->maxmark > 0) { $a = new stdClass(); $a->mark = format_float($fraction * $subq->maxmark, $options->markdp); - $a->max = format_float($subq->maxmark, $options->markdp); + $a->max = format_float($subq->maxmark, $options->markdp); $feedback[] = html_writer::tag('div', get_string('markoutofmax', 'question', $a)); } diff --git a/question/type/multianswer/tests/helper.php b/question/type/multianswer/tests/helper.php index 7c40776ee01..1ba3408e5ce 100644 --- a/question/type/multianswer/tests/helper.php +++ b/question/type/multianswer/tests/helper.php @@ -91,7 +91,7 @@ class qtype_multianswer_test_helper extends question_test_helper { $mc->generalfeedback = ''; $mc->generalfeedbackformat = FORMAT_HTML; - $mc->shuffleanswers = 1; + $mc->shuffleanswers = 0; $mc->answernumbering = 'none'; $mc->layout = qtype_multichoice_base::LAYOUT_DROPDOWN; @@ -163,7 +163,7 @@ class qtype_multianswer_test_helper extends question_test_helper { $mc->options = new stdClass(); $mc->options->layout = 0; $mc->options->single = 1; - $mc->options->shuffleanswers = 1; + $mc->options->shuffleanswers = 0; $mc->options->correctfeedback = ''; $mc->options->correctfeedbackformat = 1; $mc->options->partiallycorrectfeedback = ''; @@ -227,7 +227,7 @@ class qtype_multianswer_test_helper extends question_test_helper { $mc->options = new stdClass(); $mc->options->layout = 0; $mc->options->single = 1; - $mc->options->shuffleanswers = 1; + $mc->options->shuffleanswers = 0; $mc->options->correctfeedback = ''; $mc->options->correctfeedbackformat = 1; $mc->options->partiallycorrectfeedback = ''; @@ -312,9 +312,8 @@ class qtype_multianswer_test_helper extends question_test_helper { 3 => array('qt' => '{1:MULTICHOICE:=California#OK~Arizona#Wrong}', 'California' => 'OK', 'Arizona' => 'Wrong'), 4 => array('qt' => '{1:MULTICHOICE:%0%California#Wrong~=Arizona#OK}', 'California' => 'Wrong', 'Arizona' => 'OK'), ); - foreach ($subqdata as $i => $data) { - // Multiple-choice subquestion. + // Multiple-choice subquestion. question_bank::load_question_definition_classes('multichoice'); $mc = new qtype_multichoice_single_question(); test_question_maker::initialise_a_question($mc); @@ -333,7 +332,7 @@ class qtype_multianswer_test_helper extends question_test_helper { 10 * $i => new question_answer(13, 'California', (float) ($data['California'] == 'OK'), $data['California'], FORMAT_HTML), 10 * $i + 1 => new question_answer(14, 'Arizona', (float) ($data['Arizona'] == 'OK'), - $data['Arizona'], FORMAT_HTML), + $data['Arizona'], FORMAT_HTML), ); $mc->qtype = question_bank::get_qtype('multichoice'); $mc->maxmark = 1; @@ -387,4 +386,5 @@ class qtype_multianswer_test_helper extends question_test_helper { return $q; } + } diff --git a/question/type/multianswer/tests/question_test.php b/question/type/multianswer/tests/question_test.php index 21f8beefcd0..9390ad2bced 100644 --- a/question/type/multianswer/tests/question_test.php +++ b/question/type/multianswer/tests/question_test.php @@ -191,7 +191,7 @@ class qtype_multianswer_question_test extends advanced_testcase { // Compute right and wrong response for subquestion 2. $rightchoice = $question->subquestions[2]->get_correct_response(); $right = reset($rightchoice); - $wrong = ($right +1) % 3; + $wrong = ($right + 1) % 3; // Get subquestion 1 right at 2nd try and subquestion 2 right at 3rd try. $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $wrong), @@ -199,7 +199,7 @@ class qtype_multianswer_question_test extends advanced_testcase { 2 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right), ); $finalgrade = $question->compute_final_grade($responses, 1); - $this->assertEquals(1/3*(1 - 0.2) + 2/3*(1 - 2*0.2), $finalgrade); + $this->assertEquals(1 / 3 * (1 - 0.2) + 2 / 3 * (1 - 2 * 0.2), $finalgrade); // Get subquestion 1 right at 3rd try and subquestion 2 right at 2nd try. $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $wrong), @@ -208,7 +208,7 @@ class qtype_multianswer_question_test extends advanced_testcase { 3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right), ); $finalgrade = $question->compute_final_grade($responses, 1); - $this->assertEquals(1/3*(1 - 2*0.2) + 2/3*(1 - 0.2), $finalgrade); + $this->assertEquals(1 / 3 * (1 - 2 * 0.2) + 2 / 3 * (1 - 0.2), $finalgrade); // Get subquestion 1 right at 4th try and subquestion 2 right at 1st try. $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $right), @@ -217,7 +217,7 @@ class qtype_multianswer_question_test extends advanced_testcase { 3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right), ); $finalgrade = $question->compute_final_grade($responses, 1); - $this->assertEquals(1/3*(1 - 3*0.2) + 2/3, $finalgrade); + $this->assertEquals(1 / 3 * (1 - 3 * 0.2) + 2 / 3, $finalgrade); // Get subquestion 1 right at 4th try and subquestion 2 right 3rd try. // Subquestion 2 was right at 1st try, but last change is at 3rd try. @@ -227,7 +227,7 @@ class qtype_multianswer_question_test extends advanced_testcase { 3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right), ); $finalgrade = $question->compute_final_grade($responses, 1); - $this->assertEquals(1/3*(1 - 3*0.2) + 2/3*(1 - 2*0.2), $finalgrade); + $this->assertEquals(1 / 3 * (1 - 3 * 0.2) + 2 / 3 * (1 - 2 * 0.2), $finalgrade); // Incomplete responses. Subquestion 1 is right at 4th try and subquestion 2 at 3rd try. $responses = array(0 => array('sub1_answer' => 'Dog'), @@ -236,6 +236,6 @@ class qtype_multianswer_question_test extends advanced_testcase { 3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right), ); $finalgrade = $question->compute_final_grade($responses, 1); - $this->assertEquals(1/3*(1 - 3*0.2) + 2/3*(1 - 2*0.2), $finalgrade); + $this->assertEquals(1 / 3 * (1 - 3 * 0.2) + 2 / 3 * (1 - 2 * 0.2), $finalgrade); } } diff --git a/question/type/multianswer/tests/questiontype_test.php b/question/type/multianswer/tests/questiontype_test.php index 241742d96ac..ef93d14b94f 100644 --- a/question/type/multianswer/tests/questiontype_test.php +++ b/question/type/multianswer/tests/questiontype_test.php @@ -187,4 +187,47 @@ class qtype_multianswer_test extends advanced_testcase { } } } + /** + * Verify that the multiplechoice variants parameters are correctly interpreted from + * the question text + * + * + */ + public function test_questiontext_extraction_of_multiplechoice_subquestions_variants() { + $questiontext = array(); + $questiontext['format'] = FORMAT_HTML; + $questiontext['itemid'] = ''; + $questiontext['text'] = '
Match the following cities with the correct state:
+