Commit | Line | Data |
---|---|---|
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 | * Short answer | |
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 |
27 | defined('MOODLE_INTERNAL') || die(); |
28 | ||
0a4abb73 SH |
29 | /** Short answer question type */ |
30 | define("LESSON_PAGE_SHORTANSWER", "1"); | |
31 | ||
32 | class lesson_page_type_shortanswer extends lesson_page { | |
33 | ||
34 | protected $type = lesson_page::TYPE_QUESTION; | |
35 | protected $typeidstring = 'shortanswer'; | |
36 | protected $typeid = LESSON_PAGE_SHORTANSWER; | |
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 = new lesson_display_answer_form_shortanswer($CFG->wwwroot.'/mod/lesson/continue.php', array('contents'=>$this->get_contents())); | |
54 | $data = new stdClass; | |
55 | $data->id = $PAGE->cm->id; | |
56 | $data->pageid = $this->properties->id; | |
57 | if (isset($USER->modattempts[$this->lesson->id])) { | |
58 | $data->answer = s($attempt->useranswer); | |
59 | } | |
60 | $mform->set_data($data); | |
61 | return $mform->display(); | |
62 | } | |
63 | public function check_answer() { | |
64 | global $CFG; | |
65 | $result = parent::check_answer(); | |
66 | ||
67 | $mform = new lesson_display_answer_form_shortanswer($CFG->wwwroot.'/mod/lesson/continue.php', array('contents'=>$this->get_contents())); | |
68 | $data = $mform->get_data(); | |
69 | require_sesskey(); | |
70 | ||
71 | $studentanswer = trim($data->answer); | |
72 | if ($studentanswer === '') { | |
73 | $result->noanswer = true; | |
74 | return $result; | |
75 | } | |
76 | $studentanswer = s($studentanswer); | |
77 | ||
78 | $i=0; | |
79 | $answers = $this->get_answers(); | |
80 | foreach ($answers as $answer) { | |
81 | $i++; | |
82 | $expectedanswer = $answer->answer; // for easier handling of $answer->answer | |
83 | $ismatch = false; | |
84 | $markit = false; | |
85 | $useregexp = ($this->qoption); | |
86 | ||
87 | if ($useregexp) { //we are using 'normal analysis', which ignores case | |
88 | $ignorecase = ''; | |
89 | if (substr($expectedanswer,0,-2) == '/i') { | |
90 | $expectedanswer = substr($expectedanswer,0,-2); | |
91 | $ignorecase = 'i'; | |
92 | } | |
93 | } else { | |
94 | $expectedanswer = str_replace('*', '#####', $expectedanswer); | |
95 | $expectedanswer = preg_quote($expectedanswer, '/'); | |
96 | $expectedanswer = str_replace('#####', '.*', $expectedanswer); | |
97 | } | |
98 | // see if user typed in any of the correct answers | |
99 | if ((!$this->lesson->custom && $this->lesson->jumpto_is_correct($pageid, $answer->jumpto)) or ($this->lesson->custom && $answer->score > 0) ) { | |
100 | if (!$useregexp) { // we are using 'normal analysis', which ignores case | |
101 | if (preg_match('/^'.$expectedanswer.'$/i',$studentanswer)) { | |
102 | $ismatch = true; | |
103 | } | |
104 | } else { | |
105 | if (preg_match('/^'.$expectedanswer.'$/'.$ignorecase,$studentanswer)) { | |
106 | $ismatch = true; | |
107 | } | |
108 | } | |
109 | if ($ismatch == true) { | |
110 | $result->correctanswer = true; | |
111 | } | |
112 | } else { | |
113 | if (!$useregexp) { //we are using 'normal analysis' | |
114 | // see if user typed in any of the wrong answers; don't worry about case | |
115 | if (preg_match('/^'.$expectedanswer.'$/i',$studentanswer)) { | |
116 | $ismatch = true; | |
117 | } | |
118 | } else { // we are using regular expressions analysis | |
119 | $startcode = substr($expectedanswer,0,2); | |
120 | switch ($startcode){ | |
121 | //1- check for absence of required string in $studentanswer (coded by initial '--') | |
122 | case "--": | |
123 | $expectedanswer = substr($expectedanswer,2); | |
124 | if (!preg_match('/^'.$expectedanswer.'$/'.$ignorecase,$studentanswer)) { | |
125 | $ismatch = true; | |
126 | } | |
127 | break; | |
128 | //2- check for code for marking wrong strings (coded by initial '++') | |
129 | case "++": | |
130 | $expectedanswer=substr($expectedanswer,2); | |
131 | $markit = true; | |
132 | //check for one or several matches | |
133 | if (preg_match_all('/'.$expectedanswer.'/'.$ignorecase,$studentanswer, $matches)) { | |
134 | $ismatch = true; | |
135 | $nb = count($matches[0]); | |
136 | $original = array(); | |
137 | $marked = array(); | |
138 | $fontStart = '<span class="incorrect matches">'; | |
139 | $fontEnd = '</span>'; | |
140 | for ($i = 0; $i < $nb; $i++) { | |
141 | array_push($original,$matches[0][$i]); | |
142 | array_push($marked,$fontStart.$matches[0][$i].$fontEnd); | |
143 | } | |
144 | $studentanswer = str_replace($original, $marked, $studentanswer); | |
145 | } | |
146 | break; | |
147 | //3- check for wrong answers belonging neither to -- nor to ++ categories | |
148 | default: | |
149 | if (preg_match('/^'.$expectedanswer.'$/'.$ignorecase,$studentanswer, $matches)) { | |
150 | $ismatch = true; | |
151 | } | |
152 | break; | |
153 | } | |
154 | $result->correctanswer = false; | |
155 | } | |
156 | } | |
157 | if ($ismatch) { | |
158 | $result->newpageid = $answer->jumpto; | |
159 | if (trim(strip_tags($answer->response))) { | |
160 | $result->response = $answer->response; | |
161 | } | |
162 | $result->answerid = $answer->id; | |
163 | break; // quit answer analysis immediately after a match has been found | |
164 | } | |
165 | } | |
166 | $result->studentanswer = $result->userresponse = $studentanswer; | |
167 | return $result; | |
168 | } | |
169 | ||
170 | public function option_description_string() { | |
171 | if ($this->properties->qoption) { | |
172 | return " - ".get_string("casesensitive", "lesson"); | |
173 | } | |
174 | return parent::option_description_string(); | |
175 | } | |
176 | ||
177 | public function display_answers(html_table $table) { | |
178 | $answers = $this->get_answers(); | |
179 | $options = new stdClass; | |
180 | $options->noclean = true; | |
181 | $options->para = false; | |
182 | $i = 1; | |
183 | foreach ($answers as $answer) { | |
184 | $cells = array(); | |
185 | if ($this->lesson->custom && $answer->score > 0) { | |
186 | // if the score is > 0, then it is correct | |
187 | $cells[] = '<span class="labelcorrect">'.get_string("answer", "lesson")." $i</span>: \n"; | |
188 | } else if ($this->lesson->custom) { | |
189 | $cells[] = '<span class="label">'.get_string("answer", "lesson")." $i</span>: \n"; | |
190 | } else if ($this->lesson->jumpto_is_correct($this->properties->id, $answer->jumpto)) { | |
191 | // underline correct answers | |
192 | $cells[] = '<span class="correct">'.get_string("answer", "lesson")." $i</span>: \n"; | |
193 | } else { | |
194 | $cells[] = '<span class="labelcorrect">'.get_string("answer", "lesson")." $i</span>: \n"; | |
195 | } | |
01c37ef1 | 196 | $cells[] = format_text($answer->answer, $answer->answerformat, $options); |
8cea545e | 197 | $table->data[] = new html_table_row($cells); |
0a4abb73 SH |
198 | |
199 | $cells = array(); | |
200 | $cells[] = "<span class=\"label\">".get_string("response", "lesson")." $i</span>"; | |
01c37ef1 | 201 | $cells[] = format_text($answer->response, $answer->responseformat, $options); |
8cea545e | 202 | $table->data[] = new html_table_row($cells); |
0a4abb73 SH |
203 | |
204 | $cells = array(); | |
205 | $cells[] = "<span class=\"label\">".get_string("score", "lesson").'</span>'; | |
206 | $cells[] = $answer->score; | |
8cea545e | 207 | $table->data[] = new html_table_row($cells); |
0a4abb73 SH |
208 | |
209 | $cells = array(); | |
210 | $cells[] = "<span class=\"label\">".get_string("jump", "lesson").'</span>'; | |
211 | $cells[] = $this->get_jump_name($answer->jumpto); | |
8cea545e | 212 | $table->data[] = new html_table_row($cells); |
0a4abb73 SH |
213 | if ($i === 1){ |
214 | $table->data[count($table->data)-1]->cells[0]->style = 'width:20%;'; | |
215 | } | |
216 | $i++; | |
217 | } | |
218 | return $table; | |
219 | } | |
220 | public function stats(array &$pagestats, $tries) { | |
221 | if(count($tries) > $this->lesson->maxattempts) { // if there are more tries than the max that is allowed, grab the last "legal" attempt | |
222 | $temp = $tries[$this->lesson->maxattempts - 1]; | |
223 | } else { | |
224 | // else, user attempted the question less than the max, so grab the last one | |
225 | $temp = end($tries); | |
226 | } | |
227 | if (isset($pagestats[$temp->pageid][$temp->useranswer])) { | |
228 | $pagestats[$temp->pageid][$temp->useranswer]++; | |
229 | } else { | |
230 | $pagestats[$temp->pageid][$temp->useranswer] = 1; | |
231 | } | |
232 | if (isset($pagestats[$temp->pageid]["total"])) { | |
233 | $pagestats[$temp->pageid]["total"]++; | |
234 | } else { | |
235 | $pagestats[$temp->pageid]["total"] = 1; | |
236 | } | |
237 | return true; | |
238 | } | |
239 | ||
240 | public function report_answers($answerpage, $answerdata, $useranswer, $pagestats, &$i, &$n) { | |
241 | $answers = $this->get_answers(); | |
242 | $formattextdefoptions = new stdClass; | |
243 | $formattextdefoptions->para = false; //I'll use it widely in this page | |
244 | foreach ($answers as $answer) { | |
245 | if ($useranswer == null && $i == 0) { | |
246 | // I have the $i == 0 because it is easier to blast through it all at once. | |
247 | if (isset($pagestats[$this->properties->id])) { | |
248 | $stats = $pagestats[$this->properties->id]; | |
249 | $total = $stats["total"]; | |
250 | unset($stats["total"]); | |
251 | foreach ($stats as $valentered => $ntimes) { | |
252 | $data = '<input type="text" size="50" disabled="disabled" readonly="readonly" value="'.s($valentered).'" />'; | |
253 | $percent = $ntimes / $total * 100; | |
254 | $percent = round($percent, 2); | |
255 | $percent .= "% ".get_string("enteredthis", "lesson"); | |
256 | $answerdata->answers[] = array($data, $percent); | |
257 | } | |
258 | } else { | |
259 | $answerdata->answers[] = array(get_string("nooneansweredthisquestion", "lesson"), " "); | |
260 | } | |
261 | $i++; | |
262 | } else if ($useranswer != null && ($answer->id == $useranswer->answerid || ($answer == end($answers) && empty($answerdata)))) { | |
263 | // get in here when what the user entered is not one of the answers | |
264 | $data = '<input type="text" size="50" disabled="disabled" readonly="readonly" value="'.s($useranswer->useranswer).'">'; | |
265 | if (isset($pagestats[$this->properties->id][$useranswer->useranswer])) { | |
266 | $percent = $pagestats[$this->properties->id][$useranswer->useranswer] / $pagestats[$this->properties->id]["total"] * 100; | |
267 | $percent = round($percent, 2); | |
268 | $percent .= "% ".get_string("enteredthis", "lesson"); | |
269 | } else { | |
270 | $percent = get_string("nooneenteredthis", "lesson"); | |
271 | } | |
272 | $answerdata->answers[] = array($data, $percent); | |
273 | ||
274 | if ($answer->id == $useranswer->answerid) { | |
275 | if ($answer->response == NULL) { | |
276 | if ($useranswer->correct) { | |
277 | $answerdata->response = get_string("thatsthecorrectanswer", "lesson"); | |
278 | } else { | |
279 | $answerdata->response = get_string("thatsthewronganswer", "lesson"); | |
280 | } | |
281 | } else { | |
282 | $answerdata->response = $answer->response; | |
283 | } | |
284 | if ($this->lesson->custom) { | |
285 | $answerdata->score = get_string("pointsearned", "lesson").": ".$answer->score; | |
286 | } elseif ($useranswer->correct) { | |
287 | $answerdata->score = get_string("receivedcredit", "lesson"); | |
288 | } else { | |
289 | $answerdata->score = get_string("didnotreceivecredit", "lesson"); | |
290 | } | |
291 | } else { | |
292 | $answerdata->response = get_string("thatsthewronganswer", "lesson"); | |
293 | if ($this->lesson->custom) { | |
294 | $answerdata->score = get_string("pointsearned", "lesson").": 0"; | |
295 | } else { | |
296 | $answerdata->score = get_string("didnotreceivecredit", "lesson"); | |
297 | } | |
298 | } | |
299 | } | |
300 | $answerpage->answerdata = $answerdata; | |
301 | } | |
302 | return $answerpage; | |
303 | } | |
304 | } | |
305 | ||
306 | ||
307 | class lesson_add_page_form_shortanswer extends lesson_add_page_form_base { | |
308 | public $qtype = 'shortanswer'; | |
309 | public $qtypestring = 'shortanswer'; | |
310 | ||
311 | public function custom_definition() { | |
312 | ||
f31daba4 PS |
313 | $this->_form->addElement('checkbox', 'qoption', get_string('options', 'lesson'), get_string('casesensitive', 'lesson')); //oh my, this is a regex option! |
314 | $this->_form->setDefault('qoption', 0); | |
4c80a990 | 315 | $this->_form->addHelpButton('qoption', 'casesensitive', 'lesson'); |
0a4abb73 SH |
316 | |
317 | for ($i = 0; $i < $this->_customdata['lesson']->maxanswers; $i++) { | |
318 | $this->_form->addElement('header', 'answertitle'.$i, get_string('answer').' '.($i+1)); | |
319 | $this->add_answer($i); | |
320 | $this->add_response($i); | |
cc6d233d | 321 | $this->add_jumpto($i, NULL, ($i == 0 ? LESSON_NEXTPAGE : LESSON_THISPAGE)); |
0a4abb73 SH |
322 | $this->add_score($i, null, ($i===0)?1:0); |
323 | } | |
324 | } | |
325 | } | |
326 | ||
327 | class lesson_display_answer_form_shortanswer extends moodleform { | |
328 | ||
329 | public function definition() { | |
330 | global $OUTPUT; | |
331 | $mform = $this->_form; | |
332 | $contents = $this->_customdata['contents']; | |
333 | ||
334 | $mform->addElement('header', 'pageheader', $OUTPUT->box($contents, 'contents')); | |
335 | ||
336 | $options = new stdClass; | |
337 | $options->para = false; | |
338 | $options->noclean = true; | |
339 | ||
340 | $mform->addElement('hidden', 'id'); | |
341 | $mform->setType('id', PARAM_INT); | |
342 | ||
343 | $mform->addElement('hidden', 'pageid'); | |
344 | $mform->setType('pageid', PARAM_INT); | |
345 | ||
346 | $mform->addElement('text', 'answer', get_string('youranswer', 'lesson'), array('size'=>'50', 'maxlength'=>'200')); | |
347 | $mform->setType('answer', PARAM_TEXT); | |
348 | ||
349 | $this->add_action_buttons(null, get_string("pleaseenteryouranswerinthebox", "lesson")); | |
350 | } | |
351 | ||
4c80a990 | 352 | } |