Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [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   * Defines the editing form 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/edit_ddtoimage_form_base.php');
  30  require_once($CFG->dirroot.'/question/type/ddmarker/shapes.php');
  31  
  32  define('QTYPE_DDMARKER_ALLOWED_TAGS_IN_MARKER', '<br><i><em><b><strong><sup><sub><u><span>');
  33  
  34  
  35  /**
  36   * Drag-and-drop images onto images  editing form definition.
  37   *
  38   * @copyright 2009 The Open University
  39   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  40   */
  41  class qtype_ddmarker_edit_form extends qtype_ddtoimage_edit_form_base {
  42      public function qtype() {
  43          return 'ddmarker';
  44      }
  45  
  46      protected function definition_inner($mform) {
  47          $mform->addElement('advcheckbox', 'showmisplaced', get_string('showmisplaced', 'qtype_ddmarker'));
  48          $mform->setDefault('showmisplaced', $this->get_default_value('showmisplaced', 0));
  49          parent::definition_inner($mform);
  50  
  51          $mform->addHelpButton('drops[0]', 'dropzones', 'qtype_ddmarker');
  52      }
  53  
  54      public function js_call() {
  55          global $PAGE;
  56          $PAGE->requires->js_call_amd('qtype_ddmarker/form', 'init');
  57      }
  58  
  59  
  60      protected function definition_draggable_items($mform, $itemrepeatsatstart) {
  61          $mform->addElement('header', 'draggableitemheader',
  62                                  get_string('markers', 'qtype_ddmarker'));
  63          $mform->addElement('advcheckbox', 'shuffleanswers', get_string('shuffleimages', 'qtype_'.$this->qtype()));
  64          $mform->setDefault('shuffleanswers', $this->get_default_value('shuffleanswers', 0));
  65          $this->repeat_elements($this->draggable_item($mform), $itemrepeatsatstart,
  66                  $this->draggable_items_repeated_options(),
  67                  'noitems', 'additems', self::ADD_NUM_ITEMS,
  68                  get_string('addmoreitems', 'qtype_ddmarker'), true);
  69      }
  70  
  71      protected function draggable_item($mform) {
  72          $draggableimageitem = array();
  73  
  74          $grouparray = array();
  75          $grouparray[] = $mform->createElement('text', 'label', '',
  76                  array('size' => 30, 'class' => 'tweakcss'));
  77          $mform->setType('text', PARAM_RAW_TRIMMED);
  78  
  79          $noofdragoptions = array(0 => get_string('infinite', 'qtype_ddmarker'));
  80          foreach (range(1, 6) as $option) {
  81              $noofdragoptions[$option] = $option;
  82          }
  83          $grouparray[] = $mform->createElement('select', 'noofdrags', get_string('noofdrags', 'qtype_ddmarker'), $noofdragoptions);
  84  
  85          $draggableimageitem[] = $mform->createElement('group', 'drags',
  86                                              get_string('marker_n', 'qtype_ddmarker'), $grouparray);
  87          return $draggableimageitem;
  88      }
  89  
  90      protected function draggable_items_repeated_options() {
  91          $repeatedoptions = array();
  92          $repeatedoptions['drags[label]']['type'] = PARAM_RAW;
  93          return $repeatedoptions;
  94      }
  95  
  96      protected function drop_zone($mform, $imagerepeats) {
  97          $grouparray = array();
  98          $shapearray = qtype_ddmarker_shape::shape_options();
  99          $grouparray[] = $mform->createElement('select', 'shape',
 100                                      get_string('shape', 'qtype_ddmarker'), $shapearray);
 101          $markernos = array();
 102          $markernos[0] = '';
 103          for ($i = 1; $i <= $imagerepeats; $i += 1) {
 104              $markernos[$i] = $i;
 105          }
 106          $grouparray[] = $mform->createElement('select', 'choice',
 107                                      get_string('marker', 'qtype_ddmarker'), $markernos);
 108          $grouparray[] = $mform->createElement('text', 'coords',
 109                  get_string('coords', 'qtype_ddmarker'),
 110                  array('size' => 30, 'class' => 'tweakcss'));
 111          $mform->setType('coords', PARAM_RAW); // These are validated manually.
 112          $dropzone = $mform->createElement('group', 'drops',
 113                  get_string('dropzone', 'qtype_ddmarker', '{no}'), $grouparray);
 114          return array($dropzone);
 115      }
 116  
 117      protected function drop_zones_repeated_options() {
 118          $repeatedoptions = array();
 119          $repeatedoptions['drops[coords]']['type'] = PARAM_RAW;
 120          return $repeatedoptions;
 121      }
 122  
 123      protected function get_hint_fields($withclearwrong = false, $withshownumpartscorrect = false) {
 124          $mform = $this->_form;
 125  
 126          $repeated = array();
 127          $repeated[] = $mform->createElement('editor', 'hint', get_string('hintn', 'question'),
 128                  array('rows' => 5), $this->editoroptions);
 129          $repeatedoptions['hint']['type'] = PARAM_RAW;
 130  
 131          $repeated[] = $mform->createElement('checkbox', 'hintshownumcorrect',
 132                          get_string('options', 'question'),
 133                          get_string('shownumpartscorrect', 'question'));
 134          $repeated[] = $mform->createElement('checkbox', 'hintoptions',
 135                          '',
 136                          get_string('stateincorrectlyplaced', 'qtype_ddmarker'));
 137          $repeated[] = $mform->createElement('checkbox', 'hintclearwrong',
 138                          '',
 139                          get_string('clearwrongparts', 'qtype_ddmarker'));
 140  
 141          return array($repeated, $repeatedoptions);
 142      }
 143  
 144      public function data_preprocessing($question) {
 145  
 146          $question = parent::data_preprocessing($question);
 147          $question = $this->data_preprocessing_combined_feedback($question, true);
 148          $question = $this->data_preprocessing_hints($question, true, true);
 149  
 150          $dragids = array(); // Drag no -> dragid.
 151          if (!empty($question->options)) {
 152              $question->shuffleanswers = $question->options->shuffleanswers;
 153              $question->showmisplaced = $question->options->showmisplaced;
 154              $question->drags = array();
 155              foreach ($question->options->drags as $drag) {
 156                  $dragindex = $drag->no - 1;
 157                  $question->drags[$dragindex] = array();
 158                  $question->drags[$dragindex]['label'] = $drag->label;
 159                  if ($drag->infinite == 1) {
 160                      $question->drags[$dragindex]['noofdrags'] = 0;
 161                  } else {
 162                      $question->drags[$dragindex]['noofdrags'] = $drag->noofdrags;
 163                  }
 164                  $dragids[$dragindex] = $drag->id;
 165              }
 166              $question->drops = array();
 167              foreach ($question->options->drops as $drop) {
 168                  $droparray = (array)$drop;
 169                  unset($droparray['id']);
 170                  unset($droparray['no']);
 171                  unset($droparray['questionid']);
 172                  $question->drops[$drop->no - 1] = $droparray;
 173              }
 174          }
 175          // Initialise file picker for bgimage.
 176          $draftitemid = file_get_submitted_draft_itemid('bgimage');
 177  
 178          file_prepare_draft_area($draftitemid, $this->context->id, 'qtype_ddmarker',
 179                                  'bgimage', !empty($question->id) ? (int) $question->id : null,
 180                                  self::file_picker_options());
 181          $question->bgimage = $draftitemid;
 182  
 183          $this->js_call();
 184  
 185          return $question;
 186      }
 187  
 188      /**
 189       * Perform the necessary preprocessing for the hint fields.
 190       *
 191       * @param object $question The data being passed to the form.
 192       * @param bool $withclearwrong Clear wrong hints.
 193       * @param bool $withshownumpartscorrect Show number correct.
 194       * @return object The modified data.
 195       */
 196      protected function data_preprocessing_hints($question, $withclearwrong = false,
 197                                                  $withshownumpartscorrect = false) {
 198          if (empty($question->hints)) {
 199              return $question;
 200          }
 201          parent::data_preprocessing_hints($question, $withclearwrong, $withshownumpartscorrect);
 202  
 203          $question->hintoptions = array();
 204          foreach ($question->hints as $hint) {
 205              $question->hintoptions[] = $hint->options;
 206          }
 207  
 208          return $question;
 209      }
 210  
 211      public function validation($data, $files) {
 212          $errors = parent::validation($data, $files);
 213          $bgimagesize = $this->get_image_size_in_draft_area($data['bgimage']);
 214          if ($bgimagesize === null) {
 215              $errors["bgimage"] = get_string('formerror_nobgimage', 'qtype_ddmarker');
 216          }
 217  
 218          for ($i = 0; $i < $data['nodropzone']; $i++) {
 219              $choice = $data['drops'][$i]['choice'];
 220              $choicepresent = ($choice !== '0');
 221  
 222              if ($choicepresent) {
 223                  // Test coords here.
 224                  if ($bgimagesize !== null) {
 225                      $shape = $data['drops'][$i]['shape'];
 226                      $coordsstring = $data['drops'][$i]['coords'];
 227                      $shapeobj = qtype_ddmarker_shape::create($shape, $coordsstring);
 228                      $interpretererror = $shapeobj->get_coords_interpreter_error();
 229                      if ($interpretererror !== false) {
 230                          $errors["drops[{$i}]"] = $interpretererror;
 231                      } else if (!$shapeobj->inside_width_height($bgimagesize)) {
 232                          $errorcode = 'shapeoutsideboundsofbgimage';
 233                          $errors["drops[{$i}]"] =
 234                                              get_string('formerror_'.$errorcode, 'qtype_ddmarker');
 235                      }
 236                  }
 237              } else {
 238                  if (trim($data['drops'][$i]['coords']) !== '') {
 239                      $errorcode = 'noitemselected';
 240                      $errors["drops[{$i}]"] = get_string('formerror_'.$errorcode, 'qtype_ddmarker');
 241                  }
 242              }
 243  
 244          }
 245          for ($dragindex = 0; $dragindex < $data['noitems']; $dragindex++) {
 246              $label = $data['drags'][$dragindex]['label'];
 247              if ($label != strip_tags($label, QTYPE_DDMARKER_ALLOWED_TAGS_IN_MARKER)) {
 248                  $errors["drags[{$dragindex}]"]
 249                      = get_string('formerror_onlysometagsallowed', 'qtype_ddmarker',
 250                                    s(QTYPE_DDMARKER_ALLOWED_TAGS_IN_MARKER));
 251              }
 252          }
 253          return $errors;
 254      }
 255  
 256      /**
 257       * Gets the width and height of a draft image.
 258       *
 259       * @param int $draftitemid ID of the draft image
 260       * @return array Return array of the width and height of the draft image.
 261       */
 262      public function get_image_size_in_draft_area($draftitemid) {
 263          global $USER;
 264          $usercontext = context_user::instance($USER->id);
 265          $fs = get_file_storage();
 266          $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id');
 267          if ($draftfiles) {
 268              foreach ($draftfiles as $file) {
 269                  if ($file->is_directory()) {
 270                      continue;
 271                  }
 272                  // Just return the data for the first good file, there should only be one.
 273                  $imageinfo = $file->get_imageinfo();
 274                  $width    = $imageinfo['width'];
 275                  $height   = $imageinfo['height'];
 276                  return array($width, $height);
 277              }
 278          }
 279          return null;
 280      }
 281  }