mod-lesson MDL-25632 Added support for displaying and progressing through a question...
[moodle.git] / mod / lesson / pagetypes / matching.php
CommitLineData
0a4abb73
SH
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
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.
9//
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.
14//
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/>.
17
18/**
19 * Matching
20 *
cc3dbaaa
PS
21 * @package mod
22 * @subpackage lesson
23 * @copyright 2009 Sam Hemelryk
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0a4abb73
SH
25 **/
26
1e7f8ea2
PS
27defined('MOODLE_INTERNAL') || die();
28
0a4abb73
SH
29/** Matching question type */
30define("LESSON_PAGE_MATCHING", "5");
31
32class lesson_page_type_matching extends lesson_page {
33
34 protected $type = lesson_page::TYPE_QUESTION;
35 protected $typeid = LESSON_PAGE_MATCHING;
36 protected $typeidstring = 'matching';
37 protected $string = null;
38
39 public function get_typeid() {
40 return $this->typeid;
41 }
42 public function get_typestring() {
43 if ($this->string===null) {
44 $this->string = get_string($this->typeidstring, 'lesson');
45 }
46 return $this->string;
47 }
48 public function get_idstring() {
49 return $this->typeidstring;
50 }
51 public function display($renderer, $attempt) {
52 global $USER, $CFG, $PAGE;
53 $mform = $this->make_answer_form($attempt);
54 $data = new stdClass;
55 $data->id = $PAGE->cm->id;
56 $data->pageid = $this->properties->id;
57 $mform->set_data($data);
58 return $mform->display();
59 }
60
61 protected function make_answer_form($attempt=null) {
62 global $USER, $CFG;
4fb3f253 63 // don't shuffle answers (could be an option??)
0a4abb73
SH
64 $answers = array_slice($this->get_answers(), 2);
65 $responses = array();
66 foreach ($answers as $answer) {
67 // get all the response
68 if ($answer->response != NULL) {
69 $responses[] = trim($answer->response);
70 }
71 }
72
04932409 73 $responseoptions = array(''=>get_string('choosedots'));
0a4abb73
SH
74 if (!empty($responses)) {
75 shuffle($responses);
76 $responses = array_unique($responses);
77 foreach ($responses as $response) {
78 $responseoptions[htmlspecialchars(trim($response))] = $response;
79 }
80 }
81 if (isset($USER->modattempts[$this->lesson->id]) && !empty($attempt->useranswer)) {
82 $useranswers = explode(',', $attempt->useranswer);
83 $t = 0;
84 } else {
85 $useranswers = array();
86 }
87
88 $action = $CFG->wwwroot.'/mod/lesson/continue.php';
89 $params = array('answers'=>$answers, 'useranswers'=>$useranswers, 'responseoptions'=>$responseoptions, 'lessonid'=>$this->lesson->id, 'contents'=>$this->get_contents());
90 $mform = new lesson_display_answer_form_matching($action, $params);
91 return $mform;
92 }
93
94 public function create_answers($properties) {
95 global $DB;
96 // now add the answers
97 $newanswer = new stdClass;
98 $newanswer->lessonid = $this->lesson->id;
99 $newanswer->pageid = $this->properties->id;
100 $newanswer->timecreated = $this->properties->timecreated;
101
102 $answers = array();
103
104 // need to add two to offset correct response and wrong response
105 $this->lesson->maxanswers = $this->lesson->maxanswers + 2;
106 for ($i = 0; $i < $this->lesson->maxanswers; $i++) {
107 $answer = clone($newanswer);
01c37ef1
PS
108 if (!empty($properties->answer_editor[$i])) {
109 $answer->answer = $properties->answer_editor[$i]['text'];
110 $answer->answerformat = $properties->answer_editor[$i]['format'];
111 if (isset($properties->response_editor[$i])) {
112 $answer->response = $properties->response_editor[$i]['text'];
113 $answer->responseformat = $properties->response_editor[$i]['format'];
0a4abb73
SH
114 }
115 if (isset($properties->jumpto[$i])) {
116 $answer->jumpto = $properties->jumpto[$i];
117 }
118 if ($this->lesson->custom && isset($properties->score[$i])) {
119 $answer->score = $properties->score[$i];
120 }
121 $answer->id = $DB->insert_record("lesson_answers", $answer);
122 $answers[$answer->id] = new lesson_page_answer($answer);
123 } else if ($i < 2) {
124 $answer->id = $DB->insert_record("lesson_answers", $answer);
125 $answers[$answer->id] = new lesson_page_answer($answer);
126 } else {
127 break;
128 }
129 }
130 $this->answers = $answers;
131 return $answers;
132 }
133
134 public function check_answer() {
df0442c6 135 global $CFG, $PAGE;
01c37ef1 136
39790bd8 137 $formattextdefoptions = new stdClass();
01c37ef1
PS
138 $formattextdefoptions->noclean = true;
139 $formattextdefoptions->para = false;
140
0a4abb73
SH
141 $result = parent::check_answer();
142
143 $mform = $this->make_answer_form();
144
145 $data = $mform->get_data();
146 require_sesskey();
147
148 if (!$data) {
a6855934 149 redirect(new moodle_url('/mod/lesson/view.php', array('id'=>$PAGE->cm->id, 'pageid'=>$this->properties->id)));
0a4abb73 150 }
2f67a9b3 151
0a4abb73
SH
152 $response = $data->response;
153 if (!is_array($response)) {
154 $result->noanswer = true;
155 return $result;
156 }
a675ada5 157
0a4abb73 158 $answers = $this->get_answers();
a675ada5
PS
159
160 $correct = array_shift($answers);
161 $wrong = array_shift($answers);
162
163 foreach ($answers as $key=>$answer) {
164 if ($answer->answer === '' or $answer->response === '') {
165 // incomplete option!
166 unset($answers[$key]);
0a4abb73 167 }
0a4abb73
SH
168 }
169 // get he users exact responses for record keeping
a675ada5 170 $hits = 0;
0a4abb73
SH
171 $userresponse = array();
172 foreach ($response as $key => $value) {
173 foreach($answers as $answer) {
a675ada5 174 if ($value === $answer->response) {
0a4abb73
SH
175 $userresponse[] = $answer->id;
176 }
177 if ((int)$answer->id === (int)$key) {
01c37ef1 178 $result->studentanswer .= '<br />'.format_text($answer->answer, $answer->answerformat, $formattextdefoptions).' = '.$value;
0a4abb73 179 }
a675ada5
PS
180 if ((int)$answer->id === (int)$key and $value === $answer->response) {
181 $hits++;
182 }
0a4abb73
SH
183 }
184 }
185 $result->userresponse = implode(",", $userresponse);
186
a675ada5 187 if ($hits == count($answers)) {
0a4abb73 188 $result->correctanswer = true;
a675ada5
PS
189 $result->response = format_text($correct->answer, $correct->answerformat, $formattextdefoptions);
190 $result->answerid = $correct->id;
191 $result->newpageid = $correct->jumpto;
0a4abb73 192 } else {
a675ada5
PS
193 $result->correctanswer = false;
194 $result->response = format_text($wrong->answer, $wrong->answerformat, $formattextdefoptions);
195 $result->answerid = $wrong->id;
196 $result->newpageid = $wrong->jumpto;
0a4abb73 197 }
a675ada5 198
0a4abb73
SH
199 return $result;
200 }
201
202 public function option_description_string() {
203 return get_string("firstanswershould", "lesson");
204 }
205
206 public function display_answers(html_table $table) {
207 $answers = $this->get_answers();
208 $options = new stdClass;
209 $options->noclean = true;
210 $options->para = false;
211 $i = 1;
212 $n = 0;
2f67a9b3 213
0a4abb73
SH
214 foreach ($answers as $answer) {
215 if ($n < 2) {
216 if ($answer->answer != NULL) {
217 $cells = array();
218 if ($n == 0) {
219 $cells[] = "<span class=\"label\">".get_string("correctresponse", "lesson").'</span>';
220 } else {
221 $cells[] = "<span class=\"label\">".get_string("wrongresponse", "lesson").'</span>';
222 }
01c37ef1 223 $cells[] = format_text($answer->answer, $answer->answerformat, $options);
8cea545e 224 $table->data[] = new html_table_row($cells);
0a4abb73
SH
225 }
226 $n++;
227 $i--;
228 } else {
229 $cells = array();
230 if ($this->lesson->custom && $answer->score > 0) {
231 // if the score is > 0, then it is correct
232 $cells[] = '<span class="labelcorrect">'.get_string("answer", "lesson")." $i</span>: \n";
233 } else if ($this->lesson->custom) {
234 $cells[] = '<span class="label">'.get_string("answer", "lesson")." $i</span>: \n";
235 } else if ($this->lesson->jumpto_is_correct($this->properties->id, $answer->jumpto)) {
236 $cells[] = '<span class="labelcorrect">'.get_string("answer", "lesson")." $i</span>: \n";
237 } else {
238 $cells[] = '<span class="label">'.get_string("answer", "lesson")." $i</span>: \n";
239 }
01c37ef1 240 $cells[] = format_text($answer->answer, $answer->answerformat, $options);
8cea545e 241 $table->data[] = new html_table_row($cells);
0a4abb73
SH
242
243 $cells = array();
244 $cells[] = '<span class="label">'.get_string("matchesanswer", "lesson")." $i</span>: ";
01c37ef1 245 $cells[] = format_text($answer->response, $answer->responseformat, $options);
8cea545e 246 $table->data[] = new html_table_row($cells);
0a4abb73
SH
247 }
248
249 if ($i == 1) {
250 $cells = array();
251 $cells[] = '<span class="label">'.get_string("correctanswerscore", "lesson")." $i</span>: ";
252 $cells[] = $answer->score;
8cea545e 253 $table->data[] = new html_table_row($cells);
0a4abb73
SH
254
255 $cells = array();
256 $cells[] = '<span class="label">'.get_string("correctanswerjump", "lesson")." $i</span>: ";
257 $cells[] = $this->get_jump_name($answer->jumpto);
8cea545e 258 $table->data[] = new html_table_row($cells);
0a4abb73
SH
259 } elseif ($i == 2) {
260 $cells = array();
261 $cells[] = '<span class="label">'.get_string("wronganswerscore", "lesson")." $i</span>: ";
262 $cells[] = $answer->score;
8cea545e 263 $table->data[] = new html_table_row($cells);
0a4abb73
SH
264
265 $cells = array();
266 $cells[] = '<span class="label">'.get_string("wronganswerjump", "lesson")." $i</span>: ";
267 $cells[] = $this->get_jump_name($answer->jumpto);
8cea545e 268 $table->data[] = new html_table_row($cells);
0a4abb73
SH
269 }
270
271 if ($i === 1){
272 $table->data[count($table->data)-1]->cells[0]->style = 'width:20%;';
273 }
274
275 $i++;
276 }
277 return $table;
278 }
279 public function update($properties) {
280 global $DB, $PAGE;
281 $answers = $this->get_answers();
282 $properties->id = $this->properties->id;
283 $properties->lessonid = $this->lesson->id;
64f93798 284 $properties = file_postupdate_standard_editor($properties, 'contents', array('noclean'=>true, 'maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$PAGE->course->maxbytes), get_context_instance(CONTEXT_MODULE, $PAGE->cm->id), 'mod_lesson', 'page_contents', $properties->id);
0a4abb73
SH
285 $DB->update_record("lesson_pages", $properties);
286
287 // need to add two to offset correct response and wrong response
288 $this->lesson->maxanswers += 2;
289 for ($i = 0; $i < $this->lesson->maxanswers; $i++) {
290 if (!array_key_exists($i, $this->answers)) {
291 $this->answers[$i] = new stdClass;
292 $this->answers[$i]->lessonid = $this->lesson->id;
293 $this->answers[$i]->pageid = $this->id;
294 $this->answers[$i]->timecreated = $this->timecreated;
295 }
01c37ef1
PS
296 if (!empty($properties->answer_editor[$i])) {
297 $this->answers[$i]->answer = $properties->answer_editor[$i]['text'];
298 $this->answers[$i]->answerformat = $properties->answer_editor[$i]['format'];
299 if (isset($properties->response_editor[$i])) {
01310dfb
PS
300 $this->answers[$i]->response = $properties->response_editor[$i]['text'];
301 $this->answers[$i]->responseformat = $properties->response_editor[$i]['format'];
0a4abb73
SH
302 }
303 if (isset($properties->jumpto[$i])) {
304 $this->answers[$i]->jumpto = $properties->jumpto[$i];
305 }
306 if ($this->lesson->custom && isset($properties->score[$i])) {
307 $this->answers[$i]->score = $properties->score[$i];
308 }
309 if (!isset($this->answers[$i]->id)) {
310 $this->answers[$i]->id = $DB->insert_record("lesson_answers", $this->answers[$i]);
311 } else {
312 $DB->update_record("lesson_answers", $this->answers[$i]->properties());
313 }
314
315 } else if ($i < 2) {
316 if (!isset($this->answers[$i]->id)) {
317 $this->answers[$i]->id = $DB->insert_record("lesson_answers", $this->answers[$i]);
318 } else {
319 $DB->update_record("lesson_answers", $this->answers[$i]->properties());
320 }
321
322 } else {
323 break;
324 }
325 }
326 return true;
327 }
328 public function stats(array &$pagestats, $tries) {
329 if(count($tries) > $this->lesson->maxattempts) { // if there are more tries than the max that is allowed, grab the last "legal" attempt
330 $temp = $tries[$this->lesson->maxattempts - 1];
331 } else {
332 // else, user attempted the question less than the max, so grab the last one
333 $temp = end($tries);
334 }
335 if ($temp->correct) {
336 if (isset($pagestats[$temp->pageid]["correct"])) {
337 $pagestats[$temp->pageid]["correct"]++;
338 } else {
339 $pagestats[$temp->pageid]["correct"] = 1;
340 }
341 }
342 if (isset($pagestats[$temp->pageid]["total"])) {
343 $pagestats[$temp->pageid]["total"]++;
344 } else {
345 $pagestats[$temp->pageid]["total"] = 1;
346 }
347 return true;
348 }
349 public function report_answers($answerpage, $answerdata, $useranswer, $pagestats, &$i, &$n) {
350 $answers = array();
351 foreach ($this->get_answers() as $answer) {
352 $answers[$answer->id] = $answer;
353 }
354 $formattextdefoptions = new stdClass;
355 $formattextdefoptions->para = false; //I'll use it widely in this page
356 foreach ($answers as $answer) {
357 if ($n == 0 && $useranswer != NULL && $useranswer->correct) {
358 if ($answer->response == NULL && $useranswer != NULL) {
359 $answerdata->response = get_string("thatsthecorrectanswer", "lesson");
360 } else {
361 $answerdata->response = $answer->response;
362 }
363 } elseif ($n == 1 && $useranswer != NULL && !$useranswer->correct) {
364 if ($answer->response == NULL && $useranswer != NULL) {
365 $answerdata->response = get_string("thatsthewronganswer", "lesson");
366 } else {
367 $answerdata->response = $answer->response;
368 }
369 } elseif ($n > 1) {
370 if ($n == 2 && $useranswer != NULL && $useranswer->correct) {
371 if ($this->lesson->custom) {
372 $answerdata->score = get_string("pointsearned", "lesson").": ".$answer->score;
373 } else {
374 $answerdata->score = get_string("receivedcredit", "lesson");
375 }
376 } elseif ($n == 3 && $useranswer != NULL && !$useranswer->correct) {
377 if ($this->lesson->custom) {
378 $answerdata->score = get_string("pointsearned", "lesson").": ".$answer->score;
379 } else {
380 $answerdata->score = get_string("didnotreceivecredit", "lesson");
381 }
382 }
383 $data = "<select disabled=\"disabled\"><option selected=\"selected\">".strip_tags(format_string($answer->answer))."</option></select>";
384 if ($useranswer != NULL) {
385 $userresponse = explode(",", $useranswer->useranswer);
386 $data .= "<select disabled=\"disabled\"><option selected=\"selected\">".strip_tags(format_string($answers[$userresponse[$i]]->response))."</option></select>";
387 } else {
388 $data .= "<select disabled=\"disabled\"><option selected=\"selected\">".strip_tags(format_string($answer->response))."</option></select>";
389 }
390
391 if ($n == 2) {
392 if (isset($pagestats[$this->properties->id])) {
393 if (!array_key_exists('correct', $pagestats[$this->properties->id])) {
394 $pagestats[$this->properties->id]["correct"] = 0;
395 }
396 $percent = $pagestats[$this->properties->id]["correct"] / $pagestats[$this->properties->id]["total"] * 100;
397 $percent = round($percent, 2);
398 $percent .= "% ".get_string("answeredcorrectly", "lesson");
399 } else {
400 $percent = get_string("nooneansweredthisquestion", "lesson");
401 }
402 } else {
403 $percent = "";
404 }
405
406 $answerdata->answers[] = array($data, $percent);
407 $i++;
408 }
409 $n++;
410 $answerpage->answerdata = $answerdata;
411 }
412 return $answerpage;
413 }
414 public function get_jumps() {
415 global $DB;
416 // The jumps for matching question type is stored
417 // in the 3rd and 4rth answer record.
418 $jumps = array();
04932409 419 if ($answers = $DB->get_records("lesson_answers", array("lessonid" => $this->lesson->id, "pageid" => $this->properties->id), 'id', '*', 0, 2)) {
0a4abb73
SH
420 foreach ($answers as $answer) {
421 $jumps[] = $this->get_jump_name($answer->jumpto);
422 }
9170dea7
SH
423 } else {
424 $jumps[] = $this->get_jump_name($this->properties->nextpageid);
0a4abb73
SH
425 }
426 return $jumps;
427 }
428}
429
430class lesson_add_page_form_matching extends lesson_add_page_form_base {
431
432 public $qtype = 'matching';
433 public $qtypestring = 'matching';
434
435 public function custom_definition() {
436
437 $this->_form->addElement('header', 'correctresponse', get_string('correctresponse', 'lesson'));
cb2c1963 438 $this->_form->addElement('editor', 'answer_editor[0]', get_string('correctresponse', 'lesson'), array('rows'=>'4', 'columns'=>'80'), array('noclean'=>true));
76f5f66f 439 $this->add_jumpto(0, get_string('correctanswerjump','lesson'), LESSON_NEXTPAGE);
a675ada5 440 $this->add_score(0, get_string("correctanswerscore", "lesson"));
0a4abb73
SH
441
442 $this->_form->addElement('header', 'wrongresponse', get_string('wrongresponse', 'lesson'));
cb2c1963 443 $this->_form->addElement('editor', 'answer_editor[1]', get_string('wrongresponse', 'lesson'), array('rows'=>'4', 'columns'=>'80'), array('noclean'=>true));
76f5f66f 444 $this->add_jumpto(1, get_string('wronganswerjump','lesson'), LESSON_THISPAGE);
a675ada5 445 $this->add_score(1, get_string("wronganswerscore", "lesson"));
0a4abb73
SH
446
447 for ($i = 2; $i < $this->_customdata['lesson']->maxanswers+2; $i++) {
448 $this->_form->addElement('header', 'matchingpair'.($i-1), get_string('matchingpair', 'lesson', $i-1));
a675ada5
PS
449 $this->add_answer($i, NULL, ($i < 4));
450 $this->add_response($i, get_string('matchesanswer','lesson'), ($i < 4));
0a4abb73
SH
451 }
452 }
453}
454
455
456class lesson_display_answer_form_matching extends moodleform {
457
458 public function definition() {
459 global $USER, $OUTPUT;
460 $mform = $this->_form;
461 $answers = $this->_customdata['answers'];
462 $useranswers = $this->_customdata['useranswers'];
463 $responseoptions = $this->_customdata['responseoptions'];
464 $lessonid = $this->_customdata['lessonid'];
465 $contents = $this->_customdata['contents'];
466
467 $mform->addElement('header', 'pageheader', $OUTPUT->box($contents, 'contents'));
468
469 $options = new stdClass;
470 $options->para = false;
471 $options->noclean = true;
472
473 $mform->addElement('hidden', 'id');
474 $mform->setType('id', PARAM_INT);
475
476 $mform->addElement('hidden', 'pageid');
477 $mform->setType('pageid', PARAM_INT);
478
479 $i = 0;
480 foreach ($answers as $answer) {
481 $mform->addElement('html', '<div class="answeroption">');
482 if ($answer->response != NULL) {
01c37ef1 483 $mform->addElement('select', 'response['.$answer->id.']', format_text($answer->answer,$answer->answerformat,$options), $responseoptions);
0a4abb73
SH
484 $mform->setType('response['.$answer->id.']', PARAM_TEXT);
485 if (isset($USER->modattempts[$lessonid])) {
7ef692b1 486 $mform->setDefault('response['.$answer->id.']', htmlspecialchars(trim($answers[$useranswers[$i]]->response))); //TODO: this is suspicious
0a4abb73
SH
487 } else {
488 $mform->setDefault('response['.$answer->id.']', 'answeroption');
489 }
490 }
491 $mform->addElement('html', '</div>');
492 $i++;
493 }
494
495 $this->add_action_buttons(null, get_string("pleasematchtheabovepairs", "lesson"));
496 }
497
498}