Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 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 39 and 311]

   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   * Examview question importer.
  19   *
  20   * @package    qformat_examview
  21   * @copyright  2005 Howard Miller
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  require_once($CFG->libdir . '/xmlize.php');
  29  
  30  
  31  /**
  32   * Examview question importer.
  33   *
  34   * @copyright  2005 Howard Miller
  35   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   */
  37  class qformat_examview extends qformat_based_on_xml {
  38  
  39      public $qtypes = array(
  40          'tf' => 'truefalse',
  41          'mc' => 'multichoice',
  42          'yn' => 'truefalse',
  43          'co' => 'shortanswer',
  44          'ma' => 'match',
  45          'mtf' => 99,
  46          'nr' => 'numerical',
  47          'pr' => 99,
  48          'es' => 'essay',
  49          'ca' => 99,
  50          'ot' => 99,
  51          'sa' => 'shortanswer',
  52      );
  53  
  54      public $matchingquestions = array();
  55  
  56      public function provide_import() {
  57          return true;
  58      }
  59  
  60      public function mime_type() {
  61          return 'application/xml';
  62      }
  63  
  64      /**
  65       * unxmlise reconstructs part of the xml data structure in order
  66       * to identify the actual data therein
  67       * @param array $xml section of the xml data structure
  68       * @return string data with evrything else removed
  69       */
  70      protected function unxmlise( $xml ) {
  71          // If it's not an array then it's probably just data.
  72          if (!is_array($xml)) {
  73              $text = s($xml);
  74          } else {
  75              // Otherwise parse the array.
  76              $text = '';
  77              foreach ($xml as $tag => $data) {
  78                  // If tag is '@' then it's attributes and we don't care.
  79                  if ($tag!=='@') {
  80                      $text = $text . $this->unxmlise( $data );
  81                  }
  82              }
  83          }
  84  
  85          // Currently we throw the tags we found.
  86          $text = strip_tags($text);
  87          return $text;
  88      }
  89  
  90      public function parse_matching_groups($matchinggroups) {
  91          if (empty($matchinggroups)) {
  92              return;
  93          }
  94          foreach ($matchinggroups as $matchgroup) {
  95              $newgroup = new stdClass();
  96              $groupname = trim($matchgroup['@']['name']);
  97              $questiontext = $this->unxmlise($matchgroup['#']['text'][0]['#']);
  98              $newgroup->questiontext = trim($questiontext);
  99              $newgroup->subchoices = array();
 100              $newgroup->subquestions = array();
 101              $newgroup->subanswers = array();
 102              $choices = $matchgroup['#']['choices']['0']['#'];
 103              foreach ($choices as $key => $value) {
 104                  if (strpos(trim($key), 'choice-') !== false) {
 105                      $key = strtoupper(trim(str_replace('choice-', '', $key)));
 106                      $newgroup->subchoices[$key] = trim($value['0']['#']);
 107                  }
 108              }
 109              $this->matching_questions[$groupname] = $newgroup;
 110          }
 111      }
 112  
 113      protected function parse_ma($qrec, $groupname) {
 114          $matchgroup = $this->matching_questions[$groupname];
 115          $phrase = trim($this->unxmlise($qrec['text']['0']['#']));
 116          $answer = trim($this->unxmlise($qrec['answer']['0']['#']));
 117          $answer = strip_tags( $answer );
 118          $matchgroup->mappings[$phrase] = $matchgroup->subchoices[$answer];
 119          $this->matching_questions[$groupname] = $matchgroup;
 120          return null;
 121      }
 122  
 123      protected function process_matches(&$questions) {
 124          if (empty($this->matching_questions)) {
 125              return;
 126          }
 127  
 128          foreach ($this->matching_questions as $matchgroup) {
 129              $question = $this->defaultquestion();
 130              $htmltext = s($matchgroup->questiontext);
 131              $question->questiontext = $htmltext;
 132              $question->questiontextformat = FORMAT_HTML;
 133              $question->questiontextfiles = array();
 134              $question->name = $this->create_default_question_name($question->questiontext, get_string('questionname', 'question'));
 135              $question->qtype = 'match';
 136              $question = $this->add_blank_combined_feedback($question);
 137              $question->subquestions = array();
 138              $question->subanswers = array();
 139              foreach ($matchgroup->subchoices as $subchoice) {
 140                  $fiber = array_keys ($matchgroup->mappings, $subchoice);
 141                  $subquestion = '';
 142                  foreach ($fiber as $subquestion) {
 143                      $question->subquestions[] = $this->text_field($subquestion);
 144                      $question->subanswers[] = $subchoice;
 145                  }
 146                  if ($subquestion == '') { // Then in this case, $subchoice is a distractor.
 147                      $question->subquestions[] = $this->text_field('');
 148                      $question->subanswers[] = $subchoice;
 149                  }
 150              }
 151              $questions[] = $question;
 152          }
 153      }
 154  
 155      protected function cleanunicode($text) {
 156          return str_replace('&#x2019;', "'", $text);
 157      }
 158  
 159      public function readquestions($lines) {
 160          // Parses an array of lines into an array of questions,
 161          // where each item is a question object as defined by
 162          // readquestion().
 163  
 164          $questions = array();
 165          $currentquestion = array();
 166  
 167          $text = implode(' ', $lines);
 168          $text = $this->cleanunicode($text);
 169  
 170          $xml = xmlize($text, 0);
 171          if (!empty($xml['examview']['#']['matching-group'])) {
 172              $this->parse_matching_groups($xml['examview']['#']['matching-group']);
 173          }
 174  
 175          $questionnode = $xml['examview']['#']['question'];
 176          foreach ($questionnode as $currentquestion) {
 177              if ($question = $this->readquestion($currentquestion)) {
 178                  $questions[] = $question;
 179              }
 180          }
 181  
 182          $this->process_matches($questions);
 183          return $questions;
 184      }
 185  
 186      public function readquestion($qrec) {
 187          global $OUTPUT;
 188  
 189          $type = trim($qrec['@']['type']);
 190          $question = $this->defaultquestion();
 191          if (array_key_exists($type, $this->qtypes)) {
 192              $question->qtype = $this->qtypes[$type];
 193          } else {
 194              $question->qtype = null;
 195          }
 196          $question->single = 1;
 197  
 198          // Only one answer is allowed.
 199          $htmltext = $this->unxmlise($qrec['#']['text'][0]['#']);
 200  
 201          $question->questiontext = $this->cleaninput($htmltext);
 202          $question->questiontextformat = FORMAT_HTML;
 203          $question->questiontextfiles = array();
 204          $question->name = $this->create_default_question_name($question->questiontext, get_string('questionname', 'question'));
 205  
 206          switch ($question->qtype) {
 207              case 'multichoice':
 208                  $question = $this->parse_mc($qrec['#'], $question);
 209                  break;
 210              case 'match':
 211                  $groupname = trim($qrec['@']['group']);
 212                  $question = $this->parse_ma($qrec['#'], $groupname);
 213                  break;
 214              case 'truefalse':
 215                  $question = $this->parse_tf_yn($qrec['#'], $question);
 216                  break;
 217              case 'shortanswer':
 218                  $question = $this->parse_co($qrec['#'], $question);
 219                  break;
 220              case 'essay':
 221                  $question = $this->parse_es($qrec['#'], $question);
 222                  break;
 223              case 'numerical':
 224                  $question = $this->parse_nr($qrec['#'], $question);
 225                  break;
 226                  break;
 227              default:
 228                  echo $OUTPUT->notification(get_string('unknownorunhandledtype', 'question', $type));
 229                  $question = null;
 230          }
 231  
 232          return $question;
 233      }
 234  
 235      protected function parse_tf_yn($qrec, $question) {
 236          $choices = array('T' => 1, 'Y' => 1, 'F' => 0, 'N' => 0 );
 237          $answer = trim($qrec['answer'][0]['#']);
 238          $question->answer = $choices[$answer];
 239          $question->correctanswer = $question->answer;
 240          if ($question->answer == 1) {
 241              $question->feedbacktrue = $this->text_field(get_string('correct', 'question'));
 242              $question->feedbackfalse = $this->text_field(get_string('incorrect', 'question'));
 243          } else {
 244              $question->feedbacktrue = $this->text_field(get_string('incorrect', 'question'));
 245              $question->feedbackfalse = $this->text_field(get_string('correct', 'question'));
 246          }
 247          return $question;
 248      }
 249  
 250      protected function parse_mc($qrec, $question) {
 251          $question = $this->add_blank_combined_feedback($question);
 252          $answer = 'choice-'.strtolower(trim($qrec['answer'][0]['#']));
 253  
 254          $choices = $qrec['choices'][0]['#'];
 255          foreach ($choices as $key => $value) {
 256              if (strpos(trim($key), 'choice-') !== false) {
 257  
 258                  $question->answer[] = $this->text_field(s($this->unxmlise($value[0]['#'])));
 259                  if (strcmp($key, $answer) == 0) {
 260                      $question->fraction[] = 1;
 261                      $question->feedback[] = $this->text_field(get_string('correct', 'question'));
 262                  } else {
 263                      $question->fraction[] = 0;
 264                      $question->feedback[] = $this->text_field(get_string('incorrect', 'question'));
 265                  }
 266              }
 267          }
 268          return $question;
 269      }
 270  
 271      protected function parse_co($qrec, $question) {
 272          $question->usecase = 0;
 273          $answer = trim($this->unxmlise($qrec['answer'][0]['#']));
 274          $answer = strip_tags( $answer );
 275          $answers = explode("\n", $answer);
 276  
 277          foreach ($answers as $key => $value) {
 278              $value = trim($value);
 279              if (strlen($value) > 0) {
 280                  $question->answer[] = $value;
 281                  $question->fraction[] = 1;
 282                  $question->feedback[] = $this->text_field(get_string('correct', 'question'));
 283              }
 284          }
 285          $question->answer[] = '*';
 286          $question->fraction[] = 0;
 287          $question->feedback[] = $this->text_field(get_string('incorrect', 'question'));
 288  
 289          return $question;
 290      }
 291  
 292      protected function parse_es($qrec, $question) {
 293          $feedback = trim($this->unxmlise($qrec['answer'][0]['#']));
 294          $question->graderinfo =  $this->text_field($feedback);
 295          $question->responsetemplate =  $this->text_field('');
 296          $question->responserequired = 1;
 297          $question->feedback = $feedback;
 298          $question->responseformat = 'editor';
 299          $question->responsefieldlines = 15;
 300          $question->attachments = 0;
 301          $question->attachmentsrequired = 0;
 302          $question->fraction = 0;
 303          return $question;
 304      }
 305  
 306      protected function parse_nr($qrec, $question) {
 307          $answer = trim($this->unxmlise($qrec['answer'][0]['#']));
 308          $answer = strip_tags( $answer );
 309          $answers = explode("\n", $answer);
 310  
 311          foreach ($answers as $key => $value) {
 312              $value = trim($value);
 313              if (is_numeric($value)) {
 314                  $errormargin = 0;
 315                  $question->answer[] = $value;
 316                  $question->fraction[] = 1;
 317                  $question->feedback[] = $this->text_field(get_string('correct', 'question'));
 318                  $question->tolerance[] = $errormargin;
 319              }
 320          }
 321          return $question;
 322      }
 323  
 324  }
 325  // End class.
 326  
 327