public function test_import_match() { $xml = $this->make_test_xml(); $importer = new qformat_blackboard_six(); $questions = $importer->readquestions($xml); $q = $questions[5]; // If qtype_ddmatch is installed, the formatter produces ddmatch // qtypes, not match ones. $ddmatchisinstalled = question_bank::is_qtype_installed('ddmatch'); $expectedq = new stdClass(); $expectedq->qtype = $ddmatchisinstalled ? 'ddmatch' : 'match'; $expectedq->name = 'Classify the animals.'; $expectedq->questiontext = '<i>Classify the animals.</i>'; $expectedq->questiontextformat = FORMAT_HTML; $expectedq->correctfeedback = array('text' => '', 'format' => FORMAT_HTML); $expectedq->partiallycorrectfeedback = array('text' => '', 'format' => FORMAT_HTML); $expectedq->incorrectfeedback = array('text' => '', 'format' => FORMAT_HTML); $expectedq->generalfeedback = ''; $expectedq->generalfeedbackformat = FORMAT_HTML; $expectedq->defaultmark = 1; $expectedq->length = 1; $expectedq->penalty = 0.3333333; $expectedq->shuffleanswers = get_config('quiz', 'shuffleanswers'); $expectedq->subquestions = array(array('text' => 'cat', 'format' => FORMAT_HTML), array('text' => '', 'format' => FORMAT_HTML), array('text' => 'frog', 'format' => FORMAT_HTML), array('text' => 'newt', 'format' => FORMAT_HTML)); if ($ddmatchisinstalled) { $expectedq->subanswers = array(array('text' => 'mammal', 'format' => FORMAT_HTML), array('text' => 'insect', 'format' => FORMAT_HTML), array('text' => 'amphibian', 'format' => FORMAT_HTML), array('text' => 'amphibian', 'format' => FORMAT_HTML)); } else { $expectedq->subanswers = array('mammal', 'insect', 'amphibian', 'amphibian'); } $this->assert(new question_check_specified_fields_expectation($expectedq), $q); }
/** * Private function to factor common code out of get_question_options(). * * @param object $question the question to tidy. * @param boolean $loadtags load the question tags from the tags table. Optional, default false. */ function _tidy_question($question, $loadtags = false) { global $CFG; // Load question-type specific fields. if (!question_bank::is_qtype_installed($question->qtype)) { $question->questiontext = html_writer::tag('p', get_string('warningmissingtype', 'qtype_missingtype')) . $question->questiontext; } question_bank::get_qtype($question->qtype)->get_question_options($question); // Convert numeric fields to float. (Prevents these being displayed as 1.0000000.) $question->defaultmark += 0; $question->penalty += 0; if (isset($question->_partiallyloaded)) { unset($question->_partiallyloaded); } if ($loadtags && !empty($CFG->usetags)) { require_once $CFG->dirroot . '/tag/lib.php'; $question->tags = tag_get_tags_array('question', $question->id); } }
/** * Process Matching Questions * Parse a matching rawquestion and add the result * to the array of questions already parsed. * @param object $quest rawquestion * @param array $questions array of Moodle questions already done. */ public function process_matching($quest, &$questions) { // Blackboard matching questions can't be imported in core Moodle without a loss in data, // as core match question don't allow HTML in subanswers. The contributed ddmatch // question type support HTML in subanswers. // The ddmatch question type is not part of core, so we need to check if it is defined. $ddmatchisinstalled = question_bank::is_qtype_installed('ddmatch'); $question = $this->process_common($quest); $question = $this->add_blank_combined_feedback($question); $question->valid = true; if ($ddmatchisinstalled) { $question->qtype = 'ddmatch'; } else { $question->qtype = 'match'; } // Construction of the array holding mappings between subanswers and subquestions. foreach ($quest->RESPONSE_BLOCK->subquestions as $qid => $subq) { foreach ($quest->responses as $rid => $resp) { if (isset($resp->ident) && $resp->ident == $subq->ident) { $correct = $resp->correct; } } foreach ($subq->choices as $cid => $choice) { if ($choice == $correct) { $mappings[$subq->ident] = $cid; } } } foreach ($subq->choices as $choiceid => $choice) { $subanswertext = $quest->RIGHT_MATCH_BLOCK->matchinganswerset[$choiceid]->text; if ($ddmatchisinstalled) { $subanswer = $this->cleaned_text_field($subanswertext); } else { $subanswertext = html_to_text($this->cleaninput($subanswertext), 0); $subanswer = $subanswertext; } if ($subanswertext != '') { // Only import non empty subanswers. $subquestion = ''; $fiber = array_keys($mappings, $choiceid); foreach ($fiber as $correctanswerid) { // We have found a correspondance for this subanswer so we need to take the associated subquestion. foreach ($quest->RESPONSE_BLOCK->subquestions as $qid => $subq) { $currentsubqid = $subq->ident; if (strcmp($currentsubqid, $correctanswerid) == 0) { $subquestion = $subq->text; break; } } $question->subquestions[] = $this->cleaned_text_field($subquestion); $question->subanswers[] = $subanswer; } if ($subquestion == '') { // Then in this case, $choice is a distractor. $question->subquestions[] = $this->text_field(''); $question->subanswers[] = $subanswer; } } } // Verify that this matching question has enough subquestions and subanswers. $subquestioncount = 0; $subanswercount = 0; $subanswers = $question->subanswers; foreach ($question->subquestions as $key => $subquestion) { $subquestion = $subquestion['text']; $subanswer = $subanswers[$key]; if ($subquestion != '') { $subquestioncount++; } $subanswercount++; } if ($subquestioncount < 2 || $subanswercount < 3) { $this->error(get_string('notenoughtsubans', 'qformat_blackboard_six', $question->questiontext)); } else { $questions[] = $question; } }
function process_matching($quest, &$questions) { // renderedmatch is an optional plugin, so we need to check if it is defined if (question_bank::is_qtype_installed('renderedmatch')) { $question = $this->process_common($quest); $question->valid = true; $question->qtype = 'renderedmatch'; foreach ($quest->RESPONSE_BLOCK->subquestions as $qid => $subq) { foreach ($quest->responses as $rid => $resp) { if ($resp->ident == $subq->ident) { $correct = $resp->correct; $feedback = $resp->feedback; } } foreach ($subq->choices as $cid => $choice) { if ($choice == $correct) { $question->subquestions[] = $subq->text; $question->subanswers[] = $quest->RIGHT_MATCH_BLOCK->matching_answerset[$cid]->text; } } } // check format $status = true; if (count($quest->RESPONSE_BLOCK->subquestions) > count($quest->RIGHT_MATCH_BLOCK->matching_answerset) || count($question->subquestions) < 2) { $status = false; } else { // need to redo to make sure that no two questions have the same answer (rudimentary now) foreach ($question->subanswers as $qstn) { if (isset($previous)) { if ($qstn == $previous) { $status = false; } } $previous = $qstn; if ($qstn == '') { $status = false; } } } if ($status) { $questions[] = $question; } else { global $COURSE, $CFG; print '<table class="boxaligncenter" border="1">'; print '<tr><td colspan="2" style="background-color:#FF8888;">This matching question is malformed. Please ensure there are no blank answers, no two questions have the same answer, and/or there are correct answers for each question. There must be at least as many subanswers as subquestions, and at least one subquestion.</td></tr>'; print "<tr><td>Question:</td><td>" . $quest->QUESTION_BLOCK->text; if (isset($quest->QUESTION_BLOCK->file)) { print '<br/><font color="red">There is a subfile contained in the zipfile that has been copied to course files: bb_import/' . basename($quest->QUESTION_BLOCK->file) . '</font>'; if (preg_match('/(gif|jpg|jpeg|png)$/i', $quest->QUESTION_BLOCK->file)) { print '<img src="' . $CFG->wwwroot . '/file.php/' . $COURSE->id . '/bb_import/' . basename($quest->QUESTION_BLOCK->file) . '" />'; } } print "</td></tr>"; print "<tr><td>Subquestions:</td><td><ul>"; foreach ($quest->responses as $rs) { $correct_responses->{$rs->ident} = $rs->correct; } foreach ($quest->RESPONSE_BLOCK->subquestions as $subq) { print '<li>' . $subq->text . '<ul>'; foreach ($subq->choices as $id => $choice) { print '<li>'; if ($choice == $correct_responses->{$subq->ident}) { print '<font color="green">'; } else { print '<font color="red">'; } print $quest->RIGHT_MATCH_BLOCK->matching_answerset[$id]->text . '</font></li>'; } print '</ul>'; } print '</ul></td></tr>'; print '<tr><td>Feedback:</td><td><ul>'; foreach ($quest->feedback as $fb) { print '<li>' . $fb->ident . ': ' . $fb->text . '</li>'; } print '</ul></td></tr></table>'; } } else { print "Matching question types are not handled because the quiz question type 'Rendered Matching' does not exist in this installation of Moodle<br/>"; print " Omitted Question: " . $quest->QUESTION_BLOCK->text . '<br/><br/>'; } }
/** * Private function to factor common code out of get_question_options(). * * @param object $question the question to tidy. * @param boolean $loadtags load the question tags from the tags table. Optional, default false. */ function _tidy_question($question, $loadtags = false) { global $CFG; if (!question_bank::is_qtype_installed($question->qtype)) { $question->questiontext = html_writer::tag('p', get_string('warningmissingtype', 'qtype_missingtype')) . $question->questiontext; } question_bank::get_qtype($question->qtype)->get_question_options($question); if (isset($question->_partiallyloaded)) { unset($question->_partiallyloaded); } if ($loadtags && !empty($CFG->usetags)) { require_once $CFG->dirroot . '/tag/lib.php'; $question->tags = tag_get_tags_array('question', $question->id); } }
/** * Process Matching Questions * @param array $xml the xml tree * @param array $questions the questions already parsed */ public function process_matching($xml, &$questions) { if ($this->getpath($xml, array('POOL', '#', 'QUESTION_MATCH'), false, false)) { $matchquestions = $this->getpath($xml, array('POOL', '#', 'QUESTION_MATCH'), false, false); } else { return; } // Blackboard questions can't be imported in core Moodle without a loss in data, // as core match question don't allow HTML in subanswers. The contributed ddmatch // question type support HTML in subanswers. // The ddmatch question type is not part of core, so we need to check if it is defined. $ddmatchisinstalled = question_bank::is_qtype_installed('ddmatch'); foreach ($matchquestions as $thisquestion) { $question = $this->process_common($thisquestion); if ($ddmatchisinstalled) { $question->qtype = 'ddmatch'; } else { $question->qtype = 'match'; } $correctfeedback = $this->getpath($thisquestion, array('#', 'GRADABLE', 0, '#', 'FEEDBACK_WHEN_CORRECT', 0, '#'), '', true); $incorrectfeedback = $this->getpath($thisquestion, array('#', 'GRADABLE', 0, '#', 'FEEDBACK_WHEN_INCORRECT', 0, '#'), '', true); $question->correctfeedback = $this->cleaned_text_field($correctfeedback); // As there is no partially correct feedback we use incorrect one. $question->partiallycorrectfeedback = $this->cleaned_text_field($incorrectfeedback); $question->incorrectfeedback = $this->cleaned_text_field($incorrectfeedback); $choices = $this->getpath($thisquestion, array('#', 'CHOICE'), false, false); // Blackboard "choices" are Moodle subanswers. $answers = $this->getpath($thisquestion, array('#', 'ANSWER'), false, false); // Blackboard "answers" are Moodle subquestions. $correctanswers = $this->getpath($thisquestion, array('#', 'GRADABLE', 0, '#', 'CORRECTANSWER'), false, false); // Mapping between choices and answers. $mappings = array(); foreach ($correctanswers as $correctanswer) { if ($correctanswer) { $correctchoiceid = $this->getpath($correctanswer, array('@', 'choice_id'), '', true); $correctanswerid = $this->getpath($correctanswer, array('@', 'answer_id'), '', true); $mappings[$correctanswerid] = $correctchoiceid; } } foreach ($choices as $choice) { if ($ddmatchisinstalled) { $choicetext = $this->cleaned_text_field($this->getpath($choice, array('#', 'TEXT', 0, '#'), '', true)); } else { $choicetext = trim(strip_tags($this->getpath($choice, array('#', 'TEXT', 0, '#'), '', true))); } if ($choicetext != '') { // Only import non empty subanswers. $subquestion = ''; $choiceid = $this->getpath($choice, array('@', 'id'), '', true); $fiber = array_search($choiceid, $mappings); $fiber = array_keys($mappings, $choiceid); foreach ($fiber as $correctanswerid) { // We have found a correspondance for this choice so we need to take the associated answer. foreach ($answers as $answer) { $currentanswerid = $this->getpath($answer, array('@', 'id'), '', true); if (strcmp($currentanswerid, $correctanswerid) == 0) { $subquestion = $this->getpath($answer, array('#', 'TEXT', 0, '#'), '', true); break; } } $question->subquestions[] = $this->cleaned_text_field($subquestion); $question->subanswers[] = $choicetext; } if ($subquestion == '') { // Then in this case, $choice is a distractor. $question->subquestions[] = $this->text_field(''); $question->subanswers[] = $choicetext; } } } // Verify that this matching question has enough subquestions and subanswers. $subquestioncount = 0; $subanswercount = 0; $subanswers = $question->subanswers; foreach ($question->subquestions as $key => $subquestion) { $subquestion = $subquestion['text']; $subanswer = $subanswers[$key]; if ($subquestion != '') { $subquestioncount++; } $subanswercount++; } if ($subquestioncount < 2 || $subanswercount < 3) { $this->error(get_string('notenoughtsubans', 'qformat_blackboard_six', $question->questiontext)); } else { $questions[] = $question; } } }