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

pull/3951/head
Angel Fernando Quiroz Campos 4 years ago
commit 3330b3a42e
  1. 19
      documentation/changelog.html
  2. 64
      main/exercise/UploadAnswer.php
  3. 35
      main/exercise/exercise.class.php
  4. 2
      main/exercise/exercise_report.php
  5. 7
      main/exercise/exercise_show.php
  6. 19
      main/exercise/exercise_submit.php
  7. 3
      main/exercise/question.class.php
  8. 19
      main/inc/ajax/document.ajax.php
  9. 65
      main/inc/ajax/exercise.ajax.php
  10. 1
      main/inc/lib/api.lib.php
  11. 1
      main/inc/lib/document.lib.php
  12. 33
      main/inc/lib/exercise.lib.php
  13. 43
      main/inc/lib/exercise_show_functions.lib.php
  14. 37
      main/lp/scorm_api.php

@ -112,7 +112,7 @@
<h3>Release notes - summary</h3> <h3>Release notes - summary</h3>
<p>Chamilo 1.11.16 is a minor security and bug fix release on top of 1.11.14.</p> <p>Chamilo 1.11.16 is a minor security and bug fix release on top of 1.11.14.</p>
<h3>Release name</h3> <h3>Release name</h3>
<p><a href="https://fr.wikipedia.org/wiki/Calci">Calci</a> is a small, quiet and rustical municipality in the neighborhood of Pisa, Tuscany, Italy. A nice place to rest before visiting the city of Pisa, Calci, just as Chamilo 1.11.16 is a nice and quiet rest before 2.0, with a few happy surprises. <p><a href="https://fr.wikipedia.org/wiki/Calci">Calci</a> is a small, quiet and rustical municipality in the neighborhood of Pisa, Tuscany, Italy. A nice place to rest before visiting the city of Pisa, Calci, just as Chamilo 1.11.16 is a nice and quiet rest before 2.0 (with a few surprises), just looking at the building of one of human's great construction projects.
</p> </p>
<h3>Security fixes</h3> <h3>Security fixes</h3>
Many vulnerabilities (more than in any previous version) have been reported to us (see <a href="https://support.chamilo.org/projects/1/wiki/Security_issues">our security page</a>) and swiflty and safely fixed. Thanks to all white hat hackers for helping us out (see their nicknames on the page). Many vulnerabilities (more than in any previous version) have been reported to us (see <a href="https://support.chamilo.org/projects/1/wiki/Security_issues">our security page</a>) and swiflty and safely fixed. Thanks to all white hat hackers for helping us out (see their nicknames on the page).
@ -204,16 +204,20 @@
<li>[2021-01-14] (<a href="https://github.com/chamilo/chamilo-lms/commit/e4781a7d15aa4df1564be4bae5d5db554d2941c8">e4781a7d</a>) Agenda: Add sec_token param in agenda events #security</li> <li>[2021-01-14] (<a href="https://github.com/chamilo/chamilo-lms/commit/e4781a7d15aa4df1564be4bae5d5db554d2941c8">e4781a7d</a>) Agenda: Add sec_token param in agenda events #security</li>
</ul> </ul>
<h3>Possibly breaking changes</h3> <h3>Possibly breaking changes</h3>
An important change has been made in the way Chamilo 1.11.16 processes exercises, questions and answers in the <ul>
<li>An important change has been made in the way Chamilo 1.11.16 processes exercises, questions and answers in the
exercises tool. We now use the iid field as a unique identifier for everything related to exercises. Although exercises tool. We now use the iid field as a unique identifier for everything related to exercises. Although
the corresponding code has been well tested, an issue has appeared which only affects portals that were the corresponding code has been well tested, an issue has appeared which only affects portals that were
initially installed with a version prior to year 2016. If you are in this case, you will need to initially installed with a version prior to year 2016. If you are in this case, you will need to
run the tests/scripts/fix_quiz_id_to_iid.php script (available <a href="https://raw.githubusercontent.com/chamilo/chamilo-lms/5e0fc76649a7377c12cd0220ad1c0eae3f5eeaeb/tests/scripts/fix_quiz_id_to_iid.php">here</a>). run the tests/scripts/fix_quiz_id_to_iid.php script (available <a href="https://raw.githubusercontent.com/chamilo/chamilo-lms/5e0fc76649a7377c12cd0220ad1c0eae3f5eeaeb/tests/scripts/fix_quiz_id_to_iid.php">here</a>).
This script must be edited and line "exit;" on line 15 must be removed, then the script can be loaded and could This script must be edited and line "exit;" on line 15 must be removed, then the script can be loaded and could
last a considerable time to execute. It should be failsafe, so if it fails once, reloading it should just last a considerable time to execute. It should be failsafe, so if it fails once, reloading it should just
continue executing the process. continue executing the process. Despite the possible extra effort, we believe you will appreciate the advantages of
We re sorry for the extra effort to deal with this issue. We believe you will appreciate the advantages of a faster exercises tool in Chamilo 1.11.16 and the possibility to re-use (*or* copy) questions between courses.</li>
a faster exercises tool in Chamilo 1.11.16 and the possibility to re-use (*or* copy) questions between courses. <li>A new change in behaviour in the upload of images in the WYSIWYG editor mean new files that have names
that already exist on the system will be renamed automatically. The previous behaviour was to warn the user,
but this didn't appear on screen due to the WYSIWYG editor.</li>
</ul>
<h3>Notable new Features</h3> <h3>Notable new Features</h3>
<h4>For end-users, teachers and Chamilo admins</h4> <h4>For end-users, teachers and Chamilo admins</h4>
@ -995,10 +999,13 @@
<li>[2021-05-21] (<a href="https://github.com/chamilo/chamilo-lms/commit/bb42741e35ab8e525965fc64cbebc6e434ed4d51">bb42741e</a> - <a href="https://task.beeznest.com/issues/18673">BT#18673</a>) Webservices: Fix GET_COURSES_FROM_EXTRA_FIELD</li> <li>[2021-05-21] (<a href="https://github.com/chamilo/chamilo-lms/commit/bb42741e35ab8e525965fc64cbebc6e434ed4d51">bb42741e</a> - <a href="https://task.beeznest.com/issues/18673">BT#18673</a>) Webservices: Fix GET_COURSES_FROM_EXTRA_FIELD</li>
<li>[2021-05-12] (<a href="https://github.com/chamilo/chamilo-lms/commit/a203caa83572e76e7b44b34bd864f2c2eebc594a">a203caa8</a> - <a href="https://task.beeznest.com/issues/18673">BT#18673</a>) Webservices: Add get_users_subscribed_to_course webservice + Fix save_course with extra fields</li> <li>[2021-05-12] (<a href="https://github.com/chamilo/chamilo-lms/commit/a203caa83572e76e7b44b34bd864f2c2eebc594a">a203caa8</a> - <a href="https://task.beeznest.com/issues/18673">BT#18673</a>) Webservices: Add get_users_subscribed_to_course webservice + Fix save_course with extra fields</li>
<li>[2021-05-07] (<a href="https://github.com/chamilo/chamilo-lms/commit/4c6f9cdbb9f366e8c3c5fc6e201a41b3aabee4b5">4c6f9cdb</a> - <a href="https://task.beeznest.com/issues/18673">BT#18673</a>) Webservices: REST: Updates save_course: Add 'remove_campus_id_from_wanted_code save_user' and 'auth_source'</li> <li>[2021-05-07] (<a href="https://github.com/chamilo/chamilo-lms/commit/4c6f9cdbb9f366e8c3c5fc6e201a41b3aabee4b5">4c6f9cdb</a> - <a href="https://task.beeznest.com/issues/18673">BT#18673</a>) Webservices: REST: Updates save_course: Add 'remove_campus_id_from_wanted_code save_user' and 'auth_source'</li>
<li>[2021-04-14] (<a href="https://github.com/chamilo/chamilo-lms/commit/dac68b2bd4ba1ef0d8be7b84d6d95562544f7f95">dac68b2b</a> - <a href="https://task.beeznest.com/issues/18673">BT#18673</a>) Webservice: Add new webservices UNSUBSCRIBE_USER_TO_COURSE, GET_COURSES_FROM_EXTRA_FIELD, DELETE_COURSE</li> <li>[2021-04-08] (<a href="https://github.com/chamilo/chamilo-lms/commit/91455a233fcc1befe4ed71a78e89ecf3d8c598b0">91455a23</a> - <a href="https://task.beeznest.com/issues/18653">BT#18653</a>) Session: Add audit event when deleting/adding a user</li> <li>[2021-04-14] (<a href="https://github.com/chamilo/chamilo-lms/commit/dac68b2bd4ba1ef0d8be7b84d6d95562544f7f95">dac68b2b</a> - <a href="https://task.beeznest.com/issues/18673">BT#18673</a>) Webservice: Add new webservices UNSUBSCRIBE_USER_TO_COURSE, GET_COURSES_FROM_EXTRA_FIELD, DELETE_COURSE</li>
<li>[2021-04-08] (<a href="https://github.com/chamilo/chamilo-lms/commit/91455a233fcc1befe4ed71a78e89ecf3d8c598b0">91455a23</a> - <a href="https://task.beeznest.com/issues/18653">BT#18653</a>) Session: Add audit event when deleting/adding a user</li>
</ul> </ul>
<h3>Removals</h3> <h3>Removals</h3>
<ul aria-live="off"> <ul aria-live="off">
<li>Pixlr (image edition from the documents tool) service has been disabled due to introducing security issues in Chamilo. We hope to provide a suitable replacement in future versions.</li>
<li>BigBlueButton: Removed support for the Flash interface</li>
<li>[2021-03-02] (<a href="https://github.com/chamilo/chamilo-lms/commit/d87d35290a018444418c0e98e17019e152aa4569">d87d3529</a> - <a href="https://task.beeznest.com/issues/18443">BT#18443</a>) Tracking: Remove configuration setting 'use_new_tracking_in_lp_item'. The new tracking system depends on the course/session, not on the learning path.</li> <li>[2021-03-02] (<a href="https://github.com/chamilo/chamilo-lms/commit/d87d35290a018444418c0e98e17019e152aa4569">d87d3529</a> - <a href="https://task.beeznest.com/issues/18443">BT#18443</a>) Tracking: Remove configuration setting 'use_new_tracking_in_lp_item'. The new tracking system depends on the course/session, not on the learning path.</li>
</ul> </ul>
<h3>Known issues</h3> <h3>Known issues</h3>

@ -0,0 +1,64 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Question with file upload, where the file is the answer.
* Acts as an open question: requires teacher's review for a score.
*/
class UploadAnswer extends Question
{
public $typePicture = 'file_upload_question.png';
public $explanationLangVar = 'UploadAnswer';
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
$this->type = UPLOAD_ANSWER;
$this->isContent = $this->getIsContent();
}
/**
* {@inheritdoc}
*/
public function createAnswersForm($form)
{
$form->addElement('text', 'weighting', get_lang('Weighting'));
global $text;
// setting the save button here and not in the question class.php
$form->addButtonSave($text, 'submitQuestion');
if (!empty($this->iid)) {
$form->setDefaults(['weighting' => float_format($this->weighting, 1)]);
} else {
if ($this->isContent == 1) {
$form->setDefaults(['weighting' => '10']);
}
}
}
/**
* {@inheritdoc}
*/
public function processAnswersCreation($form, $exercise)
{
$this->weighting = $form->getSubmitValue('weighting');
$this->save($exercise);
}
/**
* {@inheritdoc}
*/
public function return_header(Exercise $exercise, $counter = null, $score = [])
{
$score['revised'] = $this->isQuestionWaitingReview($score);
$header = parent::return_header($exercise, $counter, $score);
$header .= '<table class="'.$this->question_table_class.'" >
<tr>
<th>'.get_lang('Answer').'</th>
</tr>';
return $header;
}
}

@ -3805,7 +3805,8 @@ class Exercise
if ($answerType == FREE_ANSWER || if ($answerType == FREE_ANSWER ||
$answerType == ORAL_EXPRESSION || $answerType == ORAL_EXPRESSION ||
$answerType == CALCULATED_ANSWER || $answerType == CALCULATED_ANSWER ||
$answerType == ANNOTATION $answerType == ANNOTATION ||
$answerType == UPLOAD_ANSWER
) { ) {
$nbrAnswers = 1; $nbrAnswers = 1;
} }
@ -4542,6 +4543,7 @@ class Exercise
} }
} }
break; break;
case UPLOAD_ANSWER:
case FREE_ANSWER: case FREE_ANSWER:
if ($from_database) { if ($from_database) {
$sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT $sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
@ -5177,6 +5179,15 @@ class Exercise
$questionScore, $questionScore,
$results_disabled $results_disabled
); );
} elseif ($answerType == UPLOAD_ANSWER) {
ExerciseShowFunctions::displayUploadAnswer(
$feedback_type,
$choice,
$exeId,
$questionId,
$questionScore,
$results_disabled
);
} elseif ($answerType == ORAL_EXPRESSION) { } elseif ($answerType == ORAL_EXPRESSION) {
// to store the details of open questions in an array to be used in mail // to store the details of open questions in an array to be used in mail
/** @var OralExpression $objQuestionTmp */ /** @var OralExpression $objQuestionTmp */
@ -5564,6 +5575,16 @@ class Exercise
$results_disabled $results_disabled
); );
break; break;
case UPLOAD_ANSWER:
echo ExerciseShowFunctions::displayUploadAnswer(
$feedback_type,
$choice,
$exeId,
$questionId,
$questionScore,
$results_disabled
);
break;
case ORAL_EXPRESSION: case ORAL_EXPRESSION:
echo '<tr> echo '<tr>
<td valign="top">'. <td valign="top">'.
@ -6144,6 +6165,18 @@ class Exercise
false, false,
$questionDuration $questionDuration
); );
} elseif ($answerType == UPLOAD_ANSWER) {
$answer = $choice;
Event::saveQuestionAttempt(
$questionScore,
$answer,
$quesId,
$exeId,
0,
$this->iid,
false,
$questionDuration
);
} elseif ($answerType == ORAL_EXPRESSION) { } elseif ($answerType == ORAL_EXPRESSION) {
$answer = $choice; $answer = $choice;
Event::saveQuestionAttempt( Event::saveQuestionAttempt(

@ -199,7 +199,7 @@ if (isset($_REQUEST['comments']) &&
// From the database. // From the database.
$marksFromDatabase = $questionListData[$questionId]['marks']; $marksFromDatabase = $questionListData[$questionId]['marks'];
if (in_array($question->type, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION])) { if (in_array($question->type, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION, UPLOAD_ANSWER])) {
// From the form. // From the form.
$params['marks'] = $marks; $params['marks'] = $marks;
if ($marksFromDatabase != $marks) { if ($marksFromDatabase != $marks) {

@ -441,6 +441,7 @@ foreach ($questionList as $questionId) {
case CALCULATED_ANSWER: case CALCULATED_ANSWER:
case GLOBAL_MULTIPLE_ANSWER: case GLOBAL_MULTIPLE_ANSWER:
case FREE_ANSWER: case FREE_ANSWER:
case UPLOAD_ANSWER:
case ORAL_EXPRESSION: case ORAL_EXPRESSION:
case MATCHING: case MATCHING:
case DRAGGABLE: case DRAGGABLE:
@ -604,7 +605,7 @@ foreach ($questionList as $questionId) {
if ($isFeedbackAllowed && $action !== 'export') { if ($isFeedbackAllowed && $action !== 'export') {
$name = 'fckdiv'.$questionId; $name = 'fckdiv'.$questionId;
$marksname = 'marksName'.$questionId; $marksname = 'marksName'.$questionId;
if (in_array($answerType, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION])) { if (in_array($answerType, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION, UPLOAD_ANSWER])) {
$url_name = get_lang('EditCommentsAndMarks'); $url_name = get_lang('EditCommentsAndMarks');
} else { } else {
$url_name = get_lang('AddComments'); $url_name = get_lang('AddComments');
@ -679,7 +680,7 @@ foreach ($questionList as $questionId) {
} }
if ($is_allowedToEdit && $isFeedbackAllowed && $action !== 'export') { if ($is_allowedToEdit && $isFeedbackAllowed && $action !== 'export') {
if (in_array($answerType, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION])) { if (in_array($answerType, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION, UPLOAD_ANSWER])) {
$marksname = 'marksName'.$questionId; $marksname = 'marksName'.$questionId;
$arrmarks[] = $questionId; $arrmarks[] = $questionId;
@ -836,7 +837,7 @@ foreach ($questionList as $questionId) {
} }
} }
if (in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION])) { if (in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION, UPLOAD_ANSWER])) {
$scoreToReview = [ $scoreToReview = [
'score' => $my_total_score, 'score' => $my_total_score,
'comments' => isset($comnt) ? $comnt : null, 'comments' => isset($comnt) ? $comnt : null,

@ -70,6 +70,7 @@ $htmlHeadXtra[] = api_get_js('epiclock/renderers/minute/epiclock.minute.js');
$htmlHeadXtra[] = '<link rel="stylesheet" href="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/css/hotspot.css">'; $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).'hotspot/js/hotspot.js"></script>';
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'annotation/js/annotation.js"></script>'; $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'annotation/js/annotation.js"></script>';
$htmlHeadXtra[] = api_get_jquery_libraries_js(['jquery-ui', 'jquery-upload']);
if (api_get_configuration_value('quiz_prevent_copy_paste')) { if (api_get_configuration_value('quiz_prevent_copy_paste')) {
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'jquery.nocopypaste.js"></script>'; $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'jquery.nocopypaste.js"></script>';
} }
@ -741,6 +742,12 @@ if ($formSent && isset($_POST)) {
$choice = [$hotspot_id => '']; $choice = [$hotspot_id => ''];
} }
// Only for upload answer
if (!isset($choice) && isset($_REQUEST['uploadChoice'])) {
$uploadAnswerFileNames = $_REQUEST['uploadChoice'];
$choice = implode('|', $uploadAnswerFileNames[$questionId]);
}
// if the user has answered at least one question // if the user has answered at least one question
if (is_array($choice)) { if (is_array($choice)) {
if ($debug) { if ($debug) {
@ -1407,6 +1414,9 @@ echo '<script>
// 4. choice for degree of certainty // 4. choice for degree of certainty
var my_choiceDc = $(\'*[name*="choiceDegreeCertainty[\'+question_id+\']"]\').serialize(); var my_choiceDc = $(\'*[name*="choiceDegreeCertainty[\'+question_id+\']"]\').serialize();
// 5. upload answer files
var uploadAnswerFiles = $(\'*[name*="uploadChoice[\'+question_id+\'][]"]\').serialize();
// Checking CkEditor // Checking CkEditor
if (question_id) { if (question_id) {
if (CKEDITOR.instances["choice["+question_id+"]"]) { if (CKEDITOR.instances["choice["+question_id+"]"]) {
@ -1429,6 +1439,7 @@ echo '<script>
dataparam += hotspot ? ("&" + hotspot) : ""; dataparam += hotspot ? ("&" + hotspot) : "";
dataparam += remind_list ? ("&" + remind_list) : ""; dataparam += remind_list ? ("&" + remind_list) : "";
dataparam += my_choiceDc ? ("&" + my_choiceDc) : ""; dataparam += my_choiceDc ? ("&" + my_choiceDc) : "";
dataparam += uploadAnswerFiles ? ("&" + uploadAnswerFiles) : "";
$("#save_for_now_"+question_id).html(\''.$loading.'\'); $("#save_for_now_"+question_id).html(\''.$loading.'\');
$.ajax({ $.ajax({
@ -1535,12 +1546,18 @@ echo '<script>
var question_list = ['.implode(',', $questionList).']; var question_list = ['.implode(',', $questionList).'];
var free_answers = {}; var free_answers = {};
$.each(question_list, function(index, my_question_id) { $.each(question_list, function(index, my_question_id) {
// Checking Ckeditor // Checking Ckeditor and upload answer
if (my_question_id) { if (my_question_id) {
if (CKEDITOR.instances["choice["+my_question_id+"]"]) { if (CKEDITOR.instances["choice["+my_question_id+"]"]) {
var ckContent = CKEDITOR.instances["choice["+my_question_id+"]"].getData(); var ckContent = CKEDITOR.instances["choice["+my_question_id+"]"].getData();
free_answers["free_choice["+my_question_id+"]"] = ckContent; free_answers["free_choice["+my_question_id+"]"] = ckContent;
} }
if ($(\'*[name*="uploadChoice[\'+my_question_id+\']"]\').length) {
var uploadChoice = $(\'*[name*="uploadChoice[\'+my_question_id+\']"]\').serializeArray();
$.each(uploadChoice, function(i, obj) {
free_answers["uploadChoice["+my_question_id+"]["+i+"]"] = uploadChoice[i].value;
});
}
} }
}); });

@ -68,6 +68,7 @@ abstract class Question
//MEDIA_QUESTION => array('media_question.class.php' , 'MediaQuestion') //MEDIA_QUESTION => array('media_question.class.php' , 'MediaQuestion')
ANNOTATION => ['Annotation.php', 'Annotation'], ANNOTATION => ['Annotation.php', 'Annotation'],
READING_COMPREHENSION => ['ReadingComprehension.php', 'ReadingComprehension'], READING_COMPREHENSION => ['ReadingComprehension.php', 'ReadingComprehension'],
UPLOAD_ANSWER => ['UploadAnswer.php', 'UploadAnswer'],
]; ];
/** /**
@ -103,6 +104,7 @@ abstract class Question
ORAL_EXPRESSION, ORAL_EXPRESSION,
CALCULATED_ANSWER, CALCULATED_ANSWER,
ANNOTATION, ANNOTATION,
UPLOAD_ANSWER,
]; ];
} }
@ -2204,6 +2206,7 @@ abstract class Question
switch ($this->type) { switch ($this->type) {
case FREE_ANSWER: case FREE_ANSWER:
case UPLOAD_ANSWER:
case ORAL_EXPRESSION: case ORAL_EXPRESSION:
case ANNOTATION: case ANNOTATION:
$score['revised'] = isset($score['revised']) ? $score['revised'] : false; $score['revised'] = isset($score['revised']) ? $score['revised'] : false;

@ -176,7 +176,7 @@ switch ($action) {
'', '',
'', '',
0, 0,
'', 'rename',
false, false,
false, false,
'files' 'files'
@ -191,13 +191,22 @@ switch ($action) {
} else { } else {
$userId = api_get_user_id(); $userId = api_get_user_id();
$syspath = UserManager::getUserPathById($userId, 'system').'my_files'.$currentDirectory; $syspath = UserManager::getUserPathById($userId, 'system').'my_files'.$currentDirectory;
mkdir($syspath, api_get_permissions_for_new_directories(), true); if (!is_dir($syspath)) {
mkdir($syspath, api_get_permissions_for_new_directories(), true);
}
$webpath = UserManager::getUserPathById($userId, 'web').'my_files'.$currentDirectory; $webpath = UserManager::getUserPathById($userId, 'web').'my_files'.$currentDirectory;
if (move_uploaded_file($fileUpload['tmp_name'], $syspath.$fileUpload['name'])) { $fileUploadName = $fileUpload['name'];
$url = $webpath.$fileUpload['name']; if (file_exists($syspath.$fileUploadName)) {
$extension = pathinfo($fileUploadName, PATHINFO_EXTENSION);
$fileName = pathinfo($fileUploadName, PATHINFO_FILENAME);
$suffix = '_'.uniqid();
$fileUploadName = $fileName.$suffix.'.'.$extension;
}
if (move_uploaded_file($fileUpload['tmp_name'], $syspath.$fileUploadName)) {
$url = $webpath.$fileUploadName;
$data = [ $data = [
'uploaded' => 1, 'uploaded' => 1,
'fileName' => $fileUpload['name'], 'fileName' => $fileUploadName,
'url' => $url, 'url' => $url,
]; ];
} }

@ -497,6 +497,9 @@ switch ($action) {
// Hot spot coordinates from all questions. // Hot spot coordinates from all questions.
$hot_spot_coordinates = isset($_REQUEST['hotspot']) ? $_REQUEST['hotspot'] : []; $hot_spot_coordinates = isset($_REQUEST['hotspot']) ? $_REQUEST['hotspot'] : [];
// the filenames in upload answer type
$uploadAnswerFileNames = isset($_REQUEST['uploadChoice']) ? $_REQUEST['uploadChoice'] : [];
// There is a reminder? // There is a reminder?
$remind_list = isset($_REQUEST['remind_list']) && !empty($_REQUEST['remind_list']) $remind_list = isset($_REQUEST['remind_list']) && !empty($_REQUEST['remind_list'])
? array_keys($_REQUEST['remind_list']) : []; ? array_keys($_REQUEST['remind_list']) : [];
@ -511,6 +514,7 @@ switch ($action) {
error_log("choice = ".print_r($choice, 1)." "); error_log("choice = ".print_r($choice, 1)." ");
error_log("hot_spot_coordinates = ".print_r($hot_spot_coordinates, 1)); error_log("hot_spot_coordinates = ".print_r($hot_spot_coordinates, 1));
error_log("remind_list = ".print_r($remind_list, 1)); error_log("remind_list = ".print_r($remind_list, 1));
error_log("uploadAnswerFileNames = ".print_r($uploadAnswerFileNames, 1));
error_log("--------------------------------"); error_log("--------------------------------");
} }
@ -663,7 +667,12 @@ switch ($action) {
$myChoiceDegreeCertainty = $choiceDegreeCertainty[$my_question_id]; $myChoiceDegreeCertainty = $choiceDegreeCertainty[$my_question_id];
} }
} }
if ($objQuestionTmp->type === UPLOAD_ANSWER) {
$my_choice = '';
if (!empty($uploadAnswerFileNames)) {
$my_choice = implode('|', $uploadAnswerFileNames[$my_question_id]);
}
}
// Getting free choice data. // Getting free choice data.
if ('all' === $type && in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION])) { if ('all' === $type && in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION])) {
$my_choice = isset($_REQUEST['free_choice'][$my_question_id]) && !empty($_REQUEST['free_choice'][$my_question_id]) $my_choice = isset($_REQUEST['free_choice'][$my_question_id]) && !empty($_REQUEST['free_choice'][$my_question_id])
@ -1082,6 +1091,60 @@ switch ($action) {
} }
echo 0; echo 0;
break; break;
case 'upload_answer':
api_block_anonymous_users();
if (!empty($_FILES)) {
$currentDirectory = Security::remove_XSS($_REQUEST['curdirpath']);
$userId = api_get_user_id();
// Upload answer path is created inside user personal folder my_files/upload_answer/[exe_id]/[question_id]
$syspath = UserManager::getUserPathById($userId, 'system').'my_files'.$currentDirectory;
@mkdir($syspath, api_get_permissions_for_new_directories(), true);
$webpath = UserManager::getUserPathById($userId, 'web').'my_files'.$currentDirectory;
$files = $_FILES['files'];
$fileList = [];
foreach ($files as $name => $array) {
$counter = 0;
foreach ($array as $data) {
$fileList[$counter][$name] = $data;
$counter++;
}
}
$resultList = [];
foreach ($fileList as $file) {
$json = [];
$filename = api_replace_dangerous_char($file['name']);
$filename = disable_dangerous_file($filename);
if (move_uploaded_file($file['tmp_name'], $syspath.$filename)) {
$title = $filename;
$url = $webpath.$filename;
$json['name'] = api_htmlentities($title);
$json['link'] = Display::url(
api_htmlentities($title),
api_htmlentities($url),
['target' => '_blank']
);
$json['url'] = $url;
$json['size'] = format_file_size($file['size']);
$json['type'] = api_htmlentities($file['type']);
$json['result'] = Display::return_icon(
'accept.png',
get_lang('Uploaded')
);
} else {
$json['name'] = isset($file['name']) ? $filename : get_lang('Unknown');
$json['url'] = '';
$json['error'] = get_lang('Error');
}
$resultList[] = $json;
}
echo json_encode(['files' => $resultList]);
exit;
}
break;
default: default:
echo ''; echo '';
} }

@ -518,6 +518,7 @@ define('MATCHING_DRAGGABLE', 19);
define('ANNOTATION', 20); define('ANNOTATION', 20);
define('READING_COMPREHENSION', 21); define('READING_COMPREHENSION', 21);
define('MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY', 22); define('MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY', 22);
define('UPLOAD_ANSWER', 23);
define('EXERCISE_CATEGORY_RANDOM_SHUFFLED', 1); define('EXERCISE_CATEGORY_RANDOM_SHUFFLED', 1);
define('EXERCISE_CATEGORY_RANDOM_ORDERED', 2); define('EXERCISE_CATEGORY_RANDOM_ORDERED', 2);

@ -6072,6 +6072,7 @@ class DocumentManager
public static function is_my_shared_folder($user_id, $path, $sessionId) public static function is_my_shared_folder($user_id, $path, $sessionId)
{ {
$clean_path = Security::remove_XSS($path).'/'; $clean_path = Security::remove_XSS($path).'/';
$user_id = (int) $user_id;
//for security does not remove the last slash //for security does not remove the last slash
$main_user_shared_folder = '/shared_folder\/sf_user_'.$user_id.'\//'; $main_user_shared_folder = '/shared_folder\/sf_user_'.$user_id.'\//';
//for security does not remove the last slash //for security does not remove the last slash

@ -111,7 +111,7 @@ class ExerciseLib
} }
} }
if (in_array($answerType, [FREE_ANSWER, ORAL_EXPRESSION]) && $freeze) { if (in_array($answerType, [FREE_ANSWER, ORAL_EXPRESSION, UPLOAD_ANSWER]) && $freeze) {
return ''; return '';
} }
@ -204,6 +204,35 @@ class ExerciseLib
$form->setDefaults(["choice[".$questionId."]" => $fck_content]); $form->setDefaults(["choice[".$questionId."]" => $fck_content]);
$s .= $form->returnForm(); $s .= $form->returnForm();
break; break;
case UPLOAD_ANSWER:
global $exe_id;
$path = '/upload_answer/'.$exe_id.'/'.$questionId.'/';
$url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=upload_answer&curdirpath='.$path;
$multipleForm = new FormValidator(
'drag_drop',
'post',
'#',
['enctype' => 'multipart/form-data']
);
$multipleForm->addMultipleUpload($url);
$s .= '<script>
$(function() {
$("#input_file_upload").bind("fileuploaddone", function (e, data) {
$.each(data.result.files, function (index, file) {
if (file.name) {
var input = $("<input>", {
type: "hidden",
name: "uploadChoice['.$questionId.'][]",
value: file.name
})
$(data.context.children()[index]).parent().append(input);
}
});
});
});
</script>';
$s .= $multipleForm->returnForm();
break;
case ORAL_EXPRESSION: case ORAL_EXPRESSION:
// Add nanog // Add nanog
if (api_get_setting('enable_record_audio') === 'true') { if (api_get_setting('enable_record_audio') === 'true') {
@ -5009,7 +5038,7 @@ EOT;
if ($show_results) { if ($show_results) {
$score = $calculatedScore; $score = $calculatedScore;
} }
if (in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION])) { if (in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION, UPLOAD_ANSWER])) {
$reviewScore = [ $reviewScore = [
'score' => $my_total_score, 'score' => $my_total_score,
'comments' => Event::get_comments($exeId, $questionId), 'comments' => Event::get_comments($exeId, $questionId),

@ -102,6 +102,49 @@ class ExerciseShowFunctions
} }
} }
/**
* Shows the answer to an upload question.
*
* @param float|null $questionScore Only used to check if > 0
* @param int $resultsDisabled Unused
*/
public static function displayUploadAnswer(
string $feedbackType,
string $answer,
int $exeId,
int $questionId,
$questionScore = null,
$resultsDisabled = 0
) {
if (!empty($answer)) {
$exeInfo = Event::get_exercise_results_by_attempt($exeId);
if (empty($exeInfo)) {
global $exercise_stat_info;
$userId = $exercise_stat_info['exe_user_id'];
} else {
$userId = $exeInfo[$exeId]['exe_user_id'];
}
$userWebpath = UserManager::getUserPathById($userId, 'web').'my_files'.'/upload_answer/'.$exeId.'/'.$questionId.'/';
$filesNames = explode('|', $answer);
echo '<tr><td>';
foreach ($filesNames as $filename) {
$filename = Security::remove_XSS($filename);
echo '<p><a href="'.$userWebpath.$filename.'" target="_blank">'.$filename.'</a></p>';
}
echo '</td></tr>';
}
if (EXERCISE_FEEDBACK_TYPE_EXAM != $feedbackType) {
$comments = Event::get_comments($exeId, $questionId);
if ($questionScore > 0 || !empty($comments)) {
} else {
echo '<tr>';
echo Display::tag('td', ExerciseLib::getNotCorrectedYetText());
echo '</tr>';
}
}
}
/** /**
* Shows the answer to a free-answer question, as HTML. * Shows the answer to a free-answer question, as HTML.
* *

@ -2564,8 +2564,8 @@ function update_chronometer(text_hour, text_minute, text_second)
} }
/** /**
* get_local_suspend_data() * Get the locally stored suspend data
* see suspend_data case in function LMSGetValue correction bn * See suspend_data case in LMSGetValue()
*/ */
function get_local_suspend_data() function get_local_suspend_data()
{ {
@ -2574,7 +2574,7 @@ function get_local_suspend_data()
try{ try{
if (localStorage) { if (localStorage) {
mem_suspend_data = window.localStorage.getItem(idSuspendData); mem_suspend_data = window.localStorage.getItem(idSuspendData);
if (mem_suspend_data === null||mem_suspend_data == "null"){ if (mem_suspend_data === null || mem_suspend_data == "null"){
mem_suspend_data = ""; mem_suspend_data = "";
} }
if (mem_suspend_data === undefined) { if (mem_suspend_data === undefined) {
@ -2583,32 +2583,43 @@ function get_local_suspend_data()
if (typeof mem_suspend_data == 'undefined') { if (typeof mem_suspend_data == 'undefined') {
mem_suspend_data = ""; mem_suspend_data = "";
} }
if(mem_suspend_data!=""){ if (mem_suspend_data != ""){
if (olms.suspend_data.indexOf("ICPLAYER_")!=-1||mem_suspend_data.indexOf("ICPLAYER_")!=-1) { if (olms.suspend_data.indexOf("ICPLAYER_") != -1 || mem_suspend_data.indexOf("ICPLAYER_") != -1) {
final_suspend_data = "";
final_suspend_data = mem_suspend_data; final_suspend_data = mem_suspend_data;
//console.log('recovery suspend_data' + mem_suspend_data);
} }
} }
} }
}catch(err){} } catch(err) {
}
return final_suspend_data; return final_suspend_data;
} }
/** /**
* Save suspend_data in localStorage * Save suspend_data in localStorage
* see suspend_data case in function LMSSetValue * See suspend_data case in LMSSetValue()
*/ */
function save_suspend_data_in_local() function save_suspend_data_in_local()
{ {
if (localStorage) { if (localStorage) {
if (olms.suspend_data) { if (olms.suspend_data) {
var suspend_data_local = olms.suspend_data; var suspend_data_local = olms.suspend_data;
var idSuspendData = olms.lms_item_id + 'suspenddata' + olms.lms_view_id + 'u' + olms.lms_user_id; if (suspend_data_local === null||suspend_data_local == "null"){
try { suspend_data_local = "";
window.localStorage.setItem(idSuspendData,suspend_data_local); }
} catch(err) { if (suspend_data_local === undefined) {
suspend_data_local = "";
}
if (typeof suspend_data_local == 'undefined') {
suspend_data_local = "";
}
if (suspend_data_local.indexOf("ICPLAYER_")!=-1) {
var idSuspendData = olms.lms_item_id + 'suspenddata' + olms.lms_view_id + 'u' + olms.lms_user_id;
try {
window.localStorage.setItem(idSuspendData,suspend_data_local);
} catch(err) {
}
} }
} }
} }

Loading…
Cancel
Save