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