Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Cloze question type: Difference between revisions

From MoodleDocs
(26 intermediate revisions by 3 users not shown)
Line 1: Line 1:
{{Questiontype developer docs}}
{{Questiontype developer docs}}
{{Multianswer (Cloze) developer docs}}
{{Multianswer (Cloze) developer docs}}
The code for the multianswer cloze question type is in the directory question/type/multianswer.  
 
The name of the questiontype class is <code>class embedded_cloze_qtype extends default_questiontype</code>.
The code for the multianswer cloze question type is in the directory ''question/type/multianswer''.  
==Cloze question creation==
 
The name of the questiontype class is class [http://xref.moodle.org/nav.html?question/type/multianswer/questiontype.php.source.html embedded_cloze_qtype] extends ''default_questiontype''.
 
== Cloze question creation ==
A cloze question is a container for several subquestions.  
A cloze question is a container for several subquestions.  
So when you create a simple question like
So when you create a simple question like
Line 9: Line 12:
  by {1:shortanswer;=Columbus#OK}
  by {1:shortanswer;=Columbus#OK}
  who was {1:MULTICHOICE:=italian#OK~spanish#Wrong~portugese#Wrong}
  who was {1:MULTICHOICE:=italian#OK~spanish#Wrong~portugese#Wrong}
==Cloze question creation==
== Cloze question saving ==
on saving the question text is analyzed by <code>function qtype_multianswer_extract_question($text)<code>
 
which extract the three subquestions
On saving the question text is analyzed by function [http://xref.moodle.org/nav.html?question/type/multianswer/questiontype.php.source.html#l903 qtype_multianswer_extract_question($text)] which extract the three subquestions:
    for ($positionkey=1
 
        ; preg_match('/'.ANSWER_REGEX.'/', $question->questiontext, $answerregs)
<code php>
        ; ++$positionkey ) {
for ($positionkey=1
        $wrapped = new stdClass;
    ; preg_match('/'.ANSWER_REGEX.'/', $question->questiontext, $answerregs)
         ....
    ; ++$positionkey ) {
      // stored them in  
    $wrapped = new stdClass;
      $question->options->questions[$positionkey] = clone($wrapped);
         ...
    // stored them in  
    $question->options->questions[$positionkey] = clone($wrapped);
}
</code>


and replace the sub question i.e. {1:NUMERICAL:=1492:1#Feedback} by  
and replace the sub question i.e. {1:NUMERICAL:=1492:1#Feedback} by  
Line 26: Line 33:


In the multianswer->questiontext the question stored is
In the multianswer->questiontext the question stored is
 
  America was discovered in {#1}
  America was discovered in {#1}
  by {#2}
  by {#2}
Line 32: Line 39:


On saving the question the $question->options->questions are stored using the functions specific of their qtypes (short,numerical,multichoice).
On saving the question the $question->options->questions are stored using the functions specific of their qtypes (short,numerical,multichoice).
            $wrapped = $QTYPES[$wrapped->qtype]->save_question($wrapped,
                    $wrapped, $question->course);
Notice that the subquestions are identified in the database by setting their parent field with the main cloze question id.


==Cloze question edition ==
<code php>
When you edited a question the process is the reversed.
$wrapped = $QTYPES[$wrapped->qtype]->save_question($wrapped, $wrapped, $question->course);
The get_question_options get the subanswers after retrieving them
</code>
      // Get relevant data indexed by positionkey from the multianswers table
        if (!$sequence = get_field('question_multianswer', 'sequence', 'question', $question->id)) {
            notify('Error: Cloze question '.$question->id.' is missing question options!');
            return false;
        }


        $wrappedquestions = get_records_list('question', 'id', $sequence, 'id ASC');
Notice that the subquestions are identified in the database by setting their parent field with the main cloze question id.


As the original text defining a given subquestion was stored in the subquestion->questiontext, it is put back in the main multianswer cloze question->questiontext using the {#1},{#2}etc that where left as place marker.
== Cloze question editing ==


==A NEW MULTIANSWER QUESTIONTYPE==
When you edited a question the process is the reversed.
The editing interface of Cloze multianswer interface will be improve so that the user can see how his question text will be interpreted by the Moodle code to create the questions embeded in the text.
see http://moodle.org/mod/forum/discuss.php?d=84638
 
There is a need for another type of multianswers question that could incorporate other question types and use all the properties of these questions types. The following lines describe the coding requirements of the NEW MULTIANSWER QUESTIONTYPE.
 
The existing subquestion storage can be the main structure of a new interface where the user will put in the question text the the {#1},{#2}etc as place marker and fill the subquestions parameters ( answer, feedback etc.) using the same interface that are used when editing the various qtypes (shortanswer, numerical and multichoice)
.
Furthermore this could lead to an open structure allowing plug-ins to be included as long as they fullfill conditions to be defined
 
==Main design strategy==
===Backward compatibility===
The old Cloze question will NOT be compatible with the new one.
 
=='''Bold text'''=The integration of other qtypes===
THE FUNCTIONS SHOULD BE PUT IN THE edit_multianswer_form.php and should use functions in the specific  edit_qtype_form.php. This means that edit_multianswer_form should be abe to create the desired edit_qtype_form without creating the corresponding active form.
 
A closer look at the code show that the edit_qtype_form classes are called by question.php with the following  call
require_once($definition_file);
$classname = 'question_edit_'.$this->name().'_form';
if (!class_exists($classname)) {
return null;
}
return new $classname($submiturl, $question, $category, $contexts, $formeditable);
}
As there is no '''question_edit_.$this->name()._form()''' function , the call is transfered to the parent '''question_edit_form()''' function which exists...
function question_edit_form($submiturl, $question, $category, $contexts, $formeditable = true){
....
}
 
So the solution is to stop the tranfer to '''question_edit_form'''() by creating something like
function '''question_edit_shortanswer_form'''($submiturl, $question, $category, $contexts, $formeditable =  true,'''$go=true'''){
if( '''$go''') parent::'''question_edit_form'''($submiturl, $question, $category, $contexts, $formeditable );
}
The sixth new go parameter default to true will not interfered will the usual call, but allow us to obtain a valid question_edit_shortanswer_form with all the internal functions but with no parameter '''initialized'''
 
When this is done we can create $QTYPES_USEABLE_BY_MULTIANSWER to access these classes.
 
      $QTYPES_USEABLE_BY_MULTIANSWER = array();
        $displayoptions = new stdClass ;
        foreach($QTYPES as $qtype) {
            if( $qtype->is_usable_by_multianswer()){
                $definition_file =
      $CFG->dirroot.'/question/type/'.$qtype->name().'/edit_'.$qtype->name().'_form.php';
                if (is_readable($definition_file) && is_file($definition_file)) {
                    require_once($definition_file);
                    $classname = 'question_edit_'.$qtype->name().'_form';
                    if (class_exists($classname)) {
                        $QTYPES_USEABLE_BY_MULTIANSWER[$qtype->name()]= new $classname(1,2,3,4,5,false);
//question_edit_shortanswer_form($submiturl, $question, $category, $contexts, $formeditable = true,$go=true) ;
                    }
                }
            }
        }
 
The code flow of the question_edit_QTYPES_USEABLE_BY_MULTIANSWER[$qtype->name()]_form are described in 
 
[[question_edit_QTYPES_USEABLE_BY_MULTIANSWER_form_functions]]
 
===On return from the question_edit_multianswer_form===
The $form object returned to the multianswer->save_question(($authorizedquestion, $form, $course) {
contains the subquestion values as
    [sub_1_name] => {#1}
    [sub_1_questiontext] => // '' or the oldtype subquestion as {1:shortanswer;=Columbus#OK}
    [sub_1_defaultgrade] => 1
    [sub_1_penalty] => 0.1
    [sub_1_qtype] => shortanswer
    [selectchangeto1] => multichoice
    [sub_1_usecase] => 0
    [sub_1_noanswers] => 2
    [sub_1_answer] => Array
        (
            [0] => 1
            [1] => 2
        )
    [sub_1_fraction] => Array
        (
            [0] => 1
            [1] => 0.7
        )
    [sub_1_feedback] => Array
        (
            [0] =>  ok1
            [1] =>  ok2
        )
    [sub_2_name] => {#2}
    [sub_2_questiontext] => // '' or the oldtype subquestion as {1:shortanswer;=Columbus#OK}
    [sub_2_defaultgrade] => 1


The $form object parameter are extracted as
The [http://xref.moodle.org/nav.html?question/type/multianswer/questiontype.php.html#get_question_options get_question_options(&$question)] gets the subanswers after retrieving them:
    $keys = array_keys(get_object_vars($form));
giving
    [14] => sub_1_name
    [15] => sub_1_questiontext
    [16] => sub_1_defaultgrade
    [17] => sub_1_penalty
    [18] => sub_1_qtype
    [19] => selectchangeto1
    [20] => sub_1_usecase
    [21] => sub_1_noanswers
    [22] => sub_1_answer
    [23] => sub_1_fraction
    [24] => sub_1_feedback
    [25] => sub_2_name
    [26] => sub_2_questiontext
    [27] => sub_2_defaultgrade


They are converted for each subquestion to the normal name without the subscript
<code php>
// Get relevant data indexed by positionkey from the multianswers table
if (!$sequence = get_field('question_multianswer', 'sequence', 'question', $question->id)) {
    notify('Error: Cloze question '.$question->id.' is missing question options!');
    return false;
}


    for ($sub = 1;$sub <= $form->totsubquestion;$sub++){
$wrappedquestions = get_records_list('question', 'id', $sequence, 'id ASC');
        $wrapped = new stdClass;
</code>
          foreach($keys as $keyname){
            if ($pos = strstr($keyname,'sub_'.$sub.'_')){
                        $kname = '''substr'''($keyname,$pos+ strlen('sub_'.$sub.'_'));
                        $wrapped->$kname =$form->$keyname ;
                        unset($form->$keyname);
                    }
                }
                $wrapped->questiontextformat = 0;
              if (strstr($question->questiontext,'{#'.$sub.'}') ) $question->defaultgrade += $wrapped->defaultgrade;
        $question->options->questions[$sub] = clone($wrapped);
    }


and put in the $question->options->questions array.
As the original text defining a given subquestion was stored in the subquestion->questiontext, it is put back in the main multianswer cloze question->questiontext using the {#1},{#2}, etc. that were left as place marker.


The question is then saved as usual.
==Cloze question editing improvements==
There are many internal plug-ins or external tools that are proposed as a substitute to previous or actual (2,1) editing interface.
The editing help should be of increasing complexity to handle the different users needs.


==Old or new editing form==
===Adding questions "squeletal examples" to copy and paste===
As the help available in 2,0 is less detailed, a casual reminder of the different questions format could be usefull.
A select button synchronize to an input element (with a copy button) is one solution


The code to give the options of editing in the old or new way is not completed actually but I plan to use the
===The decode question text should pinpoint syntax error===
'''question_multianswer database sequence''' field to discriminate between the new and old style.
The analysis code should be extended to be more helpfull ( +- at a debugger level).


In the old style it contains the subquestions id in a string like 23,25,45 etc.
===The edition could be done with an detailed interface similar to the moodle form===
If the question is saved from the new editing form (which allows more parameters without any limitation in the characters used and is not backward compatible) the '''question_multianswer database sequence''' will contain 'new:23,25,45 etc'.
This could be done integrated to the HTML editor or as a a variable editform.
There are many arguments pro and con that need to be discussed.


A simple test will allow to discriminate the versions
[[User:Pierre Pichet|Pierre Pichet]] 20:43, 7 August 2011 (WST)
Using the already existing database structure will allow to merge to "old" versions like 1.7 where the new edit_form has been merged.


to be continued [[User:Pierre Pichet|Pierre Pichet]] 09:08, 15 November 2007 (CST)
== Older version ==


==Older version==
Most of the methods of this questiontype simply call the corresponding methods of the wrapped questions. So mostly this questiontype class only has to do clever bookkeeping.
Most of the methods of this questiontype simply call the corresponding methods of the wrapped questions. So mostly this questiontype class only has to do clever bookkeeping.


Line 221: Line 111:
The fact that the sequence number of the subquestion, rather than the id of the subquestion is used in the response storage is a pity. It could leads to problems when the teacher changes the layout of the cloze question.
The fact that the sequence number of the subquestion, rather than the id of the subquestion is used in the response storage is a pity. It could leads to problems when the teacher changes the layout of the cloze question.


[[Category:Cloze question type]]
[[Category:Quiz]]
[[Category:Quiz]]

Revision as of 14:07, 7 August 2011


The code for the multianswer cloze question type is in the directory question/type/multianswer.

The name of the questiontype class is class embedded_cloze_qtype extends default_questiontype.

Cloze question creation

A cloze question is a container for several subquestions. So when you create a simple question like

America was discovered in {1:NUMERICAL:=1492:0.1#Feedback}
by {1:shortanswer;=Columbus#OK}
who was {1:MULTICHOICE:=italian#OK~spanish#Wrong~portugese#Wrong}

Cloze question saving

On saving the question text is analyzed by function qtype_multianswer_extract_question($text) which extract the three subquestions:

for ($positionkey=1

   ; preg_match('/'.ANSWER_REGEX.'/', $question->questiontext, $answerregs)
   ; ++$positionkey ) {
   $wrapped = new stdClass;
       ...
   // stored them in 
   $question->options->questions[$positionkey] = clone($wrapped);

}

and replace the sub question i.e. {1:NUMERICAL:=1492:1#Feedback} by {#$positionkey} i.e.{#1} {#2} {#3} successively. the original definition i.e. {1:NUMERICAL:=1492:1#Feedback} being stored in

$wrapped->questiontext= = $answerregs[0];

In the multianswer->questiontext the question stored is

America was discovered in {#1}
by {#2}
who was {#3}

On saving the question the $question->options->questions are stored using the functions specific of their qtypes (short,numerical,multichoice).

$wrapped = $QTYPES[$wrapped->qtype]->save_question($wrapped, $wrapped, $question->course);

Notice that the subquestions are identified in the database by setting their parent field with the main cloze question id.

Cloze question editing

When you edited a question the process is the reversed.

The get_question_options(&$question) gets the subanswers after retrieving them:

// Get relevant data indexed by positionkey from the multianswers table if (!$sequence = get_field('question_multianswer', 'sequence', 'question', $question->id)) {

    notify('Error: Cloze question '.$question->id.' is missing question options!');
    return false;

}

$wrappedquestions = get_records_list('question', 'id', $sequence, 'id ASC');

As the original text defining a given subquestion was stored in the subquestion->questiontext, it is put back in the main multianswer cloze question->questiontext using the {#1},{#2}, etc. that were left as place marker.

Cloze question editing improvements

There are many internal plug-ins or external tools that are proposed as a substitute to previous or actual (2,1) editing interface. The editing help should be of increasing complexity to handle the different users needs.

Adding questions "squeletal examples" to copy and paste

As the help available in 2,0 is less detailed, a casual reminder of the different questions format could be usefull. A select button synchronize to an input element (with a copy button) is one solution

The decode question text should pinpoint syntax error

The analysis code should be extended to be more helpfull ( +- at a debugger level).

The edition could be done with an detailed interface similar to the moodle form

This could be done integrated to the HTML editor or as a a variable editform. There are many arguments pro and con that need to be discussed.

Pierre Pichet 20:43, 7 August 2011 (WST)

Older version

Most of the methods of this questiontype simply call the corresponding methods of the wrapped questions. So mostly this questiontype class only has to do clever bookkeeping.

The way the questiontype it is implemented at the moment all wrapped questions must be of a type that use a single response field $state->responses[''].

Data structure

quiz_multianswers database table

The quiz_multianswers table belongs to the multianswer questiontype and is an extension of the quiz_questions table. It merely stores a comma separated list of question ids in the sequence field, which is important, because that's the only way to know which sub question belongs to which position in the questiontext.

id
int(10) unsigned NOT NULL auto_increment,
Primary key
question
int(10) unsigned NOT NULL default '0',
Foreign key refering to the id field in the quiz_questions table
sequence
varchar(255) NOT NULL default ,
A comma separated list of question ids in the order in which they appear in the questiontext.

Response storage model

The multianswer question uses a serialized format consisting of a comma separated list of pairs. Each pair saves the response for one of the subquestions and is itself separated with a hyphen ('-'). In front of the hyphen there is the sequence number of the subquestion (starting at 1). After the hyphen is the raw response, that was submitted by the student. The serialized format could look like the following example:

1-34,2-Napoleon,3-4,4-correct response,5-1

All commas and hyphens that are part of the raw answer are HTML entity encoded to avoid problems.

The fact that the sequence number of the subquestion, rather than the id of the subquestion is used in the response storage is a pity. It could leads to problems when the teacher changes the layout of the cloze question.