6a66ad797e65414a5d5149ce73b5c17a7e33cbd0
[moodle.git] / mod / lesson / pagetypes / matching.php
1 <?php
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/>.
18 /**
19  * Matching
20  *
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
25  **/
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() {
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     }
61     protected function make_answer_form($attempt=null) {
62         global $USER, $CFG;
63         // don't shuffle answers (could be an option??)
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         }
73         $responseoptions = array(''=>get_string('choosedots'));
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         }
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     }
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;
102         $answers = array();
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);
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'];
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     }
134     public function check_answer() {
135         global $CFG;
137         $formattextdefoptions = new object();
138         $formattextdefoptions->noclean = true;
139         $formattextdefoptions->para = false;
141         $result = parent::check_answer();
143         $mform = $this->make_answer_form();
145         $data = $mform->get_data();
146         require_sesskey();
148         if (!$data) {
149             redirect(new moodle_url('/mod/lesson/view.php', array('id'=>$PAGE->cm->id, 'pageid'=>$this->properties->id)));
150         }
152         $response = $data->response;
153         if (!is_array($response)) {
154             $result->noanswer = true;
155             return $result;
156         }
158         $answers = $this->get_answers();
160         $correct = array_shift($answers);
161         $wrong   = array_shift($answers);
163         foreach ($answers as $key=>$answer) {
164             if ($answer->answer === '' or $answer->response === '') {
165                 // incomplete option!
166                 unset($answers[$key]);
167             }
168         }
169         // get he users exact responses for record keeping
170         $hits = 0;
171         $userresponse = array();
172         foreach ($response as $key => $value) {
173             foreach($answers as $answer) {
174                 if ($value === $answer->response) {
175                     $userresponse[] = $answer->id;
176                 }
177                 if ((int)$answer->id === (int)$key) {
178                     $result->studentanswer .= '<br />'.format_text($answer->answer, $answer->answerformat, $formattextdefoptions).' = '.$value;
179                 }
180                 if ((int)$answer->id === (int)$key and $value === $answer->response) {
181                     $hits++;
182                 }
183             }
184         }
185         $result->userresponse = implode(",", $userresponse);
187         if ($hits == count($answers)) {
188             $result->correctanswer = true;
189             $result->response      = format_text($correct->answer, $correct->answerformat, $formattextdefoptions);
190             $result->answerid      = $correct->id;
191             $result->newpageid     = $correct->jumpto;
192         } else {
193             $result->correctanswer = false;
194             $result->response      = format_text($wrong->answer, $wrong->answerformat, $formattextdefoptions);
195             $result->answerid      = $wrong->id;
196             $result->newpageid     = $wrong->jumpto;
197         }
199         return $result;
200     }
202     public function option_description_string() {
203         return get_string("firstanswershould", "lesson");
204     }
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;
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                     }
223                     $cells[] = format_text($answer->answer, $answer->answerformat, $options);
224                     $table->data[] = new html_table_row($cells);
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                 }
240                 $cells[] = format_text($answer->answer, $answer->answerformat, $options);
241                 $table->data[] = new html_table_row($cells);
243                 $cells = array();
244                 $cells[] = '<span class="label">'.get_string("matchesanswer", "lesson")." $i</span>: ";
245                 $cells[] = format_text($answer->response, $answer->responseformat, $options);
246                 $table->data[] = new html_table_row($cells);
247             }
249             if ($i == 1) {
250                 $cells = array();
251                 $cells[] = '<span class="label">'.get_string("correctanswerscore", "lesson")." $i</span>: ";
252                 $cells[] = $answer->score;
253                 $table->data[] = new html_table_row($cells);
255                 $cells = array();
256                 $cells[] = '<span class="label">'.get_string("correctanswerjump", "lesson")." $i</span>: ";
257                 $cells[] = $this->get_jump_name($answer->jumpto);
258                 $table->data[] = new html_table_row($cells);
259             } elseif ($i == 2) {
260                 $cells = array();
261                 $cells[] = '<span class="label">'.get_string("wronganswerscore", "lesson")." $i</span>: ";
262                 $cells[] = $answer->score;
263                 $table->data[] = new html_table_row($cells);
265                 $cells = array();
266                 $cells[] = '<span class="label">'.get_string("wronganswerjump", "lesson")." $i</span>: ";
267                 $cells[] = $this->get_jump_name($answer->jumpto);
268                 $table->data[] = new html_table_row($cells);
269             }
271             if ($i === 1){
272                 $table->data[count($table->data)-1]->cells[0]->style = 'width:20%;';
273             }
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;
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);
285         $DB->update_record("lesson_pages", $properties);
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             }
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])) {
300                     $this->answers[$i]->response = $properties->response_editor[$i]['text'];
301                     $this->answers[$i]->responseformat = $properties->response_editor[$i]['format'];
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                 }
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                 }
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                 }
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                 }
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();
419         if ($answers = $DB->get_records("lesson_answers", array("lessonid" => $this->lesson->id, "pageid" => $this->properties->id), 'id', '*', 0, 2)) {
420             foreach ($answers as $answer) {
421                 $jumps[] = $this->get_jump_name($answer->jumpto);
422             }
423         }
424         return $jumps;
425     }
428 class lesson_add_page_form_matching extends lesson_add_page_form_base {
430     public $qtype = 'matching';
431     public $qtypestring = 'matching';
433     public function custom_definition() {
435         $this->_form->addElement('header', 'correctresponse', get_string('correctresponse', 'lesson'));
436         $this->_form->addElement('editor', 'answer_editor[0]', get_string('correctresponse', 'lesson'), array('rows'=>'4', 'columns'=>'80'), array('noclean'=>true));
437         $this->add_jumpto(0, get_string('correctanswerjump','lesson'), LESSON_NEXTPAGE);
438         $this->add_score(0, get_string("correctanswerscore", "lesson"));
440         $this->_form->addElement('header', 'wrongresponse', get_string('wrongresponse', 'lesson'));
441         $this->_form->addElement('editor', 'answer_editor[1]', get_string('wrongresponse', 'lesson'), array('rows'=>'4', 'columns'=>'80'), array('noclean'=>true));
442         $this->add_jumpto(1, get_string('wronganswerjump','lesson'), LESSON_THISPAGE);
443         $this->add_score(1, get_string("wronganswerscore", "lesson"));
445         for ($i = 2; $i < $this->_customdata['lesson']->maxanswers+2; $i++) {
446             $this->_form->addElement('header', 'matchingpair'.($i-1), get_string('matchingpair', 'lesson', $i-1));
447             $this->add_answer($i, NULL, ($i < 4));
448             $this->add_response($i, get_string('matchesanswer','lesson'), ($i < 4));
449         }
450     }
454 class lesson_display_answer_form_matching extends moodleform {
456     public function definition() {
457         global $USER, $OUTPUT;
458         $mform = $this->_form;
459         $answers = $this->_customdata['answers'];
460         $useranswers = $this->_customdata['useranswers'];
461         $responseoptions = $this->_customdata['responseoptions'];
462         $lessonid = $this->_customdata['lessonid'];
463         $contents = $this->_customdata['contents'];
465         $mform->addElement('header', 'pageheader', $OUTPUT->box($contents, 'contents'));
467         $options = new stdClass;
468         $options->para = false;
469         $options->noclean = true;
471         $mform->addElement('hidden', 'id');
472         $mform->setType('id', PARAM_INT);
474         $mform->addElement('hidden', 'pageid');
475         $mform->setType('pageid', PARAM_INT);
477         $i = 0;
478         foreach ($answers as $answer) {
479             $mform->addElement('html', '<div class="answeroption">');
480             if ($answer->response != NULL) {
481                 $mform->addElement('select', 'response['.$answer->id.']', format_text($answer->answer,$answer->answerformat,$options), $responseoptions);
482                 $mform->setType('response['.$answer->id.']', PARAM_TEXT);
483                 if (isset($USER->modattempts[$lessonid])) {
484                     $mform->setDefault('response['.$answer->id.']', htmlspecialchars(trim($answers[$useranswers[$t]]->response)));
485                 } else {
486                     $mform->setDefault('response['.$answer->id.']', 'answeroption');
487                 }
488             }
489             $mform->addElement('html', '</div>');
490             $i++;
491         }
493         $this->add_action_buttons(null, get_string("pleasematchtheabovepairs", "lesson"));
494     }