Commit 0d24b17a authored by Tim Hunt's avatar Tim Hunt
Browse files

MDL-47494 gapselect: work-in-progress converting the ddwtos and gapselect qtypes.

parents
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="question/type/gapselect/db" VERSION="20080909" COMMENT="XMLDB file for Moodle question/type/gapselect"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="question_gapselect" COMMENT="Defines select missing words questions">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="questionid"/>
<FIELD NAME="questionid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="id" NEXT="shuffleanswers"/>
<FIELD NAME="shuffleanswers" TYPE="int" LENGTH="4" NOTNULL="true" UNSIGNED="true" DEFAULT="1" SEQUENCE="false" PREVIOUS="questionid" NEXT="correctfeedback"/>
<FIELD NAME="correctfeedback" TYPE="text" LENGTH="small" NOTNULL="true" SEQUENCE="false" PREVIOUS="shuffleanswers" NEXT="partiallycorrectfeedback"/>
<FIELD NAME="partiallycorrectfeedback" TYPE="text" LENGTH="small" NOTNULL="true" SEQUENCE="false" PREVIOUS="correctfeedback" NEXT="incorrectfeedback"/>
<FIELD NAME="incorrectfeedback" TYPE="text" LENGTH="small" NOTNULL="true" SEQUENCE="false" PREVIOUS="partiallycorrectfeedback" NEXT="shownumcorrect"/>
<FIELD NAME="shownumcorrect" TYPE="int" LENGTH="2" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="incorrectfeedback"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="questionid"/>
<KEY NAME="questionid" TYPE="foreign" FIELDS="questionid" REFTABLE="questions" REFFIELDS="id" PREVIOUS="primary"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>
\ No newline at end of file
<?php
/**
* Elements embedded in question text editing form definition.
*
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_gapselect_edit_form_base extends question_edit_form {
/** @var array of HTML tags allowed in choices / drag boxes. */
protected $allowedhtmltags = array(
'sub',
'sup',
'b',
'i',
'em',
'strong'
);
/** @var string regex to match HTML open tags. */
private $htmltstarttagsandattributes = '/<\s*\w.*?>/';
/** @var string regex to match HTML close tags or br. */
private $htmltclosetags = '~<\s*/\s*\w\s*.*?>|<\s*br\s*>~';
/** @var string regex to select text like [[cat]] (including the square brackets). */
private $squareBracketsRegex = '/\[\[[^]]*?\]\]/';
private function get_html_tags($text) {
$textarray = array();
foreach ($this->allowedhtmltags as $htmltag) {
$tagpair = "/<\s*\/?\s*$htmltag\s*.*?>/";
preg_match_all($tagpair, $text, $textarray);
if ($textarray[0]) {
return $textarray[0];
}
}
preg_match_all($this->htmltstarttagsandattributes, $text, $textarray);
if ($textarray[0]) {
$tag = htmlspecialchars($textarray[0][0]);
$allowedtaglist = $this->get_list_of_printable_allowed_tags($this->allowedhtmltags);
return $tag . " is not allowed (only $allowedtaglist and corresponsing closing tags are allowed)";
}
preg_match_all($this->htmltclosetags, $text, $textarray);
if ($textarray[0]) {
$tag = htmlspecialchars($textarray[0][0]);
$allowedtaglist=$this->get_list_of_printable_allowed_tags($this->allowedhtmltags);
return $tag . " is not allowed HTML tag! (only $allowedtaglist and corresponsing closing tags are allowed)";
}
return false;
}
private function get_list_of_printable_allowed_tags($allowedhtmltags) {
$allowedtaglist = null;
foreach ($allowedhtmltags as $htmltag) {
$allowedtaglist .= htmlspecialchars('<'.$htmltag.'>') . ', ';
}
return $allowedtaglist;
}
/**
* definition_inner adds all specific fields to the form.
* @param object $mform (the form being built).
*/
function definition_inner(&$mform) {
global $CFG;
//add the answer (choice) fields to the form
$this->definition_answer_choice($mform);
$this->add_combined_feedback_fields(true);
$this->add_interactive_settings(true, true);
}
protected function definition_answer_choice(&$mform) {
$mform->addElement('header', 'choicehdr', get_string('choices', 'qtype_gapselect'));
$mform->addElement('checkbox', 'shuffleanswers', get_string('shuffle', 'quiz'));
$mform->setDefault('shuffleanswers', 0);
$textboxgroup = array();
$grouparray = array();
$grouparray[] =& $mform->createElement('text', 'answer', get_string('answer', 'qtype_gapselect'), array('size'=>30, 'class'=>'tweakcss'));
$grouparray[] =& $mform->createElement('static', '', '',' '.get_string('group', 'qtype_gapselect').' ');
$grouparray = $this->choice_group($mform, $grouparray);
$textboxgroup[] = $mform->createElement('group','choices', 'Choice {no}',$grouparray);
if (isset($this->question->options)) {
$countanswers = count($this->question->options->answers);
} else {
$countanswers = 0;
}
if ($this->question->formoptions->repeatelements) {
$defaultstartnumbers = QUESTION_NUMANS_START*2;
$repeatsatstart = max($defaultstartnumbers, QUESTION_NUMANS_START, $countanswers + QUESTION_NUMANS_ADD);
} else {
$repeatsatstart = $countanswers;
}
$repeatedoptions = $this->repeated_options();
$mform->setType('answer', PARAM_RAW);
$this->repeat_elements($textboxgroup, $repeatsatstart, $repeatedoptions, 'noanswers', 'addanswers', QUESTION_NUMANS_ADD, get_string('addmorechoiceblanks', 'qtype_gapselect'));
}
public function set_data($question) {
if (isset($question->options)) {
$options = $question->options;
$default_values = array();
if (count($options->answers)) {
$key = 0;
foreach ($options->answers as $answer) {
$default_values['choices['.$key.'][answer]'] = $answer->answer;
$default_values += $this->default_values_from_feedback_field($answer->feedback, $key);
$key++;
}
}
$default_values['shuffleanswers'] = $question->options->shuffleanswers;
$default_values['correctfeedback'] = $question->options->correctfeedback;
$default_values['partiallycorrectfeedback'] = $question->options->partiallycorrectfeedback;
$default_values['incorrectfeedback'] = $question->options->incorrectfeedback;
$default_values['shownumcorrect'] = $question->options->shownumcorrect;
$question = (object)((array)$question + $default_values);
}
parent::set_data($question);
}
public function validation($data, $files) {
$errors = parent::validation($data, $files);
$questiontext = $data['questiontext'];
$choices = $data['choices'];
//check the whether the slots are valid
$errorsinquestiontext = $this->validate_slots($questiontext, $choices);
if ($errorsinquestiontext) {
$errors['questiontext'] = $errorsinquestiontext;
}
foreach ($choices as $key => $choice) {
$answer = $choice['answer'];
//check whether the html-tags are allowed tags
$validtags = $this->get_html_tags($answer);
if (is_array($validtags)) {
continue;
}
if ($validtags) {
$errors['choices['.$key.']'] = $validtags;
}
}
return $errors;
}
private function validate_slots($questiontext, $choices) {
$error = 'Please check the Question text: ';
if (!$questiontext) {
return $error . 'The question text is empty!';
}
$matches = array();
preg_match_all($this->squareBracketsRegex, $questiontext, $matches);
$slots = $matches[0];
if (!$slots) {
return $error . 'The question text is not in the correct format!';
}
$output = array();
foreach ($slots as $slot) {
// The 2 is for'[[' and 4 is for '[[]]'.
$output[] = substr($slot, 2, (strlen($slot)-4));
}
$slots = $output;
$found = false;
foreach ($slots as $slot) {
$found = false;
foreach ($choices as $key => $choice) {
if ($slot == $key + 1) {
if (!$choice['answer']) {
return " Please check Choices: The choice <b>$slot</b> empty.";
}
$found = true;
break;
}
}
if (!$found) {
return $error . "<b>$slot</b> was not found in Choices! (only the choice numbers that exist in choices are allowed to be used a place holders!";
}
}
return false;
}
function qtype() {
return '';
}
protected function default_values_from_feedback_field($feedback, $key) {
$default_values = array();
return $default_values;
}
protected function repeated_options() {
$repeatedoptions = array();
return $repeatedoptions;
}
}
\ No newline at end of file
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Defines the editing form for the select missing words question type.
*
* @package qtype
* @subpackage gapselect
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once($CFG->dirroot . '/question/type/gapselect/edit_form_base.php');
/**
* Select from drop down list question editing form definition.
*
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_gapselect_edit_form extends qtype_gapselect_edit_form_base {
// HTML tags allowed in answers (choices).
protected $allowedhtmltags = array();
function qtype() {
return 'gapselect';
}
protected function default_values_from_feedback_field($feedback, $key) {
$default_values = array();
$default_values['choices['.$key.'][selectgroup]'] = $feedback;
return $default_values;
}
protected function repeated_options() {
$repeatedoptions = array();
$repeatedoptions['selectgroup']['default'] = '1';
return $repeatedoptions;
}
protected function choice_group(&$mform, $grouparray) {
$options = array();
for ($i = 1; $i <= 8; $i += 1) {
$options[$i] = $i;
}
$grouparray[] =& $mform->createElement('select', 'selectgroup', get_string('group', 'qtype_gapselect'), $options);
return $grouparray;
}
}
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Language strings for the select missing words question type.
*
* @package qtype
* @subpackage gapselect
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['addinggapselect'] = 'Adding a select missing words question';
$string['addmorechoiceblanks'] = 'Blanks for {no} more choices';
$string['answer'] = 'Answer';
$string['choices'] = 'Choices';
$string['correctansweris'] = 'The correct answer is: $a';
$string['gapselect'] = 'Select missing words';
$string['gapselect_help'] = 'Type in some question text like "The [[1]] jumped over the [[2]]", then enter the possible words to go in gaps 1 and 2 underneath.';
$string['gapselectsummary'] = 'Missing words in some text are filled in using dropdown menus.';
$string['editinggapselect'] = 'Editing a select missing words question';
$string['group'] = 'Group';
$string['pleaseputananswerineachbox'] = 'Please put an answer in each box.';
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Select from drop down list question definition class.
*
* @package qtype
* @subpackage gapselect
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once($CFG->dirroot . '/question/type/gapselect/questionbase.php');
/**
* Represents select missing words question.
*
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_gapselect_question extends qtype_gapselect_question_base {
//is actually exactly the same.
}
/**
* Represents one of the choices (select box option).
*
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_gapselect_choice {
public $text;
public $selectgroup;
public function __construct($text, $selectgroup = 1) {
$this->text = $text;
$this->selectgroup = $selectgroup;
}
public function choice_group() {
return $this->selectgroup;
}
}
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Definition class for embedded element in question text question. Parent of drag and drop and select from
* drop down list and ?others? *
*
* @package qtype
* @subpackage gapselect
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Represents embedded element in question text question. Parent of drag and drop and select from
* drop down list and ?others?
*
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_gapselect_question_base extends question_graded_automatically_with_countback {
/** @var boolean Whether the question stems should be shuffled. */
public $shufflechoices;
public $correctfeedback;
public $partiallycorrectfeedback;
public $incorrectfeedback;
/** @var array of arrays. The keys are the choice group numbers. The values
* are arrays of qtype_gapselect_choice objects. */
public $choices;
/**
* @var array place number => group number of the places in the question
* text where choices can be put. Places are numbered from 1.
*/
public $places;
/**
* @var array of strings, one longer than $places, which is achieved by
* indexing from 0. The bits of question text that go between the placeholders.
*/
public $textfragments;
/** @var array index of the right choice for each stem. */
public $rightchoices;
/** @var array shuffled choice indexes. */
protected $choiceorder;
public function init_first_step(question_attempt_step $step) {
foreach ($this->choices as $group => $choices) {
$varname = '_choiceorder' . $group;
if ($step->has_qt_var($varname)) {
$choiceorder = explode(',', $step->get_qt_var($varname));
} else {
$choiceorder = array_keys($choices);
if ($this->shufflechoices) {
shuffle($choiceorder);
}
}
foreach ($choiceorder as $key => $value) {
$this->choiceorder[$group][$key + 1] = $value;
}
if (!$step->has_qt_var($varname)) {
$step->set_qt_var($varname, implode(',', $this->choiceorder[$group]));
}
}
}
public function get_question_summary() {
$question = html_to_text($this->format_questiontext(), 0, false);
$groups = array();
foreach ($this->choices as $group => $choices) {
$cs = array();
foreach ($choices as $choice) {
$cs[] = html_to_text($this->format_text($choice->text), 0, false);
}
$groups[] = '[[' . $group . ']] -> {' . implode(' / ', $cs) . '}';
}
return $question . '; ' . implode('; ', $groups);
}
protected function get_selected_choice($group, $shuffledchoicenumber) {
$choiceno = $this->choiceorder[$group][$shuffledchoicenumber];
return $this->choices[$group][$choiceno];
}
public function summarise_response(array $response) {
$matches = array();
$allblank = true;
foreach ($this->places as $place => $group) {
if (array_key_exists($this->field($place), $response) &&
$response[$this->field($place)]) {
$choices[] = '{' . html_to_text($this->format_text($this->get_selected_choice(
$group, $response[$this->field($place)])->text), 0, false) . '}';
$allblank = false;
} else {
$choices[] = '{}';
}
}
if ($allblank) {
return null;
}
return implode(' ', $choices);
}
public function get_random_guess_score() {
$accum = 0;
foreach ($this->places as $placegroup) {
$accum += 1 / count($this->choices[$placegroup]);
}
return $accum / count($this->places);
}
public function clear_wrong_from_response(array $response) {
foreach ($this->places as $place => $notused) {
if (array_key_exists($this->field($place), $response) &&
$response[$this->field($place)] != $this->get_right_choice_for($place)) {
$response[$this->field($place)] = '0';
}
}
return $response;
}
public function get_num_parts_right(array $response) {
$numright = 0;
foreach ($this->places as $place => $notused) {
if (!array_key_exists($this->field($place), $response)) {
continue;
}
if ($response[$this->field($place)] == $this->get_right_choice_for($place)) {
$numright += 1;
}
}
return array($numright, count($this->places));
}
/**
* @param integer $key stem number
* @return string the question-type variable name.
*/
public function field($place) {
return 'p' . $place;
}
public function get_expected_data() {
$vars = array();
foreach ($this->places as $place => $notused) {
$vars[$this->field($place)] = PARAM_INTEGER;
}
return $vars;
}
public function get_correct_response() {
$response = array();
foreach ($this->places as $place => $notused) {
$response[$this->field($place)] = $this->get_right_choice_for($place);
}
return $response;
}
public function get_right_choice_for($place) {
$group = $this->places[$place];
foreach ($this->choiceorder[$group] as $choicekey => $choiceid) {
if ($this->rightchoices[$place] == $choiceid) {
return $choicekey;
}
}
}
public function get_ordered_choices($group) {
$choices = array();
foreach ($this->choiceorder[$group] as $choicekey => $choiceid) {
$choices[$choicekey] = $this->choices[$group][$choiceid];
}
return $choices;