See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 39 and 401]
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 * 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 /** Essay question type */ 29 define("LESSON_PAGE_ESSAY", "10"); 30 31 class 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 } 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_object($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 67 public function display($renderer, $attempt) { 68 global $PAGE, $CFG, $USER; 69 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); 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])) { 89 $essayinfo = self::extract_useranswer($attempt->useranswer); 90 $data->answer = $essayinfo->answer; 91 } 92 93 $data = file_prepare_standard_editor($data, 'answer', $options['editoroptions'], 94 $context, 'mod_lesson', 'essay_answers'); 95 $mform->set_data($data); 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(); 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 } 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, 150 $editoroptions['context'], 'mod_lesson', 'essay_answers', $attempt->id); 151 152 // Update the student response to have the modified link. 153 $useranswer = unserialize_object($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, 177 'mod_lesson', 'essay_answers', $options->attemptid); 178 return parent::format_answer($answer, $context, $answerformat, $options); 179 } 180 181 public function check_answer() { 182 global $PAGE, $CFG; 183 $result = parent::check_answer(); 184 $result->isessayquestion = true; 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); 197 $data = $mform->get_data(); 198 require_sesskey(); 199 200 if (!$data) { 201 $result->inmediatejump = true; 202 $result->newpageid = $this->properties->id; 203 return $result; 204 } 205 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. 209 } else { 210 $studentanswer = isset($data->answer) ? $data->answer : ''; 211 $studentanswerformat = FORMAT_HTML; 212 } 213 214 if (trim($studentanswer) === '') { 215 $result->noanswer = true; 216 return $result; 217 } 218 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; 230 $userresponse->answerformat = $studentanswerformat; 231 $userresponse->response = ''; 232 $userresponse->responseformat = FORMAT_HTML; 233 $result->userresponse = serialize($userresponse); 234 $result->studentanswerformat = $studentanswerformat; 235 $result->studentanswer = $studentanswer; 236 $result->postdata = $data; 237 return $result; 238 } 239 public function update($properties, $context = null, $maxbytes = null) { 240 global $DB, $PAGE; 241 $answers = $this->get_answers(); 242 $properties->id = $this->properties->id; 243 $properties->lessonid = $this->lesson->id; 244 $properties->timemodified = time(); 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); 246 $DB->update_record("lesson_pages", $properties); 247 248 // Trigger an event: page updated. 249 \mod_lesson\event\page_updated::create_from_lesson_page($this, $context)->trigger(); 250 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) { 272 $temp = $this->lesson->get_last_attempt($tries); 273 $essayinfo = self::extract_useranswer($temp->useranswer); 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 { 281 $essaystats = new stdClass(); 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) { 290 global $PAGE, $DB; 291 292 $formattextdefoptions = new stdClass(); 293 $formattextdefoptions->noclean = true; 294 $formattextdefoptions->para = false; 295 $formattextdefoptions->context = $answerpage->context; 296 $answers = $this->get_answers(); 297 $context = context_module::instance($PAGE->cm->id); 298 foreach ($answers as $answer) { 299 $hasattempts = $DB->record_exists('lesson_attempts', ['answerid' => $answer->id]); 300 if ($useranswer != null) { 301 $essayinfo = self::extract_useranswer($useranswer->useranswer); 302 $essayinfo->answer = file_rewrite_pluginfile_urls($essayinfo->answer, 'pluginfile.php', 303 $context->id, 'mod_lesson', 'essay_answers', $useranswer->id); 304 305 if ($essayinfo->response == null) { 306 $answerdata->response = get_string("nocommentyet", "lesson"); 307 } else { 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); 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) { 322 $answerdata->score = get_string("pointsearned", "lesson").": " . $essayinfo->score; 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 { 332 $essayinfo = new stdClass(); 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 } 339 $essayinfo->answerformat = null; 340 } 341 342 // The essay question has been graded. 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 { 348 $avescore = $hasattempts ? get_string("essaynotgradedyet", "lesson") : 349 get_string("nooneansweredthisquestion", "lesson"); 350 } 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); 354 $answerpage->answerdata = $answerdata; 355 } 356 return $answerpage; 357 } 358 public function is_unanswered($nretakes) { 359 global $DB, $USER; 360 if (!$DB->count_records("lesson_attempts", array('pageid'=>$this->properties->id, 'userid'=>$USER->id, 'retry'=>$nretakes))) { 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) { 369 $essayinfo = self::extract_useranswer($attempt->useranswer); 370 return $essayinfo->score; 371 } 372 } 373 374 class 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 387 class 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']; 393 $editoroptions = $this->_customdata['editoroptions']; 394 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; 404 $useranswertemp = lesson_page_type_essay::extract_useranswer($USER->modattempts[$lessonid]->useranswer); 405 $useranswer = htmlspecialchars_decode($useranswertemp->answer, ENT_QUOTES); 406 $useranswerraw = $useranswertemp->answer; 407 } 408 } 409 410 // Disable shortforms. 411 $mform->setDisableShortforms(); 412 413 $mform->addElement('header', 'pageheader'); 414 415 $mform->addElement('html', $OUTPUT->container($contents, 'contents')); 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 427 if ($hasattempt) { 428 $mform->addElement('hidden', 'answer', $useranswerraw); 429 $mform->setType('answer', PARAM_RAW); 430 $mform->addElement('html', $OUTPUT->container(get_string('youranswer', 'lesson'), 'youranswer')); 431 $useranswer = file_rewrite_pluginfile_urls($useranswer, 'pluginfile.php', $editoroptions['context']->id, 432 'mod_lesson', 'essay_answers', $this->_customdata['attemptid']); 433 $mform->addElement('html', $OUTPUT->container($useranswer, 'reviewessay')); 434 $this->add_action_buttons(null, get_string("nextpage", "lesson")); 435 } else { 436 $mform->addElement('editor', 'answer_editor', get_string('youranswer', 'lesson'), null, $editoroptions); 437 $mform->setType('answer_editor', PARAM_RAW); 438 $this->add_action_buttons(null, get_string("submit", "lesson")); 439 } 440 } 441 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body