Fix wrong duration time when doing an exercise inside a LP see BT#14149

Changes come from ofaj_dev branch:

- Fix firefox issue when loading events during "unload"
- Add ajax call to save exercise duration when page unload
-
pull/2483/head
jmontoyaa 8 years ago
parent 0b2c6030cd
commit 04d30c87d9
  1. 2
      main/exercise/exercise.php
  2. 4
      main/exercise/exercise_result.php
  3. 87
      main/exercise/exercise_submit.php
  4. 1
      main/exercise/fill_blanks.class.php
  5. 2
      main/exercise/overview.php
  6. 140
      main/inc/ajax/exercise.ajax.php
  7. 5
      main/inc/lib/internationalization.lib.php
  8. 5
      main/inc/lib/tracking.lib.php
  9. 18
      main/lp/learnpath.class.php
  10. 9
      main/lp/learnpathItem.class.php
  11. 1
      main/lp/lp_edit_item_prereq.php
  12. 6
      main/lp/lp_list.php
  13. 2
      main/lp/lp_view.php
  14. 2
      main/session/index.php

@ -73,6 +73,8 @@ Session::erase('questionList');
Session::erase('exerciseResult');
Session::erase('firstTime');
Session::erase('calculatedAnswerId');
Session::erase('duration_time_previous');
Session::erase('duration_time');
//General POST/GET/SESSION/COOKIES parameters recovery
$origin = api_get_origin();

@ -202,6 +202,8 @@ if ($origin != 'learnpath') {
Session::erase('objExercise');
Session::erase('exe_id');
Session::erase('calculatedAnswerId');
Session::erase('duration_time_previous');
Session::erase('duration_time');
}
Display::display_footer();
} else {
@ -213,6 +215,8 @@ if ($origin != 'learnpath') {
Session::erase('objExercise');
Session::erase('exe_id');
Session::erase('calculatedAnswerId');
Session::erase('duration_time_previous');
Session::erase('duration_time');
}
Session::write('attempt_remaining', $remainingMessage);

@ -186,7 +186,15 @@ $current_expired_time_key = ExerciseLib::get_time_control_key(
$learnpath_item_id
);
$_SESSION['duration_time'][$current_expired_time_key] = $current_timestamp;
Session::write('duration_time_previous', [$current_expired_time_key => $current_timestamp]);
$durationTime = Session::read('duration_time');
if (!empty($durationTime) && isset($durationTime[$current_expired_time_key])) {
Session::write(
'duration_time_previous',
[$current_expired_time_key => $durationTime[$current_expired_time_key]]
);
}
Session::write('duration_time', [$current_expired_time_key => $current_timestamp]);
if ($time_control) {
// Get the expired time of the current exercise in track_e_exercises
@ -412,6 +420,7 @@ if (empty($exercise_stat_info)) {
}
}
$saveDurationUrl = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=update_duration&exe_id='.$exe_id.'&'.api_get_cidreq();
$questionListInSession = Session::read('questionList');
if (!isset($questionListInSession)) {
@ -1032,18 +1041,49 @@ if (!empty($error)) {
$onsubmit = "onsubmit=\"return validateFlashVar('".$number_of_hotspot_questions."', '".get_lang('HotspotValidateError1')."', '".get_lang('HotspotValidateError2')."');\"";
}
$saveIcon = Display::return_icon(
'save.png',
get_lang('Saved'),
[],
ICON_SIZE_SMALL,
false,
true
);
echo '<script>
function addExerciseEvent(elm, evType, fn, useCapture) {
if (elm.addEventListener) {
elm.addEventListener(evType, fn, useCapture);
return true;
} else if (elm.attachEvent) {
elm.attachEvent(\'on\' + evType, fn);
} else{
elm[\'on\'+evType] = fn;
}
}
var calledUpdateDuration = false;
function updateDuration() {
if (calledUpdateDuration === false) {
var saveDurationUrl = "'.$saveDurationUrl.'";
// Logout of course just in case
$.ajax({
async: false,
url: saveDurationUrl,
success: function (data) {
calledUpdateDuration = true;
return 1;
},
});
return 1;
}
}
$(function() {
//This pre-load the save.png icon
// This pre-load the save.png icon
var saveImage = new Image();
saveImage.src = \''.Display::return_icon(
'save.png',
get_lang('Saved'),
[],
ICON_SIZE_SMALL,
false,
true
).'\';
saveImage.src = "'.$saveIcon.'";
// Block form submition on enter
$(".block_on_enter").keypress(function(event) {
@ -1068,14 +1108,11 @@ if (!empty($error)) {
return false;
});*/
$(\'form#exercise_form\').prepend($(\'#exercise-description\'));
});
$(document).on(\'ready\', function () {
$(\'button[name="previous_question_and_save"]\').on(\'click\', function (e) {
$("form#exercise_form").prepend($("#exercise-description"));
$(\'button[name="previous_question_and_save"]\').on("click", function (e) {
e.preventDefault();
e.stopPropagation();
e.stopPropagation();
var
$this = $(this),
previousId = parseInt($this.data(\'prev\')) || 0,
@ -1087,18 +1124,15 @@ if (!empty($error)) {
$(\'button[name="save_question_list"]\').on(\'click\', function (e) {
e.preventDefault();
e.stopPropagation();
var
$this = $(this),
questionList = $this.data(\'list\').split(",");
var $this = $(this);
var questionList = $this.data(\'list\').split(",");
save_question_list(questionList);
});
$(\'button[name="save_now"]\').on(\'click\', function (e) {
e.preventDefault();
e.stopPropagation();
e.stopPropagation();
var
$this = $(this),
questionId = parseInt($this.data(\'question\')) || 0,
@ -1109,10 +1143,13 @@ if (!empty($error)) {
$(\'button[name="validate_all"]\').on(\'click\', function (e) {
e.preventDefault();
e.stopPropagation();
e.stopPropagation();
validate_all();
});
// Save attempt duration
addExerciseEvent(window, \'unload\', updateDuration , false);
addExerciseEvent(window, \'beforeunload\', updateDuration , false);
});
function previous_question(question_num) {

@ -1263,7 +1263,6 @@ class FillBlanks extends Question
$correctAnswerHtml = '';
$type = self::getFillTheBlankAnswerType($correct);
switch ($type) {
case self::FILL_THE_BLANK_MENU:
$listPossibleAnswers = self::getFillTheBlankMenuAnswers($correct, false);

@ -17,6 +17,8 @@ $current_course_tool = TOOL_QUIZ;
// Clear the exercise session just in case
Session::erase('objExercise');
Session::erase('calculatedAnswerId');
Session::erase('duration_time_previous');
Session::erase('duration_time');
$this_section = SECTION_COURSES;

@ -12,15 +12,137 @@ api_protect_course_script(true);
$action = $_REQUEST['a'];
$course_id = api_get_course_int_id();
if ($debug) {
error_log("-----------------");
error_log("$action ajax call");
error_log("-----------------");
}
$session_id = isset($_REQUEST['session_id']) ? intval($_REQUEST['session_id']) : api_get_session_id();
$course_code = isset($_REQUEST['cidReq']) ? $_REQUEST['cidReq'] : api_get_course_id();
switch ($action) {
case 'update_duration':
$exeId = isset($_REQUEST['exe_id']) ? $_REQUEST['exe_id'] : 0;
if (Session::read('login_as')) {
if ($debug) {
error_log("User is 'login as' don't update duration time.");
}
exit;
}
if (empty($exeId)) {
if ($debug) {
error_log("Exe id not provided.");
}
exit;
}
/** @var Exercise $exerciseInSession */
$exerciseInSession = Session::read('objExercise');
if (empty($exerciseInSession)) {
if ($debug) {
error_log("Exercise obj not provided.");
}
exit;
}
// If exercise was updated x seconds before, then don't updated duration.
$onlyUpdateValue = 10;
$em = Database::getManager();
/** @var \Chamilo\CoreBundle\Entity\TrackEExercises $attempt */
$attempt = $em->getRepository('ChamiloCoreBundle:TrackEExercises')->find($exeId);
if (empty($attempt)) {
if ($debug) {
error_log("Attempt #$exeId doesn't exists.");
}
exit;
}
$nowObject = api_get_utc_datetime(null, false, true);
$now = $nowObject->getTimestamp();
$exerciseId = $attempt->getExeExoId();
$userId = $attempt->getExeUserId();
$currentUserId = api_get_user_id();
if ($userId != $currentUserId) {
if ($debug) {
error_log("User $currentUserId trying to change time for user $userId");
}
exit;
}
if ($exerciseInSession->id != $exerciseId) {
if ($debug) {
error_log("Cannot update, exercise are different.");
}
exit;
}
if ($attempt->getStatus() != 'incomplete') {
if ($debug) {
error_log("Cannot update exercise is already completed.");
}
exit;
}
// Check if we are dealing with the same exercise.
$timeWithOutUpdate = $now - $attempt->getExeDate()->getTimestamp();
if ($timeWithOutUpdate > $onlyUpdateValue) {
$key = ExerciseLib::get_time_control_key(
$exerciseId,
$attempt->getOrigLpId(),
$attempt->getOrigLpItemId()
);
$durationFromObject = $attempt->getExeDuration();
$previousTime = Session::read('duration_time_previous');
if (isset($previousTime[$key]) &&
!empty($previousTime[$key])
) {
$sessionTime = $previousTime[$key];
$duration = $sessionTime = $now - $sessionTime;
/*if ($debug) {
error_log("Now in UTC: ".$nowObject->format('Y-m-d H:i:s'));
error_log("Session time in UTC: ".api_get_utc_datetime($sessionTime));
}*/
if (!empty($durationFromObject)) {
$duration += $durationFromObject;
}
$duration = (int) $duration;
if (!empty($duration)) {
if ($debug) {
error_log("Exe_id: #".$exeId);
error_log("Key: $key");
error_log("Exercise to update: #$exerciseId of user: #$userId");
error_log("Duration time found in DB before update: $durationFromObject");
error_log("Current spent time $sessionTime before an update");
error_log("Accumulate duration to save in DB: $duration");
error_log("End date (UTC) before update: ".$attempt->getExeDate()->format('Y-m-d H:i:s'));
error_log("End date (UTC) to be save in DB: ".$nowObject->format('Y-m-d H:i:s'));
}
$attempt
->setExeDuration($duration)
->setExeDate($nowObject);
$em->merge($attempt);
$em->flush();
}
} else {
if ($debug) {
error_log("Nothing to update, 'duration_time_previous' session not set");
error_log("Key: $key");
}
}
} else {
if ($debug) {
error_log("Can't update, time was already updated $timeWithOutUpdate seconds ago");
}
}
break;
case 'get_live_stats':
if (!api_is_allowed_to_edit(null, true)) {
break;
@ -482,19 +604,27 @@ switch ($action) {
$exercise_stat_info['orig_lp_item_id']
);
if (isset($_SESSION['duration_time'][$key]) && !empty($_SESSION['duration_time'][$key])) {
$duration = $now - $_SESSION['duration_time'][$key];
$durationTime = Session::read('duration_time');
if (isset($durationTime[$key]) && !empty($durationTime[$key])) {
if ($debug) {
error_log('Session time :'.$durationTime[$key]);
}
$duration = $now - $durationTime[$key];
if (!empty($exercise_stat_info['exe_duration'])) {
$duration += $exercise_stat_info['exe_duration'];
}
$duration = intval($duration);
$duration = (int) $duration;
} else {
if (!empty($exercise_stat_info['exe_duration'])) {
$duration = $exercise_stat_info['exe_duration'];
}
}
$_SESSION['duration_time'][$key] = time();
if ($debug) {
error_log('duration to save in DB:'.$duration);
}
Session::write('duration_time', [$key => $now]);
Event::updateEventExercise(
$exeId,

@ -413,8 +413,9 @@ function api_get_timezone()
* @param bool $returnNullIfInvalidDate if the date is not correct return null instead of the current date
* @param bool $returnObj
*
* @return string The DATETIME in UTC to be inserted in the DB,
* @return string|DateTime The DATETIME in UTC to be inserted in the DB,
* or null if the format of the argument is not supported
* or datetime
*
* @author Julio Montoya - Adding the 2nd parameter
* @author Guillaume Viguier <guillaume.viguier@beeznest.com>
@ -429,7 +430,7 @@ function api_get_utc_datetime(
return null;
}
if ($returnObj) {
return $date = new DateTime(gmdate('Y-m-d H:i:s'), new DateTimeZone('UTC'));
return new DateTime(gmdate('Y-m-d H:i:s'), new DateTimeZone('UTC'));
}
return gmdate('Y-m-d H:i:s');

@ -1025,9 +1025,6 @@ class Tracking
$my_score = $row_attempts['exe_result'];
$my_maxscore = $row_attempts['exe_weighting'];
$my_exe_id = $row_attempts['exe_id'];
$my_orig_lp = $row_attempts['orig_lp_id'];
$my_orig_lp_item = $row_attempts['orig_lp_item_id'];
$my_exo_exe_id = $row_attempts['exe_exo_id'];
$mktime_start_date = api_strtotime($row_attempts['start_date'], 'UTC');
$mktime_exe_date = api_strtotime($row_attempts['exe_date'], 'UTC');
$time_attemp = ' - ';
@ -3026,6 +3023,7 @@ class Tracking
if ($debug) {
echo '<h3>Final return</h3>';
}
if ($lp_with_quiz != 0) {
if (!$return_array) {
$score_of_scorm_calculate = round(($global_result / $lp_with_quiz), 2);
@ -3917,7 +3915,6 @@ class Tracking
ip.tool='work' AND
$conditionToString";
$rs = Database::query($sql);
$row = Database::fetch_array($rs, 'ASSOC');
return $row['count'];

@ -1187,9 +1187,9 @@ class learnpath
/**
* Removes an item from the current learnpath.
*
* @param int $id Elem ID (0 if first)
* @param string $remove Whether to remove the resource/data from the system
* or leave it (default: 'keep', others 'remove')
* @param int $id Elem ID (0 if first)
* @param int $remove Whether to remove the resource/data from the
* system or leave it (default: 'keep', others 'remove')
*
* @return int Number of elements moved
*
@ -2568,7 +2568,8 @@ class learnpath
}
/**
* @param string $mode can be '%' or 'abs' otherwise this value will be used $this->progress_bar_mode
* @param string $mode can be '%' or 'abs'
* otherwise this value will be used $this->progress_bar_mode
*
* @return string
*/
@ -2583,8 +2584,8 @@ class learnpath
* Gets the progress bar info to display inside the progress bar.
* Also used by scorm_api.php.
*
* @param string $mode Mode of display (can be '%' or 'abs').abs means we display a number of completed elements
* per total elements
* @param string $mode Mode of display (can be '%' or 'abs').abs means
* we display a number of completed elements per total elements
* @param int $add Additional steps to fake as completed
*
* @return array Percentage or number and symbol (% or /xx)
@ -4401,7 +4402,9 @@ class learnpath
}
/**
* Publishes a learnpath. This basically means show or hide the learnpath to normal users. Can be used as abstract.
* Publishes a learnpath. This basically means show or hide the learnpath
* to normal users.
* Can be used as abstract.
*
* @param int $lp_id Learnpath ID
* @param int $set_visibility New visibility
@ -8550,6 +8553,7 @@ class learnpath
);
$relative_prefix = '';
$editor_config = [
'ToolbarSet' => 'LearningPathDocuments',
'Width' => '100%',

@ -3403,6 +3403,10 @@ class learnpathItem
if ($debug) {
error_log('found asset - set time to '.$myTime);
}
} else {
if ($debug) {
error_log('Time not set');
}
}
} else {
switch ($format) {
@ -3421,11 +3425,16 @@ class learnpathItem
$totalSec = $hour * 3600 + $min * 60 + $sec;
if ($debug) {
error_log("totalSec : $totalSec");
error_log("Now calling to scorm_update_time()");
}
$this->scorm_update_time($totalSec);
}
break;
case 'int':
if ($debug) {
error_log("scorm_time = $scorm_time");
error_log("Now calling to scorm_update_time()");
}
$this->scorm_update_time($scorm_time);
break;
}

@ -19,6 +19,7 @@ api_protect_course_script();
/* Constants and variables */
$is_allowed_to_edit = api_is_allowed_to_edit(null, true);
$isStudentView = isset($_REQUEST['isStudentView']) ? (int) $_REQUEST['isStudentView'] : null;
$learnpath_id = isset($_REQUEST['lp_id']) ? (int) $_REQUEST['lp_id'] : null;
$submit = isset($_POST['submit_button']) ? $_POST['submit_button'] : null;

@ -889,6 +889,12 @@ foreach ($categories as $item) {
// Deleting the objects
Session::erase('oLP');
Session::erase('lpobject');
Session::erase('scorm_view_id');
Session::erase('scorm_item_id');
Session::erase('exerciseResult');
Session::erase('objExercise');
Session::erase('questionList');
learnpath::generate_learning_path_folder($courseInfo);
DocumentManager::removeGeneratedAudioTempFile();

@ -179,6 +179,8 @@ if (isset($exerciseResult) || isset($_SESSION['exerciseResult'])) {
Session::erase('exerciseResult');
Session::erase('objExercise');
Session::erase('questionList');
Session::erase('duration_time_previous');
Session::erase('duration_time');
}
// additional APIs

@ -37,6 +37,8 @@ Session::write('id_session', $session_id);
// Clear the exercise session just in case
Session::erase('objExercise');
Session::erase('duration_time_previous');
Session::erase('duration_time');
$userId = api_get_user_id();
$session_info = SessionManager::fetch($session_id);

Loading…
Cancel
Save