Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

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

   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   * Defines the renderer base class for question behaviours.
  19   *
  20   * @package    moodlecore
  21   * @subpackage questionbehaviours
  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   * Renderer base class for question behaviours.
  32   *
  33   * The methods in this class are mostly called from {@link core_question_renderer}
  34   * which coordinates the overall output of questions.
  35   *
  36   * @copyright  2009 The Open University
  37   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  abstract class qbehaviour_renderer extends plugin_renderer_base {
  40      /**
  41       * Generate some HTML (which may be blank) that appears in the question
  42       * formulation area, afer the question type generated output.
  43       *
  44       * For example.
  45       * immediatefeedback and interactive mode use this to show the Submit button,
  46       * and CBM use this to display the certainty choices.
  47       *
  48       * @param question_attempt $qa a question attempt.
  49       * @param question_display_options $options controls what should and should not be displayed.
  50       * @return string HTML fragment.
  51       */
  52      public function controls(question_attempt $qa, question_display_options $options) {
  53          return '';
  54      }
  55  
  56      /**
  57       * Generate some HTML (which may be blank) that appears in the outcome area,
  58       * after the question-type generated output.
  59       *
  60       * For example, the CBM models use this to display an explanation of the score
  61       * adjustment that was made based on the certainty selected.
  62       *
  63       * @param question_attempt $qa a question attempt.
  64       * @param question_display_options $options controls what should and should not be displayed.
  65       * @return string HTML fragment.
  66       */
  67      public function feedback(question_attempt $qa, question_display_options $options) {
  68          return '';
  69      }
  70  
  71      public function manual_comment_fields(question_attempt $qa, question_display_options $options) {
  72          global $CFG;
  73  
  74          require_once($CFG->dirroot.'/lib/filelib.php');
  75          require_once($CFG->dirroot.'/repository/lib.php');
  76  
  77          $inputname = $qa->get_behaviour_field_name('comment');
  78          $id = $inputname . '_id';
  79          list($commenttext, $commentformat, $commentstep) = $qa->get_current_manual_comment();
  80  
  81          $editor = editors_get_preferred_editor($commentformat);
  82          $strformats = format_text_menu();
  83          $formats = $editor->get_supported_formats();
  84          foreach ($formats as $fid) {
  85              $formats[$fid] = $strformats[$fid];
  86          }
  87  
  88          $draftitemareainputname = $qa->get_behaviour_field_name('comment:itemid');
  89          $draftitemid = optional_param($draftitemareainputname, false, PARAM_INT);
  90  
  91          if (!$draftitemid && $commentstep === null) {
  92              $commenttext = '';
  93              $draftitemid = file_get_unused_draft_itemid();
  94          } else if (!$draftitemid) {
  95              list($draftitemid, $commenttext) = $commentstep->prepare_response_files_draft_itemid_with_text(
  96                      'bf_comment', $options->context->id, $commenttext);
  97          }
  98  
  99          $editor->set_text($commenttext);
 100          $editor->use_editor($id, question_utils::get_editor_options($options->context),
 101                  question_utils::get_filepicker_options($options->context, $draftitemid));
 102  
 103          $commenteditor = html_writer::tag('div', html_writer::tag('textarea', s($commenttext),
 104                  array('id' => $id, 'name' => $inputname, 'rows' => 3, 'cols' => 60)));
 105  
 106          $attributes = ['type'  => 'hidden', 'name'  => $draftitemareainputname, 'value' => $draftitemid];
 107          $commenteditor .= html_writer::empty_tag('input', $attributes);
 108  
 109          $editorformat = '';
 110          if (count($formats) == 1) {
 111              reset($formats);
 112              $editorformat .= html_writer::empty_tag('input', array('type' => 'hidden',
 113                      'name' => $inputname . 'format', 'value' => key($formats)));
 114          } else {
 115              $editorformat = html_writer::start_tag('div', array('class' => 'fitem'));
 116              $editorformat .= html_writer::start_tag('div', array('class' => 'fitemtitle'));
 117              $editorformat .= html_writer::tag('label', get_string('format'), array('for'=>'menu'.$inputname.'format'));
 118              $editorformat .= html_writer::end_tag('div');
 119              $editorformat .= html_writer::start_tag('div', array('class' => 'felement fhtmleditor'));
 120              $editorformat .= html_writer::select($formats, $inputname.'format', $commentformat, '');
 121              $editorformat .= html_writer::end_tag('div');
 122              $editorformat .= html_writer::end_tag('div');
 123          }
 124  
 125          $comment = html_writer::tag('div', html_writer::tag('div',
 126                  html_writer::tag('label', get_string('comment', 'question'),
 127                  array('for' => $id)), array('class' => 'fitemtitle')) .
 128                  html_writer::tag('div', $commenteditor, array('class' => 'felement fhtmleditor', 'data-fieldtype' => "editor")),
 129                  array('class' => 'fitem'));
 130          $comment .= $editorformat;
 131  
 132          $mark = '';
 133          if ($qa->get_max_mark()) {
 134              $currentmark = $qa->get_current_manual_mark();
 135              $maxmark = $qa->get_max_mark();
 136  
 137              $fieldsize = strlen($qa->format_max_mark($options->markdp)) - 1;
 138              $markfield = $qa->get_behaviour_field_name('mark');
 139  
 140              $attributes = array(
 141                  'type' => 'text',
 142                  'size' => $fieldsize,
 143                  'name' => $markfield,
 144                  'id'=> $markfield
 145              );
 146              if (!is_null($currentmark)) {
 147                  $attributes['value'] = $currentmark;
 148              }
 149  
 150              $markrange = html_writer::empty_tag('input', array(
 151                  'type' => 'hidden',
 152                  'name' => $qa->get_behaviour_field_name('maxmark'),
 153                  'value' => $maxmark,
 154              )) . html_writer::empty_tag('input', array(
 155                  'type' => 'hidden',
 156                  'name' => $qa->get_control_field_name('minfraction'),
 157                  'value' => $qa->get_min_fraction(),
 158              )) . html_writer::empty_tag('input', array(
 159                  'type' => 'hidden',
 160                  'name' => $qa->get_control_field_name('maxfraction'),
 161                  'value' => $qa->get_max_fraction(),
 162              ));
 163  
 164              $error = $qa->validate_manual_mark($currentmark);
 165              $errorclass = '';
 166              if ($error !== '') {
 167                  $erroclass = ' error';
 168                  $error = html_writer::tag('span', $error,
 169                          array('class' => 'error')) . html_writer::empty_tag('br');
 170              }
 171  
 172              $a = new stdClass();
 173              $a->max = $qa->format_max_mark($options->markdp);
 174              $a->mark = html_writer::empty_tag('input', $attributes);
 175              $mark = html_writer::tag('div', html_writer::tag('div',
 176                          html_writer::tag('label', get_string('mark', 'question'),
 177                          array('for' => $markfield)),
 178                      array('class' => 'fitemtitle')) .
 179                      html_writer::tag('div', $error . get_string('xoutofmax', 'question', $a) .
 180                          $markrange, array('class' => 'felement ftext' . $errorclass)
 181                      ), array('class' => 'fitem'));
 182          }
 183  
 184          return html_writer::tag('fieldset', html_writer::tag('div', $comment . $mark,
 185                  array('class' => 'fcontainer clearfix')), array('class' => 'hidden'));
 186      }
 187  
 188      public function manual_comment_view(question_attempt $qa, question_display_options $options) {
 189          $output = '';
 190          if ($qa->has_manual_comment()) {
 191              $output .= get_string('commentx', 'question',
 192                      $qa->get_behaviour(false)->format_comment(null, null, $options->context));
 193          }
 194          if ($options->manualcommentlink) {
 195              $url = new moodle_url($options->manualcommentlink, array('slot' => $qa->get_slot()));
 196              $link = $this->output->action_link($url, get_string('commentormark', 'question'),
 197                      new popup_action('click', $url, 'commentquestion',
 198                      array('width' => 600, 'height' => 800)));
 199              $output .= html_writer::tag('div', $link, array('class' => 'commentlink'));
 200          }
 201          return $output;
 202      }
 203  
 204      /**
 205       * Display the manual comment, and a link to edit it, if appropriate.
 206       *
 207       * @param question_attempt $qa a question attempt.
 208       * @param question_display_options $options controls what should and should not be displayed.
 209       * @return string HTML fragment.
 210       */
 211      public function manual_comment(question_attempt $qa, question_display_options $options) {
 212          if ($options->manualcomment == question_display_options::EDITABLE) {
 213              return $this->manual_comment_fields($qa, $options);
 214  
 215          } else if ($options->manualcomment == question_display_options::VISIBLE) {
 216              return $this->manual_comment_view($qa, $options);
 217  
 218          } else {
 219              return '';
 220          }
 221      }
 222  
 223      /**
 224       * Several behaviours need a submit button, so put the common code here.
 225       * The button is disabled if the question is displayed read-only.
 226       * @param question_display_options $options controls what should and should not be displayed.
 227       * @return string HTML fragment.
 228       */
 229      protected function submit_button(question_attempt $qa, question_display_options $options) {
 230          if (!$qa->get_state()->is_active()) {
 231              return '';
 232          }
 233          $attributes = array(
 234              'type' => 'submit',
 235              'id' => $qa->get_behaviour_field_name('submit'),
 236              'name' => $qa->get_behaviour_field_name('submit'),
 237              'value' => 1,
 238              'class' => 'submit btn btn-secondary',
 239              'data-savescrollposition' => 'true',
 240          );
 241          if ($options->readonly) {
 242              $attributes['disabled'] = 'disabled';
 243          }
 244          $output = html_writer::tag('button',
 245              $options->add_question_identifier_to_label(get_string('check', 'question'), true), $attributes);
 246          if (!$options->readonly) {
 247              $this->page->requires->js_call_amd('core_question/question_engine', 'initSubmitButton', [$attributes['id']]);
 248          }
 249          return $output;
 250      }
 251  
 252      /**
 253       * Return any HTML that needs to be included in the page's <head> when
 254       * questions using this model are used.
 255       * @param $qa the question attempt that will be displayed on the page.
 256       * @return string HTML fragment.
 257       */
 258      public function head_code(question_attempt $qa) {
 259          return '';
 260      }
 261  
 262      /**
 263       * Generate the display of the marks for this question.
 264       * @param question_attempt $qa the question attempt to display.
 265       * @param core_question_renderer $qoutput the renderer for standard parts of questions.
 266       * @param question_display_options $options controls what should and should not be displayed.
 267       * @return HTML fragment.
 268       */
 269      public function mark_summary(question_attempt $qa, core_question_renderer $qoutput,
 270              question_display_options $options) {
 271          return $qoutput->standard_mark_summary($qa, $this, $options);
 272      }
 273  
 274      /**
 275       * Generate the display of the available marks for this question.
 276       * @param question_attempt $qa the question attempt to display.
 277       * @param core_question_renderer $qoutput the renderer for standard parts of questions.
 278       * @param question_display_options $options controls what should and should not be displayed.
 279       * @return HTML fragment.
 280       */
 281      public function marked_out_of_max(question_attempt $qa, core_question_renderer $qoutput,
 282              question_display_options $options) {
 283          return $qoutput->standard_marked_out_of_max($qa, $options);
 284      }
 285  
 286      /**
 287       * Generate the display of the marks for this question out of the available marks.
 288       * @param question_attempt $qa the question attempt to display.
 289       * @param core_question_renderer $qoutput the renderer for standard parts of questions.
 290       * @param question_display_options $options controls what should and should not be displayed.
 291       * @return HTML fragment.
 292       */
 293      public function mark_out_of_max(question_attempt $qa, core_question_renderer $qoutput,
 294              question_display_options $options) {
 295          return $qoutput->standard_mark_out_of_max($qa, $options);
 296      }
 297  }