Menu,several choice,resize for Fill the blank question 1.9.10

1.9.x
baelmyhu 11 years ago
parent fb7aa5cef7
commit badd895cd9
  1. 380
      main/exercice/exercise.class.php
  2. 167
      main/exercice/exercise.lib.php
  3. 913
      main/exercice/fill_blanks.class.php
  4. 3
      main/exercice/stats.php
  5. 51
      main/inc/lib/exercise_show_functions.lib.php

@ -186,13 +186,13 @@ class Exercise
//load questions only for exercises of type 'one question per page'
//this is needed only is there is no questions
/*
// @todo not sure were in the code this is used somebody mess with the exercise tool
// @todo don't know who add that config and why $_configuration['live_exercise_tracking']
global $_configuration, $questionList;
if ($this->type == ONE_PER_PAGE && $_SERVER['REQUEST_METHOD'] != 'POST' && defined('QUESTION_LIST_ALREADY_LOGGED') &&
isset($_configuration['live_exercise_tracking']) && $_configuration['live_exercise_tracking']) {
$this->questionList = $questionList;
}*/
// @todo not sure were in the code this is used somebody mess with the exercise tool
// @todo don't know who add that config and why $_configuration['live_exercise_tracking']
global $_configuration, $questionList;
if ($this->type == ONE_PER_PAGE && $_SERVER['REQUEST_METHOD'] != 'POST' && defined('QUESTION_LIST_ALREADY_LOGGED') &&
isset($_configuration['live_exercise_tracking']) && $_configuration['live_exercise_tracking']) {
$this->questionList = $questionList;
}*/
return true;
}
@ -449,7 +449,7 @@ class Exercise
$sql = "SELECT DISTINCT e.question_order
FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS q
ON (e.question_id = q.id AND e.c_id = ".$this->course_id." AND q.c_id = ".$this->course_id.")
WHERE e.exercice_id = ".intval($this->id)."";
WHERE e.exercice_id = ".intval($this->id)."";
$result = Database::query($sql);
$count_question_orders = Database::num_rows($result);
@ -457,8 +457,8 @@ class Exercise
$sql = "SELECT e.question_id, e.question_order
FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS q
ON (e.question_id= q.id AND e.c_id = ".$this->course_id." AND q.c_id = ".$this->course_id.")
WHERE e.exercice_id = ".intval($this->id)."
ORDER BY question_order";
WHERE e.exercice_id = ".intval($this->id)."
ORDER BY question_order";
$result = Database::query($sql);
// fills the array with the question ID for this exercise
@ -814,27 +814,27 @@ class Exercise
}
$sql = "UPDATE $TBL_EXERCICES SET
title='".Database::escape_string($exercise)."',
description='".Database::escape_string($description)."'";
title='".Database::escape_string($exercise)."',
description='".Database::escape_string($description)."'";
if ($type_e != 'simple') {
$sql .= ",sound='".Database::escape_string($sound)."',
type = ".intval($type).",
random = ".intval($random).",
random_answers = ".intval($random_answers).",
active = ".intval($active).",
feedback_type = ".intval($feedback_type).",
start_time = '$start_time',
end_time = '$end_time',
max_attempt = ".intval($attempts).",
expired_time = ".intval($expired_time).",
propagate_neg = ".intval($propagate_neg).",
review_answers = ".intval($review_answers).",
random_by_category= ".intval($randomByCat).",
text_when_finished = '".Database::escape_string($text_when_finished)."',
display_category_name = ".intval($display_category_name).",
type = ".intval($type).",
random = ".intval($random).",
random_answers = ".intval($random_answers).",
active = ".intval($active).",
feedback_type = ".intval($feedback_type).",
start_time = '$start_time',
end_time = '$end_time',
max_attempt = ".intval($attempts).",
expired_time = ".intval($expired_time).",
propagate_neg = ".intval($propagate_neg).",
review_answers = ".intval($review_answers).",
random_by_category= ".intval($randomByCat).",
text_when_finished = '".Database::escape_string($text_when_finished)."',
display_category_name = ".intval($display_category_name).",
pass_percentage = ".intval($pass_percentage).",
results_disabled= ".intval($results_disabled)."";
results_disabled= ".intval($results_disabled)."";
}
$sql .= " WHERE c_id = ".$this->course_id." AND id = ".intval($id)."";
Database::query($sql);
@ -868,27 +868,27 @@ class Exercise
results_disabled, max_attempt, feedback_type, expired_time, session_id, review_answers, random_by_category,
text_when_finished, display_category_name, pass_percentage
)
VALUES(
".$this->course_id.",
'$start_time','$end_time',
'".Database::escape_string($exercise)."',
'".Database::escape_string($description)."',
'".Database::escape_string($sound)."',
".intval($type).",
".intval($random).",
".intval($random_answers).",
".intval($active).",
".intval($results_disabled).",
".intval($attempts).",
".intval($feedback_type).",
".intval($expired_time).",
".intval($session_id).",
".intval($review_answers).",
".intval($randomByCat).",
'".Database::escape_string($text_when_finished)."',
".intval($display_category_name).",
VALUES(
".$this->course_id.",
'$start_time','$end_time',
'".Database::escape_string($exercise)."',
'".Database::escape_string($description)."',
'".Database::escape_string($sound)."',
".intval($type).",
".intval($random).",
".intval($random_answers).",
".intval($active).",
".intval($results_disabled).",
".intval($attempts).",
".intval($feedback_type).",
".intval($expired_time).",
".intval($session_id).",
".intval($review_answers).",
".intval($randomByCat).",
'".Database::escape_string($text_when_finished)."',
".intval($display_category_name).",
".intval($pass_percentage)."
)";
)";
Database::query($sql);
$this->id = Database::insert_id();
@ -1025,12 +1025,12 @@ class Exercise
$form->addElement('advanced_settings',
'<a href="javascript://" onclick=" return show_media()">
<span id="media_icon">
<img style="vertical-align: middle;" src="../img/looknfeel.png" alt="" />'.
<span id="media_icon">
<img style="vertical-align: middle;" src="../img/looknfeel.png" alt="" />'.
addslashes(api_htmlentities(get_lang('ExerciseDescription'))).'
</span>
</a>
');
</a>
');
$editor_config = array('ToolbarSet' => 'TestQuestionDescription', 'Width' => '100%', 'Height' => '150');
if (is_array($type)){
@ -1454,7 +1454,7 @@ class Exercise
// save it to db
$tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
$sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
VALUES (NULL , \'%s\', \'%s\', %s, %s)';
VALUES (NULL , \'%s\', \'%s\', %s, %s)';
$sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
Database::query($sql);
}
@ -1740,13 +1740,13 @@ class Exercise
$lp_item_view_id = 0;
}
$condition = ' WHERE exe_exo_id = ' . "'" . $this->id . "'" .' AND
exe_user_id = ' . "'" . api_get_user_id() . "'" . ' AND
exe_cours_id = ' . "'" . api_get_course_id() . "'" . ' AND
status = ' . "'" . Database::escape_string($status). "'" . ' AND
orig_lp_id = ' . "'" . $lp_id . "'" . ' AND
orig_lp_item_id = ' . "'" . $lp_item_id . "'" . ' AND
exe_user_id = ' . "'" . api_get_user_id() . "'" . ' AND
exe_cours_id = ' . "'" . api_get_course_id() . "'" . ' AND
status = ' . "'" . Database::escape_string($status). "'" . ' AND
orig_lp_id = ' . "'" . $lp_id . "'" . ' AND
orig_lp_item_id = ' . "'" . $lp_item_id . "'" . ' AND
orig_lp_item_view_id = ' . "'" . $lp_item_view_id . "'" . ' AND
session_id = ' . "'" . api_get_session_id() . "' LIMIT 1"; //Adding limit 1 just in case
session_id = ' . "'" . api_get_session_id() . "' LIMIT 1"; //Adding limit 1 just in case
$sql_track = 'SELECT * FROM '.$track_exercises.$condition;
@ -1847,7 +1847,7 @@ class Exercise
$label = get_lang('NextQuestion');
$class = 'btn btn-primary';
}
$class .= ' question-validate-btn'; // used to select it with jquery
$class .= ' question-validate-btn'; // used to select it with jquery
if ($this->type == ONE_PER_PAGE) {
if ($questionNum != 1) {
$prev_question = $questionNum - 2;
@ -1872,7 +1872,7 @@ class Exercise
$all_label = get_lang('EndTest');
$class = 'btn btn-warning';
}
$class .= ' question-validate-btn'; // used to select it with jquery
$class .= ' question-validate-btn'; // used to select it with jquery
$all_button = '&nbsp;<a href="javascript://" class="'.$class.'" onclick="validate_all(); ">'.$all_label.'</a>';
$all_button .= '&nbsp;<span id="save_all_reponse"></span>';
$html .= $all_button;
@ -1954,12 +1954,12 @@ class Exercise
open_clock_warning();
}
$(document).ready(function() {
$(document).ready(function() {
var current_time = new Date().getTime();
var current_time = new Date().getTime();
var time_left = parseInt(".$time_left."); // time in seconds when using minutes there are some seconds lost
var expired_time = current_time + (time_left*1000);
var expired_date = get_expired_date_string(expired_time);
var expired_time = current_time + (time_left*1000);
var expired_date = get_expired_date_string(expired_time);
$('#exercise_clock_warning').epiclock({
mode: $.epiclock.modes.countdown,
@ -1969,9 +1969,9 @@ class Exercise
}).bind('timer', function () {
onExpiredTimeExercise();
});
$('#submit_save').click(function () {});
});
</script>";
$('#submit_save').click(function () {});
});
</script>";
}
/**
@ -2464,144 +2464,74 @@ class Exercise
break;
// for fill in the blanks
case FILL_IN_BLANKS:
// 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;
$is_set_switchable = explode('@', $pre_array[$last]);
$switchable_answer_set = false;
if (isset ($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
$switchable_answer_set = true;
}
$answer = '';
for ($k = 0; $k < $last; $k++) {
$answer .= $pre_array[$k];
}
// splits weightings that are joined with a comma
$answerWeighting = explode(',', $is_set_switchable[0]);
// we save the answer because it will be modified
$temp = $answer;
$answer = '';
$j = 0;
//initialise answer tags
$user_tags = $correct_tags = $real_text = array();
// the loop will stop at the end of the text
while (1) {
// quits the loop if there are no more blanks (detect '[')
if (($pos = api_strpos($temp, '[')) === false) {
// adds the end of the text
$answer = $temp;
$real_text[] = $answer;
break; //no more "blanks", quit the loop
}
// adds the piece of text that is before the blank
//and ends with '[' into a general storage array
$real_text[] = api_substr($temp, 0, $pos +1);
$answer .= api_substr($temp, 0, $pos +1);
//take the string remaining (after the last "[" we found)
$temp = api_substr($temp, $pos +1);
// quit the loop if there are no more blanks, and update $pos to the position of next ']'
if (($pos = api_strpos($temp, ']')) === false) {
// adds the end of the text
$answer .= $temp;
break;
}
// 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[]
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');
$listCorrectAnswers = FillBlanks::getAnswerInfo($answer);
$switchableAnswerSet = $listCorrectAnswers["switchable"];
$answerWeighting = $listCorrectAnswers["tabweighting"];
// user choices is an array $choice
api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
$str = str_replace('\r\n', '', $str);
$choice = $arr[1];
if (isset($choice[$j])) {
$tmp = api_strrpos($choice[$j], ' / ');
$choice[$j] = api_substr($choice[$j], 0, $tmp);
$choice[$j] = trim($choice[$j]);
// Needed to let characters ' and " to work as part of an answer
$choice[$j] = stripslashes($choice[$j]);
} else {
$choice[$j] = null;
}
} else {
// This value is the user input, not escaped while correct answer is escaped by fckeditor
$choice[$j] = api_htmlentities(trim($choice[$j]));
}
$user_tags[] = $choice[$j];
//put the contents of the [] answer tag into correct_tags[]
$correct_tags[] = api_substr($temp, 0, $pos);
$j++;
$temp = api_substr($temp, $pos +1);
// 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'];
}
$answer = '';
$real_correct_tags = $correct_tags;
$chosen_list = array();
for ($i = 0; $i < count($real_correct_tags); $i++) {
if ($i == 0) {
$answer .= $real_text[0];
}
if (!$switchable_answer_set) {
// Needed to parse ' and " characters
$user_tags[$i] = stripslashes($user_tags[$i]);
if ($correct_tags[$i] == $user_tags[$i]) {
// 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];
// adds the word in green at the end of the string
$answer .= $correct_tags[$i];
} elseif (!empty($user_tags[$i])) {
// else if the word entered by the student IS NOT the same as the one defined by the professor
// adds the word in red at the end of the string, and strikes it
$answer .= '<font color="red"><s>' . $user_tags[$i] . '</s></font>';
} else {
// adds a tabulation if no word has been typed by the student
$answer .= ''; // remove &nbsp; that causes issue
$isAnswerCorrect = 1;
}
} else {
// switchable fill in the blanks
if (in_array($user_tags[$i], $correct_tags)) {
$chosen_list[] = $user_tags[$i];
$correct_tags = array_diff($correct_tags, $chosen_list);
// gives the related weighting to the student
$questionScore += $answerWeighting[$i];
// increments total score
$totalScore += $answerWeighting[$i];
// adds the word in green at the end of the string
$answer .= $user_tags[$i];
} elseif (!empty ($user_tags[$i])) {
// else if the word entered by the student IS NOT the same as the one defined by the professor
// adds the word in red at the end of the string, and strikes it
$answer .= '<font color="red"><s>' . $user_tags[$i] . '</s></font>';
$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 {
// adds a tabulation if no word has been typed by the student
$answer .= ''; // remove &nbsp; that causes issue
$listCorrectAnswers['studentscore'][$i] = 1;
}
}
// adds the correct word, followed by ] to close the blank
$answer .= ' / <font color="green"><b>' . $real_correct_tags[$i] . '</b></font>]';
if (isset($real_text[$i +1])) {
$answer .= $real_text[$i +1];
}
}
$answer = FillBlanks::getAnswerInStudentAttempt($listCorrectAnswers);
break;
// for calculated answer
case CALCULATED_ANSWER:
@ -2896,16 +2826,16 @@ class Exercise
}
if ($answerId===1) {
$studentChoice =$choice[$answerId];
$questionScore +=$answerWeighting;
$studentChoice =$choice[$answerId];
$questionScore +=$answerWeighting;
if ($hotspot_delineation_result[1]==1) {
$totalScore +=$answerWeighting; //adding the total
}
}
}
$_SESSION['hotspot_coord'][1] = $delineation_cord;
$_SESSION['hotspot_dest'][1] = $answer_delineation_destination;
$_SESSION['hotspot_coord'][1] = $delineation_cord;
$_SESSION['hotspot_dest'][1] = $answer_delineation_destination;
break;
} // end switch Answertype
@ -2928,11 +2858,11 @@ class Exercise
ExerciseShowFunctions::display_multiple_answer_true_false($feedback_type, $answerType, $studentChoice, $answer, $answerComment, $answerCorrect,0,$questionId,0, $results_disabled);
//}
} elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE ) {
// if ($origin!='learnpath') {
// if ($origin!='learnpath') {
ExerciseShowFunctions::display_multiple_answer_combination_true_false($feedback_type, $answerType, $studentChoice, $answer, $answerComment, $answerCorrect,0,0,0, $results_disabled);
//}
} elseif ($answerType == FILL_IN_BLANKS) {
//if ($origin!='learnpath') {
// if ($origin!='learnpath') {
ExerciseShowFunctions::display_fill_in_blanks_answer($feedback_type, $answer,0,0);
// }
} elseif ($answerType == CALCULATED_ANSWER) {
@ -3154,9 +3084,9 @@ class Exercise
break;
case ORAL_EXPRESSION:
echo '<tr>
<td valign="top">'.ExerciseShowFunctions::display_oral_expression_answer($feedback_type, $choice, $exeId, $questionId, $nano).'</td>
</tr>
</table>';
<td valign="top">'.ExerciseShowFunctions::display_oral_expression_answer($feedback_type, $choice, $exeId, $questionId, $nano).'</td>
</tr>
</table>';
break;
case HOT_SPOT:
ExerciseShowFunctions::display_hotspot_answer($feedback_type, $answerId, $answer, $studentChoice, $answerComment, $results_disabled);
@ -3425,27 +3355,27 @@ class Exercise
}
$table_resume='<table class="data_table">
<tr class="row_odd" >
<td></td>
<td ><b>'.get_lang('Requirements').'</b></td>
<td><b>'.get_lang('YourAnswer').'</b></td>
</tr>
<tr class="row_even">
<td><b>'.get_lang('Overlap').'</b></td>
<td>'.get_lang('Min').' '.$threadhold1.'</td>
<td><div style="color:'.$overlap_color.'">'.(($final_overlap < 0)?0:intval($final_overlap)).'</div></td>
</tr>
<tr>
<td><b>'.get_lang('Excess').'</b></td>
<td>'.get_lang('Max').' '.$threadhold2.'</td>
<td><div style="color:'.$excess_color.'">'.(($final_excess < 0)?0:intval($final_excess)).'</div></td>
</tr>
<tr class="row_even">
<td><b>'.get_lang('Missing').'</b></td>
<td>'.get_lang('Max').' '.$threadhold3.'</td>
<td><div style="color:'.$missing_color.'">'.(($final_missing < 0)?0:intval($final_missing)).'</div></td>
</tr>
</table>';
<tr class="row_odd" >
<td></td>
<td ><b>'.get_lang('Requirements').'</b></td>
<td><b>'.get_lang('YourAnswer').'</b></td>
</tr>
<tr class="row_even">
<td><b>'.get_lang('Overlap').'</b></td>
<td>'.get_lang('Min').' '.$threadhold1.'</td>
<td><div style="color:'.$overlap_color.'">'.(($final_overlap < 0)?0:intval($final_overlap)).'</div></td>
</tr>
<tr>
<td><b>'.get_lang('Excess').'</b></td>
<td>'.get_lang('Max').' '.$threadhold2.'</td>
<td><div style="color:'.$excess_color.'">'.(($final_excess < 0)?0:intval($final_excess)).'</div></td>
</tr>
<tr class="row_even">
<td><b>'.get_lang('Missing').'</b></td>
<td>'.get_lang('Max').' '.$threadhold3.'</td>
<td><div style="color:'.$missing_color.'">'.(($final_missing < 0)?0:intval($final_missing)).'</div></td>
</tr>
</table>';
if ($next==0) {
$try = $try_hotspot;
$lp = $lp_hotspot;
@ -3459,7 +3389,7 @@ class Exercise
}
echo '<h1><div style="color:#333;">'.get_lang('Feedback').'</div></h1>
<p style="text-align:center">';
<p style="text-align:center">';
$message='<p>'.get_lang('YourDelineation').'</p>';
$message.=$table_resume;
@ -3518,8 +3448,8 @@ class Exercise
echo '<i>'.get_lang('HotSpot').'</i><br /><br />';
echo '<object type="application/x-shockwave-flash" data="'.api_get_path(WEB_CODE_PATH).'plugin/hotspot/hotspot_solution.swf?modifyAnswers='.Security::remove_XSS($questionId).'&exe_id='.$exeId.'&from_db=1" width="552" height="352">
<param name="movie" value="../plugin/hotspot/hotspot_solution.swf?modifyAnswers='.Security::remove_XSS($questionId).'&exe_id='.$exeId.'&from_db=1" />
</object>';
<param name="movie" value="../plugin/hotspot/hotspot_solution.swf?modifyAnswers='.Security::remove_XSS($questionId).'&exe_id='.$exeId.'&from_db=1" />
</object>';
echo '</td>
</tr>';
// }

@ -436,124 +436,73 @@ function showQuestion(
$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 answer
$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 display the question with 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'] = 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])) {
// student already start this test and this question
// fill the blank with his previous answers
// may be "" if student did the question, but 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'] = detectInputAppropriateClass($size);
$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];
}
$s .= $answer;
} elseif ($answerType == CALCULATED_ANSWER) {
/*
* In the CALCULATED_ANSWER test
@ -2250,7 +2199,7 @@ function get_student_stats_by_question($question_id, $exercise_id, $course_code
* @param int $session_id
* @return int
*/
function get_number_students_question_with_answer_count($question_id, $exercise_id, $course_code, $session_id)
function get_number_students_question_with_answer_count($question_id, $exercise_id, $course_code, $session_id, $questionType = '')
{
$track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCICES);
$track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
@ -2262,6 +2211,18 @@ function get_number_students_question_with_answer_count($question_id, $exercise_
$course_code = Database::escape_string($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

@ -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
@ -27,42 +31,36 @@ class FillBlanks extends Question
* function which redefines Question::createAnswersForm
* @param the formvalidator instance
*/
function createAnswersForm($form)
{
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
$objectAnswer = new answer($this->id);
$answer = $objectAnswer->selectAnswer(1);
$listAnswersInfo = FillBlanks::getAnswerInfo($answer);
$pre_array = explode('::', $objAnswer->selectAnswer(1));
//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) {
@ -71,116 +69,329 @@ class FillBlanks extends Question
}
}
// javascript
echo '<script>
// javascript
echo '<script>
function FCKeditor_OnComplete(editorInstance) {
if (window.attachEvent) {
editorInstance.EditorDocument.attachEvent("onkeyup", updateBlanks) ;
} else {
editorInstance.EditorDocument.addEventListener("keyup", updateBlanks, true);
}
}
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() {
if (firstTime) {
field = document.getElementById("answer");
var field = document.getElementById("answer");
var answer = field.value;
} else {
var oEditor = FCKeditorAPI.GetInstance("answer");
//var answer = oEditor.GetXHTML(true);
var answer = oEditor.EditorDocument.body.innerHTML;
}
var blanks = answer.match(/\[[^\]]*\]/g);
var fields = "<div class=\"control-group\"><label class=\"control-label\">'.get_lang('Weighting').'</label><div class=\"controls\"><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 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">','id="answer" cols="122" rows="6" onkeyup="javascript: updateBlanks(this);"', array('ToolbarSet' => 'TestQuestionDescription', 'Width' => '100%', 'Height' => '350'));
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;
}
});
}
$form->addRule('answer', get_lang('GiveText'),'required');
$form->addRule('answer', get_lang('DefineBlanks'),'regex','/\[.*\]/');
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);
}
//added multiple answers
$form->addElement('checkbox', 'multiple_answer','', get_lang('FillInBlankSwitchable'));
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;
}
$form->addElement('html', '<div id="blanks_weighting"></div>');
function changeBlankSeparator()
{
var separatorNumber = $("#select_separator").val();
var tabSeparator = getSeparatorFromNumber(separatorNumber);
blankSeparatortStart = tabSeparator[0];
blankSeparatortEnd = tabSeparator[1];
blankSeparatortStartRegexp = getBlankSeparatorRegexp(blankSeparatortStart);
blankSeparatortEndRegexp = getBlankSeparatorRegexp(blankSeparatortEnd);
updateBlanks();
}
global $text, $class;
// setting the save button here and not in the question class.php
$form->addElement('style_submit_button', 'submitQuestion', $text, 'class="'.$class.'"');
// 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
*/
function processAnswersCreation($form)
{
global $charset;
if (!empty($this->id)) {
$form -> setDefaults($defaults);
} else {
if ($this->isContent == 1) {
$form->setDefaults($defaults);
}
}
}
$answer = $form->getSubmitValue('answer');
/**
* 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');
// 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 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);
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.']').',';
$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
<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();
// 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->createAnswer($answer, 0, '', 0, 1);
$objAnswer->save();
}
}
/**
* @param null $feedback_type
@ -190,11 +401,503 @@ class FillBlanks extends Question
*/
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:
case self::FILL_THE_BLANK_STANDARD:
default:
$result = Display::input('text', "choice[$questionId][]", $correctItem, $attributes);
break;
}
return $result;
}
/**
* Return an array with the differents choice avaliable when the bracket is 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 indice 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 bracket 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 so simple as equality, because of the type of Fill The Blank question
* eg : studentNaswer = '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 .= $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
* @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 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);
}
}

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

@ -17,38 +17,39 @@
*/
class ExerciseShowFunctions
{
/**
* Shows the answer to a fill-in-the-blanks question, as HTML
* @param string Answer text
* @param int Exercise ID
* @param int Question ID
* @return void
*/
static function display_fill_in_blanks_answer($feedback_type, $answer, $id, $questionId)
{
/**
* Shows the answer to a fill-in-the-blanks question, as HTML
* Display in the student result page, with score and comm
* @param string Answer text
* @param int Exercise ID
* @param int Question ID
* @return void
*/
static function display_fill_in_blanks_answer($feedbackType, $answer, $id, $questionId, $inResultsDisabled) {
$answerHTML = FillBlanks::getHtmlDisplayForAsnwer($answer, $inResultsDisabled);
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 = get_comments($id,$questionId);
?>
</td>
<?php } ?>
<?php
if (!api_is_allowed_to_edit(null,true) && $feedbackType != EXERCISE_FEEDBACK_TYPE_EXAM) { ?>
<td>
<?php
$comm = get_comments($id,$questionId);
?>
</td>
<?php } ?>
</tr>
<?php
<?php
}
}
}
/**
* Shows the answer to a calculated question, as HTML

Loading…
Cancel
Save