MDL-53966 lesson: Allow maximum number of attempts to be unlimited
[moodle.git] / mod / lesson / pagetypes / essay.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 * Essay
20 *
9b24f68b 21 * @package mod_lesson
cc3dbaaa
PS
22 * @copyright 2009 Sam Hemelryk
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0a4abb73
SH
24 **/
25
1e7f8ea2
PS
26defined('MOODLE_INTERNAL') || die();
27
28/** Essay question type */
0a4abb73
SH
29define("LESSON_PAGE_ESSAY", "10");
30
31class lesson_page_type_essay extends lesson_page {
32
33 protected $type = lesson_page::TYPE_QUESTION;
34 protected $typeidstring = 'essay';
35 protected $typeid = LESSON_PAGE_ESSAY;
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 }
ebf5ad4f
JMV
50
51 /**
52 * Unserialize attempt useranswer and add missing responseformat if needed
53 * for compatibility with old records.
54 *
55 * @param string $useranswer serialized object
56 * @return object
57 */
58 static public function extract_useranswer($useranswer) {
59 $essayinfo = unserialize($useranswer);
60 if (!isset($essayinfo->responseformat)) {
61 $essayinfo->response = text_to_html($essayinfo->response, false, false);
62 $essayinfo->responseformat = FORMAT_HTML;
63 }
64 return $essayinfo;
65 }
66
0a4abb73 67 public function display($renderer, $attempt) {
4f96fe55 68 global $PAGE, $CFG, $USER;
0a4abb73 69
3afdfce0
P
70 $context = context_module::instance($PAGE->cm->id);
71 $options = array(
72 'contents' => $this->get_contents(),
73 'lessonid' => $this->lesson->id,
74 'attemptid' => $attempt ? $attempt->id : null,
75 'editoroptions' => array(
76 'maxbytes' => $PAGE->course->maxbytes,
77 'context' => $context,
78 'noclean' => true,
79 'maxfiles' => EDITOR_UNLIMITED_FILES,
80 'enable_filemanagement' => false
81 )
82 );
83 $mform = new lesson_display_answer_form_essay($CFG->wwwroot.'/mod/lesson/continue.php', $options);
0a4abb73
SH
84
85 $data = new stdClass;
86 $data->id = $PAGE->cm->id;
87 $data->pageid = $this->properties->id;
88 if (isset($USER->modattempts[$this->lesson->id])) {
ebf5ad4f 89 $essayinfo = self::extract_useranswer($attempt->useranswer);
abd5c24e 90 $data->answer = $essayinfo->answer;
0a4abb73 91 }
3afdfce0
P
92
93 $data = file_prepare_standard_editor($data, 'answer', $options['editoroptions'],
a4606b7e 94 $context, 'mod_lesson', 'essay_answers');
0a4abb73 95 $mform->set_data($data);
8101328a
SB
96
97 // Trigger an event question viewed.
98 $eventparams = array(
99 'context' => context_module::instance($PAGE->cm->id),
100 'objectid' => $this->properties->id,
101 'other' => array(
102 'pagetype' => $this->get_typestring()
103 )
104 );
105
106 $event = \mod_lesson\event\question_viewed::create($eventparams);
107 $event->trigger();
0a4abb73
SH
108 return $mform->display();
109 }
110 public function create_answers($properties) {
111 global $DB;
112 // now add the answers
113 $newanswer = new stdClass;
114 $newanswer->lessonid = $this->lesson->id;
115 $newanswer->pageid = $this->properties->id;
116 $newanswer->timecreated = $this->properties->timecreated;
117
118 if (isset($properties->jumpto[0])) {
119 $newanswer->jumpto = $properties->jumpto[0];
120 }
121 if (isset($properties->score[0])) {
122 $newanswer->score = $properties->score[0];
123 }
124 $newanswer->id = $DB->insert_record("lesson_answers", $newanswer);
125 $answers = array($newanswer->id => new lesson_page_answer($newanswer));
126 $this->answers = $answers;
127 return $answers;
128 }
3afdfce0
P
129
130 /**
131 * Overridden function
132 *
133 * @param object $attempt
134 * @param object $result
135 * @return array
136 */
137 public function on_after_write_attempt($attempt, $result) {
138 global $PAGE;
139
140 if ($formdata = $result->postdata) {
141 // Save any linked files if we are using an editor.
142 $editoroptions = array(
143 'maxbytes' => $PAGE->course->maxbytes,
144 'context' => context_module::instance($PAGE->cm->id),
145 'noclean' => true, 'maxfiles' => EDITOR_UNLIMITED_FILES,
146 'enable_filemanagement' => false,
147 );
148
149 $formdata = file_postupdate_standard_editor($formdata, 'answer', $editoroptions,
a4606b7e 150 $editoroptions['context'], 'mod_lesson', 'essay_answers', $attempt->id);
3afdfce0
P
151
152 // Update the student response to have the modified link.
153 $useranswer = unserialize($attempt->useranswer);
154 $useranswer->answer = $formdata->answer;
155 $useranswer->answerformat = $formdata->answerformat;
156 $attempt->useranswer = serialize($useranswer);
157
158 $result->studentanswer = $formdata->answer;
159 $result->studentanswerformat = $formdata->answerformat;
160 return [$attempt, $result];
161 }
162
163 return parent::on_after_write_attempt($attempt, $result);
164 }
165
166 /**
167 * Custom formats the answer to display
168 *
169 * @param string $answer
170 * @param context $context
171 * @param int $answerformat
172 * @param array $options Optional param for additional options.
173 * @return string Returns formatted string
174 */
175 public function format_answer($answer, $context, $answerformat, $options = []) {
176 $answer = file_rewrite_pluginfile_urls($answer, 'pluginfile.php', $context->id,
a4606b7e 177 'mod_lesson', 'essay_answers', $options->attemptid);
3afdfce0
P
178 return parent::format_answer($answer, $context, $answerformat, $options);
179 }
180
0a4abb73
SH
181 public function check_answer() {
182 global $PAGE, $CFG;
183 $result = parent::check_answer();
184 $result->isessayquestion = true;
3afdfce0
P
185 $context = context_module::instance($PAGE->cm->id);
186 $options = array(
187 'contents' => $this->get_contents(),
188 'editoroptions' => array(
189 'maxbytes' => $PAGE->course->maxbytes,
190 'context' => $context,
191 'noclean' => true,
192 'maxfiles' => EDITOR_UNLIMITED_FILES,
193 'enable_filemanagement' => false,
194 )
195 );
196 $mform = new lesson_display_answer_form_essay($CFG->wwwroot.'/mod/lesson/continue.php', $options);
0a4abb73
SH
197 $data = $mform->get_data();
198 require_sesskey();
199
200 if (!$data) {
8d674838
JL
201 $result->inmediatejump = true;
202 $result->newpageid = $this->properties->id;
203 return $result;
0a4abb73
SH
204 }
205
3afdfce0
P
206 if (is_array($data->answer_editor) && strlen($data->answer_editor['text'])) {
207 $studentanswer = $data->answer_editor['text']; // Will be reset later.
208 $studentanswerformat = $data->answer_editor['format']; // Will be reset later.
54fd7cd9 209 } else {
3afdfce0 210 $studentanswer = isset($data->answer) ? $data->answer : '';
ebf5ad4f 211 $studentanswerformat = FORMAT_HTML;
54fd7cd9
RW
212 }
213
0a4abb73
SH
214 if (trim($studentanswer) === '') {
215 $result->noanswer = true;
216 return $result;
217 }
2f67a9b3 218
0a4abb73
SH
219 $answers = $this->get_answers();
220 foreach ($answers as $answer) {
221 $result->answerid = $answer->id;
222 $result->newpageid = $answer->jumpto;
223 }
224
225 $userresponse = new stdClass;
226 $userresponse->sent=0;
227 $userresponse->graded = 0;
228 $userresponse->score = 0;
229 $userresponse->answer = $studentanswer;
d3254ceb 230 $userresponse->answerformat = $studentanswerformat;
ebf5ad4f
JMV
231 $userresponse->response = '';
232 $userresponse->responseformat = FORMAT_HTML;
0a4abb73 233 $result->userresponse = serialize($userresponse);
d3254ceb 234 $result->studentanswerformat = $studentanswerformat;
59f1c9f5 235 $result->studentanswer = $studentanswer;
3afdfce0 236 $result->postdata = $data;
0a4abb73
SH
237 return $result;
238 }
0ed6f712 239 public function update($properties, $context = null, $maxbytes = null) {
0a4abb73
SH
240 global $DB, $PAGE;
241 $answers = $this->get_answers();
242 $properties->id = $this->properties->id;
243 $properties->lessonid = $this->lesson->id;
9ac3d686 244 $properties->timemodified = time();
5918e371 245 $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);
0a4abb73
SH
246 $DB->update_record("lesson_pages", $properties);
247
1cf8ca34
SB
248 // Trigger an event: page updated.
249 \mod_lesson\event\page_updated::create_from_lesson_page($this, $context)->trigger();
250
0a4abb73
SH
251 if (!array_key_exists(0, $this->answers)) {
252 $this->answers[0] = new stdClass;
253 $this->answers[0]->lessonid = $this->lesson->id;
254 $this->answers[0]->pageid = $this->id;
255 $this->answers[0]->timecreated = $this->timecreated;
256 }
257 if (isset($properties->jumpto[0])) {
258 $this->answers[0]->jumpto = $properties->jumpto[0];
259 }
260 if (isset($properties->score[0])) {
261 $this->answers[0]->score = $properties->score[0];
262 }
263 if (!isset($this->answers[0]->id)) {
264 $this->answers[0]->id = $DB->insert_record("lesson_answers", $this->answers[0]);
265 } else {
266 $DB->update_record("lesson_answers", $this->answers[0]->properties());
267 }
268
269 return true;
270 }
271 public function stats(array &$pagestats, $tries) {
252e85be 272 $temp = $this->lesson->get_last_attempt($tries);
ebf5ad4f 273 $essayinfo = self::extract_useranswer($temp->useranswer);
0a4abb73
SH
274 if ($essayinfo->graded) {
275 if (isset($pagestats[$temp->pageid])) {
276 $essaystats = $pagestats[$temp->pageid];
277 $essaystats->totalscore += $essayinfo->score;
278 $essaystats->total++;
279 $pagestats[$temp->pageid] = $essaystats;
280 } else {
92701024 281 $essaystats = new stdClass();
0a4abb73
SH
282 $essaystats->totalscore = $essayinfo->score;
283 $essaystats->total = 1;
284 $pagestats[$temp->pageid] = $essaystats;
285 }
286 }
287 return true;
288 }
289 public function report_answers($answerpage, $answerdata, $useranswer, $pagestats, &$i, &$n) {
8ac1820b
MG
290 global $PAGE, $DB;
291
ebf5ad4f
JMV
292 $formattextdefoptions = new stdClass();
293 $formattextdefoptions->noclean = true;
294 $formattextdefoptions->para = false;
295 $formattextdefoptions->context = $answerpage->context;
0a4abb73 296 $answers = $this->get_answers();
3afdfce0 297 $context = context_module::instance($PAGE->cm->id);
0a4abb73 298 foreach ($answers as $answer) {
8ac1820b 299 $hasattempts = $DB->record_exists('lesson_attempts', ['answerid' => $answer->id]);
ecea65ca 300 if ($useranswer != null) {
ebf5ad4f 301 $essayinfo = self::extract_useranswer($useranswer->useranswer);
3afdfce0 302 $essayinfo->answer = file_rewrite_pluginfile_urls($essayinfo->answer, 'pluginfile.php',
a4606b7e 303 $context->id, 'mod_lesson', 'essay_answers', $useranswer->id);
3afdfce0 304
ecea65ca 305 if ($essayinfo->response == null) {
0a4abb73
SH
306 $answerdata->response = get_string("nocommentyet", "lesson");
307 } else {
ebf5ad4f
JMV
308 $essayinfo->response = file_rewrite_pluginfile_urls($essayinfo->response, 'pluginfile.php',
309 $answerpage->context->id, 'mod_lesson', 'essay_responses', $useranswer->id);
310 $answerdata->response = format_text($essayinfo->response, $essayinfo->responseformat, $formattextdefoptions);
0a4abb73
SH
311 }
312 if (isset($pagestats[$this->properties->id])) {
313 $percent = $pagestats[$this->properties->id]->totalscore / $pagestats[$this->properties->id]->total * 100;
314 $percent = round($percent, 2);
315 $percent = get_string("averagescore", "lesson").": ". $percent ."%";
316 } else {
317 // dont think this should ever be reached....
318 $percent = get_string("nooneansweredthisquestion", "lesson");
319 }
320 if ($essayinfo->graded) {
321 if ($this->lesson->custom) {
8ac1820b 322 $answerdata->score = get_string("pointsearned", "lesson").": " . $essayinfo->score;
0a4abb73
SH
323 } elseif ($essayinfo->score) {
324 $answerdata->score = get_string("receivedcredit", "lesson");
325 } else {
326 $answerdata->score = get_string("didnotreceivecredit", "lesson");
327 }
328 } else {
329 $answerdata->score = get_string("havenotgradedyet", "lesson");
330 }
331 } else {
92701024 332 $essayinfo = new stdClass();
8ac1820b
MG
333 if ($hasattempts && has_capability('mod/lesson:grade', $answerpage->context)) {
334 $essayinfo->answer = html_writer::link(new moodle_url("/mod/lesson/essay.php",
335 ['id' => $PAGE->cm->id]), get_string("viewessayanswers", "lesson"));
336 } else {
337 $essayinfo->answer = "";
338 }
25784512 339 $essayinfo->answerformat = null;
0a4abb73
SH
340 }
341
8ac1820b 342 // The essay question has been graded.
0a4abb73
SH
343 if (isset($pagestats[$this->properties->id])) {
344 $avescore = $pagestats[$this->properties->id]->totalscore / $pagestats[$this->properties->id]->total;
345 $avescore = round($avescore, 2);
346 $avescore = get_string("averagescore", "lesson").": ". $avescore ;
347 } else {
8ac1820b
MG
348 $avescore = $hasattempts ? get_string("essaynotgradedyet", "lesson") :
349 get_string("nooneansweredthisquestion", "lesson");
0a4abb73 350 }
59f1c9f5
JMV
351 // This is the student's answer so it should be cleaned.
352 $answerdata->answers[] = array(format_text($essayinfo->answer, $essayinfo->answerformat,
353 array('para' => true, 'context' => $answerpage->context)), $avescore);
0a4abb73
SH
354 $answerpage->answerdata = $answerdata;
355 }
356 return $answerpage;
357 }
358 public function is_unanswered($nretakes) {
359 global $DB, $USER;
18ac654e 360 if (!$DB->count_records("lesson_attempts", array('pageid'=>$this->properties->id, 'userid'=>$USER->id, 'retry'=>$nretakes))) {
0a4abb73
SH
361 return true;
362 }
363 return false;
364 }
365 public function requires_manual_grading() {
366 return true;
367 }
368 public function get_earnedscore($answers, $attempt) {
ebf5ad4f 369 $essayinfo = self::extract_useranswer($attempt->useranswer);
0a4abb73
SH
370 return $essayinfo->score;
371 }
372}
373
374class lesson_add_page_form_essay extends lesson_add_page_form_base {
375
376 public $qtype = 'essay';
377 public $qtypestring = 'essay';
378
379 public function custom_definition() {
380
381 $this->add_jumpto(0);
382 $this->add_score(0, null, 1);
383
384 }
385}
386
387class lesson_display_answer_form_essay extends moodleform {
388
389 public function definition() {
390 global $USER, $OUTPUT;
391 $mform = $this->_form;
392 $contents = $this->_customdata['contents'];
3afdfce0 393 $editoroptions = $this->_customdata['editoroptions'];
0a4abb73 394
abd5c24e
RW
395 $hasattempt = false;
396 $attrs = '';
397 $useranswer = '';
398 $useranswerraw = '';
399 if (isset($this->_customdata['lessonid'])) {
400 $lessonid = $this->_customdata['lessonid'];
401 if (isset($USER->modattempts[$lessonid]->useranswer) && !empty($USER->modattempts[$lessonid]->useranswer)) {
402 $attrs = array('disabled' => 'disabled');
403 $hasattempt = true;
ebf5ad4f 404 $useranswertemp = lesson_page_type_essay::extract_useranswer($USER->modattempts[$lessonid]->useranswer);
54fd7cd9
RW
405 $useranswer = htmlspecialchars_decode($useranswertemp->answer, ENT_QUOTES);
406 $useranswerraw = $useranswertemp->answer;
abd5c24e
RW
407 }
408 }
409
5b0af6e4
JMV
410 // Disable shortforms.
411 $mform->setDisableShortforms();
412
ffdf7f8a
DM
413 $mform->addElement('header', 'pageheader');
414
415 $mform->addElement('html', $OUTPUT->container($contents, 'contents'));
0a4abb73
SH
416
417 $options = new stdClass;
418 $options->para = false;
419 $options->noclean = true;
420
421 $mform->addElement('hidden', 'id');
422 $mform->setType('id', PARAM_INT);
423
424 $mform->addElement('hidden', 'pageid');
425 $mform->setType('pageid', PARAM_INT);
426
abd5c24e
RW
427 if ($hasattempt) {
428 $mform->addElement('hidden', 'answer', $useranswerraw);
54fd7cd9 429 $mform->setType('answer', PARAM_RAW);
abd5c24e 430 $mform->addElement('html', $OUTPUT->container(get_string('youranswer', 'lesson'), 'youranswer'));
3afdfce0 431 $useranswer = file_rewrite_pluginfile_urls($useranswer, 'pluginfile.php', $editoroptions['context']->id,
a4606b7e 432 'mod_lesson', 'essay_answers', $this->_customdata['attemptid']);
abd5c24e
RW
433 $mform->addElement('html', $OUTPUT->container($useranswer, 'reviewessay'));
434 $this->add_action_buttons(null, get_string("nextpage", "lesson"));
435 } else {
3afdfce0
P
436 $mform->addElement('editor', 'answer_editor', get_string('youranswer', 'lesson'), null, $editoroptions);
437 $mform->setType('answer_editor', PARAM_RAW);
abd5c24e
RW
438 $this->add_action_buttons(null, get_string("submit", "lesson"));
439 }
0a4abb73 440 }
ffdf7f8a 441}