فهرست منبع

Add fill in blanks code with some minor corrections (function name fixed)

Julio Montoya 9 سال پیش
والد
کامیت
65778a19be

+ 72 - 2
main/exercice/exercise.class.php

@@ -2497,13 +2497,83 @@ class Exercise
                     break;
                 // for fill in the blanks
                 case FILL_IN_BLANKS:
+
+                    // insert the student result in the track_e_attempt table, field answer
+                    // $answer is the answer like in the c_quiz_answer table for the question
+                    // student datas are choice[]
+
+                    $listCorrectAnswers = FillBlanks::getAnswerInfo($answer);
+                    $switchableAnswerSet = $listCorrectAnswers["switchable"];
+                    $answerWeighting = $listCorrectAnswers["tabweighting"];
+                    // user choices is an array $choice
+
+                    // get existing user data in n the BDD
+                    if ($from_database) {
+                        $queryfill = "SELECT answer
+                                      FROM $TBL_TRACK_ATTEMPT
+                                      WHERE exe_id = $exeId
+                                      AND question_id= ".intval($questionId);
+                        $resfill = Database::query($queryfill);
+                        $str = Database::result($resfill, 0, 'answer');
+
+                        $listStudentResults = FillBlanks::getAnswerInfo($str, true);
+                        $choice = $listStudentResults['studentanswer'];
+                    }
+
+                    // loop other all blanks words
+                    if (!$switchableAnswerSet) {
+                        // not switchable answer, must be in the same place than teacher order
+                        for ($i=0; $i < count($listCorrectAnswers['tabwords']); $i++) {
+                            $studentAnswer = trim($choice[$i]);
+                            $correctAnswer = $listCorrectAnswers['tabwords'][$i];
+                            $isAnswerCorrect = 0;
+                            if (FillBlanks::isGoodStudentAnswer($studentAnswer, $correctAnswer)) {
+                                // gives the related weighting to the student
+                                $questionScore += $answerWeighting[$i];
+                                // increments total score
+                                $totalScore += $answerWeighting[$i];
+                                $isAnswerCorrect = 1;
+                            }
+                            $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
+                            $listCorrectAnswers['studentscore'][$i] = $isAnswerCorrect;
+                        }
+                    } else {
+                        // switchable answer
+                        $listStudentAsnwerTemp = $choice;
+                        $listTeacherAnswerTemp = $listCorrectAnswers['tabwords'];
+                        $listBadAnswerIndice = array();
+                        // for every teacher answer, check if there is a student answer
+                        for ($i=0; $i < count($listStudentAsnwerTemp); $i++) {
+                            $studentAnswer = trim($listStudentAsnwerTemp[$i]);
+                            $found = false;
+                            for ($j=0; $j < count($listTeacherAnswerTemp); $j++) {
+                                $correctAnswer = $listTeacherAnswerTemp[$j];
+                                if (!$found) {
+                                    if (FillBlanks::isGoodStudentAnswer($studentAnswer, $correctAnswer)) {
+                                        $questionScore += $answerWeighting[$i];
+                                        $totalScore += $answerWeighting[$i];
+                                        $listTeacherAnswerTemp[$j] = "";
+                                        $found = true;
+                                    }
+                                }
+                            }
+                            $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
+                            if (!$found) {
+                                $listCorrectAnswers['studentscore'][$i] = 0;
+                            } else {
+                                $listCorrectAnswers['studentscore'][$i] = 1;
+                            }
+                        }
+                    }
+                    $answer = FillBlanks::getAnswerInStudentAttempt($listCorrectAnswers);
+
                     // the question is encoded like this
                     // [A] B [C] D [E] F::10,10,10@1
                     // number 1 before the "@" means that is a switchable fill in blank question
                     // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
                     // means that is a normal fill blank question
                     // first we explode the "::"
-
+                    /*
                     $pre_array = explode('::', $answer);
                     // is switchable fill blank or not
                     $last = count($pre_array) - 1;
@@ -2644,7 +2714,7 @@ class Exercise
                         if (isset($real_text[$i +1])) {
                             $answer .= $real_text[$i + 1];
                         }
-                    }
+                    }*/
                     break;
                 // for calculated answer
                 case CALCULATED_ANSWER:

+ 822 - 116
main/exercice/fill_blanks.class.php

@@ -10,8 +10,12 @@
  **/
 class FillBlanks extends Question
 {
-	static $typePicture = 'fill_in_blanks.png';
-	static $explanationLangVar = 'FillBlanks';
+    static $typePicture = 'fill_in_blanks.png';
+    static $explanationLangVar = 'FillBlanks';
+
+    const FILL_THE_BLANK_STANDARD = 0;
+    const FILL_THE_BLANK_MENU = 1;
+    const FILL_THE_BLANK_SEVERAL_ANSWER = 2;
 
     /**
      * Constructor
@@ -29,40 +33,35 @@ class FillBlanks extends Question
      */
     function createAnswersForm($form)
     {
+        $fillBlanksAllowedSeparator = self::getAllowedSeparator();
+
         $defaults = array();
 
         if (!empty($this->id)) {
-            $objAnswer = new Answer($this->id);
-
-            // the question is encoded like this
-            // [A] B [C] D [E] F::10,10,10@1
-            // number 1 before the "@" means that is a switchable fill in blank question
-            // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
-            // means that is a normal fill blank question
-
-            $pre_array = explode('::', $objAnswer->selectAnswer(1));
+            $objectAnswer = new answer($this->id);
+            $answer = $objectAnswer->selectAnswer(1);
+            $listAnswersInfo = FillBlanks::getAnswerInfo($answer);
 
-            //make sure we only take the last bit to find special marks
-            $sz = count($pre_array);
-            $is_set_switchable = explode('@', $pre_array[$sz-1]);
-
-            if ($is_set_switchable[1]) {
+            if ($listAnswersInfo["switchable"]) {
                 $defaults['multiple_answer'] = 1;
             } else {
                 $defaults['multiple_answer'] = 0;
             }
-
-            //Take the complete string except after the last '::'
-
-            $defaults['answer'] = '';
-            for ($i=0; $i<($sz-1); $i++) {
-                $defaults['answer'] .= $pre_array[$i];
-            }
-            $a_weightings = explode(',', $is_set_switchable[0]);
+            //take the complete string except after the last '::'
+            $defaults['answer'] = $listAnswersInfo["text"];
+            $defaults['select_separator'] = $listAnswersInfo["blankseparatornumber"];
+            $blanksepartornumber = $listAnswersInfo["blankseparatornumber"];
         } else {
             $defaults['answer'] = get_lang('DefaultTextInBlanks');
+            $defaults['select_separator'] = 0;
+            $blanksepartornumber = 0;
         }
 
+        $blankSeparatortStart = self::getStartSeparator($blanksepartornumber);
+        $blankSeparatortEnd = self::getEndSeparator($blanksepartornumber);
+        $blankSeparatortStartRegexp = self::escapeForRegexp($blankSeparatortStart);
+        $blankSeparatortEndRegexp = self::escapeForRegexp($blankSeparatortEnd);
+
         $setValues = null;
 
         if (isset($a_weightings) && count($a_weightings) > 0) {
@@ -70,119 +69,331 @@ class FillBlanks extends Question
                 $setValues .= 'document.getElementById("weighting['.$i.']").value = "'.$weighting.'";';
             }
         }
-		// javascript
-		echo '<script>
 
-            CKEDITOR.on("instanceCreated", function(e) {
-                if (e.editor.name === "answer") {
-                    e.editor.on("change", updateBlanks);
+        // javascript
+        echo '<script>
+
+            var blankSeparatortStart = "'.$blankSeparatortStart.'";
+            var blankSeparatortEnd = "'.$blankSeparatortEnd.'";
+            var blankSeparatortStartRegexp = getBlankSeparatorRegexp(blankSeparatortStart);
+            var blankSeparatortEndRegexp = getBlankSeparatorRegexp(blankSeparatortEnd);
+
+            function FCKeditor_OnComplete( editorInstance ) {
+                if (window.attachEvent) {
+                    editorInstance.EditorDocument.attachEvent("onkeyup", updateBlanks) ;
+                } else {
+                    editorInstance.EditorDocument.addEventListener("keyup",updateBlanks,true);
                 }
-            });
+            }
 
             var firstTime = true;
 
-            function updateBlanks(e) {
+            function updateBlanks() {
                 if (firstTime) {
-                    field = document.getElementById("answer");
+                    var field = document.getElementById("answer");
                     var answer = field.value;
                 } else {
-                    var answer = e.editor.getData();
+                    var oEditor = FCKeditorAPI.GetInstance("answer");
+                    var answer = oEditor.EditorDocument.body.innerHTML;
                 }
 
-                var blanks = answer.match(/\[[^\]]*\]/g);
-                var fields = "<div class=\"form-group\"><label class=\" col-sm-2 control-label\">'.get_lang('Weighting').'</label><div class=\"col-sm-8\"><table>";
-                if (blanks!=null) {
-                    for (i=0 ; i<blanks.length ; i++){
-                        if (document.getElementById("weighting["+i+"]"))
-                            value = document.getElementById("weighting["+i+"]").value;
-                        else
-                            value = "10";
-                        fields += "<tr><td><label>"+blanks[i]+"</label></td><td><input class=\"form-control\"style=\"margin-left: 0em;\" size=\"5\" value=\""+value+"\" type=\"text\" id=\"weighting["+i+"]\" name=\"weighting["+i+"]\" /></td></tr>";
+                // disable the save button, if not blanks have been created
+                $("button").attr("disabled", "disabled");
+                $("#defineoneblank").show();
+
+                var blanksRegexp = "/"+blankSeparatortStartRegexp+"[^"+blankSeparatortStartRegexp+"]*"+blankSeparatortEndRegexp+"/g";
+
+                var blanks = answer.match(eval(blanksRegexp));
+                var fields = "<div class=\"control-group\">";
+                fields += "<label class=\"control-label\">'.get_lang('Weighting').'</label>";
+                fields += "<div class=\"controls\">";
+                fields += "<table>";
+                fields += "<tr><th style=\"padding:0 20px\">'.get_lang("WordTofind").'</th><th style=\"padding:0 20px\">'.get_lang("QuestionWeighting").'</th><th style=\"padding:0 20px\">'.get_lang("BlankInputSize").'</th></tr>";
+
+
+                if (blanks != null) {
+                    for (var i=0 ; i < blanks.length ; i++){
+                        // remove forbidden characters that causes bugs
+                        blanks[i] = removeForbiddenChars(blanks[i]);
+                        // trim blanks between brackets
+                        blanks[i] = trimBlanksBetweenSeparator(blanks[i], blankSeparatortStart, blankSeparatortEnd);
+
+                        // if the word is empty []
+                        if (blanks[i] == blankSeparatortStartRegexp+blankSeparatortEndRegexp) {
+                            break;
+                        }
+                        // get input size
+                        var lainputsize = 200;
+                        if ($("#samplesize\\\["+i+"\\\]").width()) {
+                            lainputsize = $("#samplesize\\\["+i+"\\\]").width();
+                        }
+
+                        if (document.getElementById("weighting["+i+"]")) {
+                            var value = document.getElementById("weighting["+i+"]").value;
+                        } else {
+                            var value = "10";
+                        }
+                        fields += "<tr>";
+                        fields += "<td>"+blanks[i]+"</td>";
+                        fields += "<td><input style=\"width:20px\" value=\""+value+"\" type=\"text\" id=\"weighting["+i+"]\" name=\"weighting["+i+"]\" /></td>";
+                        fields += "<td>";
+                        fields += "<input type=\"button\" value=\"-\" onclick=\"changeInputSize(-1, "+i+")\">";
+                        fields += "<input type=\"button\" value=\"+\" onclick=\"changeInputSize(1, "+i+")\">";
+                        fields += "<input value=\""+blanks[i].substr(1, blanks[i].length - 2)+"\" style=\"width:"+lainputsize+"px\" disabled=disabled id=\"samplesize["+i+"]\"/>";
+                        fields += "<input type=\"hidden\" id=\"sizeofinput["+i+"]\" name=\"sizeofinput["+i+"]\" value=\""+lainputsize+"\" \"/>";
+                        fields += "</td>";
+                        fields += "</tr>";
+                        // enable the save button
+                        $("button").removeAttr("disabled");
+                        $("#defineoneblank").hide();
                     }
                 }
                 document.getElementById("blanks_weighting").innerHTML = fields + "</table></div></div>";
                 if (firstTime) {
                     firstTime = false;
-                    '.$setValues.'
-			    }
+            ';
+
+        if (count($listAnswersInfo["tabweighting"]) > 0) {
+            foreach ($listAnswersInfo["tabweighting"] as $i => $weighting) {
+                echo 'document.getElementById("weighting['.$i.']").value = "'.$weighting.'";';
+            }
+            foreach ($listAnswersInfo["tabinputsize"] as $i => $sizeOfInput) {
+                echo 'document.getElementById("sizeofinput['.$i.']").value = "'.$sizeOfInput.'";';
+                echo '$("#samplesize\\\['.$i.'\\\]").width('.$sizeOfInput.');';
             }
+        }
+        echo '}
+            ';
 
+        echo '
+            }
             window.onload = updateBlanks;
-		</script>';
-
-		// answer
-		$form->addElement('label', null, '<br /><br />'.get_lang('TypeTextBelow').', '.get_lang('And').' '.get_lang('UseTagForBlank'));
-		$form->addElement(
-            'html_editor',
-            'answer',
-            '<img src="../img/fill_field.png">',
-            array(
-                'id' => 'answer',
-                'onkeyup' => '"javascript: updateBlanks(this);"'
-            ),
-            array('ToolbarSet' => 'TestQuestionDescription', 'Height' => '350'));
-
-		$form->addRule('answer', get_lang('GiveText'),'required');
-		$form->addRule('answer', get_lang('DefineBlanks'),'regex','/\[.*\]/');
-
-		//added multiple answers
-		$form->addElement('checkbox', 'multiple_answer','', get_lang('FillInBlankSwitchable'));
-
-		$form->addElement('html', '<div id="blanks_weighting"></div>');
-
-		global $text, $class;
-		// setting the save button here and not in the question class.php
-        $form->addButtonSave($text, 'submitQuestion');
-
-		if (!empty($this->id)) {
-			$form -> setDefaults($defaults);
-		} else {
-			if ($this->isContent == 1) {
-				$form->setDefaults($defaults);
-			}
-		}
-	}
-
-	/**
-	 * abstract function which creates the form to create / edit the answers of the question
-	 * @param FormValidator $form
-	 */
-	function processAnswersCreation($form)
-	{
-		global $charset;
-		$answer = $form->getSubmitValue('answer');
-
-		//remove the :: eventually written by the user
+
+            function getInputSize() {
+                var outTabSize = new Array();
+                $("input").each(function() {
+                    if ($(this).attr("id") && $(this).attr("id").match(/samplesize/)) {
+                        var tabidnum = $(this).attr("id").match(/\d+/);
+                        var idnum = tabidnum[0];
+                        var thewidth = $(this).next().attr("value");
+                        tabInputSize[idnum] = thewidth;
+                    }
+                });
+            }
+
+            function changeInputSize(inCoef, inIdNum)
+            {
+                var currentWidth = $("#samplesize\\\["+inIdNum+"\\\]").width();
+                var newWidth = currentWidth + inCoef * 20;
+                newWidth = Math.max(20, newWidth);
+                newWidth = Math.min(newWidth, 600);
+                $("#samplesize\\\["+inIdNum+"\\\]").width(newWidth);
+                $("#sizeofinput\\\["+inIdNum+"\\\]").attr("value", newWidth);
+            }
+
+            function removeForbiddenChars(inTxt) {
+                outTxt = inTxt;
+
+                outTxt = outTxt.replace(/&quot;/g, ""); // remove the   char
+                outTxt = outTxt.replace(/\x22/g, ""); // remove the   char
+                outTxt = outTxt.replace(/"/g, ""); // remove the   char
+                outTxt = outTxt.replace(/\\\\/g, ""); // remove the \ char
+                outTxt = outTxt.replace(/&nbsp;/g, " ");
+                outTxt = outTxt.replace(/^ +/, "");
+                outTxt = outTxt.replace(/ +$/, "");
+                return outTxt;
+            }
+
+            function changeBlankSeparator()
+            {
+                var separatorNumber = $("#select_separator").val();
+                var tabSeparator = getSeparatorFromNumber(separatorNumber);
+                blankSeparatortStart = tabSeparator[0];
+                blankSeparatortEnd = tabSeparator[1];
+                blankSeparatortStartRegexp = getBlankSeparatorRegexp(blankSeparatortStart);
+                blankSeparatortEndRegexp = getBlankSeparatorRegexp(blankSeparatortEnd);
+                updateBlanks();
+            }
+
+            // this function is the same than the PHP one
+            // if modify it modify the php one escapeForRegexp
+            function getBlankSeparatorRegexp(inTxt)
+            {
+                var tabSpecialChar = new Array(".", "+", "*", "?", "[", "^", "]", "$", "(", ")",
+                    "{", "}", "=", "!", "<", ">", "|", ":", "-", ")");
+                for (var i=0; i < tabSpecialChar.length; i++) {
+                    if (inTxt == tabSpecialChar[i]) {
+                        return "\\\"+inTxt;
+                    }
+                }
+                return inTxt;
+            }
+
+            // this function is the same than the PHP one
+            // if modify it modify the php one getAllowedSeparator
+            function getSeparatorFromNumber(innumber)
+            {
+                tabSeparator = new Array();
+                tabSeparator[0] = new Array("[", "]");
+                tabSeparator[1] = new Array("{", "}");
+                tabSeparator[2] = new Array("(", ")");
+                tabSeparator[3] = new Array("*", "*");
+                tabSeparator[4] = new Array("#", "#");
+                tabSeparator[5] = new Array("%", "%");
+                tabSeparator[6] = new Array("$", "$");
+                return tabSeparator[innumber];
+            }
+
+            function trimBlanksBetweenSeparator(inTxt, inSeparatorStart, inSeparatorEnd)
+            {
+                // blankSeparatortStartRegexp
+                // blankSeparatortEndRegexp
+                var result = inTxt
+                result = result.replace(inSeparatorStart, "");
+                result = result.replace(inSeparatorEnd, "");
+                result = result.trim();
+                return inSeparatorStart+result+inSeparatorEnd;
+            }
+        </script>';
+
+        // answer
+        $form->addElement ('label', null, '<br /><br />'.get_lang('TypeTextBelow').', '.get_lang('And').' '.get_lang('UseTagForBlank'));
+        $form->addElement ('html_editor', 'answer', '<img src="../img/fill_field.png">','id="answer" cols="122" rows="6" onkeyup="javascript: updateBlanks(this);"', array('ToolbarSet' => 'TestQuestionDescription', 'Width' => '100%', 'Height' => '350'));
+        $form -> addRule ('answer',get_lang('GiveText'),'required');
+
+        //added multiple answers
+        $form->addElement ('checkbox','multiple_answer','', get_lang('FillInBlankSwitchable'));
+        $form->addElement('select', 'select_separator', get_lang("SelectFillTheBlankSeparator"), self::getAllowedSeparatorForSelect(), ' id="select_separator"   style="width:150px" onchange="changeBlankSeparator()" ');
+        $form->addElement ('label', null, '<input type="button" onclick="updateBlanks()" value="'.get_lang('RefreshBlanks').'" class="btn" />');
+        $form->addElement('html','<div id="blanks_weighting"></div>');
+
+        global $text, $class;
+        // setting the save button here and not in the question class.php
+        $form->addElement('html','<div id="defineoneblank" style="color:#D04A66; margin-left:160px">'.get_lang('DefineBlanks').'</div>');
+        $form->addElement('style_submit_button','submitQuestion',$text, 'class="'.$class.'"');
+
+        if (!empty($this -> id)) {
+            $form -> setDefaults($defaults);
+        } else {
+            if ($this -> isContent == 1) {
+                $form -> setDefaults($defaults);
+            }
+        }
+    }
+
+    /**
+     * abstract function which creates the form to create / edit the answers of the question
+     * @param FormValidator $form
+     */
+    public function processAnswersCreation($form)
+    {
+        global $charset;
+
+        $answer = $form->getSubmitValue('answer');
+
+        // Due the fckeditor transform the elements to their HTML value
+        $answer = api_html_entity_decode($answer, ENT_QUOTES, $charset);
+
+        // remove the :: eventually written by the user
         $answer = str_replace('::', '', $answer);
 
+        // remove starting and ending space and &nbsp;
+        $answer = api_preg_replace("/\xc2\xa0/", " ", $answer);
+
+        // start and end separator
+
+        $blankStartSeparator = self::getStartSeparator($form->getSubmitValue('select_separator'));
+        $blankEndSeparator = self::getEndSeparator($form->getSubmitValue('select_separator'));
+        $blankStartSeparatorRegexp = self::escapeForRegexp($blankStartSeparator);
+        $blankEndSeparatorRegexp = self::escapeForRegexp($blankEndSeparator);
+
+        // remove spaces at the beginning and the end of text in square brackets
+        $answer = preg_replace_callback(
+            "/".$blankStartSeparatorRegexp."[^]]+".$blankEndSeparatorRegexp."/",
+            function ($matches) use ($blankStartSeparator, $blankEndSeparator) {
+                $matchingResult = $matches[0];
+                $matchingResult = trim($matchingResult, $blankStartSeparator);
+                $matchingResult = trim($matchingResult, $blankEndSeparator);
+                $matchingResult = trim($matchingResult);
+                // remove forbidden chars
+                $matchingResult = str_replace("/\\/", "", $matchingResult);
+                $matchingResult = str_replace('/"/', "", $matchingResult);
+                return $blankStartSeparator.$matchingResult.$blankEndSeparator;
+            },
+            $answer
+        );
+
         // get the blanks weightings
-        $nb = preg_match_all('/\[[^\]]*\]/', $answer, $blanks);
+        $nb = preg_match_all('/'.$blankStartSeparatorRegexp.'[^'.$blankStartSeparatorRegexp.']*'.$blankEndSeparatorRegexp.'/', $answer, $blanks);
         if (isset($_GET['editQuestion'])) {
-            $this ->weighting = 0;
-        }
-
-		if ($nb > 0) {
-		  	$answer .= '::';
-			for ($i=0 ; $i < $nb; ++$i) {
-                $blankItem = $blanks[0][$i];
-                $replace = array("[", "]");
-                $newBlankItem = str_replace($replace, "", $blankItem);
-                $newBlankItem = "[".trim($newBlankItem)."]";
-                $answer = str_replace($blankItem, $newBlankItem, $answer);
-                $answer .= $form->getSubmitValue('weighting['.$i.']').',';
+            $this -> weighting = 0;
+        }
+
+        /* if we have some [tobefound] in the text
+        build the string to save the following in the answers table
+        <p>I use a [computer] and a [pen].</p>
+        becomes
+        <p>I use a [computer] and a [pen].</p>::100,50:100,50@1
+                                              ++++++++-------**
+                                                --- -- --- -- -
+                                                  A B  (C) (D)(E)
+        +++++++ : required, weighting of each words
+        ------- : optional, input width to display, 200 if not present
+        ** : equal @1 if "Allow answers order switches" has been checked, @ otherwise
+        A : weighting for the word [computer]
+        B : weighting for the word [pen]
+        C : input width for the word [computer]
+        D : input width for the word [pen]
+        E : equal @1 if "Allow answers order switches" has been checked, @ otherwise
+        */
+        if ($nb > 0) {
+            $answer .= '::';
+            // weighting
+            for ($i=0; $i < $nb; ++$i) {
+                // enter the weighting of word $i
+                $answer .= $form->getSubmitValue('weighting['.$i.']');
+                // not the last word, add ","
+                if ($i != $nb - 1) {
+                    $answer .= ",";
+                }
+                // calculate the global weightning for the question
                 $this -> weighting += $form->getSubmitValue('weighting['.$i.']');
             }
-            $answer = api_substr($answer, 0, -1);
-		}
 
-		$is_multiple = $form->getSubmitValue('multiple_answer');
-		$answer.= '@'.$is_multiple;
+            // input width
+            $answer .= ":";
+            for ($i=0; $i < $nb; ++$i) {
+                // enter the width of input for word $i
+                $answer .= $form->getSubmitValue('sizeofinput['.$i.']');
+                // not the last word, add ","
+                if ($i != $nb - 1) {
+                    $answer .= ",";
+                }
+            }
+        }
 
-		$this->save();
-        $objAnswer = new Answer($this->id);
-        $objAnswer->createAnswer($answer, 0, '', 0, '1');
+        // write the blank separator code number
+        // see function getAllowedSeparator
+        /*
+            0 [...]
+            1 {...}
+            2 (...)
+            3 *...*
+            4 #...#
+            5 %...%
+            6 $...$
+         */
+        $answer .= ":".$form->getSubmitValue('select_separator');
+
+        // Allow answers order switches
+        $is_multiple = $form -> getSubmitValue('multiple_answer');
+        $answer.='@'.$is_multiple;
+
+        $this -> save();
+        $objAnswer = new answer($this->id);
+        $objAnswer->createAnswer($answer, 0, '', 0, 1);
         $objAnswer->save();
-	}
+    }
 
     /**
      * @param null $feedback_type
@@ -190,13 +401,508 @@ class FillBlanks extends Question
      * @param null $score
      * @return null|string
      */
-    function return_header($feedback_type = null, $counter = null, $score = null)
+    public function return_header($feedback_type = null, $counter = null, $score = null)
     {
-	    $header = parent::return_header($feedback_type, $counter, $score);
-	    $header .= '<table class="'.$this->question_table_class .'">
-			<tr>
+        $header = parent::return_header($feedback_type, $counter, $score);
+        $header .= '<table class="'.$this->question_table_class .'">
+            <tr>
                 <th>'.get_lang("Answer").'</th>
-			</tr>';
+            </tr>';
         return $header;
-	}
+    }
+
+    /**
+     * @param $separatorStartRegexp
+     * @param $separatorEndRegexp
+     * @param $correctItemRegexp
+     * @param $questionId
+     * @param $correctItem
+     * @param $attributes
+     * @param $answer
+     * @param $listAnswersInfo
+     * @param $displayForStudent
+     * @return string
+     */
+    public static function getFillTheBlankHtml($separatorStartRegexp, $separatorEndRegexp, $correctItemRegexp, $questionId, $correctItem, $attributes, $answer, $listAnswersInfo, $displayForStudent, $inBlankNumber)
+    {
+        $result = "";
+        $inTabTeacherSolution = $listAnswersInfo['tabwords'];
+        $inTeacherSolution = $inTabTeacherSolution[$inBlankNumber];
+        switch (self::getFillTheBlankAnswerType($inTeacherSolution)) {
+            case self::FILL_THE_BLANK_MENU:
+                $selected = "";
+                // the blank menu
+                $optionMenu = "";
+                // display a menu from answer separated with |
+                // if display for student, shuffle the correct answer menu
+                $listMenu = self::getFillTheBlankMenuAnswers($inTeacherSolution, $displayForStudent);
+                $result .= '<select name="choice['.$questionId.'][]">';
+                for ($k=0; $k < count($listMenu); $k++) {
+                    $selected = "";
+                    if ($correctItem == $listMenu[$k]) {
+                        $selected = " selected=selected ";
+                    }
+                    // if in teacher view, display the first item by default, which is the right answer
+                    if ($k==0 && !$displayForStudent) {
+                        $selected = " selected=selected ";
+                    }
+                    $optionMenu .= '<option '.$selected.' value="'.$listMenu[$k].'">'.$listMenu[$k].'</option>';
+                }
+                if ($selected == "") {
+                    // no good answer have been found...
+                    $selected = " selected=selected ";
+                }
+                $result .= "<option $selected value=''>--</option>";
+                $result .= $optionMenu;
+                $result .= '</select>';
+                break;
+            case self::FILL_THE_BLANK_SEVERAL_ANSWER:
+                //no break
+            case self::FILL_THE_BLANK_STANDARD:
+            default:
+                $result = Display::input('text', "choice[$questionId][]", $correctItem, $attributes);
+                break;
+        }
+        return $result;
+    }
+
+
+    /**
+     * Return an array with the different choices available when the answers between bracket show as a menu
+     * @param $correctAnswer
+     * @param $displayForStudent  true if we want to shuffle the choices of the menu for students
+     * @return array
+     */
+    public static function getFillTheBlankMenuAnswers($correctAnswer, $displayForStudent)
+    {
+        // if $inDisplayForStudent, then shuffle the result array
+        $listChoises = api_preg_split("/\|/", $correctAnswer);
+        if ($displayForStudent) {
+            shuffle($listChoises);
+        }
+        return $listChoises;
+    }
+
+
+    /**
+     * Return the array index of the student answer
+     * @param $correctAnswer  the menu Choice1|Choice2|Choice3
+     * @param $studentAnswer  the student answer must be Choice1 or Choice2 or Choice3
+     * @return int  in the exemple 0 1 or 2 depending of the choice of the student
+     */
+    public static function getFillTheBlankMenuAnswerNum($correctAnswer, $studentAnswer)
+    {
+        $listChoices = self::getFillTheBlankMenuAnswers($correctAnswer, false);
+        foreach ($listChoices as $num => $value) {
+            if ($value == $studentAnswer) {
+                return $num;
+            }
+        }
+        return -1; // should not happened, because student choose the answer in a menu of possible answers
+    }
+
+
+    /**
+     * Return the possible answer if the answer between brackets is a multiple choice menu
+     * @param $correctAnswer
+     * @return array
+     */
+    public static function getFillTheBlankSeveralAnswers($correctAnswer)
+    {
+        // is answer||Answer||response||Response , mean answer or Answer ...
+        $listSeveral = api_preg_split("/\|\|/", $correctAnswer);
+        return $listSeveral;
+    }
+
+    /**
+     * Return true if student answer is right according to the correctAnswer
+     * it is not as simple as equality, because of the type of Fill The Blank question
+     * eg : studentAnswer = 'Un' and correctAnswer = 'Un||1||un'
+     * @param $studentAnswer the [studentanswer] of the info array of the answer field
+     * @param $correctAnswer the [tabwords] of the info array of the answer field
+     * @return bool
+     */
+    public static function isGoodStudentAnswer($studentAnswer, $correctAnswer)
+    {
+        switch (self::getFillTheBlankAnswerType($correctAnswer)) {
+            case self::FILL_THE_BLANK_MENU:
+                $listMenu = self::getFillTheBlankMenuAnswers($correctAnswer, false);
+                return ($listMenu[0] == $studentAnswer);
+                break;
+            case self::FILL_THE_BLANK_SEVERAL_ANSWER:
+                // the answer must be one of the choice made
+                $listSeveral = self::getFillTheBlankSeveralAnswers($correctAnswer);
+                return (in_array($studentAnswer, $listSeveral));
+                break;
+            case self::FILL_THE_BLANK_STANDARD:
+            default:
+                return ($studentAnswer == $correctAnswer);
+        }
+    }
+
+
+    public static function getFillTheBlankAnswerType($correctAnswer)
+    {
+        if (api_strpos($correctAnswer, "|") && !api_strpos($correctAnswer, "||")) {
+            return self::FILL_THE_BLANK_MENU;
+        } elseif (api_strpos($correctAnswer, "||")) {
+            return self::FILL_THE_BLANK_SEVERAL_ANSWER;
+        } else {
+            return self::FILL_THE_BLANK_STANDARD;
+        }
+    }
+
+    /**
+     * Return information about the answer
+     * @param string $answer : the text of the answer of the question
+     * @param bool $inIsStudentAnswer : true if it is a student answer and not the empty question model
+     * @return array of information about the answer
+     */
+    public static function getAnswerInfo($userAnswer = "", $isStudentAnswer = false)
+    {
+        $listAnswerResults = array();
+        $listAnswerResults['text'] = "";
+        $listAnswerResults['wordsCount'] = 0;
+        $listAnswerResults['tabwordsbracket'] = array();
+        $listAnswerResults['tabwords'] = array();
+        $listAnswerResults['tabweighting'] = array();
+        $listAnswerResults['tabinputsize'] = array();
+        $listAnswerResults['switchable'] = "";
+        $listAnswerResults['studentanswer'] = array();
+        $listAnswerResults['studentscore'] = array();
+        $listAnswerResults['blankseparatornumber'] = 0;
+
+        api_preg_match("/(.*)::(.*)$/s", $userAnswer, $listResult);
+
+        if (count($listResult) < 2) {
+            $listDoubleColon[] = $listResult;
+            $listDoubleColon[] = "";
+        } else {
+            $listDoubleColon[] = $listResult[1];
+            $listDoubleColon[] = $listResult[2];
+        }
+
+        $listAnswerResults['systemstring'] = $listDoubleColon[1];
+
+        //make sure we only take the last bit to find special marks
+        $listArobaseSplit = explode('@', $listDoubleColon[1]);
+        if (count($listArobaseSplit) < 2) {
+            $listArobaseSplit[1] = "";
+        }
+
+        //take the complete string except after the last '::'
+        $listDetails = explode(":", $listArobaseSplit[0]);
+
+        // < number of item after the ::[score]:[size]:[separator_id]@ , here there are 3
+        if (count($listDetails) < 3) {
+            $listWeightings = explode(',', $listDetails[0]);
+            $listSizeOfInput = array();
+            for ($i=0; $i < count($listWeightings); $i++) {
+                $listSizeOfInput[] = 200;
+            }
+            $blankSeparatorNumber = 0;    // 0 is [...]
+        } else {
+            $listWeightings = explode(',', $listDetails[0]);
+            $listSizeOfInput = explode(',', $listDetails[1]);
+            $blankSeparatorNumber = $listDetails[2];
+        }
+
+        $listAnswerResults['text'] = $listDoubleColon[0];
+        $listAnswerResults['tabweighting'] = $listWeightings;
+        $listAnswerResults['tabinputsize'] = $listSizeOfInput;
+        $listAnswerResults['switchable'] = $listArobaseSplit[1];
+        $listAnswerResults['blankseparatorstart'] = self::getStartSeparator($blankSeparatorNumber);
+        $listAnswerResults['blankseparatorend'] = self::getEndSeparator($blankSeparatorNumber);
+        $listAnswerResults['blankseparatornumber'] = $blankSeparatorNumber;
+
+        $blankCharStart = self::getStartSeparator($blankSeparatorNumber);
+        $blankCharEnd = self::getEndSeparator($blankSeparatorNumber);
+        $blankCharStartForRegexp = self::escapeForRegexp($blankCharStart);
+        $blankCharEndForRegexp = self::escapeForRegexp($blankCharEnd);
+
+        // get all blanks words
+        $listAnswerResults['wordsCount'] = preg_match_all('/'.$blankCharStartForRegexp.'[^'.$blankCharEndForRegexp.']*'.$blankCharEndForRegexp.'/', $listDoubleColon[0], $listWords);
+        if ($listAnswerResults['wordsCount'] > 0) {
+            $listAnswerResults['tabwordsbracket'] = $listWords[0];
+            // remove [ and ] in string
+            array_walk(
+                $listWords[0],
+                function (&$value, $key, $tabBlankChar) {
+                    $trimChars = "";
+                    for ($i=0; $i < count($tabBlankChar); $i++) {
+                        $trimChars .= $tabBlankChar[$i];
+                    }
+                    $value = trim($value, $trimChars);
+                },
+                array($blankCharStart, $blankCharEnd)
+            );
+            $listAnswerResults['tabwords'] = $listWords[0];
+        }
+
+        // get all common words
+        $commonWords = preg_replace('/'.$blankCharStartForRegexp.'[^'.$blankCharEndForRegexp.']*'.$blankCharEndForRegexp.'/', "::", $listDoubleColon[0]);
+
+        // if student answer, the second [] is the student answer, the third is if student scored or not
+        $listBrackets = array();
+        $listWords =  array();
+        if ($isStudentAnswer) {
+            for ($i=0; $i < count($listAnswerResults['tabwords']); $i++) {
+                $listBrackets[] = $listAnswerResults['tabwordsbracket'][$i];
+                $listWords[] = $listAnswerResults['tabwords'][$i];
+                if ($i+1 < count($listAnswerResults['tabwords'])) {    // should always be
+                    $i++;
+                }
+                $listAnswerResults['studentanswer'][] = $listAnswerResults['tabwords'][$i];
+                if ($i+1 < count($listAnswerResults['tabwords'])) {    // should always be
+                    $i++;
+                }
+                $listAnswerResults['studentscore'][] = $listAnswerResults['tabwords'][$i];
+            }
+            $listAnswerResults['tabwords'] = $listWords;
+            $listAnswerResults['tabwordsbracket'] = $listBrackets;
+
+            // if we are in student view, we've got 3 times :::::: for common words
+            $commonWords = preg_replace("/::::::/", "::", $commonWords);
+        }
+        $listAnswerResults['commonwords'] = explode("::", $commonWords);
+
+        return $listAnswerResults;
+    }
+
+
+    /**
+     * Replace the occurence of blank word with [correct answer][student answer][answer is correct]
+     * @param array $listWithStudentAnswer
+     * @return string
+     */
+    public static function getAnswerInStudentAttempt($listWithStudentAnswer)
+    {
+
+        $separatorStart = $listWithStudentAnswer['blankseparatorstart'];
+        $separatorEnd = $listWithStudentAnswer['blankseparatorend'];
+        // lets rebuild the sentence with [correct answer][student answer][answer is correct]
+        $result = "";
+        for ($i=0; $i < count($listWithStudentAnswer['commonwords']) - 1; $i++) {
+            $result .= $listWithStudentAnswer['commonwords'][$i];
+            $result .= $listWithStudentAnswer['tabwordsbracket'][$i];
+            $result .= $separatorStart.$listWithStudentAnswer['studentanswer'][$i].$separatorEnd;
+            $result .= $separatorStart.$listWithStudentAnswer['studentscore'][$i].$separatorEnd;
+        }
+        $result .= $listWithStudentAnswer['commonwords'][$i];
+        $result .= "::";
+        // add the system string
+        $result .= $listWithStudentAnswer['systemstring'];
+        return $result;
+    }
+
+    /**
+     * This function is the same than the js one above getBlankSeparatorRegexp
+     * @param $inChar
+     * @return string
+     */
+    public static function escapeForRegexp($inChar)
+    {
+        if (in_array($inChar, array(".", "+", "*", "?", "[", "^", "]", "$", "(", ")", "{", "}", "=", "!", ">", "|", ":", "-", ")"))) {
+            return "\\".$inChar;
+        } else {
+            return $inChar;
+        }
+    }
+
+    /**
+     * return $text protected for use in regexp
+     * @param $text
+     * @return mixed
+     */
+    public static function getRegexpProtected($text)
+    {
+        $listRegexpCharacters = array("/", ".", "+", "*", "?", "[", "^", "]", "$", "(", ")", "{", "}", "=", "!", ">", "|", ":", "-", ")");
+        $result = $text;
+        for ($i=0; $i < count($listRegexpCharacters); $i++) {
+            $result = str_replace($listRegexpCharacters[$i], "\\".$listRegexpCharacters[$i], $result);
+        }
+        return $result;
+    }
+
+
+    /**
+     * This function must be the same than the js one getSeparatorFromNumber above
+     * @return array
+     */
+    public static function getAllowedSeparator()
+    {
+        $fillBlanksAllowedSeparator = array(
+            array('[', ']'),
+            array('{', '}'),
+            array('(', ')'),
+            array('*', '*'),
+            array('#', '#'),
+            array('%', '%'),
+            array('$', '$'),
+        );
+        return $fillBlanksAllowedSeparator;
+    }
+
+    /**
+     * return the start separator for answer
+     * @param $number
+     * @return mixed
+     */
+    public static function getStartSeparator($number)
+    {
+        $listSeparators = self::getAllowedSeparator();
+        return $listSeparators[$number][0];
+    }
+
+    /**
+     * return the end separator for answer
+     * @param $number
+     * @return mixed
+     */
+    public static function getEndSeparator($number)
+    {
+        $listSeparators = self::getAllowedSeparator();
+        return $listSeparators[$number][1];
+    }
+
+    /**
+     * return as a desciption text, array of allowed separtors for question eg: array("[...]", "(...)")
+     * @return array
+     */
+    public static function getAllowedSeparatorForSelect()
+    {
+        $listResults = array();
+        $fillBlanksAllowedSeparator = self::getAllowedSeparator();
+        for ($i=0; $i < count($fillBlanksAllowedSeparator); $i++) {
+            $listResults[] = $fillBlanksAllowedSeparator[$i][0]."...".$fillBlanksAllowedSeparator[$i][1];
+        }
+        return $listResults;
+    }
+
+    /**
+     * return the code number of the separator for the question
+     * @param $startSeparator
+     * @param $endSeparator
+     * @return int
+     */
+    public function getDefaultSeparatorNumber($startSeparator, $endSeparator)
+    {
+        $listSeparators = self::getAllowedSeparator();
+        $result = 0;
+        for ($i=0; $i < count($listSeparators); $i++) {
+            if ($listSeparators[$i][0] == $startSeparator && $listSeparators[$i][1] == $endSeparator) {
+                $result = $i;
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * return the HTML display of the answer
+     * @param $answer
+     * @return string
+     */
+    public static function getHtmlDisplayForAnswer($answer, $resultsDisabled = false)
+    {
+        $result = "";
+        $listStudentAnswerInfo = self::getAnswerInfo($answer, true);
+
+        // rebluid the answer with good HTML style
+        // this is the student answer, right or wrong
+        for ($i=0; $i < count($listStudentAnswerInfo['studentanswer']); $i++) {
+            if ($listStudentAnswerInfo['studentscore'][$i] == 1) {
+                $listStudentAnswerInfo['studentanswer'][$i] = self::getHtmlRightAnswer($listStudentAnswerInfo['studentanswer'][$i], $listStudentAnswerInfo['tabwords'][$i], $resultsDisabled);
+            } else {
+                $listStudentAnswerInfo['studentanswer'][$i] = self::getHtmlWrongAnswer($listStudentAnswerInfo['studentanswer'][$i], $listStudentAnswerInfo['tabwords'][$i], $resultsDisabled);
+            }
+        }
+
+
+        // rebuild the sentence with student answer inserted
+        for ($i=0; $i < count($listStudentAnswerInfo['commonwords']); $i++) {
+            $result .= $listStudentAnswerInfo['commonwords'][$i];
+            $result .= $listStudentAnswerInfo['studentanswer'][$i];
+        }
+
+        // the last common word (should be </p>)
+        $result .= $listStudentAnswerInfo['commonwords'][$i];
+        return $result;
+    }
+
+
+    /**
+     * return the HTML code of answer for correct and wrong answer
+     * @param $answer
+     * @param $correct
+     * @param $right
+     * @param $resultsDisabled
+     * @return string
+     */
+    public static function getHtmlAnswer($answer, $correct, $right, $resultsDisabled = false)
+    {
+        $style = "color: green";
+        if (!$right) {
+            $style = "color: red; text-decoration: line-through;";
+        }
+        $type = FillBlanks::getFillTheBlankAnswerType($correct);
+        switch ($type) {
+            case self::FILL_THE_BLANK_MENU:
+                $correctAnswerHtml = "";
+                $listPossibleAnswers = FillBlanks::getFillTheBlankMenuAnswers($correct, false);
+                $correctAnswerHtml .= "<span style='color: green'>".$listPossibleAnswers[0]."</span>";
+                $correctAnswerHtml .= " <span style='font-weight:normal'>(";
+                for ($i=1; $i < count($listPossibleAnswers); $i++) {
+                    $correctAnswerHtml .= $listPossibleAnswers[$i];
+                    if ($i != count($listPossibleAnswers) - 1) {
+                        $correctAnswerHtml .= " | ";
+                    }
+                }
+                $correctAnswerHtml .= ")</span>";
+                break;
+            case self::FILL_THE_BLANK_SEVERAL_ANSWER:
+                $listCorrects = explode("||", $correct);
+                $firstCorrect = $correct;
+                if (count($listCorrects) > 0) {
+                    $firstCorrect = $listCorrects[0];
+                }
+                $correctAnswerHtml = "<span style='color: green'>".$firstCorrect."</span>";
+                break;
+            case self::FILL_THE_BLANK_STANDARD:
+            default:
+                $correctAnswerHtml = "<span style='color: green'>".$correct."</span>";
+        }
+
+        if ($resultsDisabled) {
+            $correctAnswerHtml = "<span title='".get_lang("ExerciseWithFeedbackWithoutCorrectionComment")."'> - </span>";
+        }
+
+        $result = "<span style='border:1px solid black; border-radius:5px; padding:2px; font-weight:bold;'>";
+        $result .= "<span style='$style'>".$answer."</span>";
+        $result .= "&nbsp;<span style='font-size:120%;'>/</span>&nbsp;";
+        $result .= $correctAnswerHtml;
+        $result .= "</span>";
+        return $result;
+    }
+
+    /**
+     * return HTML code for correct answer
+     * @param $answer
+     * @param $correct
+     * @return string
+     */
+    public static function getHtmlRightAnswer($answer, $correct, $resultsDisabled = false)
+    {
+        return self::getHtmlAnswer($answer, $correct, true, $resultsDisabled);
+    }
+
+    /**
+     * return HTML code for wrong answer
+     * @param $answer
+     * @param $correct
+     * @return string
+     */
+    public static function getHtmlWrongAnswer($answer, $correct, $resultsDisabled = false)
+    {
+        return self::getHtmlAnswer($answer, $correct, false, $resultsDisabled);
+    }
 }

+ 2 - 1
main/exercice/stats.php

@@ -52,7 +52,8 @@ if (!empty($question_list)) {
             $question_id,
             $exercise_id,
             $courseCode,
-            $sessionId
+            $sessionId,
+            $question_obj->type
         );
 
         $data[$question_id]['name'] = cut($question_obj->question, 100);

+ 76 - 150
main/inc/lib/exercise.lib.php

@@ -548,170 +548,73 @@ HTML;
                     $s .= '</tr>';
 
                 } elseif ($answerType == FILL_IN_BLANKS) {
-                    /*
-                     * In the FILL_IN_BLANKS test
-                     * you mustn't have [ and ] in the textarea
-                     * you mustn't have :: in the textarea
-                     * the text to find mustn't be empty or contains only spaces
-                     * the text to find mustn't contains HTML tags
-                     * the text to find mustn't contains char "
-                     */
+                    // display the question, with field empty, for student to fill it,
+                    // or filled to display the answer in the Question preview of the exercice/admin.php page
+                    $displayForStudent = true;
+                    $listAnswerInformations = FillBlanks::getAnswerInfo($answer);
+                    $separatorStartRegexp = FillBlanks::escapeForRegexp($listAnswerInformations['blankseparatorstart']);
+                    $separatorEndRegexp = FillBlanks::escapeForRegexp($listAnswerInformations['blankseparatorend']);
 
                     list($answer) = explode('::', $answer);
 
-                    // $correct_answer_list array of array with correct anwsers 0=> [0=>[\p] 1=>[plop]]
-                    api_preg_match_all(
-                        '/\[[^]]+\]/',
-                        $answer,
-                        $correct_answer_list
-                    );
+                    //Correct answers
+                    $correctAnswerList = $listAnswerInformations['tabwords'];
 
-                    // get student answer to display it if student go back to previous fillBlank answer question in a test
+                    //Student's answer
+                    $studentAnswerList = array();
                     if (isset($user_choice[0]['answer'])) {
-                        api_preg_match_all(
-                            '/\[[^]]+\]/',
-                            $user_choice[0]['answer'],
-                            $student_answer_list
-                        );
-                        $student_answer_list_tobecleaned = $student_answer_list[0];
-                        $student_answer_list = array();
-                        // here we got the student answer in a test
-                        // let's clean up the results
-                        /*
-                        Array
-                        (
-                            [0] => Array
-                            (
-                                [0] => [<font color="red"><s>yer</s></font> / <font color="green"><b>ici</b></font>]
-                                [1] => [<font color="red"><s>plop</s></font> / <font color="green"><b>/p</b></font>]
-                            )
-                        )
-                        */
-                        for ($i = 0; $i < count(
-                            $student_answer_list_tobecleaned
-                        ); $i++) {
-                            $answer_corrected = $student_answer_list_tobecleaned[$i];
-                            /*
-                             * we got if student answer is wrong
-                             * [<font color="red"><s>rrr</s></font> / <font color="green"><b>/p</b></font>]
-                             * or if student answer is good
-                             * [plop / <font color="green"><b>plop</b></font>]
-                             * or if student didn't answer []
-                             */
-                            $answer_corrected = api_preg_replace(
-                                '| / <font color="green"><b>.*$|',
-                                '',
-                                $answer_corrected
-                            );
-                            /*
-                             * we got [<font color="red"><s>rrr</s></font> or [plop or [
-                             */
-                            $answer_corrected = api_preg_replace(
-                                '/^\[/',
-                                '',
-                                $answer_corrected
-                            );
-                            /*
-                             * we got <font color="red"><s>rrr</s></font> or plop
-                             * non breakable spaces &nbsp;&nbsp;&nbsp; from /main/exercice/exercise.class.php have been removed l 2391 and l 2370
-                             */
-                            $answer_corrected = api_preg_replace(
-                                '|^<font color="red"><s>|',
-                                '',
-                                $answer_corrected
-                            );
-                            $answer_corrected = api_preg_replace(
-                                '|</s></font>$|',
-                                '',
-                                $answer_corrected
-                            );
-                            $answer_corrected = '[' . $answer_corrected . ']';
-                            /*
-                             * we got [rrr] or [plop] or []
-                             */
-                            $student_answer_list[] = $answer_corrected;
-                        }
+                        $arrayStudentAnswer = FillBlanks::getAnswerInfo($user_choice[0]['answer'], true);
+                        $studentAnswerList = $arrayStudentAnswer['studentanswer'];
                     }
 
-                    // If display preview of answer in test view for exemple, set the student answer to the correct answers
+                    // If the question must be shown with the answer (in page exercice/admin.php) for teacher preview
+                    // set the student-answer to the correct answer
                     if ($debug_mark_answer) {
-                        // contain the rights answers surronded with brackets
-                        $student_answer_list = $correct_answer_list[0];
+                        $studentAnswerList = $correctAnswerList;
+                        $displayForStudent = false;
                     }
 
-                    /*
-                    Split the response by bracket
-                    tab_comments is an array with text surrounding the text to find
-                    we add a space before and after the answer_question to be sure to
-                    have a block of text before and after [xxx] patterns
-                    so we have n text to find ([xxx]) and n+1 block of texts before,
-                    between and after the text to find
-                    */
-                    $tab_comments = api_preg_split(
-                        '/\[[^]]+\]/',
-                        ' ' . $answer . ' '
-                    );
-
-                    if (!empty($correct_answer_list) && !empty($student_answer_list)) {
+                    if (!empty($correctAnswerList) && !empty($studentAnswerList)) {
                         $answer = "";
-
-                        $i = 0;
-                        foreach ($student_answer_list as $student_item) {
-                            // remove surronding brackets
-                            $student_response = api_substr(
-                                $student_item,
-                                1,
-                                api_strlen($student_item) - 2
-                            );
-
-                            $size = strlen($student_item);
-                            $attributes['class'] = self::detectInputAppropriateClass(
-                                $size
-                            );
-
-                            $answer .= $tab_comments[$i] .
-                                Display::input(
-                                    'text',
-                                    "choice[$questionId][]",
-                                    $student_response,
-                                    $attributes
-                                );
-                            $i++;
+                        for ($i = 0; $i < count($listAnswerInformations["commonwords"]) - 1; $i++) {
+                            // display the common word
+                            $answer .= $listAnswerInformations["commonwords"][$i];
+                            // display the blank word
+                            $correctItem = $listAnswerInformations["tabwords"][$i];
+                            $correctItemRegexp = $correctItem;
+                            // replace / with \/ to allow the preg_replace bellow and all the regexp char
+                            $correctItemRegexp = FillBlanks::getRegexpProtected($correctItemRegexp);
+                            if (isset($studentAnswerList[$i])) {
+                                // If student already started this test and answered this question,
+                                // fill the blank with his previous answers
+                                // may be "" if student viewed the question, but did not fill the blanks
+                                $correctItem = $studentAnswerList[$i];
+                            }
+                            $attributes["style"] = "width:".$listAnswerInformations["tabinputsize"][$i]."px";
+                            $answer .= FillBlanks::getFillTheBlankHtml($separatorStartRegexp, $separatorEndRegexp, $correctItemRegexp, $questionId, $correctItem, $attributes, $answer, $listAnswerInformations, $displayForStudent, $i);
                         }
-                        $answer .= $tab_comments[$i];
+                        // display the last common word
+                        $answer .= $listAnswerInformations["commonwords"][$i];
                     } else {
-                        // display exercise with empty input fields
-                        // every [xxx] are replaced with an empty input field
-                        foreach ($correct_answer_list[0] as $item) {
-                            $size = strlen($item);
-                            $attributes['class'] = self::detectInputAppropriateClass(
-                                $size
-                            );
-
-                            $attributes['class'] .= ' block_on_enter';
-
-                            $answer = str_replace(
-                                $item,
-                                Display::input(
-                                    'text',
-                                    "choice[$questionId][]",
-                                    '',
-                                    $attributes
-                                ),
-                                $answer
-                            );
+                        // display empty [input] with the right width for student to fill it
+                        $separatorStartRegexp = FillBlanks::escapeForRegexp($listAnswerInformations['blankseparatorstart']);
+                        $separatorEndRegexp = FillBlanks::escapeForRegexp($listAnswerInformations['blankseparatorend']);
+                        $answer = "";
+                        for ($i = 0; $i < count($listAnswerInformations["commonwords"]) - 1; $i++) {
+                            // display the common words
+                            $answer .= $listAnswerInformations["commonwords"][$i];
+                            // display the blank word
+                            $attributes["style"] = "width:".$listAnswerInformations["tabinputsize"][$i]."px";
+                            $correctItem = $listAnswerInformations["tabwords"][$i];
+                            $correctItemRegexp = $correctItem;
+                            // replace / with \/ to allow the preg_replace bellow and all the regexp char
+                            $correctItemRegexp = FillBlanks::getRegexpProtected($correctItemRegexp);
+                            $answer .= FillBlanks::getFillTheBlankHtml($separatorStartRegexp, $separatorEndRegexp, $correctItemRegexp, $questionId, '', $attributes, $answer, $listAnswerInformations, $displayForStudent, $i);
                         }
-                        /*$answer = api_preg_replace(
-                            '/\[[^]]+\]/',
-                            Display::input(
-                                'text',
-                                "choice[$questionId][]",
-                                '',
-                                $attributes
-                            ), $answer);*/
+                        // display the last common word
+                        $answer .= $listAnswerInformations["commonwords"][$i];
                     }
-                    $html = '<div class="fill_blanks col-md-12">'.$answer.'</div>';
-                    $s .= $html ;
+                    $s .= $answer;
 
                 } elseif ($answerType == CALCULATED_ANSWER) {
                     /*
@@ -3047,13 +2950,15 @@ HTML;
      * @param int $exercise_id
      * @param string $course_code
      * @param int $session_id
+     * @param string $questionType
      * @return int
      */
     public static function get_number_students_question_with_answer_count(
         $question_id,
         $exercise_id,
         $course_code,
-        $session_id
+        $session_id,
+        $questionType = ''
     ) {
         $track_exercises = Database::get_main_table(
             TABLE_STATISTIC_TRACK_E_EXERCISES
@@ -3072,6 +2977,27 @@ HTML;
         $courseId = api_get_course_int_id($course_code);
         $session_id = intval($session_id);
 
+        if ($questionType == FILL_IN_BLANKS) {
+            $listStudentsId = array();
+            $listAllStudentInfo = CourseManager::get_student_list_from_course_code(
+                api_get_course_id(),
+                true
+            );
+            foreach ($listAllStudentInfo as $i => $listStudentInfo) {
+                $listStudentsId[] = $listStudentInfo['user_id'];
+            }
+
+            $listFillTheBlankResult = getFillTheBlankTabResult(
+                $exercise_id,
+                $question_id,
+                $listStudentsId,
+                '1970-01-01',
+                '3000-01-01'
+            );
+
+            return getNbResultFillBlankAll($listFillTheBlankResult);
+        }
+
         if (empty($session_id)) {
             $courseCondition = "
             INNER JOIN $courseUser cu

+ 18 - 16
main/inc/lib/exercise_show_functions.lib.php

@@ -22,31 +22,33 @@ class ExerciseShowFunctions
 	 * @param string    Answer text
 	 * @param int       Exercise ID
 	 * @param int       Question ID
+     * @param int $resultsDisabled
 	 * @return void
 	 */
-	static function display_fill_in_blanks_answer($feedback_type, $answer, $id, $questionId)
+	static function display_fill_in_blanks_answer($feedbackType, $answer, $id, $questionId, $resultsDisabled)
     {
+        $answerHTML = FillBlanks::getHtmlDisplayForAnswer($answer, $resultsDisabled);
         if (empty($id)) {
-            echo '<tr><td>'. Security::remove_XSS($answer).'</td></tr>';
+            echo '<tr><td>';
+            echo Security::remove_XSS($answerHTML, COURSEMANAGERLOWSECURITY);
+            echo '</td></tr>';
         } else {
-		?>
-			<tr>
+            ?>
+            <tr>
                 <td>
-                    <?php
-                    echo Security::remove_XSS($answer);
-                    ?>
+                    <?php echo nl2br(Security::remove_XSS($answerHTML, COURSEMANAGERLOWSECURITY)); ?>
                 </td>
 
-			<?php
-			if (!api_is_allowed_to_edit(null,true) && $feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) { ?>
-				<td>
-                    <?php
-                    $comm = Event::get_comments($id,$questionId);
-                    ?>
-				</td>
-			<?php } ?>
+                <?php
+                if (!api_is_allowed_to_edit(null,true) && $feedbackType != EXERCISE_FEEDBACK_TYPE_EXAM) { ?>
+                    <td>
+                        <?php
+                        $comm = Event::get_comments($id, $questionId);
+                        ?>
+                    </td>
+                <?php } ?>
             </tr>
-		<?php
+        <?php
         }
 	}