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]

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