Skip to content

Commit 6695e47

Browse files
authored
Merge pull request #6787 from christianbeeznest/storm-22933
Exercise: Fix expired attempt no longer stuck as incomplete - refs BT#22933
2 parents 6006335 + b343265 commit 6695e47

File tree

6 files changed

+234
-132
lines changed

6 files changed

+234
-132
lines changed

public/main/exercise/exercise.class.php

Lines changed: 140 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -3374,54 +3374,75 @@ public function showSimpleTimeControl($timeLeft, $url = '')
33743374
$timeLeft = (int) $timeLeft;
33753375

33763376
return "<script>
3377-
function openClockWarning() {
3378-
$('#clock_warning').dialog({
3379-
modal:true,
3380-
height:320,
3381-
width:550,
3382-
closeOnEscape: false,
3383-
resizable: false,
3384-
buttons: {
3385-
'".addslashes(get_lang('Close'))."': function() {
3386-
$('#clock_warning').dialog('close');
3387-
}
3388-
},
3389-
close: function() {
3390-
window.location.href = '$url';
3391-
}
3392-
});
3393-
$('#clock_warning').dialog('open');
3394-
$('#counter_to_redirect').epiclock({
3395-
mode: $.epiclock.modes.countdown,
3396-
offset: {seconds: 5},
3397-
format: 's'
3398-
}).bind('timer', function () {
3399-
window.location.href = '$url';
3400-
});
3401-
}
3377+
(function ensureModalStyles() {
3378+
if (document.getElementById('ch-modal-styles')) return;
3379+
var css = `
3380+
#clock_warning { display: none; }
3381+
.ch-modal { position: fixed; inset: 0; z-index: 9999; display: grid; place-items: center; padding: 16px; }
3382+
.ch-modal::before { content: ''; position: absolute; inset: 0; background: rgba(0,0,0,.3); }
3383+
.ch-panel { position: relative; width: 100%; max-width: 520px; background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; box-shadow: 0 10px 25px rgba(0,0,0,.08); padding: 16px; color:#111827; font-family: system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif; }
3384+
.ch-close { position:absolute; right: 10px; top:10px; cursor:pointer; color:#6b7280; }
3385+
`;
3386+
var s = document.createElement('style');
3387+
s.id = 'ch-modal-styles';
3388+
s.textContent = css;
3389+
document.head.appendChild(s);
3390+
})();
3391+
3392+
function wrapClockWarning() {
3393+
var \$cw = \$('#clock_warning');
3394+
if (!\$cw.length) return \$cw;
3395+
// turn container into modal
3396+
\$cw.addClass('ch-modal');
3397+
// ensure inner panel
3398+
if (!\$cw.find('.ch-panel').length) {
3399+
\$cw.html('<div class=\"ch-panel\"><span class=\"mdi mdi-close ch-close\" title=\"Close\" aria-label=\"Close\"></span>' + \$cw.html() + '</div>');
3400+
}
3401+
// close button
3402+
\$cw.on('click', '.ch-close', function(){ closeClockWarning(); });
3403+
return \$cw;
3404+
}
3405+
3406+
function openClockWarning() {
3407+
var \$cw = wrapClockWarning();
3408+
if (!\$cw || !\$cw.length) return;
3409+
\$cw.show();
3410+
3411+
// countdown 5s -> redirect
3412+
\$('#counter_to_redirect').epiclock({
3413+
mode: \$.epiclock.modes.countdown,
3414+
offset: {seconds: 5},
3415+
format: 's'
3416+
}).bind('timer', function () {
3417+
window.location.href = '$url';
3418+
});
3419+
}
34023420
3403-
function onExpiredTimeExercise() {
3404-
$('#wrapper-clock').hide();
3405-
$('#expired-message-id').show();
3406-
// Fixes bug #5263
3407-
$('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3408-
openClockWarning();
3409-
}
3410-
3411-
$(function() {
3412-
// time in seconds when using minutes there are some seconds lost
3413-
var time_left = parseInt(".$timeLeft.");
3414-
$('#exercise_clock_warning').epiclock({
3415-
mode: $.epiclock.modes.countdown,
3416-
offset: {seconds: time_left},
3417-
format: 'x:i:s',
3418-
renderer: 'minute'
3419-
}).bind('timer', function () {
3420-
onExpiredTimeExercise();
3421-
});
3422-
$('#submit_save').click(function () {});
3423-
});
3424-
</script>";
3421+
function closeClockWarning() {
3422+
\$('#clock_warning').hide();
3423+
window.location.href = '$url';
3424+
}
3425+
3426+
function onExpiredTimeExercise() {
3427+
\$('#wrapper-clock').hide();
3428+
\$('#expired-message-id').show();
3429+
\$('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3430+
openClockWarning();
3431+
}
3432+
3433+
\$(function() {
3434+
var time_left = parseInt(".$timeLeft.");
3435+
\$('#exercise_clock_warning').epiclock({
3436+
mode: \$.epiclock.modes.countdown,
3437+
offset: {seconds: time_left},
3438+
format: 'x:i:s',
3439+
renderer: 'minute'
3440+
}).bind('timer', function () {
3441+
onExpiredTimeExercise();
3442+
});
3443+
\$('#submit_save').click(function () {});
3444+
});
3445+
</script>";
34253446
}
34263447

34273448
/**
@@ -3439,8 +3460,7 @@ public function showTimeControlJS($timeLeft, $redirectToUrl = '')
34393460
if (ALL_ON_ONE_PAGE == $this->type) {
34403461
$script = "save_now_all('validate');";
34413462
} elseif (ONE_PER_PAGE == $this->type) {
3442-
$script = 'window.quizTimeEnding = true;
3443-
$(\'[name="save_now"]\').trigger(\'click\');';
3463+
$script = 'window.quizTimeEnding = true; $(\'[name=\"save_now\"]\').trigger(\'click\');';
34443464
}
34453465

34463466
$exerciseSubmitRedirect = '';
@@ -3449,65 +3469,81 @@ public function showTimeControlJS($timeLeft, $redirectToUrl = '')
34493469
}
34503470

34513471
return "<script>
3452-
function openClockWarning() {
3453-
$('#clock_warning').dialog({
3454-
modal:true,
3455-
height:320,
3456-
width:550,
3457-
closeOnEscape: false,
3458-
resizable: false,
3459-
buttons: {
3460-
'".addslashes(get_lang('End test'))."': function() {
3461-
$('#clock_warning').dialog('close');
3462-
}
3463-
},
3464-
close: function() {
3465-
send_form();
3466-
}
3467-
});
3472+
(function ensureModalStyles() {
3473+
if (document.getElementById('ch-modal-styles')) return;
3474+
var css = `
3475+
#clock_warning { display: none; }
3476+
.ch-modal { position: fixed; inset: 0; z-index: 9999; display: grid; place-items: center; padding: 16px; }
3477+
.ch-modal::before { content: ''; position: absolute; inset: 0; background: rgba(0,0,0,.3); }
3478+
.ch-panel { position: relative; width: 100%; max-width: 520px; background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; box-shadow: 0 10px 25px rgba(0,0,0,.08); padding: 16px; color:#111827; font-family: system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif; }
3479+
.ch-close { position:absolute; right: 10px; top:10px; cursor:pointer; color:#6b7280; }
3480+
`;
3481+
var s = document.createElement('style');
3482+
s.id = 'ch-modal-styles';
3483+
s.textContent = css;
3484+
document.head.appendChild(s);
3485+
})();
3486+
3487+
function wrapClockWarning() {
3488+
var \$cw = \$('#clock_warning');
3489+
if (!\$cw.length) return \$cw;
3490+
\$cw.addClass('ch-modal');
3491+
if (!\$cw.find('.ch-panel').length) {
3492+
\$cw.html('<div class=\"ch-panel\"><span class=\"mdi mdi-close ch-close\" title=\"Close\" aria-label=\"Close\"></span>' + \$cw.html() + '</div>');
3493+
}
3494+
\$cw.on('click', '.ch-close', function(){ closeClockWarning(); });
3495+
return \$cw;
3496+
}
3497+
3498+
function openClockWarning() {
3499+
var \$cw = wrapClockWarning();
3500+
if (!\$cw || !\$cw.length) return;
3501+
\$cw.show();
3502+
3503+
\$('#counter_to_redirect').epiclock({
3504+
mode: \$.epiclock.modes.countdown,
3505+
offset: {seconds: 5},
3506+
format: 's'
3507+
}).bind('timer', function () {
3508+
send_form();
3509+
});
3510+
}
34683511
3469-
$('#clock_warning').dialog('open');
3470-
$('#counter_to_redirect').epiclock({
3471-
mode: $.epiclock.modes.countdown,
3472-
offset: {seconds: 5},
3473-
format: 's'
3474-
}).bind('timer', function () {
3475-
send_form();
3476-
});
3477-
}
3512+
function closeClockWarning() {
3513+
\$('#clock_warning').hide();
3514+
send_form();
3515+
}
34783516
3479-
function send_form() {
3480-
if ($('#exercise_form').length) {
3481-
$script
3482-
} else {
3483-
$exerciseSubmitRedirect
3484-
// In exercise_reminder.php
3485-
final_submit();
3486-
}
3517+
function send_form() {
3518+
if (\$('#exercise_form').length) {
3519+
$script
3520+
} else {
3521+
$exerciseSubmitRedirect
3522+
// In exercise_reminder.php
3523+
final_submit();
34873524
}
3525+
}
34883526
3489-
function onExpiredTimeExercise() {
3490-
$('#wrapper-clock').hide();
3491-
$('#expired-message-id').show();
3492-
// Fixes bug #5263
3493-
$('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3494-
openClockWarning();
3495-
}
3496-
3497-
$(function() {
3498-
// time in seconds when using minutes there are some seconds lost
3499-
var time_left = parseInt(".$timeLeft.");
3500-
$('#exercise_clock_warning').epiclock({
3501-
mode: $.epiclock.modes.countdown,
3502-
offset: {seconds: time_left},
3503-
format: 'x:C:s',
3504-
renderer: 'minute'
3505-
}).bind('timer', function () {
3506-
onExpiredTimeExercise();
3507-
});
3508-
$('#submit_save').click(function () {});
3509-
});
3510-
</script>";
3527+
function onExpiredTimeExercise() {
3528+
\$('#wrapper-clock').hide();
3529+
\$('#expired-message-id').show();
3530+
\$('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3531+
openClockWarning();
3532+
}
3533+
3534+
\$(function() {
3535+
var time_left = parseInt(".$timeLeft.");
3536+
\$('#exercise_clock_warning').epiclock({
3537+
mode: \$.epiclock.modes.countdown,
3538+
offset: {seconds: time_left},
3539+
format: 'x:C:s',
3540+
renderer: 'minute'
3541+
}).bind('timer', function () {
3542+
onExpiredTimeExercise();
3543+
});
3544+
\$('#submit_save').click(function () {});
3545+
});
3546+
</script>";
35113547
}
35123548

35133549
/**

public/main/exercise/exercise_submit.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,23 @@
628628
if ($time_control) {
629629
//Sends the exercise form when the expired time is finished.
630630
$htmlHeadXtra[] = $objExercise->showTimeControlJS($time_left);
631+
if (isset($exe_id) && $time_left <= 0) {
632+
Database::query("
633+
UPDATE " . Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES) . "
634+
SET status = 'completed',
635+
exe_date = FROM_UNIXTIME(LEAST(UNIX_TIMESTAMP('" . Database::escape_string($clock_expired_time) . "'), UNIX_TIMESTAMP()))
636+
WHERE exe_id = " . (int) $exe_id . "
637+
AND status = 'incomplete'
638+
");
639+
640+
$resultUrl = 'exercise_result.php?' . api_get_cidreq()
641+
. "&exe_id=$exe_id"
642+
. "&learnpath_id=$learnpath_id"
643+
. "&learnpath_item_id=$learnpath_item_id"
644+
. "&learnpath_item_view_id=$learnpath_item_view_id";
645+
header('Location: ' . $resultUrl);
646+
exit;
647+
}
631648
}
632649

633650
//in LP's is enabled the "remember question" feature?

public/main/exercise/overview.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
$htmlHeadXtra[] = api_get_build_js('legacy_exercise.js');
7070
if ($time_control) {
7171
// Get time left for expiring time
72-
$time_left = api_strtotime($clock_expired_time->format('Y-m-d H:i:s', 'UTC') - time());
72+
$time_left = api_strtotime($clock_expired_time->format('Y-m-d H:i:s'), 'UTC') - time();
7373
/*$htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_PATH).'javascript/epiclock/stylesheet/jquery.epiclock.css');
7474
$htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_PATH).'javascript/epiclock/renderers/minute/epiclock.minute.css');
7575
$htmlHeadXtra[] = api_get_js('epiclock/javascript/jquery.dateformat.min.js');
@@ -144,6 +144,17 @@
144144
0
145145
);
146146

147+
if ($time_control && !empty($exercise_stat_info['exe_id']) && !empty($clock_expired_time)) {
148+
$time_left_check = api_strtotime($clock_expired_time->format('Y-m-d H:i:s'), 'UTC') - time();
149+
if ($time_left_check <= 0) {
150+
$result_url = api_get_path(WEB_CODE_PATH).'exercise/result.php?'
151+
. api_get_cidreq()
152+
. '&show_headers=1&'
153+
. http_build_query(['id' => $exercise_stat_info['exe_id']]);
154+
api_location($result_url);
155+
}
156+
}
157+
147158
//1. Check if this is a new attempt or a previous
148159
$label = get_lang('Start test');
149160
if ($time_control && !empty($clock_expired_time) || isset($exercise_stat_info['exe_id'])) {
@@ -434,7 +445,7 @@
434445
}
435446

436447
if ($time_control) {
437-
$html .= $objExercise->returnTimeLeftDiv();
448+
// $html .= $objExercise->returnTimeLeftDiv();
438449
}
439450

440451
$html .= $message;

public/main/exercise/result.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,23 @@
6868
}
6969
}
7070

71+
if (!empty($objExercise->expired_time)) {
72+
$status = $track_exercise_info['status'] ?? '';
73+
$expiredAt = $track_exercise_info['expired_time_control'] ?? null;
74+
if ($status === 'incomplete' && !empty($expiredAt)) {
75+
$timeLeft = api_strtotime($expiredAt, 'UTC') - time();
76+
if ($timeLeft <= 0) {
77+
Database::query("
78+
UPDATE " . Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES) . "
79+
SET status = 'completed',
80+
exe_date = FROM_UNIXTIME(LEAST(UNIX_TIMESTAMP('" . Database::escape_string($expiredAt) . "'), UNIX_TIMESTAMP()))
81+
WHERE exe_id = " . $id . " AND status = 'incomplete'
82+
");
83+
$track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($id);
84+
}
85+
}
86+
}
87+
7188
$allowSignature = false;
7289
if ($student_id === $current_user_id && ExerciseSignaturePlugin::exerciseHasSignatureActivated($objExercise)) {
7390
// Check if signature exists.

public/main/inc/lib/events.lib.php

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -825,17 +825,19 @@ public static function addEvent(
825825
*/
826826
public static function getLastAttemptDateOfExercise(int $exeId): string
827827
{
828-
$track_attempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
829-
$sql = "SELECT max(tms) as last_attempt_date
830-
FROM $track_attempts
831-
WHERE exe_id = $exeId";
832-
$rs_last_attempt = Database::query($sql);
833-
if (0 == Database::num_rows($rs_last_attempt)) {
828+
$table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
829+
830+
$sql = "SELECT MAX(tms) AS last_attempt_date FROM $table WHERE exe_id = $exeId";
831+
$res = Database::query($sql);
832+
833+
if (!$res) {
834834
return '';
835835
}
836-
$row_last_attempt = Database::fetch_array($rs_last_attempt);
837836

838-
return $row_last_attempt['last_attempt_date']; //Get the date of last attempt
837+
$row = Database::fetch_array($res) ?: [];
838+
$val = $row['last_attempt_date'] ?? null;
839+
840+
return $val !== null ? (string) $val : '';
839841
}
840842

841843
/**

0 commit comments

Comments
 (0)