Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Essay question renderer class.
  19   *
  20   * @package    qtype
  21   * @subpackage essay
  22   * @copyright  2009 The Open University
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  
  30  /**
  31   * Generates the output for essay questions.
  32   *
  33   * @copyright  2009 The Open University
  34   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class qtype_essay_renderer extends qtype_renderer {
  37      public function formulation_and_controls(question_attempt $qa,
  38              question_display_options $options) {
  39  
  40          $question = $qa->get_question();
  41          $responseoutput = $question->get_format_renderer($this->page);
  42  
  43          // Answer field.
  44          $step = $qa->get_last_step_with_qt_var('answer');
  45  
  46          if (!$step->has_qt_var('answer') && empty($options->readonly)) {
  47              // Question has never been answered, fill it with response template.
  48              $step = new question_attempt_step(array('answer' => $question->responsetemplate));
  49          }
  50  
  51          if (empty($options->readonly)) {
  52              $answer = $responseoutput->response_area_input('answer', $qa,
  53                      $step, $question->responsefieldlines, $options->context);
  54  
  55          } else {
  56              $answer = $responseoutput->response_area_read_only('answer', $qa,
  57                      $step, $question->responsefieldlines, $options->context);
  58          }
  59  
  60          $files = '';
  61          if ($question->attachments) {
  62              if (empty($options->readonly)) {
  63                  $files = $this->files_input($qa, $question->attachments, $options);
  64  
  65              } else {
  66                  $files = $this->files_read_only($qa, $options);
  67              }
  68          }
  69  
  70          $result = '';
  71          $result .= html_writer::tag('div', $question->format_questiontext($qa),
  72                  array('class' => 'qtext'));
  73  
  74          $result .= html_writer::start_tag('div', array('class' => 'ablock'));
  75          $result .= html_writer::tag('div', $answer, array('class' => 'answer'));
  76          $result .= html_writer::tag('div', $files, array('class' => 'attachments'));
  77          $result .= html_writer::end_tag('div');
  78  
  79          return $result;
  80      }
  81  
  82      /**
  83       * Displays any attached files when the question is in read-only mode.
  84       * @param question_attempt $qa the question attempt to display.
  85       * @param question_display_options $options controls what should and should
  86       *      not be displayed. Used to get the context.
  87       */
  88      public function files_read_only(question_attempt $qa, question_display_options $options) {
  89          $files = $qa->get_last_qt_files('attachments', $options->context->id);
  90          $filelist = [];
  91  
  92          foreach ($files as $file) {
  93              $out = html_writer::link($qa->get_response_file_url($file),
  94                  $this->output->pix_icon(file_file_icon($file), get_mimetype_description($file),
  95                      'moodle', array('class' => 'icon')) . ' ' . s($file->get_filename()));
  96              $filelist[] = html_writer::tag('li', $out, ['class' => 'mb-2']);
  97          }
  98  
  99          $labelbyid = $qa->get_qt_field_name('attachments') . '_label';
 100  
 101          $output = html_writer::tag('h4', get_string('answerfiles', 'qtype_essay'), ['id' => $labelbyid, 'class' => 'sr-only']);
 102          $output .= html_writer::tag('ul', implode($filelist), [
 103              'aria-labelledby' => $labelbyid,
 104              'class' => 'list-unstyled m-0',
 105          ]);
 106          return $output;
 107      }
 108  
 109      /**
 110       * Displays the input control for when the student should upload a single file.
 111       * @param question_attempt $qa the question attempt to display.
 112       * @param int $numallowed the maximum number of attachments allowed. -1 = unlimited.
 113       * @param question_display_options $options controls what should and should
 114       *      not be displayed. Used to get the context.
 115       */
 116      public function files_input(question_attempt $qa, $numallowed,
 117              question_display_options $options) {
 118          global $CFG, $COURSE;
 119          require_once($CFG->dirroot . '/lib/form/filemanager.php');
 120  
 121          $pickeroptions = new stdClass();
 122          $pickeroptions->mainfile = null;
 123          $pickeroptions->maxfiles = $numallowed;
 124          $pickeroptions->itemid = $qa->prepare_response_files_draft_itemid(
 125                  'attachments', $options->context->id);
 126          $pickeroptions->context = $options->context;
 127          $pickeroptions->return_types = FILE_INTERNAL | FILE_CONTROLLED_LINK;
 128  
 129          $pickeroptions->itemid = $qa->prepare_response_files_draft_itemid(
 130                  'attachments', $options->context->id);
 131          $pickeroptions->accepted_types = $qa->get_question()->filetypeslist;
 132  
 133          $fm = new form_filemanager($pickeroptions);
 134          $fm->options->maxbytes = get_user_max_upload_file_size(
 135              $this->page->context,
 136              $CFG->maxbytes,
 137              $COURSE->maxbytes,
 138              $qa->get_question()->maxbytes
 139          );
 140          $filesrenderer = $this->page->get_renderer('core', 'files');
 141  
 142          $text = '';
 143          if (!empty($qa->get_question()->filetypeslist)) {
 144              $text = html_writer::tag('p', get_string('acceptedfiletypes', 'qtype_essay'));
 145              $filetypesutil = new \core_form\filetypes_util();
 146              $filetypes = $qa->get_question()->filetypeslist;
 147              $filetypedescriptions = $filetypesutil->describe_file_types($filetypes);
 148              $text .= $this->render_from_template('core_form/filetypes-descriptions', $filetypedescriptions);
 149          }
 150  
 151          $output = html_writer::start_tag('fieldset');
 152          $output .= html_writer::tag('legend', get_string('answerfiles', 'qtype_essay'), ['class' => 'sr-only']);
 153          $output .= $filesrenderer->render($fm);
 154          $output .= html_writer::empty_tag('input', [
 155              'type' => 'hidden',
 156              'name' => $qa->get_qt_field_name('attachments'),
 157              'value' => $pickeroptions->itemid,
 158          ]);
 159          $output .= $text;
 160          $output .= html_writer::end_tag('fieldset');
 161  
 162          return $output;
 163      }
 164  
 165      public function manual_comment(question_attempt $qa, question_display_options $options) {
 166          if ($options->manualcomment != question_display_options::EDITABLE) {
 167              return '';
 168          }
 169  
 170          $question = $qa->get_question();
 171          return html_writer::nonempty_tag('div', $question->format_text(
 172                  $question->graderinfo, $question->graderinfoformat, $qa, 'qtype_essay',
 173                  'graderinfo', $question->id), array('class' => 'graderinfo'));
 174      }
 175  }
 176  
 177  
 178  /**
 179   * A base class to abstract out the differences between different type of
 180   * response format.
 181   *
 182   * @copyright  2011 The Open University
 183   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 184   */
 185  abstract class qtype_essay_format_renderer_base extends plugin_renderer_base {
 186      /**
 187       * Render the students respone when the question is in read-only mode.
 188       * @param string $name the variable name this input edits.
 189       * @param question_attempt $qa the question attempt being display.
 190       * @param question_attempt_step $step the current step.
 191       * @param int $lines approximate size of input box to display.
 192       * @param object $context the context teh output belongs to.
 193       * @return string html to display the response.
 194       */
 195      public abstract function response_area_read_only($name, question_attempt $qa,
 196              question_attempt_step $step, $lines, $context);
 197  
 198      /**
 199       * Render the students respone when the question is in read-only mode.
 200       * @param string $name the variable name this input edits.
 201       * @param question_attempt $qa the question attempt being display.
 202       * @param question_attempt_step $step the current step.
 203       * @param int $lines approximate size of input box to display.
 204       * @param object $context the context teh output belongs to.
 205       * @return string html to display the response for editing.
 206       */
 207      public abstract function response_area_input($name, question_attempt $qa,
 208              question_attempt_step $step, $lines, $context);
 209  
 210      /**
 211       * @return string specific class name to add to the input element.
 212       */
 213      protected abstract function class_name();
 214  }
 215  
 216  /**
 217   * An essay format renderer for essays where the student should not enter
 218   * any inline response.
 219   *
 220   * @copyright  2013 Binghamton University
 221   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 222   */
 223  class qtype_essay_format_noinline_renderer extends plugin_renderer_base {
 224  
 225      protected function class_name() {
 226          return 'qtype_essay_noinline';
 227      }
 228  
 229      public function response_area_read_only($name, $qa, $step, $lines, $context) {
 230          return '';
 231      }
 232  
 233      public function response_area_input($name, $qa, $step, $lines, $context) {
 234          return '';
 235      }
 236  
 237  }
 238  
 239  /**
 240   * An essay format renderer for essays where the student should use the HTML
 241   * editor without the file picker.
 242   *
 243   * @copyright  2011 The Open University
 244   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 245   */
 246  class qtype_essay_format_editor_renderer extends plugin_renderer_base {
 247      protected function class_name() {
 248          return 'qtype_essay_editor';
 249      }
 250  
 251      public function response_area_read_only($name, $qa, $step, $lines, $context) {
 252          $labelbyid = $qa->get_qt_field_name($name) . '_label';
 253  
 254          $output = html_writer::tag('h4', get_string('answertext', 'qtype_essay'), ['id' => $labelbyid, 'class' => 'sr-only']);
 255          $output .= html_writer::tag('div', $this->prepare_response($name, $qa, $step, $context), [
 256              'role' => 'textbox',
 257              'aria-readonly' => 'true',
 258              'aria-labelledby' => $labelbyid,
 259              'class' => $this->class_name() . ' qtype_essay_response readonly',
 260              'style' => 'min-height: ' . ($lines * 1.5) . 'em;',
 261          ]);
 262          // Height $lines * 1.5 because that is a typical line-height on web pages.
 263          // That seems to give results that look OK.
 264  
 265          return $output;
 266      }
 267  
 268      public function response_area_input($name, $qa, $step, $lines, $context) {
 269          global $CFG;
 270          require_once($CFG->dirroot . '/repository/lib.php');
 271  
 272          $inputname = $qa->get_qt_field_name($name);
 273          $responseformat = $step->get_qt_var($name . 'format');
 274          $id = $inputname . '_id';
 275  
 276          $editor = editors_get_preferred_editor($responseformat);
 277          $strformats = format_text_menu();
 278          $formats = $editor->get_supported_formats();
 279          foreach ($formats as $fid) {
 280              $formats[$fid] = $strformats[$fid];
 281          }
 282  
 283          list($draftitemid, $response) = $this->prepare_response_for_editing(
 284                  $name, $step, $context);
 285  
 286          $editor->set_text($response);
 287          $editor->use_editor($id, $this->get_editor_options($context),
 288                  $this->get_filepicker_options($context, $draftitemid));
 289  
 290          $output = html_writer::tag('label', get_string('answertext', 'qtype_essay'), [
 291              'class' => 'sr-only',
 292              'for' => $id,
 293          ]);
 294          $output .= html_writer::start_tag('div', array('class' =>
 295                  $this->class_name() . ' qtype_essay_response'));
 296  
 297          $output .= html_writer::tag('div', html_writer::tag('textarea', s($response),
 298                  array('id' => $id, 'name' => $inputname, 'rows' => $lines, 'cols' => 60, 'class' => 'form-control')));
 299  
 300          $output .= html_writer::start_tag('div');
 301          if (count($formats) == 1) {
 302              reset($formats);
 303              $output .= html_writer::empty_tag('input', array('type' => 'hidden',
 304                      'name' => $inputname . 'format', 'value' => key($formats)));
 305  
 306          } else {
 307              $output .= html_writer::label(get_string('format'), 'menu' . $inputname . 'format', false);
 308              $output .= ' ';
 309              $output .= html_writer::select($formats, $inputname . 'format', $responseformat, '');
 310          }
 311          $output .= html_writer::end_tag('div');
 312  
 313          $output .= $this->filepicker_html($inputname, $draftitemid);
 314  
 315          $output .= html_writer::end_tag('div');
 316          return $output;
 317      }
 318  
 319      /**
 320       * Prepare the response for read-only display.
 321       * @param string $name the variable name this input edits.
 322       * @param question_attempt $qa the question attempt being display.
 323       * @param question_attempt_step $step the current step.
 324       * @param object $context the context the attempt belongs to.
 325       * @return string the response prepared for display.
 326       */
 327      protected function prepare_response($name, question_attempt $qa,
 328              question_attempt_step $step, $context) {
 329          if (!$step->has_qt_var($name)) {
 330              return '';
 331          }
 332  
 333          $formatoptions = new stdClass();
 334          $formatoptions->para = false;
 335          return format_text($step->get_qt_var($name), $step->get_qt_var($name . 'format'),
 336                  $formatoptions);
 337      }
 338  
 339      /**
 340       * Prepare the response for editing.
 341       * @param string $name the variable name this input edits.
 342       * @param question_attempt_step $step the current step.
 343       * @param object $context the context the attempt belongs to.
 344       * @return string the response prepared for display.
 345       */
 346      protected function prepare_response_for_editing($name,
 347              question_attempt_step $step, $context) {
 348          return array(0, $step->get_qt_var($name));
 349      }
 350  
 351      /**
 352       * @param object $context the context the attempt belongs to.
 353       * @return array options for the editor.
 354       */
 355      protected function get_editor_options($context) {
 356          // Disable the text-editor autosave because quiz has it's own auto save function.
 357          return array('context' => $context, 'autosave' => false);
 358      }
 359  
 360      /**
 361       * @param object $context the context the attempt belongs to.
 362       * @param int $draftitemid draft item id.
 363       * @return array filepicker options for the editor.
 364       */
 365      protected function get_filepicker_options($context, $draftitemid) {
 366          return array('return_types'  => FILE_INTERNAL | FILE_EXTERNAL);
 367      }
 368  
 369      /**
 370       * @param string $inputname input field name.
 371       * @param int $draftitemid draft file area itemid.
 372       * @return string HTML for the filepicker, if used.
 373       */
 374      protected function filepicker_html($inputname, $draftitemid) {
 375          return '';
 376      }
 377  }
 378  
 379  
 380  /**
 381   * An essay format renderer for essays where the student should use the HTML
 382   * editor with the file picker.
 383   *
 384   * @copyright  2011 The Open University
 385   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 386   */
 387  class qtype_essay_format_editorfilepicker_renderer extends qtype_essay_format_editor_renderer {
 388      protected function class_name() {
 389          return 'qtype_essay_editorfilepicker';
 390      }
 391  
 392      protected function prepare_response($name, question_attempt $qa,
 393              question_attempt_step $step, $context) {
 394          if (!$step->has_qt_var($name)) {
 395              return '';
 396          }
 397  
 398          $formatoptions = new stdClass();
 399          $formatoptions->para = false;
 400          $text = $qa->rewrite_response_pluginfile_urls($step->get_qt_var($name),
 401                  $context->id, 'answer', $step);
 402          return format_text($text, $step->get_qt_var($name . 'format'), $formatoptions);
 403      }
 404  
 405      protected function prepare_response_for_editing($name,
 406              question_attempt_step $step, $context) {
 407          return $step->prepare_response_files_draft_itemid_with_text(
 408                  $name, $context->id, $step->get_qt_var($name));
 409      }
 410  
 411      /**
 412       * Get editor options for question response text area.
 413       * @param object $context the context the attempt belongs to.
 414       * @return array options for the editor.
 415       */
 416      protected function get_editor_options($context) {
 417          return question_utils::get_editor_options($context);
 418      }
 419  
 420      /**
 421       * Get the options required to configure the filepicker for one of the editor
 422       * toolbar buttons.
 423       * @deprecated since 3.5
 424       * @param mixed $acceptedtypes array of types of '*'.
 425       * @param int $draftitemid the draft area item id.
 426       * @param object $context the context.
 427       * @return object the required options.
 428       */
 429      protected function specific_filepicker_options($acceptedtypes, $draftitemid, $context) {
 430          debugging('qtype_essay_format_editorfilepicker_renderer::specific_filepicker_options() is deprecated, ' .
 431              'use question_utils::specific_filepicker_options() instead.', DEBUG_DEVELOPER);
 432  
 433          $filepickeroptions = new stdClass();
 434          $filepickeroptions->accepted_types = $acceptedtypes;
 435          $filepickeroptions->return_types = FILE_INTERNAL | FILE_EXTERNAL;
 436          $filepickeroptions->context = $context;
 437          $filepickeroptions->env = 'filepicker';
 438  
 439          $options = initialise_filepicker($filepickeroptions);
 440          $options->context = $context;
 441          $options->client_id = uniqid();
 442          $options->env = 'editor';
 443          $options->itemid = $draftitemid;
 444  
 445          return $options;
 446      }
 447  
 448      /**
 449       * @param object $context the context the attempt belongs to.
 450       * @param int $draftitemid draft item id.
 451       * @return array filepicker options for the editor.
 452       */
 453      protected function get_filepicker_options($context, $draftitemid) {
 454          return question_utils::get_filepicker_options($context, $draftitemid);
 455      }
 456  
 457      protected function filepicker_html($inputname, $draftitemid) {
 458          $nonjspickerurl = new moodle_url('/repository/draftfiles_manager.php', array(
 459              'action' => 'browse',
 460              'env' => 'editor',
 461              'itemid' => $draftitemid,
 462              'subdirs' => false,
 463              'maxfiles' => -1,
 464              'sesskey' => sesskey(),
 465          ));
 466  
 467          return html_writer::empty_tag('input', array('type' => 'hidden',
 468                  'name' => $inputname . ':itemid', 'value' => $draftitemid)) .
 469                  html_writer::tag('noscript', html_writer::tag('div',
 470                      html_writer::tag('object', '', array('type' => 'text/html',
 471                          'data' => $nonjspickerurl, 'height' => 160, 'width' => 600,
 472                          'style' => 'border: 1px solid #000;'))));
 473      }
 474  }
 475  
 476  
 477  /**
 478   * An essay format renderer for essays where the student should use a plain
 479   * input box, but with a normal, proportional font.
 480   *
 481   * @copyright  2011 The Open University
 482   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 483   */
 484  class qtype_essay_format_plain_renderer extends plugin_renderer_base {
 485      /**
 486       * @return string the HTML for the textarea.
 487       */
 488      protected function textarea($response, $lines, $attributes) {
 489          $attributes['class'] = $this->class_name() . ' qtype_essay_response form-control';
 490          $attributes['rows'] = $lines;
 491          $attributes['cols'] = 60;
 492          return html_writer::tag('textarea', s($response), $attributes);
 493      }
 494  
 495      protected function class_name() {
 496          return 'qtype_essay_plain';
 497      }
 498  
 499      public function response_area_read_only($name, $qa, $step, $lines, $context) {
 500          $id = $qa->get_qt_field_name($name) . '_id';
 501  
 502          $output = html_writer::tag('label', get_string('answertext', 'qtype_essay'), ['class' => 'sr-only', 'for' => $id]);
 503          $output .= $this->textarea($step->get_qt_var($name), $lines, ['id' => $id, 'readonly' => 'readonly']);
 504          return $output;
 505      }
 506  
 507      public function response_area_input($name, $qa, $step, $lines, $context) {
 508          $inputname = $qa->get_qt_field_name($name);
 509          $id = $inputname . '_id';
 510  
 511          $output = html_writer::tag('label', get_string('answertext', 'qtype_essay'), ['class' => 'sr-only', 'for' => $id]);
 512          $output .= $this->textarea($step->get_qt_var($name), $lines, ['name' => $inputname, 'id' => $id]);
 513          $output .= html_writer::empty_tag('input', ['type' => 'hidden', 'name' => $inputname . 'format', 'value' => FORMAT_PLAIN]);
 514  
 515          return $output;
 516      }
 517  }
 518  
 519  
 520  /**
 521   * An essay format renderer for essays where the student should use a plain
 522   * input box with a monospaced font. You might use this, for example, for a
 523   * question where the students should type computer code.
 524   *
 525   * @copyright  2011 The Open University
 526   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 527   */
 528  class qtype_essay_format_monospaced_renderer extends qtype_essay_format_plain_renderer {
 529      protected function class_name() {
 530          return 'qtype_essay_monospaced';
 531      }
 532  }