Exercises: allow_time_per_question requires DB change to save spent time

BT#17791
pull/3600/head^2
Julio Montoya 5 years ago
parent ca559e154b
commit 8dfc932016
  1. 90
      main/exercise/exercise.class.php
  2. 1099
      main/exercise/exercise_submit.php
  3. 48
      main/inc/ajax/exercise.ajax.php
  4. 315
      main/inc/lib/events.lib.php
  5. 4
      main/install/configuration.dist.php

@ -3577,6 +3577,7 @@ class Exercise
* @param bool $showTotalScoreAndUserChoicesInLastAttempt
* @param bool $updateResults
* @param bool $showHotSpotDelineationTable
* @param int $questionDuration seconds
*
* @todo reduce parameters of this function
*
@ -3595,7 +3596,8 @@ class Exercise
$hotspot_delineation_result = [],
$showTotalScoreAndUserChoicesInLastAttempt = true,
$updateResults = false,
$showHotSpotDelineationTable = false
$showHotSpotDelineationTable = false,
$questionDuration = 0
) {
$debug = false;
//needed in order to use in the exercise_attempt() for the time
@ -3604,6 +3606,7 @@ class Exercise
$em = Database::getManager();
$feedback_type = $this->getFeedbackType();
$results_disabled = $this->selectResultsDisabled();
$questionDuration = (int) $questionDuration;
if ($debug) {
error_log("<------ manage_answer ------> ");
@ -5694,10 +5697,6 @@ class Exercise
</tr>
</table>';
if ($next == 0) {
/*$try = $try_hotspot;
$lp = $lp_hotspot;
$destinationid = $select_question_hotspot;
$url = $url_hotspot;*/
} else {
$comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
$answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
@ -5862,7 +5861,8 @@ class Exercise
$exeId,
$i,
$this->id,
$updateResults
$updateResults,
$questionDuration
);
}
} else {
@ -5873,7 +5873,8 @@ class Exercise
$exeId,
$i,
$this->id,
$updateResults
$updateResults,
$questionDuration
);
}
if ($debug) {
@ -5887,7 +5888,9 @@ class Exercise
$quesId,
$exeId,
0,
$this->id
$this->id,
false,
$questionDuration
);
}
} elseif ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
@ -5895,17 +5898,44 @@ class Exercise
$reply = array_keys($choice);
for ($i = 0; $i < count($reply); $i++) {
$ans = $reply[$i];
Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
Event::saveQuestionAttempt(
$questionScore,
$ans,
$quesId,
$exeId,
$i,
$this->id,
false,
$questionDuration
);
}
} else {
Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
Event::saveQuestionAttempt(
$questionScore,
0,
$quesId,
$exeId,
0,
$this->id,
false,
$questionDuration
);
}
} elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
if ($choice != 0) {
$reply = array_keys($choice);
for ($i = 0; $i < count($reply); $i++) {
$ans = $reply[$i];
Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
Event::saveQuestionAttempt(
$questionScore,
$ans,
$quesId,
$exeId,
$i,
$this->id,
false,
$questionDuration
);
}
} else {
Event::saveQuestionAttempt(
@ -5914,7 +5944,9 @@ class Exercise
$quesId,
$exeId,
0,
$this->id
$this->id,
false,
$questionDuration
);
}
} elseif (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
@ -5926,7 +5958,9 @@ class Exercise
$quesId,
$exeId,
$j,
$this->id
$this->id,
false,
$questionDuration
);
}
}
@ -5938,7 +5972,9 @@ class Exercise
$quesId,
$exeId,
0,
$this->id
$this->id,
false,
$questionDuration
);
} elseif ($answerType == ORAL_EXPRESSION) {
$answer = $choice;
@ -5950,6 +5986,7 @@ class Exercise
0,
$this->id,
false,
$questionDuration,
$objQuestionTmp->getAbsoluteFilePath()
);
} elseif (
@ -5959,7 +5996,7 @@ class Exercise
)
) {
$answer = $choice;
Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id, false, $questionDuration);
} elseif ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
$answer = [];
if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) {
@ -5998,9 +6035,27 @@ class Exercise
error_log('Empty: exerciseResultCoordinates');
}
}
Event::saveQuestionAttempt($questionScore, implode('|', $answer), $quesId, $exeId, 0, $this->id);
Event::saveQuestionAttempt(
$questionScore,
implode('|', $answer),
$quesId,
$exeId,
0,
$this->id,
false,
$questionDuration
);
} else {
Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
Event::saveQuestionAttempt(
$questionScore,
$answer,
$quesId,
$exeId,
0,
$this->id,
false,
$questionDuration
);
}
}
@ -9952,6 +10007,7 @@ class Exercise
Session::erase('exerciseResult');
Session::erase('firstTime');
Session::erase('time_per_question');
Session::erase('question_start');
Session::erase('exerciseResultCoordinates');
Session::erase('hotspot_coord');
Session::erase('hotspot_dest');

File diff suppressed because it is too large Load Diff

@ -389,8 +389,7 @@ switch ($action) {
$choice = isset($_REQUEST['choice']) ? $_REQUEST['choice'] : [];
// certainty degree choice
$choiceDegreeCertainty = isset($_REQUEST['choiceDegreeCertainty'])
? $_REQUEST['choiceDegreeCertainty'] : [];
$choiceDegreeCertainty = isset($_REQUEST['choiceDegreeCertainty'])? $_REQUEST['choiceDegreeCertainty'] : [];
// Hot spot coordinates from all questions.
$hot_spot_coordinates = isset($_REQUEST['hotspot']) ? $_REQUEST['hotspot'] : [];
@ -450,13 +449,11 @@ switch ($action) {
$exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId);
$exercise_id = $exercise_stat_info['exe_exo_id'];
$attemptList = [];
// First time here we create an attempt (getting the exe_id).
if (!empty($exercise_stat_info)) {
// We know the user we get the exe_id.
$exeId = $exercise_stat_info['exe_id'];
$total_score = $exercise_stat_info['exe_result'];
// Getting the list of attempts
$attemptList = Event::getAllExerciseEventByExeId($exeId);
}
@ -495,7 +492,7 @@ switch ($action) {
}
}
// Getting the total weight if the request is simple
// Getting the total weight if the request is simple.
$total_weight = 0;
if ($type === 'simple') {
foreach ($question_list as $my_question_id) {
@ -510,7 +507,7 @@ switch ($action) {
}
// Check we have at least one non-empty answer in the array
// provided by the user's click on the "Finish test" button
// provided by the user's click on the "Finish test" button.
if ('all' === $type) {
$atLeastOneAnswer = false;
foreach ($question_list as $my_question_id) {
@ -534,24 +531,14 @@ switch ($action) {
// Looping the question list from database (not from the user answer)
foreach ($question_list as $my_question_id) {
if ($type === 'simple' && $question_id != $my_question_id) {
if ($debug) {
error_log('Skipping question '.$my_question_id.' in single-question save action');
}
continue;
}
if ($debug) {
error_log("Saving question_id = $my_question_id ");
}
$my_choice = isset($choice[$my_question_id]) ? $choice[$my_question_id] : null;
if ($debug) {
error_log("Saving question_id = $my_question_id ");
error_log("my_choice = ".print_r($my_choice, 1)."");
}
// Creates a temporary Question object
$objQuestionTmp = Question::read($my_question_id, $objExercise->course);
$myChoiceDegreeCertainty = null;
if ($objQuestionTmp->type === MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
if (isset($choiceDegreeCertainty[$my_question_id])) {
@ -560,7 +547,7 @@ switch ($action) {
}
// Getting free choice data.
if (in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION]) && $type == 'all') {
if (in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION]) && $type === 'all') {
$my_choice = isset($_REQUEST['free_choice'][$my_question_id]) && !empty($_REQUEST['free_choice'][$my_question_id])
? $_REQUEST['free_choice'][$my_question_id]
: null;
@ -581,7 +568,7 @@ switch ($action) {
$hotspot_delineation_result = $_SESSION['hotspot_delineation_result'][$objExercise->selectId()][$my_question_id];
}
if ($type === 'simple') {
if ('simple' === $type) {
// Getting old attempt in order to decrease the total score.
$old_result = $objExercise->manage_answer(
$exeId,
@ -627,6 +614,8 @@ switch ($action) {
}
}
$questionDuration = Event::getAttemptQuestionDuration($exeId, $objQuestionTmp->iid);
// We're inside *one* question. Go through each possible answer for this question
if ($objQuestionTmp->type === MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
$myChoiceTmp = [];
@ -642,7 +631,11 @@ switch ($action) {
false,
false,
$objExercise->selectPropagateNeg(),
$hotspot_delineation_result
$hotspot_delineation_result,
true,
false,
false,
$questionDuration
);
} else {
$result = $objExercise->manage_answer(
@ -655,11 +648,15 @@ switch ($action) {
false,
false,
$objExercise->selectPropagateNeg(),
$hotspot_delineation_result
$hotspot_delineation_result,
true,
false,
false,
$questionDuration
);
}
// Adding the new score.
// Adding the new score.
$total_score += $result['score'];
if ($debug) {
@ -715,6 +712,11 @@ switch ($action) {
$remind_list
);
$questionStart = Session::read('question_start');
unset($questionStart[$my_question_id]);
array_filter($questionStart);
Session::write('question_start', $questionStart);
// Destruction of the Question object
unset($objQuestionTmp);
if ($debug) {
@ -737,7 +739,7 @@ switch ($action) {
exit;
}
if ($type == 'all') {
if ($type === 'all') {
echo 'ok';
exit;
}

@ -403,21 +403,26 @@ class Event
/**
* Update the TRACK_E_EXERCICES exercises.
* Record result of user when an exercise was done.
*
* @param int exeid id of the attempt
* @param int exo_id exercise id
* @param mixed result score
* @param int weighting ( higher score )
* @param int duration ( duration of the attempt in seconds )
* @param int session_id
* @param int learnpath_id (id of the learnpath)
* @param int learnpath_item_id (id of the learnpath_item)
* @param int $exeId
* @param int $exoId
* @param mixed $score
* @param int $weighting
* @param int $sessionId
* @param int $learnpathId
* @param int $learnpathItemId
* @param int $learnpathItemViewId
* @param int $duration
* @param array $questionsList
* @param string $status
* @param array $remindList
* @param null $endDate
*
* @return bool
*
* @author Sebastien Piraux <piraux_seb@hotmail.com>
* @author Julio Montoya Armas <gugli100@gmail.com> Reworked 2010
* @desc Record result of user when an exercise was done
*/
public static function updateEventExercise(
$exeId,
@ -437,15 +442,6 @@ class Event
if (empty($exeId)) {
return false;
}
/*
* Code commented due BT#8423 do not change the score to 0.
*
* Validation in case of fraud with actived control time
if (!ExerciseLib::exercise_time_control_is_valid($exo_id, $learnpath_id, $learnpath_item_id)) {
$score = 0;
}
*/
if (!isset($status) || empty($status)) {
$status = '';
} else {
@ -506,19 +502,20 @@ class Event
/**
* Record an event for this attempt at answering an exercise.
*
* @param float Score achieved
* @param string Answer given
* @param int Question ID
* @param int Exercise attempt ID a.k.a exe_id (from track_e_exercise)
* @param int Position
* @param int Exercise ID (from c_quiz)
* @param bool update results?
* @param $fileName string Filename (for audio answers - using nanogong)
* @param int User ID The user who's going to get this score. Default value of null means "get from context".
* @param int Course ID (from the "id" column of course table). Default value of null means "get from context".
* @param int Session ID (from the session table). Default value of null means "get from context".
* @param int Learnpath ID (from c_lp table). Default value of null means "get from context".
* @param int Learnpath item ID (from the c_lp_item table). Default value of null means "get from context".
* @param float $score Score achieved
* @param string $answer Answer given
* @param int $question_id
* @param int $exe_id Exercise attempt ID a.k.a exe_id (from track_e_exercise)
* @param int $position
* @param int $exercise_id From c_quiz
* @param bool $updateResults
* @param int $duration Time spent in seconds
* @param string $fileName Filename (for audio answers - using nanogong)
* @param int $user_id The user who's going to get this score.
* @param int $course_id Default value of null means "get from context".
* @param int $session_id Default value of null means "get from context".
* @param int $learnpath_id (from c_lp table). Default value of null means "get from context".
* @param int $learnpath_item_id (from the c_lp_item table). Default value of null means "get from context".
*
* @return bool Result of the insert query
*/
@ -530,6 +527,7 @@ class Event
$position,
$exercise_id = 0,
$updateResults = false,
$questionDuration = 0,
$fileName = null,
$user_id = null,
$course_id = null,
@ -538,12 +536,13 @@ class Event
$learnpath_item_id = null
) {
global $debug;
$question_id = Database::escape_string($question_id);
$exe_id = Database::escape_string($exe_id);
$position = Database::escape_string($position);
$now = api_get_utc_datetime();
$questionDuration = (int) $questionDuration;
$question_id = (int) $question_id;
$exe_id = (int) $exe_id;
$position = (int) $position;
$course_id = (int) $course_id;
$recording = api_get_configuration_value('quiz_answer_extra_recording') == true;
$now = api_get_utc_datetime();
$recording = api_get_configuration_value('quiz_answer_extra_recording');
// check user_id or get from context
if (empty($user_id)) {
@ -590,121 +589,119 @@ class Event
$answer = 0;
}
if (!empty($question_id) && !empty($exe_id) && !empty($user_id)) {
if (is_null($answer)) {
$answer = '';
}
if (empty($question_id) || empty($exe_id) || empty($user_id)) {
return false;
}
if (is_null($score)) {
$score = 0;
}
if (is_null($answer)) {
$answer = '';
}
$attempt = [
'user_id' => $user_id,
'question_id' => $question_id,
'answer' => $answer,
'marks' => $score,
'c_id' => $course_id,
'session_id' => $session_id,
'position' => $position,
'tms' => $now,
'filename' => !empty($fileName) ? basename($fileName) : $fileName,
'teacher_comment' => '',
];
if (is_null($score)) {
$score = 0;
}
// Check if attempt exists.
$sql = "SELECT exe_id FROM $TBL_TRACK_ATTEMPT
WHERE
c_id = $course_id AND
session_id = $session_id AND
exe_id = $exe_id AND
user_id = $user_id AND
question_id = $question_id AND
position = $position";
$result = Database::query($sql);
if (Database::num_rows($result)) {
if ($debug) {
error_log("Attempt already exist: exe_id: $exe_id - user_id:$user_id - question_id:$question_id");
}
if ($updateResults == false) {
//The attempt already exist do not update use update_event_exercise() instead
return false;
}
} else {
$attempt['exe_id'] = $exe_id;
}
$attempt = [
'user_id' => $user_id,
'question_id' => $question_id,
'answer' => $answer,
'marks' => $score,
'c_id' => $course_id,
'session_id' => $session_id,
'position' => $position,
'tms' => $now,
'filename' => !empty($fileName) ? basename($fileName) : $fileName,
'teacher_comment' => '',
];
if ($debug) {
error_log("updateResults : $updateResults");
error_log("Saving question attempt: ");
error_log($sql);
if (api_get_configuration_value('allow_time_per_question')) {
$attempt['seconds_spent'] = $questionDuration;
}
// Check if attempt exists.
$sql = "SELECT exe_id FROM $TBL_TRACK_ATTEMPT
WHERE
c_id = $course_id AND
session_id = $session_id AND
exe_id = $exe_id AND
user_id = $user_id AND
question_id = $question_id AND
position = $position";
$result = Database::query($sql);
$attemptData = [];
if (Database::num_rows($result)) {
$attemptData = Database::fetch_array($result, 'ASSOC');
if ($updateResults == false) {
// The attempt already exist do not update use update_event_exercise() instead
return false;
}
} else {
$attempt['exe_id'] = $exe_id;
}
$recording_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
if ($debug) {
error_log("updateResults : $updateResults");
error_log("Saving question attempt: ");
error_log($sql);
}
if ($updateResults == false) {
$attempt_id = Database::insert($TBL_TRACK_ATTEMPT, $attempt);
$recording_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
if ($updateResults == false) {
$attempt_id = Database::insert($TBL_TRACK_ATTEMPT, $attempt);
if ($recording) {
$attempt_recording = [
'exe_id' => $exe_id,
'question_id' => $question_id,
'answer' => $answer,
'marks' => $score,
'insert_date' => $now,
'session_id' => $session_id,
];
Database::insert($recording_table, $attempt_recording);
}
} else {
if (api_get_configuration_value('allow_time_per_question')) {
$attempt['seconds_spent'] = $questionDuration + (int) $attemptData['seconds_spent'];
}
Database::update(
$TBL_TRACK_ATTEMPT,
$attempt,
[
'exe_id = ? AND question_id = ? AND user_id = ? ' => [
$exe_id,
$question_id,
$user_id,
],
]
);
if ($debug) {
error_log("Insert attempt with id #$attempt_id");
}
if ($recording) {
$attempt_recording = [
'exe_id' => $exe_id,
'question_id' => $question_id,
'answer' => $answer,
'marks' => $score,
'insert_date' => $now,
'session_id' => $session_id,
];
if ($recording) {
if ($debug) {
error_log("Saving e attempt recording ");
}
$attempt_recording = [
'exe_id' => $exe_id,
'question_id' => $question_id,
'answer' => $answer,
'marks' => $score,
'insert_date' => $now,
'session_id' => $session_id,
];
Database::insert($recording_table, $attempt_recording);
}
} else {
Database::update(
$TBL_TRACK_ATTEMPT,
$attempt,
$recording_table,
$attempt_recording,
[
'exe_id = ? AND question_id = ? AND user_id = ? ' => [
'exe_id = ? AND question_id = ? AND session_id = ? ' => [
$exe_id,
$question_id,
$user_id,
$session_id,
],
]
);
if ($recording) {
$attempt_recording = [
'exe_id' => $exe_id,
'question_id' => $question_id,
'answer' => $answer,
'marks' => $score,
'insert_date' => $now,
'session_id' => $session_id,
];
Database::update(
$recording_table,
$attempt_recording,
[
'exe_id = ? AND question_id = ? AND session_id = ? ' => [
$exe_id,
$question_id,
$session_id,
],
]
);
}
$attempt_id = $exe_id;
}
return $attempt_id;
} else {
return false;
$attempt_id = $exe_id;
}
return $attempt_id;
}
/**
@ -1197,7 +1194,7 @@ class Event
$lpInteraction = Database::get_course_table(TABLE_LP_IV_INTERACTION);
$lpObjective = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
if (empty($course)) {
if (empty($course) || empty($user_id)) {
return false;
}
@ -1732,13 +1729,13 @@ class Event
$courseId,
$session_id = 0
) {
$table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$courseId = (int) $courseId;
$exercise_id = (int) $exercise_id;
$session_id = (int) $session_id;
$user_id = (int) $user_id;
$sql = "SELECT * FROM $table_track_exercises
$sql = "SELECT * FROM $table
WHERE
status = '' AND
c_id = $courseId AND
@ -1935,6 +1932,26 @@ class Event
return $list;
}
public static function getQuestionAttemptByExeIdAndQuestion($exeId, $questionId)
{
$table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
$exeId = (int) $exeId;
$questionId = (int) $questionId;
$sql = "SELECT * FROM $table
WHERE
exe_id = $exeId AND
question_id = $questionId
ORDER BY position";
$result = Database::query($sql);
$attempt = [];
if (Database::num_rows($result)) {
$attempt = Database::fetch_array($result, 'ASSOC');
}
return $attempt;
}
/**
* Delete one record from the track_e_attempt table (recorded quiz answer)
* and register the deletion event (LOG_QUESTION_RESULT_DELETE) in
@ -2566,4 +2583,30 @@ class Event
return true;
}
public static function getAttemptQuestionDuration($exeId, $questionId)
{
// Check current attempt.
$questionAttempt = self::getQuestionAttemptByExeIdAndQuestion($exeId, $questionId);
$alreadySpent = 0;
if (!empty($questionAttempt) && $questionAttempt['seconds_spent']) {
$alreadySpent = $questionAttempt['seconds_spent'];
}
$now = time();
$questionStart = Session::read('question_start', []);
if (!empty($questionStart) &&
isset($questionStart[$questionId]) && !empty($questionStart[$questionId])
) {
$time = $questionStart[$questionId];
} else {
$diff = 0;
if (!empty($alreadySpent)) {
$diff = $alreadySpent;
}
$time = $questionStart[$questionId] = $now - $diff;
Session::write('question_start', $questionStart);
}
return $now - $time;
}
}

@ -1727,7 +1727,9 @@ $_configuration['auth_password_links'] = [
// Resource sequence: Validate course in the same session.
//$_configuration['course_sequence_valid_only_in_same_session'] = false;
// Allow time per question. Requires a question text extra field called "time", value in seconds.
// Allow time per question. BT#17791
// Requires a question text extra field called "time", value in seconds.
// ALTER TABLE track_e_attempt ADD COLUMN seconds_spent INT;
//$_configuration['allow_time_per_question'] = true;
// Disable change user visibility tool icon.

Loading…
Cancel
Save