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   * Question type class for the drag-and-drop images onto images question type.
  19   *
  20   * @package   qtype_ddmarker
  21   * @copyright 2012 The Open University
  22   * @author    Jamie Pratt <me@jamiep.org>
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  require_once($CFG->dirroot . '/question/type/ddimageortext/questiontypebase.php');
  30  
  31  /**
  32   * Question hint for ddmarker.
  33   *
  34   * An extension of {@link question_hint} for questions like match and multiple
  35   * choice with multile answers, where there are options for whether to show the
  36   * number of parts right at each stage, and to reset the wrong parts.
  37   *
  38   * @copyright  2010 The Open University
  39   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  40   */
  41  class question_hint_ddmarker extends question_hint_with_parts {
  42  
  43      public $statewhichincorrect;
  44  
  45      /**
  46       * Constructor.
  47       * @param int the hint id from the database.
  48       * @param string $hint The hint text
  49       * @param int the corresponding text FORMAT_... type.
  50       * @param bool $shownumcorrect whether the number of right parts should be shown
  51       * @param bool $clearwrong whether the wrong parts should be reset.
  52       */
  53      public function __construct($id, $hint, $hintformat, $shownumcorrect,
  54                                                              $clearwrong, $statewhichincorrect) {
  55          parent::__construct($id, $hint, $hintformat, $shownumcorrect, $clearwrong);
  56          $this->statewhichincorrect = $statewhichincorrect;
  57      }
  58  
  59      /**
  60       * Create a basic hint from a row loaded from the question_hints table in the database.
  61       * @param object $row with property options as well as hint, shownumcorrect and clearwrong set.
  62       * @return question_hint_ddmarker
  63       */
  64      public static function load_from_record($row) {
  65          return new question_hint_ddmarker($row->id, $row->hint, $row->hintformat,
  66                  $row->shownumcorrect, $row->clearwrong, $row->options);
  67      }
  68  }
  69  
  70  
  71  
  72  /**
  73   * The drag-and-drop markers question type class.
  74   *
  75   * @copyright  2009 The Open University
  76   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  77   */
  78  class qtype_ddmarker extends qtype_ddtoimage_base {
  79  
  80      public function save_defaults_for_new_questions(stdClass $fromform): void {
  81          parent::save_defaults_for_new_questions($fromform);
  82          $this->set_default_value('showmisplaced', $fromform->showmisplaced);
  83          $this->set_default_value('shuffleanswers', $fromform->shuffleanswers);
  84      }
  85  
  86      public function save_question_options($formdata) {
  87          global $DB, $USER;
  88          $context = $formdata->context;
  89  
  90          $options = $DB->get_record('qtype_ddmarker', array('questionid' => $formdata->id));
  91          if (!$options) {
  92              $options = new stdClass();
  93              $options->questionid = $formdata->id;
  94              $options->correctfeedback = '';
  95              $options->partiallycorrectfeedback = '';
  96              $options->incorrectfeedback = '';
  97              $options->id = $DB->insert_record('qtype_ddmarker', $options);
  98          }
  99  
 100          $options->shuffleanswers = !empty($formdata->shuffleanswers);
 101          $options->showmisplaced = !empty($formdata->showmisplaced);
 102          $options = $this->save_combined_feedback_helper($options, $formdata, $context, true);
 103          $this->save_hints($formdata, true);
 104          $DB->update_record('qtype_ddmarker', $options);
 105          $DB->delete_records('qtype_ddmarker_drops', array('questionid' => $formdata->id));
 106          foreach (array_keys($formdata->drops) as $dropno) {
 107              if ($formdata->drops[$dropno]['choice'] == 0) {
 108                  continue;
 109              }
 110              $drop = new stdClass();
 111              $drop->questionid = $formdata->id;
 112              $drop->no = $dropno + 1;
 113              $drop->shape = $formdata->drops[$dropno]['shape'];
 114              $drop->coords = $formdata->drops[$dropno]['coords'];
 115              $drop->choice = $formdata->drops[$dropno]['choice'];
 116  
 117              $DB->insert_record('qtype_ddmarker_drops', $drop);
 118          }
 119  
 120          // An array of drag no -> drag id.
 121          $olddragids = $DB->get_records_menu('qtype_ddmarker_drags',
 122                                      array('questionid' => $formdata->id),
 123                                      '', 'no, id');
 124          foreach (array_keys($formdata->drags) as $dragno) {
 125              if ($formdata->drags[$dragno]['label'] !== '') {
 126                  $drag = new stdClass();
 127                  $drag->questionid = $formdata->id;
 128                  $drag->no = $dragno + 1;
 129                  if ($formdata->drags[$dragno]['noofdrags'] == 0) {
 130                      $drag->infinite = 1;
 131                      $drag->noofdrags = 1;
 132                  } else {
 133                      $drag->infinite = 0;
 134                      $drag->noofdrags = $formdata->drags[$dragno]['noofdrags'];
 135                  }
 136                  $drag->label = $formdata->drags[$dragno]['label'];
 137  
 138                  if (isset($olddragids[$dragno + 1])) {
 139                      $drag->id = $olddragids[$dragno + 1];
 140                      unset($olddragids[$dragno + 1]);
 141                      $DB->update_record('qtype_ddmarker_drags', $drag);
 142                  } else {
 143                      $drag->id = $DB->insert_record('qtype_ddmarker_drags', $drag);
 144                  }
 145              }
 146          }
 147  
 148          if (!empty($olddragids)) {
 149              list($sql, $params) = $DB->get_in_or_equal(array_values($olddragids));
 150              $DB->delete_records_select('qtype_ddmarker_drags', "id $sql", $params);
 151          }
 152          file_save_draft_area_files($formdata->bgimage, $formdata->context->id,
 153                                      'qtype_ddmarker', 'bgimage', $formdata->id,
 154                                      array('subdirs' => 0, 'maxbytes' => 0, 'maxfiles' => 1));
 155      }
 156  
 157      public function save_hints($formdata, $withparts = false) {
 158          global $DB;
 159          $context = $formdata->context;
 160  
 161          $oldhints = $DB->get_records('question_hints',
 162                  array('questionid' => $formdata->id), 'id ASC');
 163  
 164          if (!empty($formdata->hint)) {
 165              $numhints = max(array_keys($formdata->hint)) + 1;
 166          } else {
 167              $numhints = 0;
 168          }
 169  
 170          if ($withparts) {
 171              if (!empty($formdata->hintclearwrong)) {
 172                  $numclears = max(array_keys($formdata->hintclearwrong)) + 1;
 173              } else {
 174                  $numclears = 0;
 175              }
 176              if (!empty($formdata->hintshownumcorrect)) {
 177                  $numshows = max(array_keys($formdata->hintshownumcorrect)) + 1;
 178              } else {
 179                  $numshows = 0;
 180              }
 181              $numhints = max($numhints, $numclears, $numshows);
 182          }
 183  
 184          for ($i = 0; $i < $numhints; $i += 1) {
 185              if (html_is_blank($formdata->hint[$i]['text'])) {
 186                  $formdata->hint[$i]['text'] = '';
 187              }
 188  
 189              if ($withparts) {
 190                  $clearwrong = !empty($formdata->hintclearwrong[$i]);
 191                  $shownumcorrect = !empty($formdata->hintshownumcorrect[$i]);
 192                  $statewhichincorrect = !empty($formdata->hintoptions[$i]);
 193              }
 194  
 195              if (empty($formdata->hint[$i]['text']) && empty($clearwrong) &&
 196                      empty($shownumcorrect) && empty($statewhichincorrect)) {
 197                  continue;
 198              }
 199  
 200              // Update an existing hint if possible.
 201              $hint = array_shift($oldhints);
 202              if (!$hint) {
 203                  $hint = new stdClass();
 204                  $hint->questionid = $formdata->id;
 205                  $hint->hint = '';
 206                  $hint->id = $DB->insert_record('question_hints', $hint);
 207              }
 208  
 209              $hint->hint = $this->import_or_save_files($formdata->hint[$i],
 210                      $context, 'question', 'hint', $hint->id);
 211              $hint->hintformat = $formdata->hint[$i]['format'];
 212              if ($withparts) {
 213                  $hint->clearwrong = $clearwrong;
 214                  $hint->shownumcorrect = $shownumcorrect;
 215                  $hint->options = $statewhichincorrect;
 216              }
 217              $DB->update_record('question_hints', $hint);
 218          }
 219  
 220          // Delete any remaining old hints.
 221          $fs = get_file_storage();
 222          foreach ($oldhints as $oldhint) {
 223              $fs->delete_area_files($context->id, 'question', 'hint', $oldhint->id);
 224              $DB->delete_records('question_hints', array('id' => $oldhint->id));
 225          }
 226      }
 227  
 228      protected function make_hint($hint) {
 229          return question_hint_ddmarker::load_from_record($hint);
 230      }
 231      protected function make_choice($dragdata) {
 232          return new qtype_ddmarker_drag_item($dragdata->label, $dragdata->no, $dragdata->infinite, $dragdata->noofdrags);
 233      }
 234  
 235      protected function make_place($dropdata) {
 236          return new qtype_ddmarker_drop_zone($dropdata->no, $dropdata->shape, $dropdata->coords);
 237      }
 238  
 239      protected function initialise_combined_feedback(question_definition $question,
 240                                                                  $questiondata, $withparts = false) {
 241          parent::initialise_combined_feedback($question, $questiondata, $withparts);
 242          $question->showmisplaced = $questiondata->options->showmisplaced;
 243      }
 244  
 245      public function move_files($questionid, $oldcontextid, $newcontextid) {
 246          global $DB;
 247          $fs = get_file_storage();
 248  
 249          parent::move_files($questionid, $oldcontextid, $newcontextid);
 250          $fs->move_area_files_to_new_context($oldcontextid,
 251                                      $newcontextid, 'qtype_ddmarker', 'bgimage', $questionid);
 252  
 253          $this->move_files_in_combined_feedback($questionid, $oldcontextid, $newcontextid);
 254          $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid);
 255      }
 256  
 257      /**
 258       * Delete all the files belonging to this question.
 259       * @param int $questionid the question being deleted.
 260       * @param int $contextid the context the question is in.
 261       */
 262  
 263      protected function delete_files($questionid, $contextid) {
 264          global $DB;
 265          $fs = get_file_storage();
 266  
 267          parent::delete_files($questionid, $contextid);
 268  
 269          $this->delete_files_in_combined_feedback($questionid, $contextid);
 270          $this->delete_files_in_hints($questionid, $contextid);
 271      }
 272  
 273      public function export_to_xml($question, qformat_xml $format, $extra = null) {
 274          $fs = get_file_storage();
 275          $contextid = $question->contextid;
 276          $output = '';
 277  
 278          if ($question->options->shuffleanswers) {
 279              $output .= "    <shuffleanswers/>\n";
 280          }
 281          if ($question->options->showmisplaced) {
 282              $output .= "    <showmisplaced/>\n";
 283          }
 284          $output .= $format->write_combined_feedback($question->options,
 285                                                      $question->id,
 286                                                      $question->contextid);
 287          $files = $fs->get_area_files($contextid, 'qtype_ddmarker', 'bgimage', $question->id);
 288          $output .= "    ".$this->write_files($files, 2)."\n";;
 289  
 290          foreach ($question->options->drags as $drag) {
 291              $files =
 292                      $fs->get_area_files($contextid, 'qtype_ddmarker', 'dragimage', $drag->id);
 293              $output .= "    <drag>\n";
 294              $output .= "      <no>{$drag->no}</no>\n";
 295              $output .= $format->writetext($drag->label, 3);
 296              if ($drag->infinite) {
 297                  $output .= "      <infinite/>\n";
 298              }
 299              $output .= "      <noofdrags>{$drag->noofdrags}</noofdrags>\n";
 300              $output .= "    </drag>\n";
 301          }
 302          foreach ($question->options->drops as $drop) {
 303              $output .= "    <drop>\n";
 304              $output .= "      <no>{$drop->no}</no>\n";
 305              $output .= "      <shape>{$drop->shape}</shape>\n";
 306              $output .= "      <coords>{$drop->coords}</coords>\n";
 307              $output .= "      <choice>{$drop->choice}</choice>\n";
 308              $output .= "    </drop>\n";
 309          }
 310  
 311          return $output;
 312      }
 313  
 314      public function import_from_xml($data, $question, qformat_xml $format, $extra=null) {
 315          if (!isset($data['@']['type']) || $data['@']['type'] != 'ddmarker') {
 316              return false;
 317          }
 318  
 319          $question = $format->import_headers($data);
 320          $question->qtype = 'ddmarker';
 321  
 322          $question->shuffleanswers = array_key_exists('shuffleanswers',
 323                                                      $format->getpath($data, array('#'), array()));
 324          $question->showmisplaced = array_key_exists('showmisplaced',
 325                                                      $format->getpath($data, array('#'), array()));
 326  
 327          $filexml = $format->getpath($data, array('#', 'file'), array());
 328          $question->bgimage = $format->import_files_as_draft($filexml);
 329          $drags = $data['#']['drag'];
 330          $question->drags = array();
 331  
 332          foreach ($drags as $dragxml) {
 333              $dragno = $format->getpath($dragxml, array('#', 'no', 0, '#'), 0);
 334              $dragindex = $dragno - 1;
 335              $question->drags[$dragindex] = array();
 336              $question->drags[$dragindex]['label'] =
 337                          $format->getpath($dragxml, array('#', 'text', 0, '#'), '', true);
 338              if (array_key_exists('infinite', $dragxml['#'])) {
 339                  $question->drags[$dragindex]['noofdrags'] = 0; // Means infinite in the form.
 340              } else {
 341                  // Defaults to 1 if 'noofdrags' not set.
 342                  $question->drags[$dragindex]['noofdrags'] = $format->getpath($dragxml, array('#', 'noofdrags', 0, '#'), 1);
 343              }
 344          }
 345  
 346          $drops = $data['#']['drop'];
 347          $question->drops = array();
 348          foreach ($drops as $dropxml) {
 349              $dropno = $format->getpath($dropxml, array('#', 'no', 0, '#'), 0);
 350              $dropindex = $dropno - 1;
 351              $question->drops[$dropindex] = array();
 352              $question->drops[$dropindex]['choice'] =
 353                          $format->getpath($dropxml, array('#', 'choice', 0, '#'), 0);
 354              $question->drops[$dropindex]['shape'] =
 355                          $format->getpath($dropxml, array('#', 'shape', 0, '#'), '');
 356              $question->drops[$dropindex]['coords'] =
 357                          $format->getpath($dropxml, array('#', 'coords', 0, '#'), '');
 358          }
 359  
 360          $format->import_combined_feedback($question, $data, true);
 361          $format->import_hints($question, $data, true, true,
 362                  $format->get_format($question->questiontextformat));
 363  
 364          return $question;
 365      }
 366  
 367      public function get_random_guess_score($questiondata) {
 368          return null;
 369      }
 370  
 371  }