Exercise: Add "file upload" question type - refs BT#18258

Authored-by: Christian <christian1827@gmail.com>
pull/3845/head^2
christianbeeznest 4 years ago committed by GitHub
parent fba50fe120
commit 8832ddb09b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 64
      main/exercise/UploadAnswer.php
  2. 35
      main/exercise/exercise.class.php
  3. 2
      main/exercise/exercise_report.php
  4. 7
      main/exercise/exercise_show.php
  5. 19
      main/exercise/exercise_submit.php
  6. 3
      main/exercise/question.class.php
  7. 65
      main/inc/ajax/exercise.ajax.php
  8. 1
      main/inc/lib/api.lib.php
  9. 33
      main/inc/lib/exercise.lib.php
  10. 43
      main/inc/lib/exercise_show_functions.lib.php

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

@ -199,7 +199,7 @@ if (isset($_REQUEST['comments']) &&
// From the database.
$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.
$params['marks'] = $marks;
if ($marksFromDatabase != $marks) {

@ -441,6 +441,7 @@ foreach ($questionList as $questionId) {
case CALCULATED_ANSWER:
case GLOBAL_MULTIPLE_ANSWER:
case FREE_ANSWER:
case UPLOAD_ANSWER:
case ORAL_EXPRESSION:
case MATCHING:
case DRAGGABLE:
@ -604,7 +605,7 @@ foreach ($questionList as $questionId) {
if ($isFeedbackAllowed && $action !== 'export') {
$name = 'fckdiv'.$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');
} else {
$url_name = get_lang('AddComments');
@ -679,7 +680,7 @@ foreach ($questionList as $questionId) {
}
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;
$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 = [
'score' => $my_total_score,
'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[] = '<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[] = api_get_jquery_libraries_js(['jquery-ui', 'jquery-upload']);
if (api_get_configuration_value('quiz_prevent_copy_paste')) {
$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 => ''];
}
// 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 (is_array($choice)) {
if ($debug) {
@ -1407,6 +1414,9 @@ echo '<script>
// 4. choice for degree of certainty
var my_choiceDc = $(\'*[name*="choiceDegreeCertainty[\'+question_id+\']"]\').serialize();
// 5. upload answer files
var uploadAnswerFiles = $(\'*[name*="uploadChoice[\'+question_id+\'][]"]\').serialize();
// Checking CkEditor
if (question_id) {
if (CKEDITOR.instances["choice["+question_id+"]"]) {
@ -1429,6 +1439,7 @@ echo '<script>
dataparam += hotspot ? ("&" + hotspot) : "";
dataparam += remind_list ? ("&" + remind_list) : "";
dataparam += my_choiceDc ? ("&" + my_choiceDc) : "";
dataparam += uploadAnswerFiles ? ("&" + uploadAnswerFiles) : "";
$("#save_for_now_"+question_id).html(\''.$loading.'\');
$.ajax({
@ -1535,12 +1546,18 @@ echo '<script>
var question_list = ['.implode(',', $questionList).'];
var free_answers = {};
$.each(question_list, function(index, my_question_id) {
// Checking Ckeditor
// Checking Ckeditor and upload answer
if (my_question_id) {
if (CKEDITOR.instances["choice["+my_question_id+"]"]) {
var ckContent = CKEDITOR.instances["choice["+my_question_id+"]"].getData();
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')
ANNOTATION => ['Annotation.php', 'Annotation'],
READING_COMPREHENSION => ['ReadingComprehension.php', 'ReadingComprehension'],
UPLOAD_ANSWER => ['UploadAnswer.php', 'UploadAnswer'],
];
/**
@ -103,6 +104,7 @@ abstract class Question
ORAL_EXPRESSION,
CALCULATED_ANSWER,
ANNOTATION,
UPLOAD_ANSWER,
];
}
@ -2204,6 +2206,7 @@ abstract class Question
switch ($this->type) {
case FREE_ANSWER:
case UPLOAD_ANSWER:
case ORAL_EXPRESSION:
case ANNOTATION:
$score['revised'] = isset($score['revised']) ? $score['revised'] : false;

@ -497,6 +497,9 @@ switch ($action) {
// Hot spot coordinates from all questions.
$hot_spot_coordinates = isset($_REQUEST['hotspot']) ? $_REQUEST['hotspot'] : [];
// the filenames in upload answer type
$uploadAnswerFileNames = isset($_REQUEST['uploadChoice']) ? $_REQUEST['uploadChoice'] : [];
// There is a reminder?
$remind_list = isset($_REQUEST['remind_list']) && !empty($_REQUEST['remind_list'])
? array_keys($_REQUEST['remind_list']) : [];
@ -511,6 +514,7 @@ switch ($action) {
error_log("choice = ".print_r($choice, 1)." ");
error_log("hot_spot_coordinates = ".print_r($hot_spot_coordinates, 1));
error_log("remind_list = ".print_r($remind_list, 1));
error_log("uploadAnswerFileNames = ".print_r($uploadAnswerFileNames, 1));
error_log("--------------------------------");
}
@ -663,7 +667,12 @@ switch ($action) {
$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.
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])
@ -1082,6 +1091,60 @@ switch ($action) {
}
echo 0;
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:
echo '';
}

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

@ -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 '';
}
@ -204,6 +204,35 @@ class ExerciseLib
$form->setDefaults(["choice[".$questionId."]" => $fck_content]);
$s .= $form->returnForm();
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:
// Add nanog
if (api_get_setting('enable_record_audio') === 'true') {
@ -5009,7 +5038,7 @@ EOT;
if ($show_results) {
$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 = [
'score' => $my_total_score,
'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.
*

Loading…
Cancel
Save