3 // This file is part of Moodle - http://moodle.org/
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
23 * @copyright 2009 Sam Hemelryk
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 defined('MOODLE_INTERNAL') || die();
29 /** Matching question type */
30 define("LESSON_PAGE_MATCHING", "5");
32 class lesson_page_type_matching extends lesson_page {
34 protected $type = lesson_page::TYPE_QUESTION;
35 protected $typeid = LESSON_PAGE_MATCHING;
36 protected $typeidstring = 'matching';
37 protected $string = null;
39 public function get_typeid() {
42 public function get_typestring() {
43 if ($this->string===null) {
44 $this->string = get_string($this->typeidstring, 'lesson');
48 public function get_idstring() {
49 return $this->typeidstring;
51 public function display($renderer, $attempt) {
52 global $USER, $CFG, $PAGE;
53 $mform = $this->make_answer_form($attempt);
55 $data->id = $PAGE->cm->id;
56 $data->pageid = $this->properties->id;
57 $mform->set_data($data);
58 return $mform->display();
61 protected function make_answer_form($attempt=null) {
63 // don't shuffle answers (could be an option??)
64 $getanswers = array_slice($this->get_answers(), 2);
67 foreach ($getanswers as $getanswer) {
68 $answers[$getanswer->id] = $getanswer;
72 foreach ($answers as $answer) {
73 // get all the response
74 if ($answer->response != NULL) {
75 $responses[] = trim($answer->response);
79 $responseoptions = array(''=>get_string('choosedots'));
80 if (!empty($responses)) {
82 $responses = array_unique($responses);
83 foreach ($responses as $response) {
84 $responseoptions[htmlspecialchars(trim($response))] = $response;
87 if (isset($USER->modattempts[$this->lesson->id]) && !empty($attempt->useranswer)) {
88 $useranswers = explode(',', $attempt->useranswer);
91 $useranswers = array();
94 $action = $CFG->wwwroot.'/mod/lesson/continue.php';
95 $params = array('answers'=>$answers, 'useranswers'=>$useranswers, 'responseoptions'=>$responseoptions, 'lessonid'=>$this->lesson->id, 'contents'=>$this->get_contents());
96 $mform = new lesson_display_answer_form_matching($action, $params);
100 public function create_answers($properties) {
102 // now add the answers
103 $newanswer = new stdClass;
104 $newanswer->lessonid = $this->lesson->id;
105 $newanswer->pageid = $this->properties->id;
106 $newanswer->timecreated = $this->properties->timecreated;
110 // need to add two to offset correct response and wrong response
111 $this->lesson->maxanswers = $this->lesson->maxanswers + 2;
112 for ($i = 0; $i < $this->lesson->maxanswers; $i++) {
113 $answer = clone($newanswer);
114 if (!empty($properties->answer_editor[$i]) && is_array($properties->answer_editor[$i])) {
115 $answer->answer = $properties->answer_editor[$i]['text'];
116 $answer->answerformat = $properties->answer_editor[$i]['format'];
118 if (!empty($properties->response_editor[$i]) && is_array($properties->response_editor[$i])) {
119 $answer->response = $properties->response_editor[$i]['text'];
120 $answer->responseformat = $properties->response_editor[$i]['format'];
123 if (isset($properties->jumpto[$i])) {
124 $answer->jumpto = $properties->jumpto[$i];
126 if ($this->lesson->custom && isset($properties->score[$i])) {
127 $answer->score = $properties->score[$i];
130 if (isset($answer->answer) && $answer->answer != '') {
131 $answer->id = $DB->insert_record("lesson_answers", $answer);
132 $answers[$answer->id] = new lesson_page_answer($answer);
134 $answer->id = $DB->insert_record("lesson_answers", $answer);
135 $answers[$answer->id] = new lesson_page_answer($answer);
140 $this->answers = $answers;
144 public function check_answer() {
147 $formattextdefoptions = new stdClass();
148 $formattextdefoptions->noclean = true;
149 $formattextdefoptions->para = false;
151 $result = parent::check_answer();
153 $mform = $this->make_answer_form();
155 $data = $mform->get_data();
159 redirect(new moodle_url('/mod/lesson/view.php', array('id'=>$PAGE->cm->id, 'pageid'=>$this->properties->id)));
162 $response = $data->response;
163 if (!is_array($response)) {
164 $result->noanswer = true;
168 $answers = $this->get_answers();
170 $correct = array_shift($answers);
171 $wrong = array_shift($answers);
173 foreach ($answers as $key=>$answer) {
174 if ($answer->answer === '' or $answer->response === '') {
175 // incomplete option!
176 unset($answers[$key]);
179 // get he users exact responses for record keeping
181 $userresponse = array();
182 foreach ($response as $key => $value) {
183 foreach($answers as $answer) {
184 if ($value === $answer->response) {
185 $userresponse[] = $answer->id;
187 if ((int)$answer->id === (int)$key) {
188 $result->studentanswer .= '<br />'.format_text($answer->answer, $answer->answerformat, $formattextdefoptions).' = '.$value;
190 if ((int)$answer->id === (int)$key and $value === $answer->response) {
195 $result->userresponse = implode(",", $userresponse);
197 if ($hits == count($answers)) {
198 $result->correctanswer = true;
199 $result->response = format_text($correct->answer, $correct->answerformat, $formattextdefoptions);
200 $result->answerid = $correct->id;
201 $result->newpageid = $correct->jumpto;
203 $result->correctanswer = false;
204 $result->response = format_text($wrong->answer, $wrong->answerformat, $formattextdefoptions);
205 $result->answerid = $wrong->id;
206 $result->newpageid = $wrong->jumpto;
212 public function option_description_string() {
213 return get_string("firstanswershould", "lesson");
216 public function display_answers(html_table $table) {
217 $answers = $this->get_answers();
218 $options = new stdClass;
219 $options->noclean = true;
220 $options->para = false;
224 foreach ($answers as $answer) {
226 if ($answer->answer != NULL) {
229 $cells[] = "<span class=\"label\">".get_string("correctresponse", "lesson").'</span>';
231 $cells[] = "<span class=\"label\">".get_string("wrongresponse", "lesson").'</span>';
233 $cells[] = format_text($answer->answer, $answer->answerformat, $options);
234 $table->data[] = new html_table_row($cells);
239 $cells[] = '<span class="label">'.get_string("correctanswerscore", "lesson")."</span>: ";
240 $cells[] = $answer->score;
241 $table->data[] = new html_table_row($cells);
244 $cells[] = '<span class="label">'.get_string("correctanswerjump", "lesson")."</span>: ";
245 $cells[] = $this->get_jump_name($answer->jumpto);
246 $table->data[] = new html_table_row($cells);
249 $cells[] = '<span class="label">'.get_string("wronganswerscore", "lesson")."</span>: ";
250 $cells[] = $answer->score;
251 $table->data[] = new html_table_row($cells);
254 $cells[] = '<span class="label">'.get_string("wronganswerjump", "lesson")."</span>: ";
255 $cells[] = $this->get_jump_name($answer->jumpto);
256 $table->data[] = new html_table_row($cells);
260 $table->data[count($table->data)-1]->cells[0]->style = 'width:20%;';
266 if ($this->lesson->custom && $answer->score > 0) {
267 // if the score is > 0, then it is correct
268 $cells[] = '<span class="labelcorrect">'.get_string("answer", "lesson")." $i</span>: \n";
269 } else if ($this->lesson->custom) {
270 $cells[] = '<span class="label">'.get_string("answer", "lesson")." $i</span>: \n";
271 } else if ($this->lesson->jumpto_is_correct($this->properties->id, $answer->jumpto)) {
272 $cells[] = '<span class="labelcorrect">'.get_string("answer", "lesson")." $i</span>: \n";
274 $cells[] = '<span class="label">'.get_string("answer", "lesson")." $i</span>: \n";
276 $cells[] = format_text($answer->answer, $answer->answerformat, $options);
277 $table->data[] = new html_table_row($cells);
280 $cells[] = '<span class="label">'.get_string("matchesanswer", "lesson")." $i</span>: ";
281 $cells[] = format_text($answer->response, $answer->responseformat, $options);
282 $table->data[] = new html_table_row($cells);
289 * Updates the page and its answers
291 * @global moodle_database $DB
292 * @global moodle_page $PAGE
293 * @param stdClass $properties
296 public function update($properties, $context = null, $maxbytes = null) {
298 $answers = $this->get_answers();
299 $properties->id = $this->properties->id;
300 $properties->lessonid = $this->lesson->id;
301 $properties = file_postupdate_standard_editor($properties, 'contents', array('noclean'=>true, 'maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$PAGE->course->maxbytes), context_module::instance($PAGE->cm->id), 'mod_lesson', 'page_contents', $properties->id);
302 $DB->update_record("lesson_pages", $properties);
304 // need to add two to offset correct response and wrong response
305 $this->lesson->maxanswers += 2;
306 for ($i = 0; $i < $this->lesson->maxanswers; $i++) {
307 if (!array_key_exists($i, $this->answers)) {
308 $this->answers[$i] = new stdClass;
309 $this->answers[$i]->lessonid = $this->lesson->id;
310 $this->answers[$i]->pageid = $this->id;
311 $this->answers[$i]->timecreated = $this->timecreated;
314 if (!empty($properties->answer_editor[$i]) && is_array($properties->answer_editor[$i])) {
315 $this->answers[$i]->answer = $properties->answer_editor[$i]['text'];
316 $this->answers[$i]->answerformat = $properties->answer_editor[$i]['format'];
318 if (!empty($properties->response_editor[$i]) && is_array($properties->response_editor[$i])) {
319 $this->answers[$i]->response = $properties->response_editor[$i]['text'];
320 $this->answers[$i]->responseformat = $properties->response_editor[$i]['format'];
323 if (isset($properties->jumpto[$i])) {
324 $this->answers[$i]->jumpto = $properties->jumpto[$i];
326 if ($this->lesson->custom && isset($properties->score[$i])) {
327 $this->answers[$i]->score = $properties->score[$i];
330 // we don't need to check for isset here because properties called it's own isset method.
331 if ($this->answers[$i]->answer != '') {
332 if (!isset($this->answers[$i]->id)) {
333 $this->answers[$i]->id = $DB->insert_record("lesson_answers", $this->answers[$i]);
335 $DB->update_record("lesson_answers", $this->answers[$i]->properties());
338 if (!isset($this->answers[$i]->id)) {
339 $this->answers[$i]->id = $DB->insert_record("lesson_answers", $this->answers[$i]);
341 $DB->update_record("lesson_answers", $this->answers[$i]->properties());
344 } else if (isset($this->answers[$i]->id)) {
345 $DB->delete_records('lesson_answers', array('id'=>$this->answers[$i]->id));
346 unset($this->answers[$i]);
351 public function stats(array &$pagestats, $tries) {
352 if(count($tries) > $this->lesson->maxattempts) { // if there are more tries than the max that is allowed, grab the last "legal" attempt
353 $temp = $tries[$this->lesson->maxattempts - 1];
355 // else, user attempted the question less than the max, so grab the last one
358 if ($temp->correct) {
359 if (isset($pagestats[$temp->pageid]["correct"])) {
360 $pagestats[$temp->pageid]["correct"]++;
362 $pagestats[$temp->pageid]["correct"] = 1;
365 if (isset($pagestats[$temp->pageid]["total"])) {
366 $pagestats[$temp->pageid]["total"]++;
368 $pagestats[$temp->pageid]["total"] = 1;
372 public function report_answers($answerpage, $answerdata, $useranswer, $pagestats, &$i, &$n) {
374 foreach ($this->get_answers() as $answer) {
375 $answers[$answer->id] = $answer;
377 $formattextdefoptions = new stdClass;
378 $formattextdefoptions->para = false; //I'll use it widely in this page
379 foreach ($answers as $answer) {
380 if ($n == 0 && $useranswer != NULL && $useranswer->correct) {
381 if ($answer->response == NULL && $useranswer != NULL) {
382 $answerdata->response = get_string("thatsthecorrectanswer", "lesson");
384 $answerdata->response = $answer->response;
386 if ($this->lesson->custom) {
387 $answerdata->score = get_string("pointsearned", "lesson").": ".$answer->score;
389 $answerdata->score = get_string("receivedcredit", "lesson");
391 } elseif ($n == 1 && $useranswer != NULL && !$useranswer->correct) {
392 if ($answer->response == NULL && $useranswer != NULL) {
393 $answerdata->response = get_string("thatsthewronganswer", "lesson");
395 $answerdata->response = $answer->response;
397 if ($this->lesson->custom) {
398 $answerdata->score = get_string("pointsearned", "lesson").": ".$answer->score;
400 $answerdata->score = get_string("didnotreceivecredit", "lesson");
403 $data = '<label class="accesshide" for="answer_' . $n . '">' . get_string('answer', 'lesson') . '</label>';
404 $data .= "<select id=\"answer_". $n ."\" disabled=\"disabled\"><option selected=\"selected\">".strip_tags(format_string($answer->answer))."</option></select>";
405 if ($useranswer != NULL) {
406 $userresponse = explode(",", $useranswer->useranswer);
407 $data .= '<label class="accesshide" for="stu_answer_response_' . $n . '">' . get_string('matchesanswer', 'lesson') . '</label>';
408 $data .= "<select id=\"stu_answer_response_" . $n . "\" disabled=\"disabled\"><option selected=\"selected\">";
409 if (array_key_exists($i, $userresponse)) {
410 $data .= strip_tags(format_string($answers[$userresponse[$i]]->response));
412 $data .= "</option></select>";
414 $data .= '<label class="accesshide" for="answer_response_' . $n . '">' . get_string('matchesanswer', 'lesson') . '</label>';
415 $data .= "<select id=\"answer_response_" . $n . "\" disabled=\"disabled\"><option selected=\"selected\">".strip_tags(format_string($answer->response))."</option></select>";
419 if (isset($pagestats[$this->properties->id])) {
420 if (!array_key_exists('correct', $pagestats[$this->properties->id])) {
421 $pagestats[$this->properties->id]["correct"] = 0;
423 $percent = $pagestats[$this->properties->id]["correct"] / $pagestats[$this->properties->id]["total"] * 100;
424 $percent = round($percent, 2);
425 $percent .= "% ".get_string("answeredcorrectly", "lesson");
427 $percent = get_string("nooneansweredthisquestion", "lesson");
433 $answerdata->answers[] = array($data, $percent);
437 $answerpage->answerdata = $answerdata;
441 public function get_jumps() {
443 // The jumps for matching question type are stored in the 1st and 2nd answer record.
445 if ($answers = $DB->get_records("lesson_answers", array("lessonid" => $this->lesson->id, "pageid" => $this->properties->id), 'id', '*', 0, 2)) {
446 foreach ($answers as $answer) {
447 $jumps[] = $this->get_jump_name($answer->jumpto);
450 $jumps[] = $this->get_jump_name($this->properties->nextpageid);
456 class lesson_add_page_form_matching extends lesson_add_page_form_base {
458 public $qtype = 'matching';
459 public $qtypestring = 'matching';
461 public function custom_definition() {
463 $this->_form->addElement('header', 'correctresponse', get_string('correctresponse', 'lesson'));
464 $this->_form->addElement('editor', 'answer_editor[0]', get_string('correctresponse', 'lesson'), array('rows'=>'4', 'columns'=>'80'), array('noclean'=>true));
465 $this->add_jumpto(0, get_string('correctanswerjump','lesson'), LESSON_NEXTPAGE);
466 $this->add_score(0, get_string("correctanswerscore", "lesson"), 1);
468 $this->_form->addElement('header', 'wrongresponse', get_string('wrongresponse', 'lesson'));
469 $this->_form->addElement('editor', 'answer_editor[1]', get_string('wrongresponse', 'lesson'), array('rows'=>'4', 'columns'=>'80'), array('noclean'=>true));
470 $this->add_jumpto(1, get_string('wronganswerjump','lesson'), LESSON_THISPAGE);
471 $this->add_score(1, get_string("wronganswerscore", "lesson"), 0);
473 for ($i = 2; $i < $this->_customdata['lesson']->maxanswers+2; $i++) {
474 $this->_form->addElement('header', 'matchingpair'.($i-1), get_string('matchingpair', 'lesson', $i-1));
475 $this->add_answer($i, NULL, ($i < 4));
476 $this->add_response($i, get_string('matchesanswer','lesson'), ($i < 4));
482 class lesson_display_answer_form_matching extends moodleform {
484 public function definition() {
485 global $USER, $OUTPUT;
486 $mform = $this->_form;
487 $answers = $this->_customdata['answers'];
488 $useranswers = $this->_customdata['useranswers'];
489 $responseoptions = $this->_customdata['responseoptions'];
490 $lessonid = $this->_customdata['lessonid'];
491 $contents = $this->_customdata['contents'];
493 $mform->addElement('header', 'pageheader');
495 $mform->addElement('html', $OUTPUT->container($contents, 'contents'));
499 if (isset($useranswers) && !empty($useranswers)) {
501 $disabled = array('disabled' => 'disabled');
504 $options = new stdClass;
505 $options->para = false;
506 $options->noclean = true;
508 $mform->addElement('hidden', 'id');
509 $mform->setType('id', PARAM_INT);
511 $mform->addElement('hidden', 'pageid');
512 $mform->setType('pageid', PARAM_INT);
515 foreach ($answers as $answer) {
516 $mform->addElement('html', '<div class="answeroption">');
517 if ($answer->response != NULL) {
518 $responseid = 'response['.$answer->id.']';
520 $responseid = 'response_'.$answer->id;
521 $mform->addElement('hidden', 'response['.$answer->id.']', htmlspecialchars(trim($answers[$useranswers[$i]]->response)));
522 $mform->setType('response['.$answer->id.']', PARAM_TEXT);
524 $mform->addElement('select', $responseid, format_text($answer->answer,$answer->answerformat,$options), $responseoptions, $disabled);
525 $mform->setType($responseid, PARAM_TEXT);
527 $mform->setDefault($responseid, htmlspecialchars(trim($answers[$useranswers[$i]]->response))); //TODO: this is suspicious
529 $mform->setDefault($responseid, 'answeroption');
532 $mform->addElement('html', '</div>');
536 $this->add_action_buttons(null, get_string("nextpage", "lesson"));
538 $this->add_action_buttons(null, get_string("submit", "lesson"));