Upgrade 1-11.38

This commit is contained in:
xesmyd
2026-03-30 14:10:30 +02:00
parent f2a7e6d1fc
commit ac648ef29d
24665 changed files with 69682 additions and 2205004 deletions
+3 -9
View File
@@ -215,15 +215,9 @@ class Draggable extends Question
{
$header = parent::return_header($exercise, $counter, $score);
$header .= '<table class="'.$this->question_table_class.'"><tr>';
if ($exercise->showExpectedChoice()) {
$header .= '<th>'.get_lang('YourChoice').'</th>';
if ($exercise->showExpectedChoiceColumn()) {
$header .= '<th>'.get_lang('ExpectedChoice').'</th>';
}
} else {
$header .= '<th>'.get_lang('ElementList').'</th>';
$header .= '<th>'.get_lang('YourChoice').'</th>';
$header .= '<th>'.get_lang('ElementList').'</th>';
$header .= '<th>'.get_lang('YourChoice').'</th>';
if ($exercise->showExpectedChoice() || $exercise->showExpectedChoiceColumn()) {
$header .= '<th>'.get_lang('ExpectedChoice').'</th>';
}
$header .= '<th>'.get_lang('Status').'</th>';
+1 -1
View File
@@ -1050,7 +1050,7 @@ class Answer
}
// Fix correct answers
if (in_array($newQuestion->type, [DRAGGABLE, MATCHING, MATCHING_DRAGGABLE])) {
if (in_array($newQuestion->type, [DRAGGABLE, MATCHING, MATCHING_DRAGGABLE, MATCHING_COMBINATION, MATCHING_DRAGGABLE_COMBINATION])) {
$onlyAnswersFlip = array_flip($onlyAnswers);
foreach ($correctAnswers as $answer_id => $correct_answer) {
$params = [];
+34 -91
View File
@@ -1465,56 +1465,6 @@ class Exercise
}
}
/**
* changes the exercise sound file.
*
* @author Olivier Brouckaert
*
* @param string $sound - exercise sound file
* @param string $delete - ask to delete the file
*/
public function updateSound($sound, $delete)
{
global $audioPath, $documentPath;
$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
if ($sound['size'] && (strstr($sound['type'], 'audio') || strstr($sound['type'], 'video'))) {
$this->sound = $sound['name'];
if (@move_uploaded_file($sound['tmp_name'], $audioPath.'/'.$this->sound)) {
$sql = "SELECT 1 FROM $TBL_DOCUMENT
WHERE
c_id = ".$this->course_id." AND
path = '".str_replace($documentPath, '', $audioPath).'/'.$this->sound."'";
$result = Database::query($sql);
if (!Database::num_rows($result)) {
$id = add_document(
$this->course,
str_replace($documentPath, '', $audioPath).'/'.$this->sound,
'file',
$sound['size'],
$sound['name']
);
api_item_property_update(
$this->course,
TOOL_DOCUMENT,
$id,
'DocumentAdded',
api_get_user_id()
);
item_property_update_on_folder(
$this->course,
str_replace($documentPath, '', $audioPath),
api_get_user_id()
);
}
}
} elseif ($delete && is_file($audioPath.'/'.$this->sound)) {
$this->sound = '';
}
}
/**
* changes the exercise type.
*
@@ -4516,7 +4466,7 @@ class Exercise
if (!$switchableAnswerSet) {
// not switchable answer, must be in the same place than teacher order
for ($i = 0; $i < count($listCorrectAnswers['words']); $i++) {
$studentAnswer = isset($choice[$i]) ? $choice[$i] : '';
$studentAnswer = $choice[$i] ?? '';
$correctAnswer = $listCorrectAnswers['words'][$i];
if ($debug) {
@@ -4527,16 +4477,16 @@ class Exercise
// This value is the user input, not escaped while correct answer is escaped by ckeditor
// Works with cyrillic alphabet and when using ">" chars see #7718 #7610 #7618
// ENT_QUOTES is used in order to transform ' to &#039;
if (!$from_database) {
$studentAnswer = FillBlanks::clearStudentAnswer($studentAnswer);
if ($debug) {
error_log('Student answer cleaned:');
error_log($studentAnswer);
}
//if (!$from_database) {
$studentAnswer = FillBlanks::clearStudentAnswer($studentAnswer);
if ($debug) {
error_log('Student answer cleaned:');
error_log($studentAnswer);
}
//}
$isAnswerCorrect = 0;
if (FillBlanks::isStudentAnswerGood($studentAnswer, $correctAnswer, $from_database)) {
if (FillBlanks::isStudentAnswerGood($studentAnswer, $correctAnswer, $from_database, true)) {
// gives the related weighting to the student
$questionScore += $answerWeighting[$i];
// increments total score
@@ -4594,7 +4544,7 @@ class Exercise
$found = false;
for ($j = 0; $j < count($listTeacherAnswerTemp); $j++) {
$correctAnswer = isset($listTeacherAnswerTemp[$j]) ? $listTeacherAnswerTemp[$j] : '';
$correctAnswer = $listTeacherAnswerTemp[$j] ?? '';
if (is_array($listTeacherAnswerTemp)) {
$correctAnswer = implode('||', $listTeacherAnswerTemp);
}
@@ -4995,7 +4945,7 @@ class Exercise
if (false === $this->showExpectedChoice() &&
false === $showTotalScoreAndUserChoicesInLastAttempt
) {
$user_answer = '';
$this->hideExpectedAnswer = true;
}
switch ($answerType) {
case MATCHING:
@@ -5057,9 +5007,6 @@ class Exercise
echo '</tr>';
break;
case DRAGGABLE:
if (false == $showTotalScoreAndUserChoicesInLastAttempt) {
$s_answer_label = '';
}
if (RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK == $this->results_disabled) {
if (false === $showTotalScoreAndUserChoicesInLastAttempt && empty($s_user_answer)) {
break;
@@ -5067,35 +5014,15 @@ class Exercise
}
echo '<tr>';
if ($this->showExpectedChoice()) {
if (!in_array($this->results_disabled, [
RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
//RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
])
) {
echo '<td>'.$user_answer.'</td>';
} else {
$status = Display::label(get_lang('Correct'), 'success');
}
if ($this->showExpectedChoice() || $this->showExpectedChoiceColumn()) {
echo '<td>'.$s_answer_label.'</td>';
echo '<td>'.$user_answer.'</td>';
echo '<td>'.$real_list[$i_answer_correct_answer].'</td>';
echo '<td>'.$status.'</td>';
} else {
echo '<td>'.$s_answer_label.'</td>';
echo '<td>'.$user_answer.'</td>';
echo '<td>'.$counterAnswer.'</td>';
echo '<td>'.$status.'</td>';
echo '<td>';
if (in_array($answerType, [MATCHING, MATCHING_COMBINATION, MATCHING_DRAGGABLE, MATCHING_DRAGGABLE_COMBINATION])) {
if (isset($real_list[$i_answer_correct_answer]) &&
$showTotalScoreAndUserChoicesInLastAttempt === true
) {
echo Display::span(
$real_list[$i_answer_correct_answer],
['style' => 'color: #008000; font-weight: bold;']
);
}
}
echo '</td>';
}
echo '</tr>';
break;
@@ -10043,15 +9970,31 @@ class Exercise
);
} else {
if ($row['active'] == 0 || $visibility == 0) {
$visibility = Display::url(
Display::return_icon(
$visibleOnBaseCourse = api_get_item_visibility(
$courseInfo,
TOOL_QUIZ,
$row['iid'],
0
);
if ($visibleOnBaseCourse) {
$visibility = Display::url(
Display::return_icon(
'invisible.png',
get_lang('Activate'),
'',
ICON_SIZE_SMALL
),
'exercise.php?'.api_get_cidreq().'&choice=enable&sec_token='.$token.'&exerciseId='.$row['iid']
);
} else {
$visibility = Display::return_icon(
'invisible.png',
get_lang('Activate'),
'',
ICON_SIZE_SMALL
),
'exercise.php?'.api_get_cidreq().'&choice=enable&sec_token='.$token.'&exerciseId='.$row['iid']
);
);
}
} else {
// else if not active
$visibility = Display::url(
+38
View File
@@ -266,6 +266,22 @@ if (!empty($action) && $is_allowedToEdit) {
break;
}
if (!empty($sessionId)) {
$visibleOnBaseCourse = api_get_item_visibility(
$courseInfo,
TOOL_QUIZ,
$objExerciseTmp->iid,
0
);
if (!$visibleOnBaseCourse) {
Display::addFlash(Display::return_message(
sprintf(get_lang('CannotChangeVisibilityOfBaseCourseResourceX'), $objExerciseTmp->name),
'error'
));
break;
}
}
// enables an exercise
if (empty($sessionId)) {
$objExerciseTmp->enable();
@@ -368,6 +384,22 @@ if ($is_allowedToEdit) {
break;
}
if (!empty($sessionId)) {
$visibleOnBaseCourse = api_get_item_visibility(
$courseInfo,
TOOL_QUIZ,
$objExerciseTmp->iid,
0
);
if (!$visibleOnBaseCourse) {
Display::addFlash(Display::return_message(
sprintf(get_lang('CannotChangeVisibilityOfBaseCourseResourceX'), $objExerciseTmp->name),
'error'
));
break;
}
}
// Enables an exercise
if (empty($sessionId)) {
$objExerciseTmp->enable();
@@ -520,6 +552,12 @@ if ($is_allowedToEdit) {
// Teacher change exercise
break;
}
// Security: reject path traversal attempts (CWE-22)
if (!Security::check_abs_path($documentPath.$file, $documentPath.'/')) {
api_not_allowed(true);
}
// deletes an exercise
$imgparams = [];
$imgcount = 0;
+2 -2
View File
@@ -39,7 +39,7 @@ $TBL_USER = Database::get_main_table(TABLE_MAIN_USER);
$TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
$TBL_EXERCISES_QUESTION = Database::get_course_table(TABLE_QUIZ_QUESTION);
$TBL_TRACK_ATTEMPT_RECORDING = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
Display::display_header($nameTools, 'Exercise');
Display::display_header(get_lang('ViewHistoryChange'), 'Exercise');
if (isset($_GET['message'])) {
if (in_array($_GET['message'], ['ExerciseEdited'])) {
@@ -79,7 +79,7 @@ while ($row = Database::fetch_array($query)) {
echo '<td>'.$row['question'].'</td>';
echo '<td>'.$row['marks'].'</td>';
if (!empty($row['teacher_comment'])) {
echo '<td>'.$row['teacher_comment'].'</td>';
echo '<td>'.Security::remove_XSS($row['teacher_comment']).'</td>';
} else {
echo '<td>'.get_lang('WithoutComment').'</td>';
}
+5 -5
View File
@@ -491,12 +491,12 @@ class ExerciseResult
$filename = 'exercise_results_user_'.$user_id.'_'.$now.'.xlsx';
}
$spreadsheet = new PHPExcel();
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
$spreadsheet->setActiveSheetIndex(0);
$worksheet = $spreadsheet->getActiveSheet();
$line = 1; // Skip first line
$column = 0; //skip the first column (row titles)
$line = 1;
$column = 1;
// check if exists column 'user'
$with_column_user = false;
@@ -584,7 +584,7 @@ class ExerciseResult
$line++;
foreach ($this->results as $row) {
$column = 0;
$column = 1;
if ($with_column_user) {
if (api_is_western_name_order()) {
$worksheet->setCellValueByColumnAndRow(
@@ -738,7 +738,7 @@ class ExerciseResult
}
$file = api_get_path(SYS_ARCHIVE_PATH).api_replace_dangerous_char($filename);
$writer = new PHPExcel_Writer_Excel2007($spreadsheet);
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
$writer->save($file);
DocumentManager::file_send_for_download($file, true, $filename);
+5 -6
View File
@@ -111,11 +111,10 @@ if (api_is_course_admin() && !in_array($origin, ['learnpath', 'embeddable', 'ifr
);
}
$exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId);
$learnpath_id = isset($exercise_stat_info['orig_lp_id']) ? $exercise_stat_info['orig_lp_id'] : 0;
$learnpath_item_id = isset($exercise_stat_info['orig_lp_item_id']) ? $exercise_stat_info['orig_lp_item_id'] : 0;
$learnpath_item_view_id = isset($exercise_stat_info['orig_lp_item_view_id'])
? $exercise_stat_info['orig_lp_item_view_id'] : 0;
$exerciseId = isset($exercise_stat_info['exe_exo_id']) ? $exercise_stat_info['exe_exo_id'] : 0;
$learnpath_id = $exercise_stat_info['orig_lp_id'] ?? 0;
$learnpath_item_id = $exercise_stat_info['orig_lp_item_id'] ?? 0;
$learnpath_item_view_id = $exercise_stat_info['orig_lp_item_view_id'] ?? 0;
$exerciseId = $exercise_stat_info['exe_exo_id'] ?? 0;
$logInfo = [
'tool' => TOOL_QUIZ,
@@ -357,7 +356,7 @@ $template->assign('actions', $pageActions);
$template->assign('content', $template->fetch($template->get_template('exercise/result.tpl')));
$template->display_one_col_template();
function showEmbeddableFinishButton()
function showEmbeddableFinishButton(): string
{
$js = '<script>
$(function () {
+7 -1
View File
@@ -974,9 +974,15 @@ if ('export' === $action) {
$content = Security::remove_XSS($content);
$includeOfficialCode = "";
if (true === api_get_configuration_value('quiz_result_pdf_export_include_official_code_in_file_name')) {
$includeOfficialCode = $user_info['official_code'].' ';
}
$params = [
'filename' => api_replace_dangerous_char(
$objExercise->name.' '.
$includeOfficialCode.
$user_info['complete_name'].' '.
api_get_local_time()
),
@@ -999,7 +1005,7 @@ if ('export' === $action) {
if (!is_dir($exportFolderPath)) {
@mkdir($exportFolderPath);
}
$pdfFileName = $user_info['firstname'].' '.$user_info['lastname'].'-attemptId'.$id.'.pdf';
$pdfFileName = $includeOfficialCode.$user_info['firstname'].' '.$user_info['lastname'].'-attemptId'.$id.'.pdf';
$pdfFileName = api_replace_dangerous_char($pdfFileName);
$fileNameToSave = $exportFolderPath.'/'.$pdfFileName;
$pdf->html_to_pdf_with_template($content, true, false, true, [], 'F', $fileNameToSave);
+6 -6
View File
@@ -123,11 +123,11 @@ $learnpath_item_view_id = isset($_REQUEST['learnpath_item_view_id']) ? (int) $_R
$reminder = isset($_REQUEST['reminder']) ? (int) $_REQUEST['reminder'] : 0;
$remind_question_id = isset($_REQUEST['remind_question_id']) ? (int) $_REQUEST['remind_question_id'] : 0;
$exerciseId = isset($_REQUEST['exerciseId']) ? (int) $_REQUEST['exerciseId'] : 0;
$formSent = isset($_REQUEST['formSent']) ? $_REQUEST['formSent'] : null;
$exerciseResult = isset($_REQUEST['exerciseResult']) ? $_REQUEST['exerciseResult'] : null;
$exerciseResultCoordinates = isset($_REQUEST['exerciseResultCoordinates']) ? $_REQUEST['exerciseResultCoordinates'] : null;
$choice = isset($_REQUEST['choice']) ? $_REQUEST['choice'] : null;
$choice = empty($choice) ? isset($_REQUEST['choice2']) ? $_REQUEST['choice2'] : null : null;
$formSent = $_REQUEST['formSent'] ?? null;
$exerciseResult = $_REQUEST['exerciseResult'] ?? null;
$exerciseResultCoordinates = $_REQUEST['exerciseResultCoordinates'] ?? null;
$choice = $_REQUEST['choice'] ?? null;
$choice = empty($choice) ? $_REQUEST['choice2'] ?? null : null;
$current_question = $currentQuestionFromUrl = isset($_REQUEST['num']) ? (int) $_REQUEST['num'] : null;
$currentAnswer = isset($_REQUEST['num_answer']) ? (int) $_REQUEST['num_answer'] : null;
$logInfo = [
@@ -1081,7 +1081,7 @@ if (!api_is_allowed_to_session_edit()) {
}
$exercise_timeover = false;
$limit_time_exists = !empty($objExercise->start_time) || !empty($objExercise->end_time) ? true : false;
$limit_time_exists = !empty($objExercise->start_time) || !empty($objExercise->end_time);
if ($limit_time_exists) {
$exercise_start_time = api_strtotime($objExercise->start_time, 'UTC');
$exercise_end_time = api_strtotime($objExercise->end_time, 'UTC');
+2 -1
View File
@@ -688,7 +688,8 @@ function isQtiManifest($filePath)
*/
function qtiProcessManifest($filePath)
{
$xml = simplexml_load_file($filePath);
libxml_use_internal_errors(true);
$xml = simplexml_load_file($filePath, SimpleXMLElement::class, LIBXML_NONET);
$course = api_get_course_info();
$sessionId = api_get_session_id();
$courseDir = $course['path'];
+27 -15
View File
@@ -404,6 +404,8 @@ class FillBlanks extends Question
// remove starting and ending space and &nbsp;
$answer = api_preg_replace("/\xc2\xa0/", " ", $answer);
// remove invisible Unicode characters introduced by word processors
$answer = self::stripInvisibleChars($answer);
// start and end separator
$blankStartSeparator = self::getStartSeparator($form->getSubmitValue('select_separator'));
@@ -748,13 +750,13 @@ class FillBlanks extends Question
$listSeveral
);
//$studentAnswer = htmlspecialchars($studentAnswer);
$result = in_array($studentAnswer, $listSeveral);
$result = in_array(self::trimOption($studentAnswer), $listSeveral);
break;
case self::FILL_THE_BLANK_STANDARD:
default:
$correctAnswer = api_html_entity_decode($correctAnswer);
//$studentAnswer = htmlspecialchars($studentAnswer);
$result = $studentAnswer == self::trimOption($correctAnswer);
$result = self::trimOption($studentAnswer) == self::trimOption($correctAnswer);
break;
}
@@ -824,15 +826,14 @@ class FillBlanks extends Question
$listDetails = explode(':', $listArobaseSplit[0]);
// < number of item after the ::[score]:[size]:[separator_id]@ , here there are 3
$listWeightings = explode(',', $listDetails[0]);
if (count($listDetails) < 3) {
$listWeightings = explode(',', $listDetails[0]);
$listSizeOfInput = [];
for ($i = 0; $i < count($listWeightings); $i++) {
$listSizeOfInput[] = 200;
}
$blankSeparatorNumber = 0; // 0 is [...]
} else {
$listWeightings = explode(',', $listDetails[0]);
$listSizeOfInput = explode(',', $listDetails[1]);
$blankSeparatorNumber = $listDetails[2];
}
@@ -1258,8 +1259,6 @@ class FillBlanks extends Question
* @param int $feedbackType
* @param bool $resultsDisabled
* @param bool $showTotalScoreAndUserChoices
*
* @return string
*/
public static function getHtmlAnswer(
$answer,
@@ -1269,7 +1268,7 @@ class FillBlanks extends Question
$resultsDisabled = false,
$showTotalScoreAndUserChoices = false,
$exercise
) {
): string {
$hideExpectedAnswer = false;
$hideUserSelection = false;
if (!$exercise->showExpectedChoiceColumn()) {
@@ -1411,10 +1410,8 @@ class FillBlanks extends Question
* Check if a answer is correct by its text.
*
* @param string $answerText
*
* @return bool
*/
public static function isCorrect($answerText)
public static function isCorrect($answerText): bool
{
$answerInfo = self::getAnswerInfo($answerText, true);
$correctAnswerList = $answerInfo['words'];
@@ -1433,10 +1430,8 @@ class FillBlanks extends Question
* Clear the answer entered by student.
*
* @param string $answer
*
* @return string
*/
public static function clearStudentAnswer($answer)
public static function clearStudentAnswer($answer): string
{
$answer = htmlentities(api_utf8_encode($answer), ENT_QUOTES);
$answer = str_replace('&#039;', '&#39;', $answer); // fix apostrophe
@@ -1447,7 +1442,23 @@ class FillBlanks extends Question
}
/**
* Removes double spaces between words.
* Strips invisible/problematic Unicode characters introduced by word
* processors such as Microsoft Word.
* U+00A0 is normalised to a regular space; all others are removed.
*/
private static function stripInvisibleChars(string $text): string
{
$text = str_replace("\u{00A0}", ' ', $text); // Non-Breaking Space → space
return str_replace(
["\u{00AD}", "\u{200B}", "\u{200C}", "\u{200D}", "\u{2060}", "\u{FEFF}"],
'',
$text
);
}
/**
* Strips invisible characters and normalises whitespace.
*
* @param string $text
*
@@ -1456,7 +1467,8 @@ class FillBlanks extends Question
private static function trimOption($text)
{
$text = trim($text);
$text = self::stripInvisibleChars($text);
return preg_replace("/\s+/", ' ', $text);
return preg_replace("/\s+/", ' ', trim($text));
}
}
@@ -213,12 +213,12 @@ class HotpotatoesExerciseResult
$filename = 'exercise_results_user_'.$user_id.'_'.api_get_local_time().'.xls';
}
$spreadsheet = new PHPExcel();
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
$spreadsheet->setActiveSheetIndex(0);
$worksheet = $spreadsheet->getActiveSheet();
$line = 0;
$column = 0; //skip the first column (row titles)
$line = 1;
$column = 1;
// check if exists column 'user'
$with_column_user = false;
@@ -335,7 +335,7 @@ class HotpotatoesExerciseResult
$line++;
foreach ($this->results as $row) {
$column = 0;
$column = 1;
if ($with_column_user) {
$worksheet->setCellValueByColumnAndRow(
@@ -426,7 +426,7 @@ class HotpotatoesExerciseResult
}
$file = api_get_path(SYS_ARCHIVE_PATH).api_replace_dangerous_char($filename);
$writer = new PHPExcel_Writer_Excel2007($spreadsheet);
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
$writer->save($file);
DocumentManager::file_send_for_download($file, true, $filename);
+12 -6
View File
@@ -13,8 +13,12 @@ use Symfony\Component\HttpFoundation\Request;
* @author Toon Keppens
*/
$modifyAnswers = (int) $_GET['hotspotadmin'];
if (!is_object($objQuestion)) {
if (!is_object($objQuestion) || empty($objQuestion->iid) || (int) $objQuestion->iid !== $modifyAnswers) {
$objQuestion = Question::read($modifyAnswers);
if (!$objQuestion) {
api_not_allowed();
}
Session::write('objQuestion', $objQuestion);
}
$questionName = $objQuestion->selectTitle();
@@ -330,14 +334,16 @@ if ($submitAnswers || $buttonBack) {
);
$objAnswer->save();
// sets the total weighting of the question
$objQuestion->updateWeighting($questionWeighting);
$objQuestion->iid = (int) $modifyAnswers;
$objQuestion->course = api_get_course_info();
$objQuestion->updateWeighting((float) $questionWeighting);
$objQuestion->save($objExercise);
$editQuestion = $questionId;
$editQuestion = $objQuestion->iid;
unset($modifyAnswers);
echo '<script type="text/javascript">window.location.href="'.$hotspot_admin_url
.'&message=ItemUpdated"</script>';
echo '<script type="text/javascript">window.location.href="'.
$hotspot_admin_url.'&message=ItemUpdated"</script>';
}
}
}
+3
View File
@@ -337,6 +337,7 @@ if (!empty($attempts)) {
RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK,
RESULT_DISABLE_RANKING,
RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
RESULT_DISABLE_RADAR,
]
)) {
$row['result'] = $score;
@@ -353,6 +354,7 @@ if (!empty($attempts)) {
RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK,
RESULT_DISABLE_RANKING,
RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
RESULT_DISABLE_RADAR,
]
) || (
$objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ONLY &&
@@ -419,6 +421,7 @@ if (!empty($attempts)) {
case RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES:
case RESULT_DISABLE_RANKING:
case RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER:
case RESULT_DISABLE_RADAR:
$header_names = [
get_lang('Attempt'),
get_lang('StartDate'),
+48 -15
View File
@@ -16,6 +16,8 @@ $statusId = isset($_REQUEST['status']) ? (int) $_REQUEST['status'] : 0;
$questionTypeId = isset($_REQUEST['questionTypeId']) ? (int) $_REQUEST['questionTypeId'] : 0;
$exportXls = isset($_REQUEST['export_xls']) && !empty($_REQUEST['export_xls']) ? (int) $_REQUEST['export_xls'] : 0;
$action = $_REQUEST['a'] ?? null;
$startDate = isset($_REQUEST['start_date']) ? $_REQUEST['start_date'] : '';
$endDate = isset($_REQUEST['end_date']) ? $_REQUEST['end_date'] : '';
api_block_anonymous_users();
@@ -170,6 +172,14 @@ $htmlHeadXtra[] = '<script>
}
</script>';
$htmlHeadXtra[] = '<script>
$(function() {
$(".datepicker").datepicker({
dateFormat: "yy-mm-dd"
});
});
</script>';
if ($exportXls) {
ExerciseLib::exportPendingAttemptsToExcel($_REQUEST);
}
@@ -286,6 +296,22 @@ $form->addSelect(
]
);
$userOptions = [];
if (!empty($filter_user)) {
$userInfo = api_get_user_info($filter_user);
if (!empty($userInfo)) {
$userOptions[$filter_user] = $userInfo['complete_name_with_username'];
}
}
$form->addSelectAjax(
'filter_by_user',
get_lang('User'),
$userOptions,
[
'url' => api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?a=get_user_like',
]
);
$status = [
1 => get_lang('All'),
2 => get_lang('Validated'),
@@ -293,16 +319,27 @@ $status = [
4 => get_lang('Unclosed'),
5 => get_lang('Ongoing'),
];
$form->addSelect('status', get_lang('Status'), $status);
$questionType = [
0 => get_lang('All'),
1 => get_lang('QuestionsWithNoAutomaticCorrection'),
];
$form->addSelect('questionTypeId', get_lang('QuestionType'), $questionType);
$form->addElement(
'text',
'start_date',
get_lang('StartDate'),
['id' => 'start_date', 'class' => 'datepicker', 'autocomplete' => 'off', 'style' => 'width:120px']
);
$form->addElement(
'text',
'end_date',
get_lang('EndDate'),
['id' => 'end_date', 'class' => 'datepicker', 'autocomplete' => 'off', 'style' => 'width:120px']
);
$form->addButtonSearch(get_lang('Search'), 'pendingSubmit');
$content = $form->returnForm();
@@ -315,7 +352,9 @@ if (empty($statusId)) {
$url = api_get_path(WEB_AJAX_PATH).
'model.ajax.php?a=get_exercise_pending_results&filter_by_user='.$filter_user.
'&course_id='.$courseId.'&exercise_id='.$exerciseId.'&status='.$statusId.'&questionType='.$questionTypeId.'&showAttemptsInSessions='.$showAttemptsInSessions;
'&course_id='.$courseId.'&exercise_id='.$exerciseId.'&status='.$statusId.'&questionType='.$questionTypeId.
'&showAttemptsInSessions='.$showAttemptsInSessions.
'&start_date='.$startDate.'&end_date='.$endDate;
$action_links = '';
$officialCodeInList = api_get_setting('show_official_code_exercise_result_list');
@@ -375,16 +414,6 @@ $column_model = [
'align' => 'left',
'search' => 'false',
'sortable' => 'false',
//'stype' => 'select',
//for the bottom bar
/*'searchoptions' => [
'defaultValue' => '',
'value' => ':'.get_lang('All').';1:'.get_lang('Validated').';0:'.get_lang('NotValidated'),
],*/
//for the top bar
/*'editoptions' => [
'value' => ':'.get_lang('All').';1:'.get_lang('Validated').';0:'.get_lang('NotValidated'),
],*/
],
[
'name' => 'qualificator_fullname',
@@ -432,8 +461,12 @@ function action_formatter(cellvalue, options, rowObject) {
return "<span title=\""+tabLoginx[0]+rowObject[2]+tabLoginx[1]+"\">"+cellvalue+"</span>";
}';
$extra_params['autowidth'] = 'true';
$extra_params['height'] = 'auto';
$extra_params = [
'autowidth' => 'true',
'height' => 'auto',
'sortname' => 'exe_date',
'sortorder' => 'asc',
];
$gridJs = Display::grid_js(
'results',
$url,
+3
View File
@@ -66,6 +66,9 @@ if ($student_id === $current_user_id && ExerciseSignaturePlugin::exerciseHasSign
}
}
if (RESULT_DISABLE_RADAR === (int) $objExercise->results_disabled) {
$htmlHeadXtra[] = api_get_js('chartjs/Chart.min.js');
}
$htmlHeadXtra[] = '<link rel="stylesheet" href="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/css/hotspot.css">';
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/js/hotspot.js"></script>';
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'annotation/js/annotation.js"></script>';
+10 -3
View File
@@ -16,15 +16,20 @@ $_user = api_get_user_info();
$this_section = SECTION_COURSES;
$documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path']."/document";
$test = $_REQUEST['test'];
$test = $_REQUEST['test'] ?? '';
$full_file_path = $documentPath.$test;
$fileToDelete = $full_file_path.$_user['user_id'].".t.html";
my_delete($full_file_path.$_user['user_id'].".t.html");
if (!Security::check_abs_path($fileToDelete, $documentPath.'/')) {
api_not_allowed(true);
}
my_delete($fileToDelete);
$TABLETRACK_HOTPOTATOES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
$TABLE_LP_ITEM_VIEW = Database::get_course_table(TABLE_LP_ITEM_VIEW);
$score = $_REQUEST['score'];
$score = isset($_REQUEST['score']) ? Security::remove_XSS($_REQUEST['score']) : '';
$origin = api_get_origin();
$learnpath_item_id = intval($_REQUEST['learnpath_item_id']);
$lpViewId = isset($_REQUEST['lp_view_id']) ? intval($_REQUEST['lp_view_id']) : null;
@@ -45,6 +50,8 @@ function save_scores($file, $score)
global $origin;
$TABLETRACK_HOTPOTATOES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
$_user = api_get_user_info();
$file = Security::remove_XSS($file);
$score = intval($score);
// if tracking is disabled record nothing
$weighting = 100; // 100%
$date = api_get_utc_datetime();
+9 -2
View File
@@ -28,7 +28,14 @@ $lpViewId = isset($_REQUEST['lp_view_id']) ? $_REQUEST['lp_view_id'] : null;
$user_id = api_get_user_id();
$full_file_path = $document_path.$doc_url;
my_delete($full_file_path.$user_id.'.t.html');
// Security: reject path traversal attempts (CWE-22)
if (!Security::check_abs_path($full_file_path, $document_path.'/')) {
api_not_allowed(true);
}
$fileToDelete = $full_file_path.$user_id.'.t.html';
my_delete($fileToDelete);
$content = ReadFileCont($full_file_path.$user_id.'.t.html');
if ($content == '') {
@@ -95,7 +102,7 @@ $htmlHeadXtra[] = <<<HTML
});
iframe.height = maxheight;
}
$(function() {
var iframe = document.getElementById('hotpotatoe');
iframe.onload = function () {
+9 -9
View File
@@ -160,7 +160,7 @@ function lp_upload_quiz_action_handling()
$questionTypeList = [];
$answerList = [];
$quizTitle = '';
$objPHPExcel = PHPExcel_IOFactory::load($_FILES['user_upload_quiz']['tmp_name']);
$objPHPExcel = \PhpOffice\PhpSpreadsheet\IOFactory::load($_FILES['user_upload_quiz']['tmp_name']);
$objPHPExcel->setActiveSheetIndex(0);
$worksheet = $objPHPExcel->getActiveSheet();
$highestRow = $worksheet->getHighestRow(); // e.g. 10
@@ -171,9 +171,9 @@ function lp_upload_quiz_action_handling()
$useCustomScore = isset($_POST['user_custom_score']) ? true : false;
for ($row = 1; $row <= $highestRow; $row++) {
$cellTitleInfo = $worksheet->getCellByColumnAndRow(0, $row);
$cellDataInfo = $worksheet->getCellByColumnAndRow(1, $row);
$cellScoreInfo = $worksheet->getCellByColumnAndRow(2, $row);
$cellTitleInfo = $worksheet->getCellByColumnAndRow(1, $row);
$cellDataInfo = $worksheet->getCellByColumnAndRow(2, $row);
$cellScoreInfo = $worksheet->getCellByColumnAndRow(3, $row);
$title = $cellTitleInfo->getValue();
switch ($title) {
@@ -188,9 +188,9 @@ function lp_upload_quiz_action_handling()
$answerIndex = 0;
while ($continue) {
$answerRow++;
$answerInfoTitle = $worksheet->getCellByColumnAndRow(0, $answerRow);
$answerInfoData = $worksheet->getCellByColumnAndRow(1, $answerRow);
$answerInfoExtra = $worksheet->getCellByColumnAndRow(2, $answerRow);
$answerInfoTitle = $worksheet->getCellByColumnAndRow(1, $answerRow);
$answerInfoData = $worksheet->getCellByColumnAndRow(2, $answerRow);
$answerInfoExtra = $worksheet->getCellByColumnAndRow(3, $answerRow);
$answerInfoTitle = $answerInfoTitle->getValue();
if (strpos($answerInfoTitle, 'Answer') !== false) {
$answerList[$numberQuestions][$answerIndex]['data'] = $answerInfoData->getValue();
@@ -212,8 +212,8 @@ function lp_upload_quiz_action_handling()
$questionTypeIndex = 0;
while ($continue) {
$answerRow++;
$questionTypeTitle = $worksheet->getCellByColumnAndRow(0, $answerRow);
$questionTypeExtra = $worksheet->getCellByColumnAndRow(2, $answerRow);
$questionTypeTitle = $worksheet->getCellByColumnAndRow(1, $answerRow);
$questionTypeExtra = $worksheet->getCellByColumnAndRow(3, $answerRow);
$title = $questionTypeTitle->getValue();
if ($title === 'QuestionType') {
$questionTypeList[$numberQuestions] = $questionTypeExtra->getValue();