type = FILL_IN_BLANKS; $this -> isContent = $this-> getIsContent(); } /** * function which redefines Question::createAnswersForm * @param the formvalidator instance */ function createAnswersForm ($form) { $fillBlanksAllowedSeparator = self::getAllowedSeparator(); $defaults = array(); if (!empty($this->id)) { $objectAnswer = new answer($this->id); $answer = $objectAnswer->selectAnswer(1); $listAnswersInfo = FillBlanks::getAnswerInfo($answer); if ($listAnswersInfo["switchable"]) { $defaults['multiple_answer'] = 1; } else { $defaults['multiple_answer'] = 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) { foreach ($a_weightings as $i => $weighting) { $setValues .= 'document.getElementById("weighting['.$i.']").value = "'.$weighting.'";'; } } // javascript echo ''; // answer $form->addElement ('label', null, '

'.get_lang('TypeTextBelow').', '.get_lang('And').' '.get_lang('UseTagForBlank')); $form->addElement ('html_editor', 'answer', '','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, ''); $form->addElement('html','
'); global $text, $class; // setting the save button here and not in the question class.php $form->addElement('html','
'.get_lang('DefineBlanks').'
'); $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   $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('/'.$blankStartSeparatorRegexp.'[^'.$blankStartSeparatorRegexp.']*'.$blankEndSeparatorRegexp.'/', $answer, $blanks); if (isset($_GET['editQuestion'])) { $this -> weighting = 0; } /* if we have some [tobefound] in the text build the string to save the following in the answers table

I use a [computer] and a [pen].

becomes

I use a [computer] and a [pen].

::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.']'); } // 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 .= ","; } } } // 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 * @param null $counter * @param null $score * @return null|string */ public function return_header($feedback_type = null, $counter = null, $score = null) { $header = parent::return_header($feedback_type, $counter, $score); $header .= ''; 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 .= ''; 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 getHtmlDisplayForAsnwer($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::getHtmlRightAsnwer($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 .= isset($listStudentAnswerInfo['commonwords'][$i]) ? $listStudentAnswerInfo['commonwords'][$i] : ''; $result .= isset($listStudentAnswerInfo['studentanswer'][$i]) ? $listStudentAnswerInfo['studentanswer'][$i] : ''; } // the last common word (should be

) $result .= isset($listStudentAnswerInfo['commonwords'][$i]) ? $listStudentAnswerInfo['commonwords'][$i] : ''; return $result; } /** * return the HTML code of answer for correct and wrong answer * @param $answer * @param $correct * @param $right * @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 .= "".$listPossibleAnswers[0].""; $correctAnswerHtml .= " ("; for ($i=1; $i < count($listPossibleAnswers); $i++) { $correctAnswerHtml .= $listPossibleAnswers[$i]; if ($i != count($listPossibleAnswers) - 1) { $correctAnswerHtml .= " | "; } } $correctAnswerHtml .= ")"; break; case self::FILL_THE_BLANK_SEVERAL_ANSWER: $listCorrects = explode("||", $correct); $firstCorrect = $correct; if (count($listCorrects) > 0) { $firstCorrect = $listCorrects[0]; } $correctAnswerHtml = "".$firstCorrect.""; break; case self::FILL_THE_BLANK_STANDARD: default: $correctAnswerHtml = "".$correct.""; } if ($resultsDisabled) { $correctAnswerHtml = " - "; } $result = ""; $result .= "".$answer.""; $result .= " / "; $result .= $correctAnswerHtml; $result .= ""; return $result; } /** * return HTML code for correct answer * @param $answer * @param $correct * @return string */ public static function getHtmlRightAsnwer($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); } }
'.get_lang("Answer").'