From d2ac6cea2a7d6d21edeb062dcd6c17193b54b8b9 Mon Sep 17 00:00:00 2001 From: Jamie Pratt Date: Wed, 28 Sep 2011 19:31:48 +0700 Subject: [PATCH] MDL-47494 ddmarker: NOBUG question editing form is now working correctly. --- question/type/ddmarker/db/install.xml | 5 +- question/type/ddmarker/edit_ddmarker_form.php | 208 ++++++++++++++++++ .../type/ddmarker/lang/en/qtype_ddmarker.php | 46 ++-- question/type/ddmarker/questiontype.php | 193 +++++++++++++++- 4 files changed, 428 insertions(+), 24 deletions(-) diff --git a/question/type/ddmarker/db/install.xml b/question/type/ddmarker/db/install.xml index 22d6c46226171..a251d0c436f6b 100644 --- a/question/type/ddmarker/db/install.xml +++ b/question/type/ddmarker/db/install.xml @@ -1,5 +1,5 @@ - @@ -43,7 +43,8 @@ - + + diff --git a/question/type/ddmarker/edit_ddmarker_form.php b/question/type/ddmarker/edit_ddmarker_form.php index dcb0bb3212832..e8b841edbd721 100644 --- a/question/type/ddmarker/edit_ddmarker_form.php +++ b/question/type/ddmarker/edit_ddmarker_form.php @@ -36,4 +36,212 @@ class qtype_ddmarker_edit_form extends qtype_ddtoimage_edit_form_base { + public function qtype() { + return 'ddmarker'; + } + + protected function definition_inner($mform) { + $mform->addElement('advcheckbox', 'showmisplaced', ' ', + get_string('showmisplaced', 'qtype_ddmarker')); + parent::definition_inner($mform); + } + + public function js_call() { + global $PAGE; + $maxsizes =new stdClass(); + $maxsizes->bgimage = new stdClass(); + $maxsizes->bgimage->width = QTYPE_DDMARKER_BGIMAGE_MAXWIDTH; + $maxsizes->bgimage->height = QTYPE_DDMARKER_BGIMAGE_MAXHEIGHT; + + $params = array('maxsizes' => $maxsizes, + 'topnode' => 'fieldset#previewareaheader'); + + $PAGE->requires->yui_module('moodle-qtype_ddmarker-form', + 'M.qtype_ddmarker.init_form', + array($params)); + } + + protected function definition_draggable_items($mform, $itemrepeatsatstart) { + + $mform->addElement('header', 'draggableitemheader', + get_string('markers', 'qtype_ddmarker')); + $this->repeat_elements($this->draggable_item($mform), $itemrepeatsatstart, + $this->draggable_items_repeated_options(), + 'noitems', 'additems', self::ADD_NUM_ITEMS, + get_string('addmoreitems', 'qtype_ddmarker')); + } + + protected function draggable_item($mform) { + $draggableimageitem = array(); + + $grouparray= array(); + $grouparray[] = $mform->createElement('text', 'label', + get_string('marker_n', 'qtype_ddmarker'), + array('size'=>30, 'class'=>'tweakcss')); + $mform->setType('text', PARAM_RAW_TRIMMED); + + $grouparray[] = $mform->createElement('advcheckbox', 'infinite', ' ', + get_string('infinite', 'qtype_ddmarker')); + $draggableimageitem[] = $mform->createElement('group', 'drags', + get_string('marker_n', 'qtype_ddmarker'), $grouparray); + return $draggableimageitem; + } + + protected function draggable_items_repeated_options() { + return array(); + } + + + + + protected function drop_zone($mform, $imagerepeats) { + $dropzoneitem = array(); + + $grouparray = array(); + foreach (array('circle', 'rectangle', 'polygon') as $shape) { + $shapearray[$shape] = get_string('shape_'.$shape, 'qtype_ddmarker'); + } + $grouparray[] = $mform->createElement('select', 'shape', + get_string('marker', 'qtype_ddmarker'), $shapearray); + $grouparray[] = $mform->createElement('text', 'coords', + get_string('coords', 'qtype_ddmarker'), + array('size'=>20, 'class'=>'tweakcss')); + $mform->setType('coords', PARAM_NOTAGS); + $markernos = array(); + $markernos[0] = ''; + for ($i = 1; $i <= $imagerepeats; $i += 1) { + $markernos[$i] = $i; + } + $grouparray[] = $mform->createElement('static', '', '', ' ' . + get_string('marker', 'qtype_ddmarker').' '); + $grouparray[] = $mform->createElement('select', 'choice', + get_string('marker', 'qtype_ddmarker'), $markernos); + $grouparray[] = $mform->createElement('static', '', '', ' ' . + get_string('alttext', 'qtype_ddmarker').' '); + $grouparray[] = $mform->createElement('text', 'label', + get_string('alttext', 'qtype_ddmarker'), + array('size'=>10, 'class'=>'tweakcss')); + $dropzone = $mform->createElement('group', 'drops', + get_string('dropzone', 'qtype_ddmarker', '{no}'), $grouparray); + return array($dropzone); + } + + protected function drop_zones_repeated_options() { + $repeatedoptions = array(); + return $repeatedoptions; + } + + protected function get_hint_fields($withclearwrong = false, $withshownumpartscorrect = false) { + $mform = $this->_form; + + $repeated = array(); + $repeated[] = $mform->createElement('header', 'hinthdr', get_string('hintn', 'question')); + $repeated[] = $mform->createElement('editor', 'hint', get_string('hinttext', 'question'), + array('rows' => 5), $this->editoroptions); + $repeatedoptions['hint']['type'] = PARAM_RAW; + + $repeated[] = $mform->createElement('checkbox', 'hintshownumcorrect', + get_string('options', 'question'), + get_string('shownumpartscorrect', 'question')); + $repeated[] = $mform->createElement('checkbox', 'hintstatewhichincorrect', + '', + get_string('stateincorrectlyplaced', 'qtype_ddmarker')); + $repeated[] = $mform->createElement('checkbox', 'hintclearwrong', + '', + get_string('clearwrongparts', 'qtype_ddmarker')); + + return array($repeated, $repeatedoptions); + } + + public function data_preprocessing($question) { + + $question = parent::data_preprocessing($question); + $question = $this->data_preprocessing_combined_feedback($question, true); + $question = $this->data_preprocessing_hints($question); + + $dragids = array(); // drag no -> dragid + if (!empty($question->options)) { + $question->shuffleanswers = $question->options->shuffleanswers; + $question->showmisplaced = $question->options->showmisplaced; + $question->drags = array(); + foreach ($question->options->drags as $drag) { + $dragindex = $drag->no -1; + $question->drags[$dragindex] = array(); + $question->drags[$dragindex]['label'] = $drag->label; + $question->drags[$dragindex]['infinite'] = $drag->infinite; + $dragids[$dragindex] = $drag->id; + } + $question->drops = array(); + foreach ($question->options->drops as $drop) { + $droparray = (array)$drop; + unset($droparray['id']); + unset($droparray['no']); + unset($droparray['questionid']); + $question->drops[$drop->no -1] = $droparray; + } + } + //initialise file picker for bgimage + $draftitemid = file_get_submitted_draft_itemid('bgimage'); + + file_prepare_draft_area($draftitemid, $this->context->id, 'qtype_ddmarker', + 'bgimage', !empty($question->id) ? (int) $question->id : null, + self::file_picker_options()); + $question->bgimage = $draftitemid; + + //$this->js_call(); + + return $question; + } + /** + * Perform the necessary preprocessing for the hint fields. + * @param object $question the data being passed to the form. + * @return object $question the modified data. + */ + protected function data_preprocessing_hints($question) { + if (empty($question->hints)) { + return $question; + } + parent::data_preprocessing_hints($question, true, true); + + $question->hintoptions = array(); + foreach ($question->hints as $hint) { + $question->hintstatewhichincorrect[] = $hint->options; + } + + return $question; + } + + public function validation($data, $files) { + $errors = parent::validation($data, $files); + if (!self::file_uploaded($data['bgimage'])) { + $errors["bgimage"] = get_string('formerror_nobgimage', 'qtype_ddmarker'); + } + + $allchoices = array(); + for ($i=0; $i < $data['nodropzone']; $i++) { + $labelpresent = (trim($data['drops'][$i]['label']) !== ''); + $choice = $data['drops'][$i]['choice']; + $choicepresent = ($choice !== '0'); + + if ($choicepresent) { + //test coords here + + + } else { + if (trim($data['drops'][$i]['coords']) !== '') { + $errors["drops[{$i}]"] + = get_string('formerror_noitemselected', 'qtype_ddmarker'); + } + } + } + for ($dragindex=0; $dragindex < $data['noitems']; $dragindex++) { + $label = $data['drags'][$dragindex]['label']; + if ($label != strip_tags($label)) { + $errors["drags[{$dragindex}]"] + = get_string('formerror_notagsallowed', 'qtype_ddmarker'); + } + + } + return $errors; + } } diff --git a/question/type/ddmarker/lang/en/qtype_ddmarker.php b/question/type/ddmarker/lang/en/qtype_ddmarker.php index 666ff34959087..2f14a1cf0df58 100644 --- a/question/type/ddmarker/lang/en/qtype_ddmarker.php +++ b/question/type/ddmarker/lang/en/qtype_ddmarker.php @@ -22,15 +22,17 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -$string['addingddmarker'] = 'Adding drag and drop: images or text onto image'; -$string['addmoredropzones'] = 'Blanks for {no} More Drop Zones'; -$string['addmoreimages'] = 'Blanks for {no} More Draggable Items'; +$string['addingddmarker'] = 'Adding drag and drop markers'; +$string['addmoredropzones'] = 'Blanks for {no} more drop zones'; +$string['addmoreitems'] = 'Blanks for {no} more markers'; +$string['alttext'] = 'Alt text'; $string['answer'] = 'Answer'; -$string['bgimage'] = 'Background Image'; +$string['bgimage'] = 'Background image'; +$string['coords'] = 'Coords'; $string['correctansweris'] = 'The correct answer is: {$a}'; -$string['ddmarker'] = 'Drag and drop: markers onto image'; -$string['ddmarker_help'] = 'Select a background image file, select draggable images or enter text and define the drop zones on the background image to which they must be dragged.'; -$string['ddmarkersummary'] = 'Images or text labels are dragged and dropped into drop zones on a background image.'; +$string['ddmarker'] = 'Drag and drop markers'; +$string['ddmarker_help'] = 'Select a background image file, enter text labels for markers and define the drop zones on the background image to which they must be dragged.'; +$string['ddmarkersummary'] = 'Markers are dragged and dropped onto a background image.'; $string['draggableimage'] = 'Draggable image'; $string['draggableitem'] = 'Draggable item'; $string['draggableitemheader'] = 'Draggable item {$a}'; @@ -38,27 +40,31 @@ $string['draggableword'] = 'Draggable text'; $string['dropzone'] = 'Drop zone {$a}'; $string['dropzoneheader'] = 'Drop zones'; -$string['editingddmarker'] = 'Editing drag and drop: images or text onto image'; -$string['formerror_disallowedtags'] = 'You have used html tags here that are not allowed in a draggable text drag item type.'; -$string['formerror_noallowedtags'] = 'No html tags are allowed in this text which is the alt text for a draggable image'; -$string['formerror_noytop'] = 'You must provide a value for the y coords for the top left corner of this drop area. You can drag and drop the drop area above to set the coordinates or enter them manually here.'; -$string['formerror_noxleft'] = 'You must provide a value for the y coords for the top left corner of this drop area. You can drag and drop the drop area above to set the coordinates or enter them manually here.'; -$string['formerror_nofile'] = 'You need to upload or select a file to use here.'; -$string['formerror_nofile3'] = 'You need to select an image file here, or delete the associated label and uncheck the infinite checkbox.'; -$string['formerror_multipledraginstance'] = 'You have selected this image {$a} more than once as the correct choice for a drop zone but it is not marked as being an infinite drag item.'; -$string['formerror_multipledraginstance2'] = 'You have selected this image more than once as the correct choice for a drop zone but it is not marked as being an infinite drag item.'; -$string['formerror_noimageselected'] = 'You need to select a drag item to be the correct choice for this drop zone.'; +$string['editingddmarker'] = 'Editing drag and drop markers'; $string['formerror_nobgimage'] = 'You need to select an image to use as the background for the drag and drop area.'; +$string['formerror_noitemselected'] = 'You have specified a drop zone but not chosen a marker that must be dragged to the zone'; +$string['formerror_notagsallowed'] = 'No html tags are allowed in the label for a marker'; $string['infinite'] = 'Infinite'; -$string['label'] = 'Text'; +$string['marker'] = 'Marker'; +$string['marker_n'] = 'Marker {no}'; +$string['markers'] = 'Markers'; $string['nolabel'] = 'No label text'; $string['pleasedraganimagetoeachdropregion'] = 'Your answer is not complete, please drag an item to each drop region.'; $string['previewarea'] = 'Preview area -'; $string['previewareaheader'] = 'Preview'; -$string['previewareamessage'] = 'Select a background image file and select draggable images or just enter text that will be made draggable. Then choose a drag item for each \'drop zone\', and drag the drag item to where the student should drag it to.'; +$string['previewareamessage'] = 'Select a background image file, enter text labels for markers and define the drop zones on the background image to which they must be dragged.'; $string['refresh'] = 'Refresh Preview'; +$string['clearwrongparts'] = 'Move incorrectly placed markers back to default start position below image'; +$string['shape'] = 'Shape'; +$string['shape_circle'] = 'Circle'; +$string['shape_circle_coords'] = 'centerx, centery; radius'; +$string['shape_rectangle'] = 'Rectangle'; +$string['shape_rectangle_coords'] = 'leftx, topy; width, height'; +$string['shape_polygon'] = 'Polygon'; +$string['shape_polygon_coords'] = 'x1,y1;x2,y2;x3,y3;x4,y4....'; +$string['showmisplaced'] = 'Highlight drop zones which have not had the correct drag item dropped on them'; $string['shuffleimages'] = 'Shuffle Drag Items Each Time Question Is Attempted'; +$string['stateincorrectlyplaced'] = 'State which markers are incorrectly placed'; $string['summariseplace'] = '{$a->no}. {$a->text}'; $string['summariseplaceno'] = 'Drop zone {$a}'; -$string['xleft'] = 'Left'; $string['ytop'] = 'Top'; \ No newline at end of file diff --git a/question/type/ddmarker/questiontype.php b/question/type/ddmarker/questiontype.php index 034ee131bd868..1104d4a7a54d4 100644 --- a/question/type/ddmarker/questiontype.php +++ b/question/type/ddmarker/questiontype.php @@ -28,8 +28,52 @@ require_once($CFG->dirroot . '/question/type/ddimageortext/questiontypebase.php'); -define('QTYPE_ddmarker_BGIMAGE_MAXWIDTH', 600); -define('QTYPE_ddmarker_BGIMAGE_MAXHEIGHT', 400); +define('QTYPE_DDMARKER_BGIMAGE_MAXWIDTH', 600); +define('QTYPE_DDMARKER_BGIMAGE_MAXHEIGHT', 400); + +/** + * An extension of {@link question_hint} for questions like match and multiple + * choice with multile answers, where there are options for whether to show the + * number of parts right at each stage, and to reset the wrong parts. + * + * @copyright 2010 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class question_hint_ddmarker extends question_hint_with_parts { + + public $statewhichincorrect; + + /** + * Constructor. + * @param int the hint id from the database. + * @param string $hint The hint text + * @param int the corresponding text FORMAT_... type. + * @param bool $shownumcorrect whether the number of right parts should be shown + * @param bool $clearwrong whether the wrong parts should be reset. + */ + public function __construct($id, $hint, $hintformat, $shownumcorrect, + $clearwrong, $statewhichincorrect) { + parent::__construct($id, $hint, $hintformat, $shownumcorrect, $clearwrong); + $this->statewhichincorrect = $statewhichincorrect; + } + + /** + * Create a basic hint from a row loaded from the question_hints table in the database. + * @param object $row with property options as well as hint, shownumcorrect and clearwrong set. + * @return question_hint_ddmarker + */ + public static function load_from_record($row) { + return new question_hint_ddmarker($row->id, $row->hint, $row->hintformat, + $row->shownumcorrect, $row->clearwrong, $row->options); + } + + public function adjust_display_options(question_display_options $options) { + parent::adjust_display_options($options); + $options->statewhichincorrect = $this->statewhichincorrect; + } +} + + /** * The drag-and-drop words into sentences question type class. @@ -42,5 +86,150 @@ class qtype_ddmarker extends qtype_ddtoimage_base { public function requires_qtypes() { return array_merge(parent::requires_qtypes(), array('ddimageortext')); } + public function save_question_options($formdata) { + global $DB, $USER; + $context = $formdata->context; + + $options = $DB->get_record('qtype_ddmarker', array('questionid' => $formdata->id)); + if (!$options) { + $options = new stdClass(); + $options->questionid = $formdata->id; + $options->correctfeedback = ''; + $options->partiallycorrectfeedback = ''; + $options->incorrectfeedback = ''; + $options->id = $DB->insert_record('qtype_ddmarker', $options); + } + + $options->shuffleanswers = !empty($formdata->shuffleanswers); + $options->showmisplaced = !empty($formdata->showmisplaced); + $options = $this->save_combined_feedback_helper($options, $formdata, $context, true); + $this->save_hints($formdata, true); + $DB->update_record('qtype_ddmarker', $options); + $DB->delete_records('qtype_ddmarker_drops', array('questionid' => $formdata->id)); + foreach (array_keys($formdata->drops) as $dropno) { + if ($formdata->drops[$dropno]['choice'] == 0) { + continue; + } + $drop = new stdClass(); + $drop->questionid = $formdata->id; + $drop->no = $dropno + 1; + $drop->shape = $formdata->drops[$dropno]['shape']; + $drop->coords = $formdata->drops[$dropno]['coords']; + $drop->choice = $formdata->drops[$dropno]['choice']; + $drop->label = $formdata->drops[$dropno]['label']; + + $DB->insert_record('qtype_ddmarker_drops', $drop); + } + + //an array of drag no -> drag id + $olddragids = $DB->get_records_menu('qtype_ddmarker_drags', + array('questionid' => $formdata->id), + '', 'no, id'); + foreach (array_keys($formdata->drags) as $dragno) { + if (!empty($formdata->drags[$dragno]['label'])) { + $drag = new stdClass(); + $drag->questionid = $formdata->id; + $drag->no = $dragno + 1; + $drag->infinite = empty($formdata->drags[$dragno]['infinite'])? 0 : 1; + $drag->label = $formdata->drags[$dragno]['label']; + + if (isset($olddragids[$dragno +1])) { + $drag->id = $olddragids[$dragno +1]; + unset($olddragids[$dragno +1]); + $DB->update_record('qtype_ddmarker_drags', $drag); + } else { + $drag->id = $DB->insert_record('qtype_ddmarker_drags', $drag); + } + + } + + } + if (!empty($olddragids)) { + list($sql, $params) = $DB->get_in_or_equal(array_values($olddragids)); + $DB->delete_records_select('qtype_ddmarker_drags', "id $sql", $params); + } + + self::constrain_image_size_in_draft_area($formdata->bgimage, + QTYPE_DDMARKER_BGIMAGE_MAXWIDTH, + QTYPE_DDMARKER_BGIMAGE_MAXHEIGHT); + file_save_draft_area_files($formdata->bgimage, $formdata->context->id, + 'qtype_ddmarker', 'bgimage', $formdata->id, + array('subdirs' => 0, 'maxbytes' => 0, 'maxfiles' => 1)); + } + + public function save_hints($formdata, $withparts = false) { + global $DB; + $context = $formdata->context; + + $oldhints = $DB->get_records('question_hints', + array('questionid' => $formdata->id), 'id ASC'); + + if (!empty($formdata->hint)) { + $numhints = max(array_keys($formdata->hint)) + 1; + } else { + $numhints = 0; + } + + if ($withparts) { + if (!empty($formdata->hintclearwrong)) { + $numclears = max(array_keys($formdata->hintclearwrong)) + 1; + } else { + $numclears = 0; + } + if (!empty($formdata->hintshownumcorrect)) { + $numshows = max(array_keys($formdata->hintshownumcorrect)) + 1; + } else { + $numshows = 0; + } + $numhints = max($numhints, $numclears, $numshows); + } + + for ($i = 0; $i < $numhints; $i += 1) { + if (html_is_blank($formdata->hint[$i]['text'])) { + $formdata->hint[$i]['text'] = ''; + } + + if ($withparts) { + $clearwrong = !empty($formdata->hintclearwrong[$i]); + $shownumcorrect = !empty($formdata->hintshownumcorrect[$i]); + $statewhichincorrect = !empty($formdata->hintstatewhichincorrect[$i]); + } + + if (empty($formdata->hint[$i]['text']) && empty($clearwrong) && + empty($shownumcorrect) && empty($statewhichincorrect)) { + continue; + } + + // Update an existing hint if possible. + $hint = array_shift($oldhints); + if (!$hint) { + $hint = new stdClass(); + $hint->questionid = $formdata->id; + $hint->hint = ''; + $hint->id = $DB->insert_record('question_hints', $hint); + } + + $hint->hint = $this->import_or_save_files($formdata->hint[$i], + $context, 'question', 'hint', $hint->id); + $hint->hintformat = $formdata->hint[$i]['format']; + if ($withparts) { + $hint->clearwrong = $clearwrong; + $hint->shownumcorrect = $shownumcorrect; + $hint->options = $statewhichincorrect; + } + $DB->update_record('question_hints', $hint); + } + + // Delete any remaining old hints. + $fs = get_file_storage(); + foreach ($oldhints as $oldhint) { + $fs->delete_area_files($context->id, 'question', 'hint', $oldhint->id); + $DB->delete_records('question_hints', array('id' => $oldhint->id)); + } + } + + protected function make_hint($hint) { + return question_hint_ddmarker::load_from_record($hint); + } }