diff --git a/main/exercice/exercise.class.php b/main/exercice/exercise.class.php index e98aa6e591..1104da16dd 100755 --- a/main/exercice/exercise.class.php +++ b/main/exercice/exercise.class.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', ' - - '. + + '. addslashes(api_htmlentities(get_lang('ExerciseDescription'))).' - - '); + + '); $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 = ' '.$all_label.''; $all_button .= ' '; $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 () {}); - }); - "; + $('#submit_save').click(function () {}); + }); + "; } /** @@ -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 .= '' . $user_tags[$i] . ''; - } else { - // adds a tabulation if no word has been typed by the student - $answer .= ''; // remove   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 .= '' . $user_tags[$i] . ''; + $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   that causes issue + $listCorrectAnswers['studentscore'][$i] = 1; } } - // adds the correct word, followed by ] to close the blank - $answer .= ' / ' . $real_correct_tags[$i] . ']'; - 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 ' - '.ExerciseShowFunctions::display_oral_expression_answer($feedback_type, $choice, $exeId, $questionId, $nano).' - - '; + '.ExerciseShowFunctions::display_oral_expression_answer($feedback_type, $choice, $exeId, $questionId, $nano).' + + '; break; case HOT_SPOT: ExerciseShowFunctions::display_hotspot_answer($feedback_type, $answerId, $answer, $studentChoice, $answerComment, $results_disabled); @@ -3425,27 +3355,27 @@ class Exercise } $table_resume=' - - - - - - - - - - - - - - - - - - - - -
'.get_lang('Requirements').''.get_lang('YourAnswer').'
'.get_lang('Overlap').''.get_lang('Min').' '.$threadhold1.'
'.(($final_overlap < 0)?0:intval($final_overlap)).'
'.get_lang('Excess').''.get_lang('Max').' '.$threadhold2.'
'.(($final_excess < 0)?0:intval($final_excess)).'
'.get_lang('Missing').''.get_lang('Max').' '.$threadhold3.'
'.(($final_missing < 0)?0:intval($final_missing)).'
'; + + + '.get_lang('Requirements').' + '.get_lang('YourAnswer').' + + + '.get_lang('Overlap').' + '.get_lang('Min').' '.$threadhold1.' +
'.(($final_overlap < 0)?0:intval($final_overlap)).'
+ + + '.get_lang('Excess').' + '.get_lang('Max').' '.$threadhold2.' +
'.(($final_excess < 0)?0:intval($final_excess)).'
+ + + '.get_lang('Missing').' + '.get_lang('Max').' '.$threadhold3.' +
'.(($final_missing < 0)?0:intval($final_missing)).'
+ + '; if ($next==0) { $try = $try_hotspot; $lp = $lp_hotspot; @@ -3459,7 +3389,7 @@ class Exercise } echo '

'.get_lang('Feedback').'

-

'; +

'; $message='

'.get_lang('YourDelineation').'

'; $message.=$table_resume; @@ -3518,8 +3448,8 @@ class Exercise echo ''.get_lang('HotSpot').'

'; echo ' - - '; + + '; echo ' '; // } diff --git a/main/exercice/exercise.lib.php b/main/exercice/exercise.lib.php index 902e28a1e7..c06a4e3c9e 100755 --- a/main/exercice/exercise.lib.php +++ b/main/exercice/exercise.lib.php @@ -436,124 +436,73 @@ function showQuestion( $s .=''; } 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] => [yer / ici] - [1] => [plop / /p] - ) - ) - */ - for ($i=0; $i < count($student_answer_list_tobecleaned); $i++) { - $answer_corrected = $student_answer_list_tobecleaned[$i]; - /* - * we got if student answer is wrong - * [rrr / /p] - * or if student answer is good - * [plop / plop] - * or if student didn't answer [] - */ - $answer_corrected = api_preg_replace('| / .*$|', '', $answer_corrected); - /* - * we got [rrr or [plop or [ - */ - $answer_corrected = api_preg_replace('/^\[/', '', $answer_corrected); - /* - * we got rrr or plop - * non breakable spaces     from /main/exercice/exercise.class.php have been removed l 2391 and l 2370 - */ - $answer_corrected = api_preg_replace('|^|', '', $answer_corrected); - $answer_corrected = api_preg_replace('|$|', '', $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'] = 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'] = 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 diff --git a/main/exercice/fill_blanks.class.php b/main/exercice/fill_blanks.class.php index 7c61bc2a38..9abbf204e7 100755 --- a/main/exercice/fill_blanks.class.php +++ b/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 @@ -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,330 @@ class FillBlanks extends Question } } - // 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'); - $form->addRule('answer', get_lang('DefineBlanks'),'regex','/\[.*\]/'); - - //added multiple answers - $form->addElement('checkbox', 'multiple_answer','', get_lang('FillInBlankSwitchable')); - - $form->addElement('html', '
'); - - global $text, $class; - // setting the save button here and not in the question class.php - $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; - $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(/"/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(/ /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; + } + '; + + // 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('/\[[^\]]*\]/', $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 +

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.']'); } - $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 @@ -188,13 +400,507 @@ 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 .= ' - + $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 .= $listStudentAnswerInfo['commonwords'][$i]; + $result .= $listStudentAnswerInfo['studentanswer'][$i]; + } + + // the last common word (should be

) + $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 .= "".$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); + } } diff --git a/main/exercice/stats.php b/main/exercice/stats.php index c9023d1c1e..064a2d955b 100755 --- a/main/exercice/stats.php +++ b/main/exercice/stats.php @@ -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); diff --git a/main/inc/lib/exercise_show_functions.lib.php b/main/inc/lib/exercise_show_functions.lib.php index a83d7178e0..33412b228e 100755 --- a/main/inc/lib/exercise_show_functions.lib.php +++ b/main/inc/lib/exercise_show_functions.lib.php @@ -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 ''; + echo ''; } else { - ?> - + ?> + - - - + + + -
'.get_lang("Answer").'
'. (Security::remove_XSS($answer)).'
'; + echo Security::remove_XSS($answerHTML, COURSEMANAGERLOWSECURITY); + echo '
- + - - + +