Merge branch '1.11.x' of https://github.com/chamilo/chamilo-lms into 1.11.x

pull/3619/head
Alex Aragon 5 years ago
commit 3c1b201b8c
  1. 226
      main/exercise/exercise.class.php
  2. 3
      main/exercise/exercise_history.php
  3. 15
      main/exercise/exercise_question_reminder.php
  4. 118
      main/exercise/exercise_reminder.php
  5. 9
      main/exercise/exercise_result.php
  6. 8
      main/exercise/exercise_show.php
  7. 1117
      main/exercise/exercise_submit.php
  8. 22
      main/exercise/live_stats.php
  9. 2
      main/exercise/question.class.php
  10. 14
      main/exercise/result.php
  11. 4
      main/group/group_category.php
  12. 82
      main/inc/ajax/exercise.ajax.php
  13. 4
      main/inc/ajax/model.ajax.php
  14. 4
      main/inc/lib/api.lib.php
  15. 314
      main/inc/lib/events.lib.php
  16. 4
      main/install/configuration.dist.php
  17. 6
      main/template/default/exercise/partials/result_exercise.tpl
  18. 7
      main/template/default/exercise/result.tpl
  19. 17
      plugin/exercise_signature/lib/ExerciseSignature.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
);
}
}
@ -6348,19 +6403,14 @@ class Exercise
}
$signature = '';
/*if (api_get_configuration_value('')) {
$extraFieldValue = new ExtraFieldValue('exercise');
$result = $extraFieldValue->get_values_by_handler_and_field_variable($objExercise->iId, 'signature_activated');
if ($result && isset($result['value']) && 1 === (int) $result['value']) {
$allowSignature = true;
}
$signature
}*/
if (ExerciseSignaturePlugin::exerciseHasSignatureActivated($this)) {
$signature = ExerciseSignaturePlugin::getSignature($trackExerciseInfo['exe_user_id'], $trackExerciseInfo);
}
$tpl = new Template(null, false, false, false, false, false, false);
$tpl->assign('data', $data);
$tpl->assign('allow_signature', $allowSignature);
//$tpl->assign('signature', $signature);
$tpl->assign('signature', $signature);
$tpl->assign('allow_export_pdf', $allowExportPdf);
$tpl->assign('export_url', api_get_path(WEB_CODE_PATH).'exercise/result.php?action=export&id='.$exeId.'&'.api_get_cidreq());
@ -9952,6 +10002,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');
@ -10078,7 +10129,6 @@ class Exercise
public function getUserAnswersSavedInExercise($attemptId)
{
$exerciseResult = [];
$attemptList = Event::getAllExerciseEventByExeId($attemptId);
foreach ($attemptList as $questionId => $options) {
@ -10184,6 +10234,128 @@ class Exercise
return $lpList;
}
public function getReminderTable($questionList, $exercise_stat_info)
{
$learnpath_id = isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : 0;
$learnpath_item_id = isset($_REQUEST['learnpath_item_id']) ? (int) $_REQUEST['learnpath_item_id'] : 0;
$learnpath_item_view_id = isset($_REQUEST['learnpath_item_view_id']) ? (int) $_REQUEST['learnpath_item_view_id'] : 0;
$remind_list = $exercise_stat_info['questions_to_check'];
$remind_list = explode(',', $remind_list);
$exeId = $exercise_stat_info['exe_id'];
$exerciseId = $exercise_stat_info['exe_exo_id'];
$exercise_result = $this->getUserAnswersSavedInExercise($exeId);
$content = Display::label(get_lang('QuestionWithNoAnswer'), 'danger');
$content .= '<div class="clear"></div><br />';
$table = '';
$counter = 0;
// Loop over all question to show results for each of them, one by one
foreach ($questionList as $questionId) {
// destruction of the Question object
unset($objQuestionTmp);
// creates a temporary Question object
$objQuestionTmp = Question:: read($questionId);
$check_id = 'remind_list['.$questionId.']';
$attributes = ['id' => $check_id, 'onclick' => "save_remind_item(this, '$questionId');"];
if (in_array($questionId, $remind_list)) {
$attributes['checked'] = 1;
}
$checkbox = Display::input('checkbox', 'remind_list['.$questionId.']', '', $attributes);
$checkbox = '<div class="pretty p-svg p-curve">
'.$checkbox.'
<div class="state p-primary ">
<svg class="svg svg-icon" viewBox="0 0 20 20">
<path d="M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z" style="stroke: white;fill:white;"></path>
</svg>
<label>&nbsp;</label>
</div>
</div>';
$counter++;
$questionTitle = $counter.'. '.strip_tags($objQuestionTmp->selectTitle());
// Check if the question doesn't have an answer
if (!in_array($questionId, $exercise_result)) {
$questionTitle = Display::label($questionTitle, 'danger');
}
$label_attributes = [];
$label_attributes['for'] = $check_id;
$questionTitle = Display::tag('label', $checkbox.$questionTitle, $label_attributes);
$table .= Display::div($questionTitle, ['class' => 'exercise_reminder_item ']);
}
$content .= Display::div('', ['id' => 'message']).
Display::div($table, ['class' => 'question-check-test']);
$content .= '<script>
var lp_data = $.param({
"learnpath_id": '.$learnpath_id.',
"learnpath_item_id" : '.$learnpath_item_id.',
"learnpath_item_view_id": '.$learnpath_item_view_id.'
});
function final_submit() {
// Normal inputs
window.location = "'.api_get_path(WEB_CODE_PATH).'exercise/exercise_result.php?'.api_get_cidreq().'&exe_id='.$exeId.'&" + lp_data;
}
function changeOptionStatus(status)
{
$("input[type=checkbox]").each(function () {
$(this).prop("checked", status);
});
var action = "";
var extraOption = "remove_all";
if (status == 1) {
extraOption = "add_all";
}
$.ajax({
url: "'.api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=add_question_to_reminder",
data: "option="+extraOption+"&exe_id='.$exeId.'&action="+action,
success: function(returnValue) {
}
});
}
function review_questions() {
var isChecked = 1;
$("input[type=checkbox]").each(function () {
if ($(this).prop("checked")) {
isChecked = 2;
return false;
}
});
if (isChecked == 1) {
$("#message").addClass("warning-message");
$("#message").html("'.addslashes(get_lang('SelectAQuestionToReview')).'");
} else {
window.location = "exercise_submit.php?'.api_get_cidreq().'&exerciseId='.$exerciseId.'&reminder=2&" + lp_data;
}
}
function save_remind_item(obj, question_id) {
var action = "";
if ($(obj).prop("checked")) {
action = "add";
} else {
action = "delete";
}
$.ajax({
url: "'.api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=add_question_to_reminder",
data: "question_id="+question_id+"&exe_id='.$exeId.'&action="+action,
success: function(returnValue) {
}
});
}
</script>';
return $content;
}
/**
* Get number of questions in exercise by user attempt.
*

@ -1,4 +1,5 @@
<?php
/* For licensing terms, see /license.txt */
/**
@ -13,8 +14,6 @@ $this_section = SECTION_COURSES;
api_protect_course_script(true);
$show = isset($_GET['show']) && $_GET['show'] === 'result' ? 'result' : 'test';
/* Constants and variables */
$is_allowedToEdit = api_is_allowed_to_edit(null, true);
$is_tutor = api_is_allowed_to_edit(true);

@ -41,9 +41,8 @@ $categoryId = $categoryObj->id;
$params = "exe_id=$exeId&exerciseId=$exerciseId&learnpath_id=$learnpath_id&learnpath_item_id=$learnpath_item_id&learnpath_item_view_id=$learnpath_item_view_id&".api_get_cidreq();
$url = api_get_path(WEB_CODE_PATH).'exercise/exercise_submit.php?'.$params;
$validateUrl = api_get_self().'?'.$params.'&category_id='.$categoryId.'&validate=1';
$time_control = false;
/*
$time_control = false;
$clock_expired_time = ExerciseLib::get_session_time_control_key(
$objExercise->id,
$learnpath_id,
@ -64,7 +63,7 @@ if ($time_control) {
$htmlHeadXtra[] = api_get_js('epiclock/renderers/minute/epiclock.minute.js');
$htmlHeadXtra[] = $objExercise->showTimeControlJS($time_left);
}
$htmlHeadXtra[] = api_get_css_asset('pretty-checkbox/dist/pretty-checkbox.min.css');*/
$htmlHeadXtra[] = api_get_css_asset('pretty-checkbox/dist/pretty-checkbox.min.css');
$trackInfo = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId);
if (empty($trackInfo)) {
@ -111,12 +110,20 @@ echo Display::page_header($categoryObj->name);
echo '<p>'.Security::remove_XSS($categoryObj->description).'</p>';
echo '<p>'.get_lang('BlockCategoryExplanation').'</p>';
if ($objExercise->review_answers) {
$questionList = [];
$categoryList = Session::read('categoryList');
if (isset($categoryList[$categoryId])) {
$questionList = $categoryList[$categoryId];
}
echo $objExercise->getReminderTable($questionList, $trackInfo);
}
if ($time_control) {
echo $objExercise->returnTimeLeftDiv();
}
echo Display::div('', ['id' => 'message']);
$previousQuestion = $currentQuestion - 1;
echo '<script>
var lp_data = $.param({

@ -66,8 +66,8 @@ if (isset($_GET['exe_id'])) {
}
$exe_id = (int) Session::read('exe_id');
$exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exe_id);
$question_list = [];
if (!empty($exercise_stat_info['data_tracking'])) {
$question_list = explode(',', $exercise_stat_info['data_tracking']);
}
@ -78,7 +78,6 @@ if (empty($exercise_stat_info) || empty($question_list)) {
$nameTools = get_lang('Exercises');
$interbreadcrumb[] = ['url' => 'exercise.php?'.api_get_cidreq(), 'name' => get_lang('Exercises')];
$hideHeaderAndFooter = in_array($origin, ['learnpath', 'embeddable']);
if (!$hideHeaderAndFooter) {
@ -88,8 +87,6 @@ if (!$hideHeaderAndFooter) {
Display::display_reduced_header();
}
/* DISPLAY AND MAIN PROCESS */
// I'm in a preview mode as course admin. Display the action menu.
if (api_is_course_admin() && !$hideHeaderAndFooter) {
echo '<div class="actions">';
@ -104,118 +101,7 @@ echo Display::page_header(get_lang('QuestionsToReview'));
if ($time_control) {
echo $objExercise->returnTimeLeftDiv();
}
echo Display::div('', ['id' => 'message']);
echo '<script>
var lp_data = $.param({"learnpath_id": '.$learnpath_id.', "learnpath_item_id" : '.$learnpath_item_id.', "learnpath_item_view_id": '.$learnpath_item_view_id.'});
function final_submit() {
// Normal inputs
window.location = "'.api_get_path(WEB_CODE_PATH).'exercise/exercise_result.php?'.api_get_cidreq().'&exe_id='.$exe_id.'&" + lp_data;
}
function changeOptionStatus(status)
{
$("input[type=checkbox]").each(function () {
$(this).prop("checked", status);
});
var action = "";
var extraOption = "remove_all";
if (status == 1) {
extraOption = "add_all";
}
$.ajax({
url: "'.api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=add_question_to_reminder",
data: "option="+extraOption+"&exe_id='.$exe_id.'&action="+action,
success: function(returnValue) {
}
});
}
function review_questions() {
var isChecked = 1;
$("input[type=checkbox]").each(function () {
if ($(this).prop("checked")) {
isChecked = 2;
return false;
}
});
if (isChecked == 1) {
$("#message").addClass("warning-message");
$("#message").html("'.addslashes(get_lang('SelectAQuestionToReview')).'");
} else {
window.location = "exercise_submit.php?'.api_get_cidreq().'&exerciseId='.$objExercise->id.'&reminder=2&" + lp_data;
}
}
function save_remind_item(obj, question_id) {
var action = "";
if ($(obj).prop("checked")) {
action = "add";
} else {
action = "delete";
}
$.ajax({
url: "'.api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=add_question_to_reminder",
data: "question_id="+question_id+"&exe_id='.$exe_id.'&action="+action,
success: function(returnValue) {
}
});
}
</script>';
$attempt_list = Event::getAllExerciseEventByExeId($exe_id);
$remind_list = $exercise_stat_info['questions_to_check'];
$remind_list = explode(',', $remind_list);
$exercise_result = $objExercise->getUserAnswersSavedInExercise($exe_id);
echo Display::label(get_lang('QuestionWithNoAnswer'), 'danger');
echo '<div class="clear"></div><br />';
$table = '';
$counter = 0;
// Loop over all question to show results for each of them, one by one
foreach ($question_list as $questionId) {
// destruction of the Question object
unset($objQuestionTmp);
// creates a temporary Question object
$objQuestionTmp = Question:: read($questionId);
$check_id = 'remind_list['.$questionId.']';
$attributes = ['id' => $check_id, 'onclick' => "save_remind_item(this, '$questionId');"];
if (in_array($questionId, $remind_list)) {
$attributes['checked'] = 1;
}
$checkbox = Display::input('checkbox', 'remind_list['.$questionId.']', '', $attributes);
$checkbox = '<div class="pretty p-svg p-curve">
'.$checkbox.'
<div class="state p-primary ">
<svg class="svg svg-icon" viewBox="0 0 20 20">
<path d="M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z" style="stroke: white;fill:white;"></path>
</svg>
<label>&nbsp;</label>
</div>
</div>';
$counter++;
$questionTitle = $counter.'. '.strip_tags($objQuestionTmp->selectTitle());
// Check if the question doesn't have an answer
if (!in_array($questionId, $exercise_result)) {
$questionTitle = Display::label($questionTitle, 'danger');
}
$label_attributes = [];
$label_attributes['for'] = $check_id;
$questionTitle = Display::tag('label', $checkbox.$questionTitle, $label_attributes);
$table .= Display::div($questionTitle, ['class' => 'exercise_reminder_item ']);
} // end foreach() block that loops over all questions
echo Display::div($table, ['class' => 'question-check-test']);
echo $objExercise->getReminderTable($question_list, $exercise_stat_info);
$exerciseActions = Display::url(
get_lang('ReviewQuestions'),

@ -119,14 +119,7 @@ $logInfo = [
];
Event::registerLog($logInfo);
$allowSignature = false;
if ('true' === api_get_plugin_setting('exercise_signature', 'tool_enable')) {
$extraFieldValue = new ExtraFieldValue('exercise');
$result = $extraFieldValue->get_values_by_handler_and_field_variable($objExercise->iId, 'signature_activated');
if ($result && isset($result['value']) && 1 === (int) $result['value']) {
$allowSignature = true;
}
}
$allowSignature = ExerciseSignaturePlugin::exerciseHasSignatureActivated($objExercise);
if ($allowSignature) {
$htmlHeadXtra[] = api_get_asset('signature_pad/signature_pad.umd.js');
}

@ -845,7 +845,7 @@ foreach ($questionList as $questionId) {
$contents = ob_get_clean();
$question_content = '<div class="question_row">';
if ($show_results && $objQuestionTmp) {
$objQuestionTmp->export = $action == 'export';
$objQuestionTmp->export = $action === 'export';
// Shows question title an description
$question_content .= $objQuestionTmp->return_header(
$objExercise,
@ -913,10 +913,7 @@ if (!empty($category_list) && ($show_results || $show_only_total_score || $showT
'score' => $myTotalScoreTemp,
'total' => $totalWeighting,
];
echo TestCategory::get_stats_table_by_attempt(
$objExercise->id,
$category_list
);
echo TestCategory::get_stats_table_by_attempt($objExercise->id, $category_list);
}
if (in_array(
@ -1051,7 +1048,6 @@ if ($isFeedbackAllowed && $origin !== 'learnpath' && $origin !== 'student_progre
$url
);
$emailForm->setDefaults(['notification_content' => $content]);
$emailForm->addButtonSend(
get_lang('CorrectTest'),
'submit',

File diff suppressed because it is too large Load Diff

@ -1,13 +1,13 @@
<?php
/* See license terms in /license.txt */
require_once __DIR__.'/../inc/global.inc.php';
$this_section = SECTION_COURSES;
$exercise_id = isset($_GET['exerciseId']) && !empty($_GET['exerciseId']) ? (int) ($_GET['exerciseId']) : 0;
// Access control
api_protect_course_script(true);
api_protect_course_script(true);
if (!api_is_allowed_to_edit()) {
api_not_allowed();
}
@ -35,12 +35,18 @@ $htmlHeadXtra[] = api_get_jqgrid_js();
Display::display_header(get_lang('StudentsWhoAreTakingTheExerciseRightNow'));
//jqgrid will use this URL to do the selects
$minutes = 60;
$url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=get_live_stats&exercise_id='.$objExercise->id.'&minutes='.$minutes;
$url = api_get_path(WEB_AJAX_PATH).
'exercise.ajax.php?'.api_get_cidreq().'&a=get_live_stats&exercise_id='.$objExercise->id.'&minutes='.$minutes;
//The order is important you need to check the the $column variable in the model.ajax.php file
$columns = [get_lang('FirstName'), get_lang('LastName'), get_lang('Time'), get_lang('QuestionsAlreadyAnswered'), get_lang('Score')];
$columns = [
get_lang('FirstName'),
get_lang('LastName'),
get_lang('Time'),
get_lang('QuestionsAlreadyAnswered'),
get_lang('Score'),
];
//Column config
$column_model = [
@ -77,9 +83,7 @@ $column_model = [
'sortable' => 'false',
],
];
//Autowidth
$extra_params['autowidth'] = 'true';
//height auto
$extra_params['height'] = 'auto';
?>
<script>
@ -108,10 +112,8 @@ $(function() {
</script>
<?php
$actions = '<a href="exercise_report.php?exerciseId='.intval($_GET['exerciseId']).'&'.api_get_cidreq().'">'.
$actions = '<a href="exercise_report.php?exerciseId='.$exercise_id.'&'.api_get_cidreq().'">'.
Display::return_icon('back.png', get_lang('GoBackToQuestionList'), '', ICON_SIZE_MEDIUM).'</a>';
echo $actions = Display::div($actions, ['class' => 'actions']);
echo Display::grid_html('live_stats');
Display::display_footer();

@ -1675,12 +1675,10 @@ abstract class Question
});
$("input[name=\'imageZoom\']").on("click", function(){
console.log("click en campo");
var elf = $("#elfinder").elfinder({
url : "'.api_get_path(WEB_LIBRARY_PATH).'elfinder/connectorAction.php?'.api_get_cidreq().'",
getFileCallback: function(file) {
var filePath = file; //file contains the relative url.
console.log(filePath);
var imgPath = "<img src = \'"+filePath+"\'/>";
$("input[name=\'imageZoom\']").val(filePath.url);
$("#elfinder").remove(); //close the window after image is selected

@ -57,15 +57,11 @@ if (!$is_allowedToEdit) {
}
$allowSignature = false;
if ($student_id === $current_user_id && 'true' === api_get_plugin_setting('exercise_signature', 'tool_enable')) {
$extraFieldValue = new ExtraFieldValue('exercise');
$result = $extraFieldValue->get_values_by_handler_and_field_variable($exercise_id, 'signature_activated');
if ($result && isset($result['value']) && 1 === (int) $result['value']) {
// Check if signature exists.
$signature = ExerciseSignaturePlugin::getSignature($current_user_id, $track_exercise_info);
if (false === $signature) {
$allowSignature = true;
}
if ($student_id === $current_user_id && ExerciseSignaturePlugin::exerciseHasSignatureActivated($objExercise)) {
// Check if signature exists.
$signature = ExerciseSignaturePlugin::getSignature($current_user_id, $track_exercise_info);
if (false === $signature) {
$allowSignature = true;
}
}

@ -1,14 +1,14 @@
<?php
/* For licensing terms, see /license.txt */
require_once __DIR__.'/../inc/global.inc.php';
$this_section = SECTION_COURSES;
$current_course_tool = TOOL_GROUP;
// Notice for unauthorized people.
api_protect_course_script(true);
if (api_get_setting('allow_group_categories') == 'false') {
if ('false' === api_get_setting('allow_group_categories')) {
api_not_allowed(true);
}

@ -356,15 +356,10 @@ switch ($action) {
switch ($option) {
case 'add_all':
$questionListInSession = Session::read('questionList');
$objExercise->addAllQuestionToRemind(
$exeId,
$questionListInSession
);
$objExercise->addAllQuestionToRemind($exeId, $questionListInSession);
break;
case 'remove_all':
$objExercise->removeAllQuestionToRemind(
$exeId
);
$objExercise->removeAllQuestionToRemind($exeId);
break;
default:
$objExercise->editQuestionToRemind(
@ -374,6 +369,8 @@ switch ($action) {
);
break;
}
echo 1;
exit;
}
break;
case 'save_exercise_by_now':
@ -389,8 +386,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 +446,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 +489,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 +504,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 +528,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 +544,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 +565,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,
@ -598,6 +582,22 @@ switch ($action) {
$total_score = $total_score - $old_result['score'];
}
$questionDuration = 0;
if (api_get_configuration_value('allow_time_per_question')) {
$extraFieldValue = new ExtraFieldValue('question');
$value = $extraFieldValue->get_values_by_handler_and_field_variable($objQuestionTmp->iid, 'time');
if (!empty($value) && isset($value['value']) && !empty($value['value'])) {
$questionDuration = Event::getAttemptQuestionDuration($exeId, $objQuestionTmp->iid);
if (empty($questionDuration)) {
echo 'error';
if ($debug) {
error_log("Question duration = 0, in exeId: $exeId, question_id: $my_question_id");
}
exit;
}
}
}
// Deleting old attempt
if (isset($attemptList) && !empty($attemptList[$my_question_id])) {
if ($debug) {
@ -642,7 +642,11 @@ switch ($action) {
false,
false,
$objExercise->selectPropagateNeg(),
$hotspot_delineation_result
$hotspot_delineation_result,
true,
false,
false,
$questionDuration
);
} else {
$result = $objExercise->manage_answer(
@ -655,11 +659,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 +723,17 @@ switch ($action) {
$remind_list
);
if (api_get_configuration_value('allow_time_per_question')) {
$questionStart = Session::read('question_start', []);
if (!empty($questionStart)) {
if (isset($questionStart[$my_question_id])) {
unset($questionStart[$my_question_id]);
}
array_filter($questionStart);
Session::write('question_start', $questionStart);
}
}
// Destruction of the Question object
unset($objQuestionTmp);
if ($debug) {
@ -737,7 +756,7 @@ switch ($action) {
exit;
}
if ($type == 'all') {
if ($type === 'all') {
echo 'ok';
exit;
}
@ -903,11 +922,14 @@ switch ($action) {
if ('true' !== api_get_plugin_setting('exercise_signature', 'tool_enable')) {
exit;
}
$file = isset($_REQUEST['file']) ? $_REQUEST['file'] : '';
if (empty($exeId) || empty($file)) {
echo 0;
exit;
}
$file = str_replace(' ', '+', $file);
$track = ExerciseLib::get_exercise_track_exercise_info($exeId);
if ($track) {
$result = ExerciseSignaturePlugin::saveSignature($currentUserId, $track, $file);

@ -1398,10 +1398,10 @@ switch ($action) {
break;
case 'get_work_user_list':
$plagiarismColumns = [];
if (api_get_configuration_value('allow_compilatio_tool')) {
if (api_get_configuration_value('allow_compilatio_tool') && api_is_allowed_to_edit()) {
$plagiarismColumns = ['compilatio'];
}
if (isset($_GET['type']) && $_GET['type'] == 'simple') {
if (isset($_GET['type']) && $_GET['type'] === 'simple') {
$columns = [
'type', 'title', 'qualification', 'sent_date', 'qualificator_id',
];

@ -8235,8 +8235,8 @@ function api_set_settings_and_plugins()
function api_set_more_memory_and_time_limits()
{
if (function_exists('ini_set')) {
api_set_memory_limit('256M');
ini_set('max_execution_time', 1800);
api_set_memory_limit('2048M');
ini_set('max_execution_time', 3600);
}
}

@ -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,118 @@ 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 +1193,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 +1728,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 +1931,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 +2582,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.

@ -13,7 +13,11 @@
<img src="{{ data.avatar }}">
</div>
<div class="user-info">
<strong>{{ data.name_url }}</strong><br>
<strong>{{ data.name_url }}</strong>
<br />
{% if signature %}
<img src="{{ signature }}" />
{% endif %}
</div>
</div>
<div class="col-md-9">

@ -29,9 +29,10 @@
</div>
<script>
var imageFormat = 'image/png';
var canvas = document.querySelector("canvas");
var signaturePad = new SignaturePad(canvas);
var dataURL = signaturePad.toDataURL("image/jpeg");
var dataURL = signaturePad.toDataURL(imageFormat);
var url = "{{ _p.web_ajax }}exercise.ajax.php?{{ _p.web_cid_query }}";
var exeId = "{{ exe_id }}";
@ -54,10 +55,8 @@
return false;
}
var dataURL = signaturePad.toDataURL("image/jpeg");
var dataURL = signaturePad.toDataURL(imageFormat);
$.ajax({
contentType: "application/x-www-form-urlencoded",
beforeSend: function(result) {
$('#loading').show();
},

@ -26,6 +26,23 @@ class ExerciseSignaturePlugin extends Plugin
return $instance ? $instance : $instance = new self();
}
public static function exerciseHasSignatureActivated(Exercise $exercise)
{
if (empty($exercise->iId)) {
return false;
}
if ('true' === api_get_plugin_setting('exercise_signature', 'tool_enable')) {
$extraFieldValue = new ExtraFieldValue('exercise');
$result = $extraFieldValue->get_values_by_handler_and_field_variable($exercise->iId, 'signature_activated');
if ($result && isset($result['value']) && 1 === (int) $result['value']) {
return true;
}
}
return false;
}
public static function saveSignature($userId, $trackInfo, $file)
{
if (false === self::validateSignatureAccess($userId, $trackInfo)) {

Loading…
Cancel
Save