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.
   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   * Base class for rendering question types like this one.
  19   *
  20   * @package    qtype_gapselect
  21   * @copyright  2011 The Open University
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  
  29  /**
  30   * Renders question types where the question includes embedded interactive elements.
  31   *
  32   * @copyright  2011 The Open University
  33   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34   */
  35  abstract class qtype_elements_embedded_in_question_text_renderer
  36          extends qtype_with_combined_feedback_renderer {
  37      public function formulation_and_controls(question_attempt $qa,
  38              question_display_options $options) {
  39  
  40          $question = $qa->get_question();
  41  
  42          $questiontext = '';
  43          // Glue question fragments together using unique placeholders, apply format_text to the result
  44          // and then substitute each placeholder with the embedded element.
  45          // This will ensure that format_text() is applied to the whole question but not to the embedded elements.
  46          $placeholders = $this->get_fragments_glue_placeholders($question->textfragments);
  47          foreach ($question->textfragments as $i => $fragment) {
  48              if ($i > 0) {
  49                  $questiontext .= $placeholders[$i];
  50                  // There is a preg_replace 11 lines ahead where the $embeddedelements is used as the replace.
  51                  // If there are currency like options ($4) in the select then the preg_replace treats them as backreferences.
  52                  // So we need to escape the backreferences here.
  53                  $embeddedelements[$placeholders[$i]] =
  54                          preg_replace('/\$(\d)/', '\\\$$1', $this->embedded_element($qa, $i, $options));
  55              }
  56              $questiontext .= $fragment;
  57          }
  58          $questiontext = $question->format_text($questiontext,
  59              $question->questiontextformat, $qa, 'question', 'questiontext', $question->id);
  60          foreach ($placeholders as $i => $placeholder) {
  61              $questiontext = preg_replace('/'. preg_quote($placeholder, '/') . '/',
  62                  $embeddedelements[$placeholder], $questiontext);
  63          }
  64  
  65          $result = '';
  66          $result .= html_writer::tag('div', $questiontext, array('class' => 'qtext'));
  67  
  68          $result .= $this->post_qtext_elements($qa, $options);
  69  
  70          if ($qa->get_state() == question_state::$invalid) {
  71              $result .= html_writer::nonempty_tag('div',
  72                      $question->get_validation_error($qa->get_last_qt_data()),
  73                      array('class' => 'validationerror'));
  74          }
  75  
  76          return $result;
  77      }
  78  
  79      /**
  80       * Find strings that we can use to glue the fragments with
  81       *
  82       * These strings have to be all different and neither of them can be present in the text
  83       *
  84       * @param array $fragments
  85       * @return array array with indexes from 1 to count($fragments)-1
  86       */
  87      protected function get_fragments_glue_placeholders($fragments) {
  88          $fragmentscount = count($fragments);
  89          if ($fragmentscount <= 1) {
  90              return [];
  91          }
  92          $prefix = '[[$';
  93          $postfix = ']]';
  94          $text = join('', $fragments);
  95          while (preg_match('/' . preg_quote($prefix, '/') . '\\d+' . preg_quote($postfix, '/') . '/', $text)) {
  96              $prefix .= '$';
  97          }
  98          $glues = [];
  99          for ($i = 1; $i < $fragmentscount; $i++) {
 100              $glues[$i] = $prefix . $i . $postfix;
 101          }
 102          return $glues;
 103      }
 104  
 105      protected function qtext_id($qa) {
 106          return str_replace(':', '_', $qa->get_qt_field_name(''));
 107      }
 108  
 109      protected abstract function embedded_element(question_attempt $qa, $place,
 110              question_display_options $options);
 111  
 112      protected function post_qtext_elements(question_attempt $qa,
 113              question_display_options $options) {
 114          return '';
 115      }
 116  
 117      protected function box_id(question_attempt $qa, $place) {
 118          return str_replace(':', '_', $qa->get_qt_field_name($place));
 119      }
 120  
 121      public function specific_feedback(question_attempt $qa) {
 122          return $this->combined_feedback($qa);
 123      }
 124  
 125      public function correct_response(question_attempt $qa) {
 126          $question = $qa->get_question();
 127  
 128          $correctanswer = '';
 129          foreach ($question->textfragments as $i => $fragment) {
 130              if ($i > 0) {
 131                  $group = $question->places[$i];
 132                  $choice = $question->choices[$group][$question->rightchoices[$i]];
 133                  $correctanswer .= '[' . str_replace('-', '&#x2011;',
 134                          $choice->text) . ']';
 135              }
 136              $correctanswer .= $fragment;
 137          }
 138  
 139          if (!empty($correctanswer)) {
 140              return get_string('correctansweris', 'qtype_gapselect',
 141                      $question->format_text($correctanswer, $question->questiontextformat,
 142                              $qa, 'question', 'questiontext', $question->id));
 143          }
 144      }
 145  }