Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 *
21
 * @package mod_lesson
22
 * @copyright  2009 Sam Hemelryk
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 **/
25
 
26
defined('MOODLE_INTERNAL') || die();
27
 
28
 /** Short answer question type */
29
define("LESSON_PAGE_SHORTANSWER",   "1");
30
 
31
class lesson_page_type_shortanswer extends lesson_page {
32
 
33
    protected $type = lesson_page::TYPE_QUESTION;
34
    protected $typeidstring = 'shortanswer';
35
    protected $typeid = LESSON_PAGE_SHORTANSWER;
36
    protected $string = null;
37
 
38
    public function get_typeid() {
39
        return $this->typeid;
40
    }
41
    public function get_typestring() {
42
        if ($this->string===null) {
43
            $this->string = get_string($this->typeidstring, 'lesson');
44
        }
45
        return $this->string;
46
    }
47
    public function get_idstring() {
48
        return $this->typeidstring;
49
    }
50
    public function display($renderer, $attempt) {
51
        global $USER, $CFG, $PAGE;
52
        $mform = new lesson_display_answer_form_shortanswer($CFG->wwwroot.'/mod/lesson/continue.php', array('contents'=>$this->get_contents(), 'lessonid'=>$this->lesson->id));
53
        $data = new stdClass;
54
        $data->id = $PAGE->cm->id;
55
        $data->pageid = $this->properties->id;
56
        if (isset($USER->modattempts[$this->lesson->id])) {
57
            $data->answer = s($attempt->useranswer);
58
        }
59
        $mform->set_data($data);
60
 
61
        // Trigger an event question viewed.
62
        $eventparams = array(
63
            'context' => context_module::instance($PAGE->cm->id),
64
            'objectid' => $this->properties->id,
65
            'other' => array(
66
                    'pagetype' => $this->get_typestring()
67
                )
68
            );
69
 
70
        $event = \mod_lesson\event\question_viewed::create($eventparams);
71
        $event->trigger();
72
        return $mform->display();
73
    }
74
 
75
    /**
76
     * Creates answers for this page type.
77
     *
78
     * @param  object $properties The answer properties.
79
     */
80
    public function create_answers($properties) {
81
        if (isset($properties->enableotheranswers) && $properties->enableotheranswers) {
82
            $properties->response_editor = array_values($properties->response_editor);
83
            $properties->jumpto = array_values($properties->jumpto);
84
            $properties->score = array_values($properties->score);
85
            $wrongresponse = end($properties->response_editor);
86
            $wrongkey = key($properties->response_editor);
87
            $properties->answer_editor[$wrongkey] = LESSON_OTHER_ANSWERS;
88
        }
89
        parent::create_answers($properties);
90
    }
91
 
92
    /**
93
     * Update the answers for this page type.
94
     *
95
     * @param  object $properties The answer properties.
96
     * @param  context $context The context for this module.
97
     * @param  int $maxbytes The maximum bytes for any uploades.
98
     */
99
    public function update($properties, $context = null, $maxbytes = null) {
100
        if ($properties->enableotheranswers) {
101
            $properties->response_editor = array_values($properties->response_editor);
102
            $properties->jumpto = array_values($properties->jumpto);
103
            $properties->score = array_values($properties->score);
104
            $wrongresponse = end($properties->response_editor);
105
            $wrongkey = key($properties->response_editor);
106
            $properties->answer_editor[$wrongkey] = LESSON_OTHER_ANSWERS;
107
        }
108
        parent::update($properties, $context, $maxbytes);
109
    }
110
 
111
 
112
    public function check_answer() {
113
        global $CFG;
114
        $result = parent::check_answer();
115
 
116
        $mform = new lesson_display_answer_form_shortanswer($CFG->wwwroot.'/mod/lesson/continue.php', array('contents'=>$this->get_contents()));
117
        $data = $mform->get_data();
118
        require_sesskey();
119
 
120
        $studentanswer = trim($data->answer);
121
        if ($studentanswer === '') {
122
            $result->noanswer = true;
123
            return $result;
124
        }
125
 
126
        $i=0;
127
        $answers = $this->get_answers();
128
        foreach ($answers as $answer) {
129
            $answer = parent::rewrite_answers_urls($answer, false);
130
            $i++;
131
            // Applying PARAM_TEXT as it is applied to the answer submitted by the user.
132
            $expectedanswer  = clean_param($answer->answer, PARAM_TEXT);
133
            $ismatch         = false;
134
            $markit          = false;
135
            $useregexp       = ($this->qoption);
136
 
137
            if ($useregexp) { //we are using 'normal analysis', which ignores case
138
                $ignorecase = '';
139
                if (substr($expectedanswer, -2) == '/i') {
140
                    $expectedanswer = substr($expectedanswer, 0, -2);
141
                    $ignorecase = 'i';
142
                }
143
            } else {
144
                $expectedanswer = str_replace('*', '%@@%@@%', $expectedanswer);
145
                $expectedanswer = preg_quote($expectedanswer, '/');
146
                $expectedanswer = str_replace('%@@%@@%', '.*', $expectedanswer);
147
            }
148
            // see if user typed in any of the correct answers
149
            if ((!$this->lesson->custom && $this->lesson->jumpto_is_correct($this->properties->id, $answer->jumpto)) or ($this->lesson->custom && $answer->score > 0) ) {
150
                if (!$useregexp) { // we are using 'normal analysis', which ignores case
151
                    if (preg_match('/^'.$expectedanswer.'$/i',$studentanswer)) {
152
                        $ismatch = true;
153
                    }
154
                } else {
155
                    if (preg_match('/^'.$expectedanswer.'$/'.$ignorecase,$studentanswer)) {
156
                        $ismatch = true;
157
                    }
158
                }
159
                if ($ismatch == true) {
160
                    $result->correctanswer = true;
161
                }
162
            } else {
163
               if (!$useregexp) { //we are using 'normal analysis'
164
                    // see if user typed in any of the wrong answers; don't worry about case
165
                    if (preg_match('/^'.$expectedanswer.'$/i',$studentanswer)) {
166
                        $ismatch = true;
167
                    }
168
                } else { // we are using regular expressions analysis
169
                    $startcode = substr($expectedanswer,0,2);
170
                    switch ($startcode){
171
                        //1- check for absence of required string in $studentanswer (coded by initial '--')
172
                        case "--":
173
                            $expectedanswer = substr($expectedanswer,2);
174
                            if (!preg_match('/^'.$expectedanswer.'$/'.$ignorecase,$studentanswer)) {
175
                                $ismatch = true;
176
                            }
177
                            break;
178
                        //2- check for code for marking wrong strings (coded by initial '++')
179
                        case "++":
180
                            $expectedanswer=substr($expectedanswer,2);
181
                            $markit = true;
182
                            //check for one or several matches
183
                            if (preg_match_all('/'.$expectedanswer.'/'.$ignorecase,$studentanswer, $matches)) {
184
                                $ismatch   = true;
185
                                $nb        = count($matches[0]);
186
                                $original  = array();
187
                                $marked    = array();
188
                                $fontStart = '<span class="incorrect matches">';
189
                                $fontEnd   = '</span>';
190
                                for ($i = 0; $i < $nb; $i++) {
191
                                    array_push($original,$matches[0][$i]);
192
                                    array_push($marked,$fontStart.$matches[0][$i].$fontEnd);
193
                                }
194
                                $studentanswer = str_replace($original, $marked, $studentanswer);
195
                            }
196
                            break;
197
                        //3- check for wrong answers belonging neither to -- nor to ++ categories
198
                        default:
199
                            if (preg_match('/^'.$expectedanswer.'$/'.$ignorecase,$studentanswer, $matches)) {
200
                                $ismatch = true;
201
                            }
202
                            break;
203
                    }
204
                    $result->correctanswer = false;
205
                }
206
            }
207
            if ($ismatch) {
208
                $result->newpageid = $answer->jumpto;
209
                $options = new stdClass();
210
                $options->para = false;
211
                $options->noclean = true;
212
                $result->response = format_text($answer->response, $answer->responseformat, $options);
213
                $result->answerid = $answer->id;
214
                break; // quit answer analysis immediately after a match has been found
215
            }
216
        }
217
 
218
        // We could check here to see if we have a wrong answer jump to use.
219
        if ($result->answerid == 0) {
220
            // Use the all other answers jump details if it is set up.
221
            $lastanswer = end($answers);
222
            // Double check that this is the @#wronganswer#@ answer.
223
            if (strpos($lastanswer->answer, LESSON_OTHER_ANSWERS) !== false) {
224
                $otheranswers = end($answers);
225
                $result->newpageid = $otheranswers->jumpto;
226
                $options = new stdClass();
227
                $options->para = false;
228
                $result->response = format_text($otheranswers->response, $otheranswers->responseformat, $options);
229
                // Does this also need to do the jumpto_is_correct?
230
                if ($this->lesson->custom) {
231
                    $result->correctanswer = ($otheranswers->score > 0);
232
                }
233
                $result->answerid = $otheranswers->id;
234
            }
235
        }
236
 
237
        $result->userresponse = $studentanswer;
238
        //clean student answer as it goes to output.
239
        $result->studentanswer = s($studentanswer);
240
        return $result;
241
    }
242
 
243
    public function option_description_string() {
244
        if ($this->properties->qoption) {
245
            return " - ".get_string("casesensitive", "lesson");
246
        }
247
        return parent::option_description_string();
248
    }
249
 
250
    public function display_answers(html_table $table) {
251
        $answers = $this->get_answers();
252
        $options = new stdClass;
253
        $options->noclean = true;
254
        $options->para = false;
255
        $i = 1;
256
        foreach ($answers as $answer) {
257
            $answer = parent::rewrite_answers_urls($answer, false);
258
            $cells = array();
259
            if ($this->lesson->custom && $answer->score > 0) {
260
                // if the score is > 0, then it is correct
261
                $cells[] = '<label class="correct">' . get_string('answer', 'lesson') . ' ' . $i . '</label>:';
262
            } else if ($this->lesson->custom) {
263
                $cells[] = '<label>' . get_string('answer', 'lesson') . ' ' . $i . '</label>:';
264
            } else if ($this->lesson->jumpto_is_correct($this->properties->id, $answer->jumpto)) {
265
                // underline correct answers
266
                $cells[] = '<span class="correct">' . get_string('answer', 'lesson') . ' ' . $i . '</span>:' . "\n";
267
            } else {
268
                $cells[] = '<label class="correct">' . get_string('answer', 'lesson') . ' ' . $i . '</label>:';
269
            }
270
            $cells[] = format_text($answer->answer, $answer->answerformat, $options);
271
            $table->data[] = new html_table_row($cells);
272
 
273
            $cells = array();
274
            $cells[] = '<label>' . get_string('response', 'lesson') . ' ' . $i . '</label>:';
275
            $cells[] = format_text($answer->response, $answer->responseformat, $options);
276
            $table->data[] = new html_table_row($cells);
277
 
278
            $cells = array();
279
            $cells[] = '<label>' . get_string('score', 'lesson') . '</label>:';
280
            $cells[] = $answer->score;
281
            $table->data[] = new html_table_row($cells);
282
 
283
            $cells = array();
284
            $cells[] = '<label>' . get_string('jump', 'lesson') . '</label>:';
285
            $cells[] = $this->get_jump_name($answer->jumpto);
286
            $table->data[] = new html_table_row($cells);
287
            if ($i === 1){
288
                $table->data[count($table->data)-1]->cells[0]->style = 'width:20%;';
289
            }
290
            $i++;
291
        }
292
        return $table;
293
    }
294
    public function stats(array &$pagestats, $tries) {
295
        $temp = $this->lesson->get_last_attempt($tries);
296
        if (isset($pagestats[$temp->pageid][$temp->useranswer])) {
297
            $pagestats[$temp->pageid][$temp->useranswer]++;
298
        } else {
299
            $pagestats[$temp->pageid][$temp->useranswer] = 1;
300
        }
301
        if (isset($pagestats[$temp->pageid]["total"])) {
302
            $pagestats[$temp->pageid]["total"]++;
303
        } else {
304
            $pagestats[$temp->pageid]["total"] = 1;
305
        }
306
        return true;
307
    }
308
 
309
    public function report_answers($answerpage, $answerdata, $useranswer, $pagestats, &$i, &$n) {
310
        global $PAGE;
311
 
312
        $answers = $this->get_answers();
313
        $formattextdefoptions = new stdClass;
314
        $formattextdefoptions->para = false;  //I'll use it widely in this page
315
        foreach ($answers as $answer) {
316
            $answer = parent::rewrite_answers_urls($answer, false);
317
            if ($useranswer == null && $i == 0) {
318
                // I have the $i == 0 because it is easier to blast through it all at once.
319
                if (isset($pagestats[$this->properties->id])) {
320
                    $stats = $pagestats[$this->properties->id];
321
                    $total = $stats["total"];
322
                    unset($stats["total"]);
323
                    foreach ($stats as $valentered => $ntimes) {
324
                        $data = '<input type="text" size="50" disabled="disabled" class="form-control" ' .
325
                                'readonly="readonly" value="'.s($valentered).'" />';
326
                        $percent = $ntimes / $total * 100;
327
                        $percent = round($percent, 2);
328
                        $percent .= "% ".get_string("enteredthis", "lesson");
329
                        $answerdata->answers[] = array($data, $percent);
330
                    }
331
                } else {
332
                    $answerdata->answers[] = array(get_string("nooneansweredthisquestion", "lesson"), " ");
333
                }
334
                $i++;
335
            } else if ($useranswer != null && ($answer->id == $useranswer->answerid || $answer == end($answers))) {
336
                 // get in here when what the user entered is not one of the answers
337
                $data = '<input type="text" size="50" disabled="disabled" class="form-control" ' .
338
                        'readonly="readonly" value="'.s($useranswer->useranswer).'">';
339
                if (isset($pagestats[$this->properties->id][$useranswer->useranswer])) {
340
                    $percent = $pagestats[$this->properties->id][$useranswer->useranswer] / $pagestats[$this->properties->id]["total"] * 100;
341
                    $percent = round($percent, 2);
342
                    $percent .= "% ".get_string("enteredthis", "lesson");
343
                } else {
344
                    $percent = get_string("nooneenteredthis", "lesson");
345
                }
346
                $answerdata->answers[] = array($data, $percent);
347
 
348
                if ($answer->id == $useranswer->answerid) {
349
                    if ($answer->response == null) {
350
                        if ($useranswer->correct) {
351
                            $answerdata->response = get_string("thatsthecorrectanswer", "lesson");
352
                        } else {
353
                            $answerdata->response = get_string("thatsthewronganswer", "lesson");
354
                        }
355
                    } else {
356
                        $answerdata->response = $answer->response;
357
                    }
358
                    if ($this->lesson->custom) {
359
                        $answerdata->score = get_string("pointsearned", "lesson").": ".$answer->score;
360
                    } elseif ($useranswer->correct) {
361
                        $answerdata->score = get_string("receivedcredit", "lesson");
362
                    } else {
363
                        $answerdata->score = get_string("didnotreceivecredit", "lesson");
364
                    }
365
                    // We have found the correct answer, do not process any more answers.
366
                    $answerpage->answerdata = $answerdata;
367
                    break;
368
                } else {
369
                    $answerdata->response = get_string("thatsthewronganswer", "lesson");
370
                    if ($this->lesson->custom) {
371
                        $answerdata->score = get_string("pointsearned", "lesson").": 0";
372
                    } else {
373
                        $answerdata->score = get_string("didnotreceivecredit", "lesson");
374
                    }
375
                }
376
            }
377
            $answerpage->answerdata = $answerdata;
378
        }
379
        return $answerpage;
380
    }
381
 
382
    /**
383
     * Make updates to the form data if required. In this case to put the all other answer data into the write section of the form.
384
     *
385
     * @param stdClass $data The form data to update.
386
     * @return stdClass The updated fom data.
387
     */
388
    public function update_form_data(stdClass $data): stdClass {
389
        $answercount = count($this->get_answers());
390
        // Check for other answer entry.
391
        $lastanswer = $data->{'answer_editor[' . ($answercount - 1) . ']'};
392
        if (strpos($lastanswer, LESSON_OTHER_ANSWERS) !== false) {
393
            $data->{'answer_editor[' . ($this->lesson->maxanswers + 1) . ']'} =
394
                    $data->{'answer_editor[' . ($answercount - 1) . ']'};
395
            $data->{'response_editor[' . ($this->lesson->maxanswers + 1) . ']'} =
396
                    $data->{'response_editor[' . ($answercount - 1) . ']'};
397
            $data->{'jumpto[' . ($this->lesson->maxanswers + 1) . ']'} = $data->{'jumpto[' . ($answercount - 1) . ']'};
398
            $data->{'score[' . ($this->lesson->maxanswers + 1) . ']'} = $data->{'score[' . ($answercount - 1) . ']'};
399
            $data->enableotheranswers = true;
400
            // Unset the old values.
401
            unset($data->{'answer_editor[' . ($answercount - 1) . ']'});
402
            unset($data->{'response_editor[' . ($answercount - 1) . ']'});
403
            unset($data->{'jumpto[' . ($answercount - 1) . ']'});
404
            unset($data->{'score[' . ($answercount - 1) . ']'});
405
        }
406
        return $data;
407
    }
408
}
409
 
410
 
411
class lesson_add_page_form_shortanswer extends lesson_add_page_form_base {
412
    public $qtype = 'shortanswer';
413
    public $qtypestring = 'shortanswer';
414
    protected $answerformat = '';
415
    protected $responseformat = LESSON_ANSWER_HTML;
416
 
417
    public function custom_definition() {
418
 
419
        $this->_form->addElement('checkbox', 'qoption', get_string('options', 'lesson'), get_string('casesensitive', 'lesson')); //oh my, this is a regex option!
420
        $this->_form->setDefault('qoption', 0);
421
        $this->_form->addHelpButton('qoption', 'casesensitive', 'lesson');
422
 
423
        $answercount = $this->_customdata['lesson']->maxanswers;
424
        for ($i = 0; $i < $answercount; $i++) {
425
            $this->_form->addElement('header', 'answertitle'.$i, get_string('answer').' '.($i+1));
426
            // Only first answer is required.
427
            $this->add_answer($i, null, ($i < 1));
428
            $this->add_response($i);
429
            $this->add_jumpto($i, null, ($i == 0 ? LESSON_NEXTPAGE : LESSON_THISPAGE));
430
            $this->add_score($i, null, ($i===0)?1:0);
431
        }
432
 
433
        // Other answer jump.
434
        $this->_form->addElement('header', 'wronganswer', get_string('allotheranswers', 'lesson'));
435
        $newcount = $answercount + 1;
436
        $this->_form->addElement('advcheckbox', 'enableotheranswers', get_string('enabled', 'lesson'));
437
        $this->add_response($newcount);
438
        $this->add_jumpto($newcount, get_string('allotheranswersjump', 'lesson'), LESSON_NEXTPAGE);
439
        $this->add_score($newcount, get_string('allotheranswersscore', 'lesson'), 0);
440
    }
441
}
442
 
443
class lesson_display_answer_form_shortanswer extends moodleform {
444
 
445
    public function definition() {
446
        global $OUTPUT, $USER;
447
        $mform = $this->_form;
448
        $contents = $this->_customdata['contents'];
449
 
450
        $hasattempt = false;
451
        $attrs = array('size'=>'50', 'maxlength'=>'200');
452
        if (isset($this->_customdata['lessonid'])) {
453
            $lessonid = $this->_customdata['lessonid'];
454
            if (isset($USER->modattempts[$lessonid]->useranswer)) {
455
                $attrs['readonly'] = 'readonly';
456
                $hasattempt = true;
457
            }
458
        }
459
 
460
        $placeholder = false;
461
        if (preg_match('/_____+/', $contents, $matches)) {
462
            $placeholder = $matches[0];
463
            $contentsparts = explode( $placeholder, $contents, 2);
464
            $attrs['size'] = round(strlen($placeholder) * 1.1);
465
        }
466
 
467
        // Disable shortforms.
468
        $mform->setDisableShortforms();
469
 
470
        $mform->addElement('header', 'pageheader');
471
        $mform->addElement('hidden', 'id');
472
        $mform->setType('id', PARAM_INT);
473
 
474
        $mform->addElement('hidden', 'pageid');
475
        $mform->setType('pageid', PARAM_INT);
476
 
477
        if ($placeholder) {
478
            $contentsgroup = array();
479
            $contentsgroup[] = $mform->createElement('static', '', '', $contentsparts[0]);
480
            $contentsgroup[] = $mform->createElement('text', 'answer', '', $attrs);
481
            $contentsgroup[] = $mform->createElement('static', '', '', $contentsparts[1]);
482
            $mform->addGroup($contentsgroup, '', '', '', false);
483
        } else {
484
            $mform->addElement('html', $OUTPUT->container($contents, 'contents'));
485
            $mform->addElement('text', 'answer', get_string('youranswer', 'lesson'), $attrs);
486
 
487
        }
488
        $mform->setType('answer', PARAM_TEXT);
489
 
490
        if ($hasattempt) {
491
            $this->add_action_buttons(null, get_string("nextpage", "lesson"));
492
        } else {
493
            $this->add_action_buttons(null, get_string("submit", "lesson"));
494
        }
495
    }
496
 
497
}