Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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  }