diff --git a/question/type/ddmarker/README.md b/question/type/ddmarker/README.md new file mode 100644 index 00000000000..29cddfa6509 --- /dev/null +++ b/question/type/ddmarker/README.md @@ -0,0 +1,131 @@ +Drag-and-drop markers question type +----------------------------------- + +You can use markers with text labels as drag items onto rectangular, and in +Moodle 2.2+ version circular and polygon defined drop zones on a background image. + +This question type requires that gapselect question type +https://github.com/moodleou/moodle-qtype_ddimageortext/ and +https://github.com/moodleou/moodle-qtype_gapselect/ +to be installed in order to work. + +This question type was written by Jamie Pratt (http://jamiep.org/). + +This question type is compatible with Moodle 2.1+ (MOODLE_21_STABLE branch) or +2.2+ (master branch). + +###Installation + +####Installation Using Git + +To install using git for a 2.2+ Moodle installation, type this command in the +root of your Moodle install: + +git clone git://github.com/moodleou/moodle-qtype_ddmarker.git question/type/ddmarker + +To install using git for a 2.1+ Moodle installation, type this command in the +root of your Moodle install: + +git clone -b MOODLE_21_STABLE git://github.com/moodleou/moodle-qtype_ddmarker.git question/type/ddmarker + +Then add question/type/ddmarker to your git ignore. + +####Installation From Downloaded zip file + +Alternatively, download the zip from : + +* Moodle 2.2+ - https://github.com/moodleou/moodle-qtype_ddmarker/zipball/master +* Moodle 2.1+ - https://github.com/moodleou/moodle-qtype_ddmarker/zipball/MOODLE_21_STABLE + +unzip it into the question/type folder, and then rename the new folder to ddmarker. + +###Converting 'image target' type questions to this type + +The imagetarget question type question type will not be upgraded to use with Moodle beyond version 1.9 it seems. + +But you can convert your existing image target questions and question attempt data to be drag and drop marker questions and they +will work as the image target previously worked. + +There are two ways to convert your imagetarget questions to ddmarker question types. + +####Automatic Conversion (recommended) + +Conversion will happen automatically when you upgrade for Moodle 2.0. + +It is recommended you follow the following steps : + +Upgrade your site to Moodle 2.0. This involves : + +* Upgrade the code base to Moodle 2.0 +* Remove the imagetarget question type code from question/type/imagetarget/ +* Go to your http://{moodleroot}/admin/ to trigger the upgrade of the db + +At this point your imagetarget questions won't work and will show up as 'missing type' but once that upgrade is +done change your Moodle code to Moodle 2.2+ + +* install the ddmarker question type code in question type ddmarker. +* then go to your http://{moodleroot}/admin/ to trigger the upgrade of the db + +This will convert all your imagetarget questions to ddmarker automatically. + +####Manual Conversion + +You can also use a manual script to convert imagetarget questions to ddmarker after upgrading to Moodle 2.1 or greater. + +* Log in as admin. +* You will find a script in the admin menu under plugins/question types/ to convert your imagetarget questions to ddmarker. +(This just converts your questions themselves.) +* In order to convert your question attempt data to be used with the ddmarker question type and Moodle 2.1 or greater you need to +find the 'question engine upgrade helper' which will appear at the root of the admin menu. Use this script to : +* _Reset the upgrade of all attempt data._ +* _Run the attempt data upgrade again._ + +###Issues with converting image target question type questions + +####Background image shrinkage + +The ddmarker question type will shrink the background image of your questions to be within a maximum size and width while at the +time preserving aspect ratio of the image. The default max width is 600 pixels and height 400 pixels. + +The ddmarker question type does this whenever you edit a question and save it again. + +Unfortunately when you open up a question to edit it this means that if the image is to big it will be shrunk but at present the +position of your drop zones are not moved to compensate for this shrinkage. There won't be a problem until you try to edit and save +a question but when you open it in the editor the drop zones will not be in the correct position. If you save the question they +they will then be saved in the wrong position. + +####Work around for background image shrinkage + +Either : + +* do not edit questions which have sizes above the allowed max. +* or you can change the allowed maximum size of images there are two constant at the top of question/type/ddmarker/questiontype.php +that define the size limits. + +The limit is just there to shrink outrageously large images down to a reasonable size automatically. You could set the value of the +constants defining the max size to a very large value to effectively disable the image size constraints all together. + +####Lack of drag label in imagetarget questions + +Normally each draggable marker in the ddmarker question type has a text label. There are no labels for the imagetarget markers +though, I needed to pick something that will work in any language as the label for the single imagetarget draggable marker +so I picked 'X'. + +####Work around for lack of drag label in imagetarget questions + +Your teachers can edit this label after the question has been created. And they might also like to take advantage of the ability in +the ddmarker question type to be able to specify the correct drop zones for more than one drag marker onto the same image, +giving each drag marker a different label. + +You can change the default drag marker label which you can find defined in a constant at the top of question/type/ddmarker/lib.php + +####No automatic feedback telling the user whether they got the question right, partially right or wrong. + +In the imagetarget question you get a text message telling the user whether they got the question correct, partially correct or +wrong. In many of the new question types in Moodle 2.1 onwards the teacher is expected to enter the message that the user will +see in the question definition. This is called 'combined feedback' and by default it is blank. So questions that have been +converted from per 2.1 Moodle, from imagetarget question types that used to give some feedback to the user will no longer do so. + +####Work around for lack of automatic feedback telling the user whether they got the question right, partially right or wrong. + +You can use this plug in https://github.com/jamiepratt/moodle-admin_tool_questionaddfeedback to bulk add feedback to questions. diff --git a/question/type/ddmarker/README.txt b/question/type/ddmarker/README.txt deleted file mode 100644 index f096c4f1606..00000000000 --- a/question/type/ddmarker/README.txt +++ /dev/null @@ -1,31 +0,0 @@ -Drag-and-drop markers question type - -You can use markers with text labels as drag items onto rectangular, and in -Moodle 2.2+ version circular and polygon defined drop zones on a background image. - -This question type requires that gapselect question type -https://github.com/moodleou/moodle-qtype_ddimageortext/ and -https://github.com/moodleou/moodle-qtype_gapselect/ -to be installed in order to work. - -This question type was written by Jamie Pratt (http://jamiep.org/). - -This question type is compatible with Moodle 2.1+ (MOODLE_21_STABLE branch) or -2.2+ (master branch). - -To install using git for a 2.2+ Moodle installation, type this command in the -root of your Moodle install: - -git clone git://github.com/moodleou/moodle-qtype_ddmarker.git question/type/ddmarker - -To install using git for a 2.1+ Moodle installation, type this command in the -root of your Moodle install: - -git clone -b MOODLE_21_STABLE git://github.com/moodleou/moodle-qtype_ddmarker.git question/type/ddmarker - -Then add question/type/ddmarker to your git ignore. - -Alternatively, download the zip from - Moodle 2.2+ - https://github.com/moodleou/moodle-qtype_ddmarker/zipball/master - Moodle 2.1+ - https://github.com/moodleou/moodle-qtype_ddmarker/zipball/MOODLE_21_STABLE -unzip it into the question/type folder, and then rename the new folder to ddmarker. diff --git a/question/type/ddmarker/backup/moodle2/backup_qtype_ddmarker_plugin.class.php b/question/type/ddmarker/backup/moodle2/backup_qtype_ddmarker_plugin.class.php index d304edb9886..fc40930bc67 100644 --- a/question/type/ddmarker/backup/moodle2/backup_qtype_ddmarker_plugin.class.php +++ b/question/type/ddmarker/backup/moodle2/backup_qtype_ddmarker_plugin.class.php @@ -15,9 +15,10 @@ // along with Moodle. If not, see . /** - * @package moodlecore - * @subpackage backup-moodle2 - * @copyright 2011 The Open University + * @package qtype + * @subpackage ddmarker + * @copyright 2012 The Open University + * @author Jamie Pratt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); diff --git a/question/type/ddmarker/backup/moodle2/restore_qtype_ddmarker_plugin.class.php b/question/type/ddmarker/backup/moodle2/restore_qtype_ddmarker_plugin.class.php index ea190807504..25e7233c479 100644 --- a/question/type/ddmarker/backup/moodle2/restore_qtype_ddmarker_plugin.class.php +++ b/question/type/ddmarker/backup/moodle2/restore_qtype_ddmarker_plugin.class.php @@ -15,9 +15,10 @@ // along with Moodle. If not, see . /** - * @package moodlecore - * @subpackage backup-moodle2 - * @copyright 2011 The Open University + * @package qtype + * @subpackage ddmarker + * @copyright 2012 The Open University + * @author Jamie Pratt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/question/type/ddmarker/db/install.php b/question/type/ddmarker/db/install.php new file mode 100644 index 00000000000..7d46131f560 --- /dev/null +++ b/question/type/ddmarker/db/install.php @@ -0,0 +1,79 @@ +. + +/** + * ddmarker question type installation code. + * + * @package qtype + * @subpackage ddmarker + * @copyright 2012 The Open University + * @author Jamie Pratt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die(); + + +/** + * Installation code for the ddmarker question type. It converts all existing imagetarget questions to ddmarker + */ +function xmldb_qtype_ddmarker_install() { + global $DB, $OUTPUT; + + $from = 'FROM {question_categories} cat, {question} q'; + $where = ' WHERE q.qtype = \'imagetarget\' AND q.category = cat.id '; + + $sql = 'SELECT q.*, cat.contextid '.$from.$where.'ORDER BY cat.id, q.name'; + + $questions = $DB->get_records_sql($sql); + + if (!empty($questions)) { + require_once(dirname(__FILE__).'/../lib.php'); + $dragssql = 'SELECT drag.* '.$from.', {qtype_ddmarker_drags} drag'.$where.' AND drag.questionid = q.id'; + $drags = xmldb_qtype_ddmarker_index_array_of_records_by_key('questionid', $DB->get_records_sql($dragssql)); + + $dropssql = 'SELECT drp.* '.$from.', {qtype_ddmarker_drops} drp'.$where.' AND drp.questionid = q.id'; + $drops = xmldb_qtype_ddmarker_index_array_of_records_by_key('questionid', $DB->get_records_sql($dropssql)); + + $answerssql = 'SELECT answer.* '.$from.', {question_answers} answer'.$where.' AND answer.question = q.id'; + $answers = xmldb_qtype_ddmarker_index_array_of_records_by_key('question', $DB->get_records_sql($answerssql)); + + $imgfiles = $DB->get_records_sql_menu('SELECT question, qimage FROM {question_imagetarget}'); + $progressbar = new progress_bar('qtype_ddmarker_convert_from_imagetarget'); + $progressbar->create(); + $done = 0; + foreach ($questions as $question) { + qtype_ddmarker_convert_image_target_question($question, $imgfiles[$question->id], $answers[$question->id]); + $done++; + $progressbar->update($done, count($questions), get_string('convertingimagetargetquestion', 'qtype_ddmarker', $question)); + } + list($qsql, $qparams) = $DB->get_in_or_equal(array_keys($questions)); + $DB->delete_records_select('question_answers', 'question '.$qsql, $qparams); + $dbman = $DB->get_manager(); + $dbman->drop_table(new xmldb_table('question_imagetarget')); + } +} +function xmldb_qtype_ddmarker_index_array_of_records_by_key($key, $recs) { + $out = array(); + foreach ($recs as $id => $rec) { + if (!isset($out[$rec->{$key}])) { + $out[$rec->{$key}] = array(); + } + $out[$rec->{$key}][$id] = $rec; + } + return $out; +} diff --git a/question/type/ddmarker/db/upgradelib.php b/question/type/ddmarker/db/upgradelib.php new file mode 100644 index 00000000000..fd2e3810edd --- /dev/null +++ b/question/type/ddmarker/db/upgradelib.php @@ -0,0 +1,85 @@ +. + +/** + * Upgrade library code for the ddmarker question type. This will only get triggered by the code + * to convert imagetarget questions to ddmarker. + * + * @package qtype + * @subpackage ddmarker + * @copyright 2012 The Open University + * @author Jamie Pratt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for converting attempt data from imagetarget questions when converting + * attempts to the new question engine. + * + * This class is used by the code in question/engine/upgrade/upgradelib.php. + * + * @copyright 2010 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class qtype_ddmarker_qe2_attempt_updater extends question_qtype_attempt_updater { + public function right_answer() { + $drag = reset($this->question->options->drags); + return '{'.get_string('dropzone', 'qtype_ddmarker', '1')." -> ".$drag->label.'}'; + } + + public function was_answered($state) { + return !empty($state->answer); + } + + public function response_summary($state) { + if (!empty($state->answer)) { + $drag = reset($this->question->options->drags); + foreach ($this->question->options->drops as $drop) { + list($xy, $wh) = explode(';', $drop->coords); + list($x, $y) = explode(',', $xy); + list($w, $h) = explode(',', $wh); + list($answerx, $answery) = explode(',', $state->answer); + if (($answerx >= $x && $answerx <= ($x + $w)) && ($answery >= $y && $answery <= ($y + $h))) { + return '{'.get_string('dropzone', 'qtype_ddmarker', $drop->no)." -> ".$drag->label.'}'; + } + } + return ''; + } else { + return null; + } + } + + public function question_summary() { + $drag = reset($this->question->options->drags); + return parent::question_summary().'[['.get_string('dropzone', 'qtype_ddmarker', '1')."]] -> {".$drag->label.'}'; + } + + public function set_first_step_data_elements($state, &$data) { + $data['_choiceorder1'] = '1'; + } + + public function supply_missing_first_step_data(&$data) { + } + + public function set_data_elements_for_step($state, &$data) { + if (!empty($state->answer)) { + $data['c1'] = $state->answer; + } + } +} diff --git a/question/type/ddmarker/edit_ddmarker_form.php b/question/type/ddmarker/edit_ddmarker_form.php index 9fc2d8efe76..5969a3367d8 100644 --- a/question/type/ddmarker/edit_ddmarker_form.php +++ b/question/type/ddmarker/edit_ddmarker_form.php @@ -24,7 +24,8 @@ define('QTYPE_DDMARKER_ALLOWED_TAGS_IN_MARKER', '
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/question/type/ddmarker/imagetargetconverter.php b/question/type/ddmarker/imagetargetconverter.php new file mode 100644 index 00000000000..1ab0f542c35 --- /dev/null +++ b/question/type/ddmarker/imagetargetconverter.php @@ -0,0 +1,158 @@ +. + +/** + * This page lets admin convert imagetarget questions to the ddmarker question type. + * + * @package qtype + * @subpackage ddmarker + * @copyright 2012 The Open University + * @author Jamie Pratt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +require_once(dirname(__FILE__) . '/../../../config.php'); +require_once($CFG->libdir . '/adminlib.php'); +require_once(dirname(__FILE__).'/questionlists.php'); +require_once(dirname(__FILE__).'/lib.php'); + +class qtype_ddmarker_question_converter_list extends qtype_ddmarker_question_list { + protected function new_list_item($stringidentifier, $link, $record) { + return new qtype_ddmarker_question_converter_list_item($stringidentifier, $link, $record, $this, $this->categorylist); + } + public function prepare_for_processing($top) { + global $DB; + $questionids = $top->question_ids(); + list($inorequalsql, $inorequalparams) = $DB->get_in_or_equal($questionids); + $imagetargetrecords = $DB->get_records_select('question_imagetarget', 'question '.$inorequalsql, $inorequalparams); + $answers = array(); + foreach ($imagetargetrecords as $imagetargetrecord) { + $this->get_instance($imagetargetrecord->question)->imagetargetrecord = $imagetargetrecord; + } + $answerrecords = $DB->get_records_select('question_answers', 'question '.$inorequalsql, $inorequalparams); + foreach ($answerrecords as $answerrecord) { + $this->get_instance($answerrecord->question)->answers[] = $answerrecord; + } + } +} +class qtype_ddmarker_question_converter_list_item extends qtype_ddmarker_question_list_item { + public $imagetargetrecord = null; + public $answers = array(); + public function process($renderer) { + qtype_ddmarker_convert_image_target_question($this->record, $this->imagetargetrecord->qimage, $this->answers); + parent::process($renderer);//outputs progress message + } +} + +$categoryid = optional_param('categoryid', 0, PARAM_INT); +$qcontextid = optional_param('contextid', 0, PARAM_INT); +$questionid = optional_param('questionid', 0, PARAM_INT); +$confirm = optional_param('confirm', 0, PARAM_INT); +// Check the user is logged in. +require_login(); +$context = get_context_instance(CONTEXT_SYSTEM); +require_capability('moodle/question:config', $context); + +admin_externalpage_setup('qtypeddmarkerfromimagetarget'); + +// Header. +$renderer = $PAGE->get_renderer('qtype_ddmarker', 'list'); +echo $renderer->header(); +echo $renderer->heading(get_string('imagetargetconverter', 'qtype_ddmarker'), 2); + + +$params = array(); +$from = 'FROM {question_categories} cat, {question} q'; +$where = ' WHERE q.qtype = \'imagetarget\' AND q.category = cat.id '; + +if ($qcontextid) { + $qcontext = get_context_instance_by_id($qcontextid, MUST_EXIST); + $from .= ', {context} context'; + $where .= 'AND cat.contextid = context.id AND (context.path LIKE :path OR context.id = :id) '; + $params['path'] = $qcontext->path.'/%'; + $params['id'] = $qcontext->id; +} else if ($categoryid) { + //fetch all questions from this cats context + $from .= ', {question_categories} cat2'; + $where .= 'AND cat.contextid = cat2.contextid AND cat2.id = :categoryid '; + $params['categoryid'] = $categoryid; +} else if ($questionid) { + //fetch all questions from this cats context + $where .= 'AND q.id = :questionid '; + $params['questionid'] = $questionid; +} +$sql = 'SELECT q.*, cat.contextid '.$from.$where.'ORDER BY cat.id, q.name'; + +$questions = $DB->get_records_sql($sql, $params); + +if (!count($questions)) { + echo html_writer::tag('div', get_string('noquestionsfound', 'qtype_ddmarker')); +} else { + $contextids = array(); + foreach ($questions as $question) { + $contextids[] = $question->contextid; + } + + $questionsselected = (bool) ($categoryid || $qcontextid || $questionid); + if (!$confirm) { + if (!$questionsselected) { + $pagestate = 'listall'; + } else { + $pagestate = 'confirm'; + } + } else if (confirm_sesskey()) { + $pagestate = 'processing'; + } + $link = ($pagestate == 'listall'); + $contextlist = new qtype_ddmarker_context_list($pagestate, $link, array_unique($contextids)); + $categorylist = new qtype_ddmarker_category_list($pagestate, $link, $contextids, $contextlist); + $questionlist = new qtype_ddmarker_question_converter_list($pagestate, $link, $questions, $categorylist); + + foreach ($questions as $question) { + $questionlist->leaf_node($question->id, 1); + } + if ($questionid) { + $top = $questionlist->get_instance($questionid); + } else if ($categoryid) { + $top = $categorylist->get_instance($categoryid); + } else if ($qcontextid) { + $top = $contextlist->get_instance($qcontextid); + } else { + $top = $contextlist->root_node(); + } + switch ($pagestate) { + case 'listall' : + echo $renderer->render_qtype_ddmarker_list($top); + break; + case 'confirm' : + echo $renderer->render_qtype_ddmarker_list($top); + $cofirmedurl = new moodle_url($PAGE->url, compact('categoryid', 'contextid', 'questionid') + array('confirm'=>1)); + $cancelurl = new moodle_url($PAGE->url); + echo $renderer->confirm(get_string('confirmimagetargetconversion', 'qtype_ddmarker'), $cofirmedurl, $cancelurl); + break; + case 'processing' : + $questionlist->prepare_for_processing($top); + echo '
    '; + $top->process($renderer); + echo '
'; + break; + default : + break; + } +} +// Footer. +echo $renderer->footer(); diff --git a/question/type/ddmarker/lang/en/qtype_ddmarker.php b/question/type/ddmarker/lang/en/qtype_ddmarker.php index 9e156918d52..d0c0c3fb838 100644 --- a/question/type/ddmarker/lang/en/qtype_ddmarker.php +++ b/question/type/ddmarker/lang/en/qtype_ddmarker.php @@ -18,7 +18,8 @@ * * @package qtype * @subpackage ddmarker - * @copyright 2011 The Open University + * @copyright 2012 The Open University + * @author Jamie Pratt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -26,7 +27,9 @@ $string['addmoreitems'] = 'Blanks for {no} more markers'; $string['alttext'] = 'Alt text'; $string['answer'] = 'Answer'; $string['bgimage'] = 'Background image'; +$string['confirmimagetargetconversion'] = 'You are about to convert the above image target questions to the drag and drop markers question type.'; $string['coords'] = 'Coords'; +$string['convertingimagetargetquestion'] = 'Converted question "{$a->name}"'; $string['correctansweris'] = 'The correct answer is: {$a}'; $string['draggableimage'] = 'Draggable image'; $string['draggableitem'] = 'Draggable item'; @@ -48,11 +51,22 @@ $string['formerror_shapeoutsideboundsofbgimage'] = 'The shape you have defined g $string['formerror_toomanysemicolons'] = 'There are too many semi colon separated parts to the coordinates you have specified. Your coordinates for a {$a->shape} should be expressed as - {$a->coordsstring}.'; $string['formerror_unrecognisedwidthheightpart'] = 'We do not recognise the width and height you have specified. Your coordinates for a {$a->shape} should be expressed as - {$a->coordsstring}.'; $string['formerror_unrecognisedxypart'] = 'We do not recognise the x,y coordinates you have specified. Your coordinates for a {$a->shape} should be expressed as - {$a->coordsstring}.'; +$string['imagetargetconverter'] = 'Convert image target questions to drag and drop marker'; $string['infinite'] = 'Infinite'; +$string['listitemconfirmcategory'] = 'About to convert all imagetarget questions in category "{$a->name}" (contains {$a->qcount} imagetarget questions)'; +$string['listitemconfirmcontext'] = 'About to convert all imagetarget questions in context "{$a->name}" (contains {$a->qcount} imagetarget questions)'; +$string['listitemconfirmquestion'] = 'About to convert question "{$a->name}"'; +$string['listitemlistallcategory'] = 'Select all imagetarget questions in category "{$a->name}" (contains {$a->qcount} imagetarget questions)'; +$string['listitemlistallcontext'] = 'Select all imagetarget questions in context "{$a->name}" (contains {$a->qcount} imagetarget questions)'; +$string['listitemlistallquestion'] = 'Select question "{$a->name}"'; +$string['listitemprocessingcategory'] = 'Converting all imagetarget questions in category "{$a->name}" (contains {$a->qcount} imagetarget questions)'; +$string['listitemprocessingcontext'] = 'Converting all imagetarget questions in context "{$a->name}" (contains {$a->qcount} imagetarget questions)'; +$string['listitemprocessingquestion'] = 'Converted question "{$a->name}"'; $string['marker'] = 'Marker'; $string['marker_n'] = 'Marker {no}'; $string['markers'] = 'Markers'; $string['nolabel'] = 'No label text'; +$string['noquestionsfound'] = 'No questions found to convert here.'; $string['pleasedragatleastonemarker'] = 'Your answer is not complete, you must place at least one marker on the image.'; $string['pluginname'] = 'Drag and drop markers'; $string['pluginname_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.'; diff --git a/question/type/ddmarker/lib.php b/question/type/ddmarker/lib.php index 8cc24264add..7b93681ad79 100644 --- a/question/type/ddmarker/lib.php +++ b/question/type/ddmarker/lib.php @@ -17,16 +17,21 @@ /** * Serve question type files * - * @since 2.0 * @package qtype - * @subpackage essay - * @copyright Dongsheng Cai + * @subpackage ddmarker + * @copyright 2012 The Open University + * @author Jamie Pratt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); +/** + * + * @var string label to use for drag items when converting image target questions to ddmarker question type + */ +define('QTYPE_DDMARKER_LABEL_FOR_MARKER_FOR_IMAGE_TARGET_QS', 'X'); /** * Checks file access for essay questions. @@ -36,3 +41,101 @@ function qtype_ddmarker_pluginfile($course, $cm, $context, $filearea, $args, $fo require_once($CFG->libdir . '/questionlib.php'); question_pluginfile($course, $context, 'qtype_ddmarker', $filearea, $args, $forcedownload, $options); } + + + +function qtype_ddmarker_course_context_id($catcontextid) { + $context = get_context_instance_by_id($catcontextid); + while ($context->contextlevel != CONTEXT_COURSE) { + $context = get_context_instance_by_id(get_parent_contextid($context)); + } + return $context->id; +} + +function qtype_ddmarker_convert_image_target_question($question, $imgfilename, $answers) { + global $DB, $OUTPUT; + $correctfeedback = ''; + $correctfeedbackformat = 1; + $incorrectfeedback = ''; + $incorrectfeedbackformat = 1; + $foundincorrectanswer = false; + foreach ($answers as $answer) { + $no = 1; + if ('*' !== $answer->answer) { + $drop = new stdClass(); + $drop->questionid = $question->id; + $drop->shape = 'rectangle'; + $drop->no = $no; + list($x1, $y1, $x2, $y2) = explode(',', $answer->answer); + $width = $x2 - $x1; + $height = $y2 - $y1; + $drop->coords = "{$x1},{$y1};{$width},{$height}"; + $drop->choice = 1; + $DB->insert_record('qtype_ddmarker_drops', $drop); + $no++; + $correctfeedback = $answer->feedback; + $correctfeedbackformat = $answer->feedbackformat; + } else { + $foundincorrectanswer = true; + $incorrectfeedback = $answer->feedback; + $incorrectfeedbackformat = $answer->feedbackformat; + } + } + if (count($answers) < 2) { + echo $OUTPUT->notification('There are less than 2 answers. '. + '(Normally we expect at least a correct and incorrect answer). '. + 'For question id '.$question->id.' "'.$question->name.'".', + 'notifyproblem'); + } + if (!$foundincorrectanswer) { + echo $OUTPUT->notification('No incorrect answer found for question id '.$question->id.' "'.$question->name.'".', + 'notifyproblem'); + } + $drag = new stdClass(); + $drag->questionid = $question->id; + $drag->no = 1; + $drag->label = QTYPE_DDMARKER_LABEL_FOR_MARKER_FOR_IMAGE_TARGET_QS; + $drag->infinite = 0; + $DB->insert_record('qtype_ddmarker_drags', $drag); + + $ddmarker = new stdClass(); + $ddmarker->questionid = $question->id; + $ddmarker->shuffleanswers = 0; + $ddmarker->correctfeedback = $correctfeedback; + $ddmarker->correctfeedbackformat = $correctfeedbackformat; + $ddmarker->partiallycorrectfeedback = ''; + $ddmarker->partiallycorrectfeedbackformat = 1; + $ddmarker->incorrectfeedback = $incorrectfeedback; + $ddmarker->incorrectfeedbackformat = $incorrectfeedbackformat; + $ddmarker->shownumcorrect = 0; + $ddmarker->showmisplaced = 0; + $DB->insert_record('qtype_ddmarker', $ddmarker); + + $newrec = clone($question); + unset($newrec->contextid); + $newrec->qtype = 'ddmarker'; + $newrec->timemodified = time(); + $DB->update_record('question', $newrec); + + $fs = get_file_storage(); + //we need to look in the course legacy files area for file + $bgimagefile = $fs->get_file(qtype_ddmarker_course_context_id($question->contextid), + 'course', + 'legacy', + '0', + '/'.dirname($imgfilename).'/', + basename($imgfilename)); + if ($bgimagefile === false) { + echo $OUTPUT->notification('File "'.$imgfilename.'" not found in legacy course files area. '. + 'For question id '.$question->id.' "'.$question->name.'".', + 'notifyproblem'); + } else { + $newbgimagefile = new stdClass(); + $newbgimagefile->component = 'qtype_ddmarker'; + $newbgimagefile->filearea = 'bgimage'; + $newbgimagefile->filepath = '/'; + $newbgimagefile->itemid = $question->id; + $newbgimagefile->contextid = $question->contextid; + $fs->create_file_from_storedfile($newbgimagefile, $bgimagefile); + } +} diff --git a/question/type/ddmarker/question.php b/question/type/ddmarker/question.php index 22cb99e408d..568313d4b50 100644 --- a/question/type/ddmarker/question.php +++ b/question/type/ddmarker/question.php @@ -17,8 +17,10 @@ /** * Drag-and-drop markers question definition class. * - * @package qtype_ddmarker - * @copyright 2009 The Open University + * @package qtype + * @subpackage ddmarker + * @copyright 2012 The Open University + * @author Jamie Pratt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/question/type/ddmarker/questionlists.php b/question/type/ddmarker/questionlists.php new file mode 100644 index 00000000000..df17ff52a7c --- /dev/null +++ b/question/type/ddmarker/questionlists.php @@ -0,0 +1,273 @@ +. + +defined('MOODLE_INTERNAL') || die(); + +/** + * These classes handle transforming arrays of records into a linked tree of contexts, categories and questions. + * + * @package qtype + * @subpackage ddmarker + * @copyright 2012 Jamie Pratt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class qtype_ddmarker_list_item implements renderable { + + /** + * @var count of questions contained in this item and sub items. + */ + protected $qcount = 0; + /** + * @var children array of pointers to list_items either category_list_items or context_list_items + */ + protected $children = array(); + + protected $stringidentifier; + protected $link; + protected $record; + protected $list; + protected $parentlist; + protected $listtype = null; + + public function __construct($stringidentifier, $link, $record, $list, $parentlist = null) { + $this->stringidentifier = $stringidentifier; + $this->link = $link; + $this->record = $record; + $this->list = $list; + $this->parentlist = $parentlist; + } + + public function add_child($child) { + $this->children[] = $child; + //array_unique relies on __toString() returning a unique string to determine if objects in array + //are the same or not + $this->children = array_unique($this->children); + } + + abstract protected function parent_node (); + + + public function item_name() { + return $this->record->name; + } + + public function id_param_name() { + return $this->listtype.'id'; + } + public function get_id() { + return $this->record->id; + } + public function get_q_count() { + return $this->qcount; + } + public function get_string_identifier() { + return $this->stringidentifier; + } + public function get_linked() { + return $this->link; + } + public function get_list_type() { + return $this->listtype; + } + public function get_children() { + return $this->children; + } + public function leaf_to_root($qcount) { + $this->qcount += $qcount; + $parent = $this->parent_node(); + if ($parent !== null) { + $parent->add_child($this); + $parent->leaf_to_root($qcount); + } + } + + public function process($renderer) { + echo '
  • '; + echo $renderer->item($this); + $this->process_children($renderer); + echo '
  • '; + flush(); + } + + protected function process_children($renderer) { + echo '
      '; + foreach ($this->children as $child) { + $child->process($renderer); + } + echo '
    '; + } + + public function question_ids() { + return $this->child_question_ids(); + } + + protected function child_question_ids() { + $ids = array(); + foreach ($this->children as $child) { + $ids = array_merge($ids, $child->question_ids()); + } + return $ids; + } + + + public function __toString() { + return get_class($this).' '.$this->record->id; + } + + /** + * @return the course id in which this item is contained or the id of the front page course + */ + public function course_context_id() { + return $this->parent_node()->course_context_id(); + } + +} +class qtype_ddmarker_category_list_item extends qtype_ddmarker_list_item { + protected $listtype = 'category'; + + public function parent_node() { + if ($this->record->parent == 0) { + return $this->parentlist->get_instance($this->record->contextid); + } else { + return $this->list->get_instance($this->record->parent); + } + } +} +class qtype_ddmarker_question_list_item extends qtype_ddmarker_list_item { + protected $listtype = 'question'; + + public function parent_node() { + return $this->parentlist->get_instance($this->record->category); + } + + public function question_ids() { + return array($this->record->id); + } +} +class qtype_ddmarker_context_list_item extends qtype_ddmarker_list_item { + protected $listtype = 'context'; + public function parent_node() { + $pathids = explode('/', $this->record->path); + if (count($pathids) >= 3) { + return $this->list->get_instance($pathids[count($pathids)-2]); + } else { + return null; + } + } + + public function item_name() { + return print_context_name($this->record); + } + + public function course_context_id() { + if ((int)$this->record->contextlevel === CONTEXT_COURSE) { + return $this->record->id; + } else { + return parent::course_context_id(); + } + } +} +/** + * Describes a nested list of listitems. This class and sub classes contain the functionality to build the nested list. + **/ +abstract class qtype_ddmarker_list { + protected $records = array(); + protected $instances = array(); + abstract protected function new_list_item($stringidentifier, $link, $record); + protected function make_list_item_instances_from_records($stringidentifier, $link) { + if (!empty($this->records)) { + foreach ($this->records as $id => $record) { + $this->instances[$id] = $this->new_list_item($stringidentifier, $link, $record); + } + } + } + public function get_instance($id) { + return $this->instances[$id]; + } + + public function leaf_node ($id, $qcount) { + $instance = $this->get_instance($id); + $instance->leaf_to_root($qcount); + } + +} + +class qtype_ddmarker_context_list extends qtype_ddmarker_list { + + protected function new_list_item($stringidentifier, $link, $record) { + return new qtype_ddmarker_context_list_item($stringidentifier, $link, $record, $this); + } + + public function __construct($stringidentifier, $link, $contextids) { + global $DB; + $this->records = array(); + foreach ($contextids as $contextid) { + if (!isset($this->records[$contextid])) { + $this->records[$contextid] = get_context_instance_by_id($contextid, MUST_EXIST); + } + $parents = get_parent_contexts($this->records[$contextid]); + foreach ($parents as $parentcontextid) { + if (!isset($this->records[$parentcontextid])) { + $this->records[$parentcontextid] = + get_context_instance_by_id($parentcontextid, MUST_EXIST); + } + } + } + $this->make_list_item_instances_from_records($stringidentifier, $link); + } + public function render($roottorender = null) { + if ($roottorender === null) { + $roottorender = $this->root_node(); + } + $rootitem = html_writer::tag('li', $roottorender->render()); + return html_writer::tag('ul', $rootitem); + } + public function root_node () { + return $this->get_instance(get_context_instance(CONTEXT_SYSTEM)->id); + } +} + + + +class qtype_ddmarker_category_list extends qtype_ddmarker_list { + protected $contextlist; + protected function new_list_item($stringidentifier, $link, $record) { + return new qtype_ddmarker_category_list_item($stringidentifier, $link, $record, $this, $this->contextlist); + } + public function __construct($stringidentifier, $link, $contextids, $contextlist) { + global $DB; + $this->contextlist = $contextlist; + //probably most efficient way to reconstruct question category tree is to load all q cats in relevant contexts + list($sql, $params) = $DB->get_in_or_equal($contextids); + $this->records = $DB->get_records_select('question_categories', "contextid ".$sql, $params); + $this->make_list_item_instances_from_records($stringidentifier, $link); + } +} + +class qtype_ddmarker_question_list extends qtype_ddmarker_list { + protected $categorylist; + protected function new_list_item($stringidentifier, $link, $record) { + return new qtype_ddmarker_question_list_item($stringidentifier, $link, $record, $this, $this->categorylist); + } + public function __construct($stringidentifier, $link, $questions, $categorylist) { + global $DB; + $this->categorylist = $categorylist; + $this->records = $questions; + $this->make_list_item_instances_from_records($stringidentifier, $link); + } + public function prepare_for_processing($top) { + } +} \ No newline at end of file diff --git a/question/type/ddmarker/questiontype.php b/question/type/ddmarker/questiontype.php index ee03154bc5b..24f5071df19 100644 --- a/question/type/ddmarker/questiontype.php +++ b/question/type/ddmarker/questiontype.php @@ -19,7 +19,8 @@ * * @package qtype * @subpackage ddmarker - * @copyright 2009 The Open University + * @copyright 2012 The Open University + * @author Jamie Pratt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/question/type/ddmarker/renderer.php b/question/type/ddmarker/renderer.php index 0c047631d22..48952905e49 100644 --- a/question/type/ddmarker/renderer.php +++ b/question/type/ddmarker/renderer.php @@ -17,14 +17,17 @@ /** * Drag-and-drop markers question renderer class. * - * @package qtype_ddmarker - * @copyright 2010 The Open University + * @package qtype + * @subpackage ddmarker + * @copyright 2012 The Open University + * @author Jamie Pratt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); +require_once($CFG->dirroot . '/question/type/rendererbase.php'); require_once($CFG->dirroot . '/question/type/ddimageortext/rendererbase.php'); @@ -156,4 +159,36 @@ class qtype_ddmarker_renderer extends qtype_ddtoimage_renderer_base { $output .= parent::hint($qa, $hint); return $output; } + } +class qtype_ddmarker_list_renderer extends plugin_renderer_base { + + public function render_qtype_ddmarker_list(qtype_ddmarker_list_item $top) { + $list = html_writer::tag('ul', html_writer::tag('li', $this->render_qtype_ddmarker_list_item($top))); + return $this->output->container($list, 'listofquestions'); + } + public function render_qtype_ddmarker_list_item(qtype_ddmarker_list_item $listitem) { + return $this->item($listitem).$this->children($listitem); + } + + public function item(qtype_ddmarker_list_item $item) { + global $PAGE; + $a = new stdClass(); + $a->qcount = $item->get_q_count(); + $a->name = $item->item_name(); + $thisitem = get_string('listitem'.$item->get_string_identifier().$item->get_list_type(), 'qtype_ddmarker', $a); + if ($item->get_linked()) { + $actionurl = new moodle_url($PAGE->url, array($item->id_param_name() => $item->get_id())); + $thisitem = html_writer::tag('a', $thisitem, array('href' => $actionurl)); + } + return $thisitem; + } + + protected function children(qtype_ddmarker_list_item $item) { + $children = array(); + foreach ($item->get_children() as $child) { + $children[] = $this->render_qtype_ddmarker_list_item($child); + } + return html_writer::alist($children); + } +} \ No newline at end of file diff --git a/question/type/ddmarker/settings.php b/question/type/ddmarker/settings.php new file mode 100644 index 00000000000..2dd2cc81d6e --- /dev/null +++ b/question/type/ddmarker/settings.php @@ -0,0 +1,32 @@ +. + +/** + * + * @package qtype + * @subpackage ddmarker + * @copyright 2012 The Open University + * @author Jamie Pratt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die(); + +$settings = new admin_externalpage('qtypeddmarkerfromimagetarget', + get_string('imagetargetconverter', 'qtype_ddmarker'), + new moodle_url('/question/type/ddmarker/imagetargetconverter.php'), + 'moodle/question:config'); diff --git a/question/type/ddmarker/shapes.php b/question/type/ddmarker/shapes.php index eae350ced22..e645fc76b55 100644 --- a/question/type/ddmarker/shapes.php +++ b/question/type/ddmarker/shapes.php @@ -17,8 +17,10 @@ /** * Drag-and-drop markers classes for dealing with shapes on the server side. * - * @package qtype_ddmarker - * @copyright 2009 The Open University + * @package qtype + * @subpackage ddmarker + * @copyright 2012 The Open University + * @author Jamie Pratt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class qtype_ddmarker_shape { diff --git a/question/type/ddmarker/simpletest/helper.php b/question/type/ddmarker/simpletest/helper.php index 1c773da62d3..a78d3e08bcf 100644 --- a/question/type/ddmarker/simpletest/helper.php +++ b/question/type/ddmarker/simpletest/helper.php @@ -17,8 +17,10 @@ /** * Test helpers for the drag-and-drop markers question type. * - * @package qtype_ddmarker - * @copyright 2010 The Open University + * @package qtype + * @subpackage ddmarker + * @copyright 2012 The Open University + * @author Jamie Pratt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/question/type/ddmarker/simpletest/testquestion.php b/question/type/ddmarker/simpletest/testquestion.php index 705f95a49d0..7d1cafd86e1 100644 --- a/question/type/ddmarker/simpletest/testquestion.php +++ b/question/type/ddmarker/simpletest/testquestion.php @@ -17,8 +17,10 @@ /** * Unit tests for the drag-and-drop markers question definition class. * - * @package qtype_ddmarker - * @copyright 2010 The Open University + * @package qtype + * @subpackage ddmarker + * @copyright 2012 The Open University + * @author Jamie Pratt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/question/type/ddmarker/simpletest/testquestiontype.php b/question/type/ddmarker/simpletest/testquestiontype.php index 63d0cdd1e7d..4c0b632a46f 100644 --- a/question/type/ddmarker/simpletest/testquestiontype.php +++ b/question/type/ddmarker/simpletest/testquestiontype.php @@ -17,8 +17,10 @@ /** * Unit tests for the drag-and-drop markers question definition class. * - * @package qtype_ddmarker - * @copyright 2010 The Open University + * @package qtype + * @subpackage ddmarker + * @copyright 2012 The Open University + * @author Jamie Pratt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/question/type/ddmarker/simpletest/testshapes.php b/question/type/ddmarker/simpletest/testshapes.php index 42f8a5bfa0b..17279fc1036 100644 --- a/question/type/ddmarker/simpletest/testshapes.php +++ b/question/type/ddmarker/simpletest/testshapes.php @@ -19,7 +19,8 @@ * * @package qtype * @subpackage ddmarker - * @copyright 2010 The Open University + * @copyright 2012 The Open University + * @author Jamie Pratt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/question/type/ddmarker/simpletest/testwalkthrough.php b/question/type/ddmarker/simpletest/testwalkthrough.php index 62411531aa9..3fc1a9187a5 100644 --- a/question/type/ddmarker/simpletest/testwalkthrough.php +++ b/question/type/ddmarker/simpletest/testwalkthrough.php @@ -17,8 +17,10 @@ /** * Unit tests for the drag-and-drop markers question type. * - * @package qtype_ddmarker - * @copyright 2010 The Open University + * @package qtype + * @subpackage ddmarker + * @copyright 2012 The Open University + * @author Jamie Pratt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/question/type/ddmarker/version.php b/question/type/ddmarker/version.php index f5270ffb2fc..398dac632d1 100644 --- a/question/type/ddmarker/version.php +++ b/question/type/ddmarker/version.php @@ -17,8 +17,10 @@ /** * Version information for the drag-and-drop markers question type. * - * @package qtype_ddmarker - * @copyright 2011 The Open University + * @package qtype + * @subpackage ddmarker + * @copyright 2012 The Open University + * @author Jamie Pratt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */