swad-core/swad_exam_print.c

1106 lines
41 KiB
C
Raw Normal View History

2020-05-07 12:57:12 +02:00
// swad_exam_print.c: exam prints (each copy of an exam in an event for a student)
/*
SWAD (Shared Workspace At a Distance),
is a web platform developed at the University of Granada (Spain),
and used to support university teaching.
This file is part of SWAD core.
Copyright (C) 1999-2020 Antonio Ca<EFBFBD>as Vargas
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*****************************************************************************/
/********************************* Headers ***********************************/
/*****************************************************************************/
#define _GNU_SOURCE // For asprintf
#include <linux/limits.h> // For PATH_MAX
#include <stddef.h> // For NULL
#include <stdio.h> // For asprintf
#include <stdlib.h> // For calloc
#include <string.h> // For string functions
#include "swad_box.h"
#include "swad_database.h"
#include "swad_exam.h"
#include "swad_exam_event.h"
#include "swad_exam_result.h"
#include "swad_exam_set.h"
#include "swad_exam_type.h"
2020-05-09 21:07:50 +02:00
#include "swad_form.h"
2020-05-07 12:57:12 +02:00
#include "swad_global.h"
/*****************************************************************************/
/************** External global variables from others modules ****************/
/*****************************************************************************/
extern struct Globals Gbl;
/*****************************************************************************/
/***************************** Private constants *****************************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
#define ExaPrn_MAX_QUESTIONS_PER_EXAM_PRINT 100 // Absolute maximum number of questions in an exam print
2020-05-07 12:57:12 +02:00
/*****************************************************************************/
/******************************* Private types *******************************/
/*****************************************************************************/
struct ExaPrn_Print
{
2020-05-07 18:33:26 +02:00
long PrnCod; // Exam print code
2020-05-10 14:03:40 +02:00
long ExaCod; // Exam code
2020-05-10 01:42:30 +02:00
long EvtCod; // Event code associated to this print
long UsrCod; // User who answered the exam print
2020-05-07 18:33:26 +02:00
time_t TimeUTC[Dat_NUM_START_END_TIME];
unsigned NumQsts; // Number of questions
unsigned NumQstsNotBlank; // Number of questions not blank
bool Sent; // This exam print has been sent or not?
// "Sent" means that user has clicked "Send" button after finishing
double Score; // Total score of the exam print
struct TstPrn_PrintedQuestion PrintedQuestions[ExaPrn_MAX_QUESTIONS_PER_EXAM_PRINT];
2020-05-07 12:57:12 +02:00
};
/*****************************************************************************/
/***************************** Private constants *****************************/
/*****************************************************************************/
/*****************************************************************************/
/***************************** Private variables *****************************/
/*****************************************************************************/
/*****************************************************************************/
/***************************** Private prototypes ****************************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
static void ExaPrn_ResetPrint (struct ExaPrn_Print *Print);
2020-05-10 01:42:30 +02:00
static void ExaPrn_ResetPrintExceptEvtCodAndUsrCod (struct ExaPrn_Print *Print);
2020-05-07 12:57:12 +02:00
2020-05-10 01:42:30 +02:00
static void ExaPrn_GetPrintDataByEvtCodAndUsrCod (struct ExaPrn_Print *Print);
2020-05-10 14:03:40 +02:00
static void ExaPrn_GetQuestionsForNewPrintFromDB (struct ExaPrn_Print *Print);
2020-05-09 01:37:00 +02:00
static unsigned ExaPrn_GetSomeQstsFromSetToPrint (struct ExaPrn_Print *Print,
struct ExaSet_Set *Set,
unsigned *NumQstInPrint);
2020-05-10 14:03:40 +02:00
static void ExaPrn_CreatePrintInDB (struct ExaPrn_Print *Print);
2020-05-10 01:42:30 +02:00
static void ExaPrn_GetPrintQuestionsFromDB (struct ExaPrn_Print *Print);
2020-05-10 14:03:40 +02:00
static void ExaPrn_ShowExamPrintToFillIt (const char *Title,
2020-05-09 21:07:50 +02:00
struct ExaPrn_Print *Print);
2020-05-10 14:03:40 +02:00
static void ExaPrn_ShowTableWithQstsToFill (struct ExaPrn_Print *Print);
static void ExaPrn_WriteQstAndAnsToFill (struct ExaPrn_Print *Print,
2020-05-09 23:12:53 +02:00
unsigned NumQst,
const struct Tst_Question *Question);
2020-05-10 14:03:40 +02:00
static void ExaPrn_WriteAnswersToFill (const struct ExaPrn_Print *Print,
2020-05-09 23:12:53 +02:00
unsigned NumQst,
const struct Tst_Question *Question);
2020-05-11 13:05:38 +02:00
static void ExaPrn_WriteIntAnsToFill (const struct ExaPrn_Print *Print,
2020-05-09 23:12:53 +02:00
unsigned NumQst);
2020-05-11 13:05:38 +02:00
static void ExaPrn_WriteFloatAnsToFill (const struct ExaPrn_Print *Print,
2020-05-09 23:12:53 +02:00
unsigned NumQst);
2020-05-11 13:05:38 +02:00
static void ExaPrn_WriteTFAnsToFill (const struct ExaPrn_Print *Print,
2020-05-09 23:12:53 +02:00
unsigned NumQst);
2020-05-11 13:05:38 +02:00
static void ExaPrn_WriteChoiceAnsToFill (const struct ExaPrn_Print *Print,
2020-05-09 23:12:53 +02:00
unsigned NumQst,
const struct Tst_Question *Question);
2020-05-11 13:05:38 +02:00
static void ExaPrn_WriteTextAnsToFill (const struct ExaPrn_Print *Print,
2020-05-09 23:12:53 +02:00
unsigned NumQst);
2020-05-09 21:07:50 +02:00
2020-05-11 02:28:38 +02:00
static unsigned ExaPrn_GetAnswerFromForm (struct ExaPrn_Print *Print);
// static void ExaPrn_PutParamNumQst (unsigned NumQst);
static unsigned ExaPrn_GetParamQstInd (void);
static void ExaPrn_ComputeScoresAndStoreQuestionsOfPrint (struct ExaPrn_Print *Print,
bool UpdateQstScore);
static void ExaPrn_ComputeScoreAndStoreQuestionOfPrint (struct ExaPrn_Print *Print,
unsigned NumQst);
static void ExaPrn_StoreOneQstOfPrintInDB (const struct ExaPrn_Print *Print,
unsigned NumQst);
static void ExaPrn_UpdatePrintInDB (const struct ExaPrn_Print *Print);
// static void ExaPrn_PutParamPrnCod (long ExaCod);
2020-05-09 23:12:53 +02:00
// static long ExaPrn_GetParamPrnCod (void);
2020-05-07 18:33:26 +02:00
/*****************************************************************************/
/**************************** Reset exam print *******************************/
/*****************************************************************************/
static void ExaPrn_ResetPrint (struct ExaPrn_Print *Print)
{
2020-05-10 01:42:30 +02:00
Print->EvtCod = -1L;
Print->UsrCod = -1L;
ExaPrn_ResetPrintExceptEvtCodAndUsrCod (Print);
2020-05-07 18:33:26 +02:00
}
2020-05-10 01:42:30 +02:00
static void ExaPrn_ResetPrintExceptEvtCodAndUsrCod (struct ExaPrn_Print *Print)
2020-05-07 18:33:26 +02:00
{
2020-05-10 01:42:30 +02:00
Print->PrnCod = -1L;
2020-05-10 14:03:40 +02:00
Print->ExaCod = -1L;
2020-05-07 18:33:26 +02:00
Print->TimeUTC[Dat_START_TIME] =
Print->TimeUTC[Dat_END_TIME ] = (time_t) 0;
Print->NumQsts =
Print->NumQstsNotBlank = 0;
Print->Sent = false; // After creating an exam print, it's not sent
Print->Score = 0.0;
}
2020-05-07 12:57:12 +02:00
/*****************************************************************************/
2020-05-09 21:07:50 +02:00
/********************** Show print of an exam in an event ********************/
2020-05-07 12:57:12 +02:00
/*****************************************************************************/
2020-05-09 21:07:50 +02:00
void ExaPrn_ShowExamPrint (void)
2020-05-07 12:57:12 +02:00
{
struct Exa_Exams Exams;
struct Exa_Exam Exam;
struct ExaEvt_Event Event;
struct ExaPrn_Print Print;
/***** Reset exams context *****/
Exa_ResetExams (&Exams);
Exa_ResetExam (&Exam);
ExaEvt_ResetEvent (&Event);
2020-05-07 18:33:26 +02:00
ExaPrn_ResetPrint (&Print);
2020-05-07 12:57:12 +02:00
/***** Get and check parameters *****/
ExaEvt_GetAndCheckParameters (&Exams,&Exam,&Event);
2020-05-10 01:42:30 +02:00
/***** Get print data from database *****/
Print.EvtCod = Event.EvtCod;
Print.UsrCod = Gbl.Usrs.Me.UsrDat.UsrCod;
ExaPrn_GetPrintDataByEvtCodAndUsrCod (&Print);
2020-05-07 12:57:12 +02:00
2020-05-10 01:42:30 +02:00
if (Print.PrnCod > 0) // Print exists
2020-05-09 01:37:00 +02:00
{
2020-05-10 15:23:11 +02:00
Ale_ShowAlert (Ale_INFO,"Examen existente.");
2020-05-10 01:42:30 +02:00
/***** Get questions and answers from database *****/
ExaPrn_GetPrintQuestionsFromDB (&Print);
2020-05-09 01:37:00 +02:00
}
2020-05-09 21:07:50 +02:00
else
{
2020-05-10 15:23:11 +02:00
Ale_ShowAlert (Ale_INFO,"Examen nuevo.");
2020-05-09 21:07:50 +02:00
/***** Get questions from database *****/
2020-05-11 02:28:38 +02:00
// Print doesn't exists ==> exam code is -1 ==> so initialize it
Print.ExaCod = Exam.ExaCod;
2020-05-10 14:03:40 +02:00
ExaPrn_GetQuestionsForNewPrintFromDB (&Print);
2020-05-09 21:07:50 +02:00
if (Print.NumQsts)
{
/***** Create/update new exam print in database *****/
2020-05-10 14:03:40 +02:00
ExaPrn_CreatePrintInDB (&Print);
2020-05-09 21:07:50 +02:00
ExaPrn_ComputeScoresAndStoreQuestionsOfPrint (&Print,
false); // Don't update question score
}
}
/***** Show test exam to be answered *****/
2020-05-10 14:03:40 +02:00
ExaPrn_ShowExamPrintToFillIt (Exam.Title,&Print);
2020-05-07 12:57:12 +02:00
}
2020-05-09 21:07:50 +02:00
/*****************************************************************************/
2020-05-10 01:42:30 +02:00
/********* Get data of an exam print using event code and user code **********/
2020-05-09 21:07:50 +02:00
/*****************************************************************************/
2020-05-10 01:42:30 +02:00
static void ExaPrn_GetPrintDataByEvtCodAndUsrCod (struct ExaPrn_Print *Print)
2020-05-09 21:07:50 +02:00
{
2020-05-10 01:42:30 +02:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
/***** Make database query *****/
if (DB_QuerySELECT (&mysql_res,"can not get data of an exam print",
2020-05-11 02:28:38 +02:00
"SELECT exa_prints.PrnCod," // row[0]
"exa_exams.ExaCod," // row[1]
"UNIX_TIMESTAMP(exa_prints.StartTime)," // row[2]
"UNIX_TIMESTAMP(exa_prints.EndTime)," // row[3]
"exa_prints.NumQsts," // row[4]
"exa_prints.NumQstsNotBlank," // row[5]
"exa_prints.Sent," // row[6]
"exa_prints.Score" // row[7]
" FROM exa_prints,exa_events,exa_exams"
" WHERE exa_prints.EvtCod=%ld"
" AND exa_prints.UsrCod=%ld" // Extra check: it belong to user
" AND exa_prints.EvtCod=exa_events.EvtCod"
" AND (NOW() BETWEEN exa_events.StartTime AND exa_events.EndTime)" // Extra check: event is open
" AND exa_events.ExaCod=exa_exams.ExaCod"
" AND exa_exams.CrsCod=%ld", // Extra check: it belong to current course
Print->EvtCod,
Print->UsrCod,
Gbl.Hierarchy.Crs.CrsCod) == 1)
2020-05-10 01:42:30 +02:00
{
row = mysql_fetch_row (mysql_res);
/* Get print code (row[0]) */
Print->PrnCod = Str_ConvertStrCodToLongCod (row[0]);
2020-05-11 02:28:38 +02:00
/* Get exam code (row[1]) */
Print->ExaCod = Str_ConvertStrCodToLongCod (row[1]);
2020-05-10 01:42:30 +02:00
2020-05-11 02:28:38 +02:00
/* Get date-time (row[2] and row[3] hold UTC date-time) */
Print->TimeUTC[Dat_START_TIME] = Dat_GetUNIXTimeFromStr (row[2]);
Print->TimeUTC[Dat_END_TIME ] = Dat_GetUNIXTimeFromStr (row[3]);
/* Get number of questions (row[4]) */
if (sscanf (row[4],"%u",&Print->NumQsts) != 1)
2020-05-10 01:42:30 +02:00
Print->NumQsts = 0;
2020-05-11 02:28:38 +02:00
/* Get number of questions not blank (row[5]) */
if (sscanf (row[5],"%u",&Print->NumQstsNotBlank) != 1)
2020-05-10 01:42:30 +02:00
Print->NumQstsNotBlank = 0;
2020-05-11 02:28:38 +02:00
/* Get if exam has been sent (row[6]) */
Print->Sent = (row[6][0] == 'Y');
2020-05-10 01:42:30 +02:00
2020-05-11 02:28:38 +02:00
/* Get score (row[7]) */
2020-05-10 01:42:30 +02:00
Str_SetDecimalPointToUS (); // To get the decimal point as a dot
2020-05-11 02:28:38 +02:00
if (sscanf (row[7],"%lf",&Print->Score) != 1)
2020-05-10 01:42:30 +02:00
Print->Score = 0.0;
Str_SetDecimalPointToLocal (); // Return to local system
}
else
ExaPrn_ResetPrintExceptEvtCodAndUsrCod (Print);
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
2020-05-09 21:07:50 +02:00
}
2020-05-07 12:57:12 +02:00
/*****************************************************************************/
/*********** Get questions for a new exam print from the database ************/
/*****************************************************************************/
2020-05-10 14:03:40 +02:00
static void ExaPrn_GetQuestionsForNewPrintFromDB (struct ExaPrn_Print *Print)
2020-05-07 12:57:12 +02:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumSets;
unsigned NumSet;
struct ExaSet_Set Set;
2020-05-09 01:37:00 +02:00
unsigned NumQstsFromSet;
unsigned NumQstInPrint = 0;
2020-05-07 12:57:12 +02:00
/***** Get data of set of questions from database *****/
NumSets = (unsigned)
DB_QuerySELECT (&mysql_res,"can not get sets of questions",
"SELECT SetCod," // row[0]
"NumQstsToPrint," // row[1]
"Title" // row[2]
" FROM exa_sets"
" WHERE ExaCod=%ld"
" ORDER BY SetInd",
2020-05-10 14:03:40 +02:00
Print->ExaCod);
2020-05-07 12:57:12 +02:00
2020-05-09 01:37:00 +02:00
/***** Get questions from all sets *****/
Print->NumQsts = 0;
2020-05-07 12:57:12 +02:00
if (NumSets)
2020-05-09 01:37:00 +02:00
/***** For each set in exam... *****/
2020-05-07 12:57:12 +02:00
for (NumSet = 0;
NumSet < NumSets;
NumSet++)
{
/***** Create set of questions *****/
ExaSet_ResetSet (&Set);
/***** Get set data *****/
row = mysql_fetch_row (mysql_res);
/*
row[0] SetCod
row[1] NumQstsToPrint
row[2] Title
*/
/* Get set code (row[0]) */
Set.SetCod = Str_ConvertStrCodToLongCod (row[0]);
/* Get set index (row[1]) */
Set.NumQstsToPrint = Str_ConvertStrToUnsigned (row[1]);
/* Get the title of the set (row[2]) */
Str_Copy (Set.Title,row[2],
ExaSet_MAX_BYTES_TITLE);
2020-05-07 18:33:26 +02:00
/***** Questions in this set *****/
2020-05-09 01:37:00 +02:00
NumQstsFromSet = ExaPrn_GetSomeQstsFromSetToPrint (Print,&Set,&NumQstInPrint);
Print->NumQsts += NumQstsFromSet;
2020-05-07 12:57:12 +02:00
}
2020-05-09 01:37:00 +02:00
/***** Check *****/
if (Print->NumQsts != NumQstInPrint)
Lay_ShowErrorAndExit ("Wrong number of questions.");
2020-05-07 12:57:12 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
2020-05-07 18:33:26 +02:00
/*****************************************************************************/
/************************ Show questions from a set **************************/
/*****************************************************************************/
2020-05-09 01:37:00 +02:00
static unsigned ExaPrn_GetSomeQstsFromSetToPrint (struct ExaPrn_Print *Print,
struct ExaSet_Set *Set,
unsigned *NumQstInPrint)
2020-05-07 18:33:26 +02:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
2020-05-09 01:37:00 +02:00
unsigned NumQstsInSet;
unsigned NumQstInSet;
2020-05-07 18:33:26 +02:00
Tst_AnswerType_t AnswerType;
bool Shuffle;
/***** Get questions from database *****/
2020-05-09 01:37:00 +02:00
NumQstsInSet = (unsigned)
DB_QuerySELECT (&mysql_res,"can not get questions from set",
"SELECT tst_questions.QstCod," // row[0]
"tst_questions.AnsType," // row[1]
"tst_questions.Shuffle" // row[2]
" FROM exa_questions,tst_questions"
" WHERE exa_questions.setCod=%ld"
" AND exa_questions.QstCod=tst_questions.QstCod"
" ORDER BY RAND()" // Don't use RAND(NOW()) because the same ordering will be repeated across sets
" LIMIT %u",
Set->SetCod,
Set->NumQstsToPrint);
2020-05-07 18:33:26 +02:00
/***** Questions in this set *****/
2020-05-09 01:37:00 +02:00
for (NumQstInSet = 0;
NumQstInSet < NumQstsInSet;
NumQstInSet++, (*NumQstInPrint)++)
2020-05-07 18:33:26 +02:00
{
Gbl.RowEvenOdd = 1 - Gbl.RowEvenOdd;
/***** Get question data *****/
row = mysql_fetch_row (mysql_res);
/*
row[0] QstCod
row[1] AnsType
row[2] Shuffle
*/
/* Get question code (row[0]) */
2020-05-09 01:37:00 +02:00
Print->PrintedQuestions[*NumQstInPrint].QstCod = Str_ConvertStrCodToLongCod (row[0]);
2020-05-07 18:33:26 +02:00
2020-05-09 21:07:50 +02:00
/* Set set of questions */
Print->PrintedQuestions[*NumQstInPrint].SetCod = Set->SetCod;
2020-05-07 18:33:26 +02:00
/* Get answer type (row[1]) */
AnswerType = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[1]);
/* Get shuffle (row[2]) */
Shuffle = (row[2][0] == 'Y');
/* Set indexes of answers */
switch (AnswerType)
{
case Tst_ANS_INT:
case Tst_ANS_FLOAT:
case Tst_ANS_TRUE_FALSE:
case Tst_ANS_TEXT:
2020-05-09 01:37:00 +02:00
Print->PrintedQuestions[*NumQstInPrint].StrIndexes[0] = '\0';
2020-05-07 18:33:26 +02:00
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
/* If answer type is unique or multiple option,
generate indexes of answers depending on shuffle */
2020-05-09 01:37:00 +02:00
Tst_GenerateChoiceIndexesDependingOnShuffle (&Print->PrintedQuestions[*NumQstInPrint],Shuffle);
2020-05-07 18:33:26 +02:00
break;
default:
break;
}
/* Reset user's answers.
Initially user has not answered the question ==> initially all the answers will be blank.
If the user does not confirm the submission of their exam ==>
==> the exam may be half filled ==> the answers displayed will be those selected by the user. */
2020-05-09 01:37:00 +02:00
Print->PrintedQuestions[*NumQstInPrint].StrAnswers[0] = '\0';
2020-05-07 18:33:26 +02:00
}
2020-05-09 01:37:00 +02:00
return NumQstsInSet;
}
/*****************************************************************************/
/***************** Create new blank exam print in database *******************/
/*****************************************************************************/
2020-05-10 14:03:40 +02:00
static void ExaPrn_CreatePrintInDB (struct ExaPrn_Print *Print)
2020-05-09 01:37:00 +02:00
{
/***** Insert new exam print into table *****/
Print->PrnCod =
DB_QueryINSERTandReturnCode ("can not create new exam print",
"INSERT INTO exa_prints"
" (EvtCod,UsrCod,StartTime,EndTime,NumQsts,NumQstsNotBlank,Sent,Score)"
" VALUES"
" (%ld,%ld,NOW(),NOW(),%u,0,'N',0)",
2020-05-10 14:03:40 +02:00
Print->EvtCod,
2020-05-09 01:37:00 +02:00
Gbl.Usrs.Me.UsrDat.UsrCod,
Print->NumQsts);
}
2020-05-10 01:42:30 +02:00
/*****************************************************************************/
/************* Get the questions of an exam print from database **************/
/*****************************************************************************/
static void ExaPrn_GetPrintQuestionsFromDB (struct ExaPrn_Print *Print)
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumQsts;
unsigned NumQst;
Tst_AnswerType_t AnswerType;
/***** Get questions of an exam print from database *****/
NumQsts =
(unsigned) DB_QuerySELECT (&mysql_res,"can not get questions"
" of an exam print",
"SELECT exa_print_questions.QstCod," // row[0]
"exa_print_questions.SetCod," // row[1]
"tst_questions.AnsType," // row[2]
"exa_print_questions.Indexes," // row[3]
"exa_print_questions.Answers" // row[4]
" FROM exa_print_questions,tst_questions"
" WHERE exa_print_questions.PrnCod=%ld"
" AND exa_print_questions.QstCod=tst_questions.QstCod"
" ORDER BY exa_print_questions.QstInd",
Print->PrnCod);
/***** Get questions *****/
2020-05-11 14:11:15 +02:00
// Some questions may be deleted, so the number of questions retrieved
// could be lower than the original number of questions in the exam print
if (NumQsts <= Print->NumQsts)
2020-05-10 01:42:30 +02:00
for (NumQst = 0;
NumQst < NumQsts;
NumQst++)
{
row = mysql_fetch_row (mysql_res);
/* Get question code (row[0]) */
if ((Print->PrintedQuestions[NumQst].QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
Lay_ShowErrorAndExit ("Wrong code of question.");
/* Get set code (row[1]) */
if ((Print->PrintedQuestions[NumQst].SetCod = Str_ConvertStrCodToLongCod (row[1])) < 0)
Lay_ShowErrorAndExit ("Wrong code of set.");
/* Get answer type (row[2]) */
AnswerType = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[2]);
/* Get indexes for this question (row[3]) */
Str_Copy (Print->PrintedQuestions[NumQst].StrIndexes,row[3],
Tst_MAX_BYTES_INDEXES_ONE_QST);
/* Get answers selected by user for this question (row[4]) */
Str_Copy (Print->PrintedQuestions[NumQst].StrAnswers,row[4],
Tst_MAX_BYTES_ANSWERS_ONE_QST);
/* Replace each comma by a separator of multiple parameters */
/* In database commas are used as separators instead of special chars */
Par_ReplaceCommaBySeparatorMultiple (Print->PrintedQuestions[NumQst].StrIndexes);
if (AnswerType == Tst_ANS_MULTIPLE_CHOICE)
// Only multiple choice questions have multiple answers separated by commas
// Other types of questions have a unique answer, and comma may be part of that answer
Par_ReplaceCommaBySeparatorMultiple (Print->PrintedQuestions[NumQst].StrAnswers);
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
2020-05-11 14:11:15 +02:00
if (NumQsts > Print->NumQsts)
2020-05-10 01:42:30 +02:00
Lay_WrongExamExit ();
}
2020-05-09 21:07:50 +02:00
/*****************************************************************************/
2020-05-10 14:03:40 +02:00
/******************** Show an exam print to be answered **********************/
2020-05-09 21:07:50 +02:00
/*****************************************************************************/
2020-05-10 14:03:40 +02:00
static void ExaPrn_ShowExamPrintToFillIt (const char *Title,
2020-05-09 21:07:50 +02:00
struct ExaPrn_Print *Print)
{
extern const char *Hlp_ASSESSMENT_Exams;
/***** Begin box *****/
2020-05-10 14:03:40 +02:00
Box_BoxBegin (NULL,Title,
2020-05-09 21:07:50 +02:00
NULL,NULL,
Hlp_ASSESSMENT_Exams,Box_NOT_CLOSABLE);
Lay_WriteHeaderClassPhoto (false,false,
Gbl.Hierarchy.Ins.InsCod,
Gbl.Hierarchy.Deg.DegCod,
Gbl.Hierarchy.Crs.CrsCod);
if (Print->NumQsts)
{
2020-05-10 14:03:40 +02:00
/***** Show table with questions to answer *****/
2020-05-11 02:28:38 +02:00
Frm_StartFormNoAction (); // Form that can not be submitted, to avoid enter key to send it
HTM_DIV_Begin ("id=\"examprint\""); // Used for AJAX based refresh
2020-05-10 14:03:40 +02:00
ExaPrn_ShowTableWithQstsToFill (Print);
2020-05-11 02:28:38 +02:00
HTM_DIV_End (); // Used for AJAX based refresh
Frm_EndForm ();
2020-05-09 21:07:50 +02:00
2020-05-11 02:28:38 +02:00
/***** Form to end/close this exam print *****/
Frm_StartForm (ActEndExaPrn);
// ExaEvt_PutParamEvtCod (Print->EvtCod);
2020-05-10 15:23:11 +02:00
Btn_PutCreateButton ("He terminado"); // TODO: Translate!!!
2020-05-10 14:03:40 +02:00
Frm_EndForm ();
}
2020-05-09 21:07:50 +02:00
2020-05-10 14:03:40 +02:00
/***** End box *****/
Box_BoxEnd ();
}
2020-05-09 21:07:50 +02:00
2020-05-10 14:03:40 +02:00
/*****************************************************************************/
/********* Show the main part (table) of an exam print to be answered ********/
/*****************************************************************************/
2020-05-09 21:07:50 +02:00
2020-05-10 14:03:40 +02:00
static void ExaPrn_ShowTableWithQstsToFill (struct ExaPrn_Print *Print)
{
unsigned NumQst;
struct Tst_Question Question;
2020-05-09 21:07:50 +02:00
2020-05-10 14:03:40 +02:00
/***** Begin table *****/
HTM_TABLE_BeginWideMarginPadding (10);
2020-05-09 21:07:50 +02:00
2020-05-10 14:03:40 +02:00
/***** Write one row for each question *****/
for (NumQst = 0;
NumQst < Print->NumQsts;
NumQst++)
{
Gbl.RowEvenOdd = NumQst % 2;
2020-05-09 21:07:50 +02:00
2020-05-10 14:03:40 +02:00
/* Create test question */
Tst_QstConstructor (&Question);
Question.QstCod = Print->PrintedQuestions[NumQst].QstCod;
/* Show question */
if (!Tst_GetQstDataFromDB (&Question)) // Question exists
Lay_ShowErrorAndExit ("Wrong question.");
/* Write question and answers */
ExaPrn_WriteQstAndAnsToFill (Print,NumQst,&Question);
/* Destroy test question */
Tst_QstDestructor (&Question);
2020-05-09 21:07:50 +02:00
}
2020-05-10 14:03:40 +02:00
/***** End table *****/
HTM_TABLE_End ();
2020-05-09 21:07:50 +02:00
}
2020-05-09 23:12:53 +02:00
/*****************************************************************************/
/********** Write a row of a test, with one question and its answer **********/
/*****************************************************************************/
2020-05-10 14:03:40 +02:00
static void ExaPrn_WriteQstAndAnsToFill (struct ExaPrn_Print *Print,
2020-05-09 23:12:53 +02:00
unsigned NumQst,
const struct Tst_Question *Question)
{
static struct ExaSet_Set CurrentSet =
{
.ExaCod = -1L,
.SetCod = -1L,
.SetInd = 0,
.NumQstsToPrint = 0,
.Title[0] = '\0'
};
2020-05-10 14:03:40 +02:00
if (Print->PrintedQuestions[NumQst].SetCod != CurrentSet.SetCod)
2020-05-09 23:12:53 +02:00
{
/***** Get data of this set *****/
2020-05-10 14:03:40 +02:00
CurrentSet.ExaCod = Print->ExaCod;
CurrentSet.SetCod = Print->PrintedQuestions[NumQst].SetCod;
2020-05-11 02:28:38 +02:00
2020-05-09 23:12:53 +02:00
ExaSet_GetDataOfSetByCod (&CurrentSet);
/***** Title for this set *****/
HTM_TR_Begin (NULL);
HTM_TD_Begin ("colspan=\"2\"");
ExaSet_WriteSetTitle (&CurrentSet);
HTM_TD_End ();
HTM_TR_End ();
}
/***** Begin row *****/
HTM_TR_Begin (NULL);
/***** Number of question and answer type *****/
HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd);
Tst_WriteNumQst (NumQst + 1);
Tst_WriteAnswerType (Question->Answer.Type);
HTM_TD_End ();
/***** Stem, media and answers *****/
HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd);
/* Stem */
Tst_WriteQstStem (Question->Stem,"TEST_EXA",true);
/* Media */
Med_ShowMedia (&Question->Media,
"TEST_MED_SHOW_CONT",
"TEST_MED_SHOW");
/* Answers */
2020-05-10 14:03:40 +02:00
ExaPrn_WriteAnswersToFill (Print,NumQst,Question);
2020-05-09 23:12:53 +02:00
HTM_TD_End ();
/***** End row *****/
HTM_TR_End ();
}
/*****************************************************************************/
/***************** Write answers of a question to fill them ******************/
/*****************************************************************************/
2020-05-10 14:03:40 +02:00
static void ExaPrn_WriteAnswersToFill (const struct ExaPrn_Print *Print,
2020-05-09 23:12:53 +02:00
unsigned NumQst,
const struct Tst_Question *Question)
{
/***** Write answer depending on type *****/
switch (Question->Answer.Type)
{
case Tst_ANS_INT:
2020-05-11 13:05:38 +02:00
ExaPrn_WriteIntAnsToFill (Print,NumQst);
2020-05-09 23:12:53 +02:00
break;
case Tst_ANS_FLOAT:
2020-05-11 13:05:38 +02:00
ExaPrn_WriteFloatAnsToFill (Print,NumQst);
2020-05-09 23:12:53 +02:00
break;
case Tst_ANS_TRUE_FALSE:
2020-05-11 13:05:38 +02:00
ExaPrn_WriteTFAnsToFill (Print,NumQst);
2020-05-09 23:12:53 +02:00
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
2020-05-11 13:05:38 +02:00
ExaPrn_WriteChoiceAnsToFill (Print,NumQst,Question);
2020-05-09 23:12:53 +02:00
break;
case Tst_ANS_TEXT:
2020-05-11 13:05:38 +02:00
ExaPrn_WriteTextAnsToFill (Print,NumQst);
2020-05-09 23:12:53 +02:00
break;
default:
break;
}
}
/*****************************************************************************/
/****************** Write integer answer when seeing a test ******************/
/*****************************************************************************/
2020-05-11 13:05:38 +02:00
static void ExaPrn_WriteIntAnsToFill (const struct ExaPrn_Print *Print,
2020-05-09 23:12:53 +02:00
unsigned NumQst)
{
2020-05-11 02:28:38 +02:00
char Id[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x"
2020-05-09 23:12:53 +02:00
/***** Write input field for the answer *****/
2020-05-11 02:28:38 +02:00
snprintf (Id,sizeof (Id),
2020-05-09 23:12:53 +02:00
"Ans%010u",
NumQst);
2020-05-11 02:28:38 +02:00
HTM_TxtF ("<input type=\"text\" id=\"%s\" name=\"%s\""
" size=\"11\" maxlength=\"11\" value=\"%s\"",
Id,Id,
Print->PrintedQuestions[NumQst].StrAnswers);
HTM_TxtF (" onchange=\"updateExamPrint('examprint','%s','%s',"
"'act=%ld&ses=%s&EvtCod=%ld&NumQst=%u');"
" return false;\"", // return false is necessary to not submit form
Id,Id,
Act_GetActCod (ActAnsExaPrn),
Gbl.Session.Id,Print->EvtCod,NumQst);
HTM_Txt (" />");
2020-05-09 23:12:53 +02:00
}
/*****************************************************************************/
/****************** Write float answer when seeing a test ********************/
/*****************************************************************************/
2020-05-11 13:05:38 +02:00
static void ExaPrn_WriteFloatAnsToFill (const struct ExaPrn_Print *Print,
2020-05-09 23:12:53 +02:00
unsigned NumQst)
{
2020-05-11 13:05:38 +02:00
char Id[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x"
2020-05-09 23:12:53 +02:00
/***** Write input field for the answer *****/
2020-05-11 13:05:38 +02:00
snprintf (Id,sizeof (Id),
2020-05-09 23:12:53 +02:00
"Ans%010u",
NumQst);
2020-05-11 13:05:38 +02:00
HTM_TxtF ("<input type=\"text\" id=\"%s\" name=\"%s\""
" size=\"11\" maxlength=\"%u\" value=\"%s\"",
Id,Id,Tst_MAX_BYTES_FLOAT_ANSWER,
Print->PrintedQuestions[NumQst].StrAnswers);
HTM_TxtF (" onchange=\"updateExamPrint('examprint','%s','%s',"
"'act=%ld&ses=%s&EvtCod=%ld&NumQst=%u');"
" return false;\"", // return false is necessary to not submit form
Id,Id,
Act_GetActCod (ActAnsExaPrn),
Gbl.Session.Id,Print->EvtCod,NumQst);
HTM_Txt (" />");
2020-05-09 23:12:53 +02:00
}
/*****************************************************************************/
/************** Write false / true answer when seeing a test ****************/
/*****************************************************************************/
2020-05-11 13:05:38 +02:00
static void ExaPrn_WriteTFAnsToFill (const struct ExaPrn_Print *Print,
2020-05-09 23:12:53 +02:00
unsigned NumQst)
{
extern const char *Txt_TF_QST[2];
2020-05-11 13:23:42 +02:00
char Id[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x"
2020-05-09 23:12:53 +02:00
/***** Write selector for the answer *****/
/* Initially user has not answered the question ==> initially all the answers will be blank.
If the user does not confirm the submission of their exam ==>
==> the exam may be half filled ==> the answers displayed will be those selected by the user. */
2020-05-11 13:23:42 +02:00
snprintf (Id,sizeof (Id),
"Ans%010u",
NumQst);
HTM_TxtF ("<select id=\"%s\" name=\"%s\"",Id,Id);
HTM_TxtF (" onchange=\"updateExamPrint('examprint','%s','%s',"
"'act=%ld&ses=%s&EvtCod=%ld&NumQst=%u');"
" return false;\"", // return false is necessary to not submit form
Id,Id,
Act_GetActCod (ActAnsExaPrn),
Gbl.Session.Id,Print->EvtCod,NumQst);
HTM_Txt (" />");
2020-05-10 14:03:40 +02:00
HTM_OPTION (HTM_Type_STRING,"" ,Print->PrintedQuestions[NumQst].StrAnswers[0] == '\0',false,"&nbsp;");
HTM_OPTION (HTM_Type_STRING,"T",Print->PrintedQuestions[NumQst].StrAnswers[0] == 'T' ,false,"%s",Txt_TF_QST[0]);
HTM_OPTION (HTM_Type_STRING,"F",Print->PrintedQuestions[NumQst].StrAnswers[0] == 'F' ,false,"%s",Txt_TF_QST[1]);
2020-05-11 13:23:42 +02:00
HTM_Txt ("</select>");
2020-05-09 23:12:53 +02:00
}
/*****************************************************************************/
/******** Write single or multiple choice answer when seeing a test **********/
/*****************************************************************************/
2020-05-11 13:05:38 +02:00
static void ExaPrn_WriteChoiceAnsToFill (const struct ExaPrn_Print *Print,
2020-05-09 23:12:53 +02:00
unsigned NumQst,
const struct Tst_Question *Question)
{
unsigned NumOpt;
unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION]; // Indexes of all answers of this question
bool UsrAnswers[Tst_MAX_OPTIONS_PER_QUESTION];
2020-05-11 14:11:15 +02:00
char Id[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x"
2020-05-09 23:12:53 +02:00
/***** Get indexes for this question from string *****/
2020-05-10 14:03:40 +02:00
TstPrn_GetIndexesFromStr (Print->PrintedQuestions[NumQst].StrIndexes,Indexes);
2020-05-09 23:12:53 +02:00
/***** Get the user's answers for this question from string *****/
2020-05-10 14:03:40 +02:00
TstPrn_GetAnswersFromStr (Print->PrintedQuestions[NumQst].StrAnswers,UsrAnswers);
2020-05-09 23:12:53 +02:00
/***** Begin table *****/
HTM_TABLE_BeginPadding (2);
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
{
/***** Indexes are 0 1 2 3... if no shuffle
or 3 1 0 2... (example) if shuffle *****/
HTM_TR_Begin (NULL);
/***** Write selectors and letter of this option *****/
/* Initially user has not answered the question ==> initially all the answers will be blank.
If the user does not confirm the submission of their exam ==>
==> the exam may be half filled ==> the answers displayed will be those selected by the user. */
HTM_TD_Begin ("class=\"LT\"");
2020-05-11 14:11:15 +02:00
snprintf (Id,sizeof (Id),
2020-05-09 23:12:53 +02:00
"Ans%010u",
NumQst);
if (Question->Answer.Type == Tst_ANS_UNIQUE_CHOICE)
2020-05-11 14:11:15 +02:00
HTM_INPUT_RADIO (Id,false,
2020-05-09 23:12:53 +02:00
"id=\"Ans%010u_%u\" value=\"%u\"%s"
" onclick=\"selectUnselectRadio(this,this.form.Ans%010u,%u);\"",
NumQst,NumOpt,
Indexes[NumOpt],
UsrAnswers[Indexes[NumOpt]] ? " checked=\"checked\"" :
"",
NumQst,Question->Answer.NumOptions);
else // Answer.Type == Tst_ANS_MULTIPLE_CHOICE
2020-05-11 14:11:15 +02:00
HTM_INPUT_CHECKBOX (Id,HTM_DONT_SUBMIT_ON_CHANGE,
2020-05-09 23:12:53 +02:00
"id=\"Ans%010u_%u\" value=\"%u\"%s",
NumQst,NumOpt,
Indexes[NumOpt],
UsrAnswers[Indexes[NumOpt]] ? " checked=\"checked\"" :
"");
2020-05-11 14:11:15 +02:00
/* HTM_TxtF (" onchange=\"updateExamPrint('examprint','%s','%s',"
"'act=%ld&ses=%s&EvtCod=%ld&NumQst=%u');"
" return false;\"", // return false is necessary to not submit form
Id,Id,
Act_GetActCod (ActAnsExaPrn),
Gbl.Session.Id,Print->EvtCod,NumQst);
HTM_Txt (" />"); */
2020-05-09 23:12:53 +02:00
HTM_TD_End ();
HTM_TD_Begin ("class=\"LT\"");
HTM_LABEL_Begin ("for=\"Ans%010u_%u\" class=\"ANS_TXT\"",NumQst,NumOpt);
HTM_TxtF ("%c)&nbsp;",'a' + (char) NumOpt);
HTM_LABEL_End ();
HTM_TD_End ();
/***** Write the option text *****/
HTM_TD_Begin ("class=\"LT\"");
HTM_LABEL_Begin ("for=\"Ans%010u_%u\" class=\"ANS_TXT\"",NumQst,NumOpt);
HTM_Txt (Question->Answer.Options[Indexes[NumOpt]].Text);
HTM_LABEL_End ();
Med_ShowMedia (&Question->Answer.Options[Indexes[NumOpt]].Media,
"TEST_MED_SHOW_CONT",
"TEST_MED_SHOW");
HTM_TD_End ();
HTM_TR_End ();
}
/***** End table *****/
HTM_TABLE_End ();
}
/*****************************************************************************/
/******************** Write text answer when seeing a test *******************/
/*****************************************************************************/
2020-05-11 13:05:38 +02:00
static void ExaPrn_WriteTextAnsToFill (const struct ExaPrn_Print *Print,
2020-05-09 23:12:53 +02:00
unsigned NumQst)
{
2020-05-11 13:05:38 +02:00
char Id[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x"
2020-05-09 23:12:53 +02:00
/***** Write input field for the answer *****/
2020-05-11 13:05:38 +02:00
snprintf (Id,sizeof (Id),
2020-05-09 23:12:53 +02:00
"Ans%010u",
NumQst);
2020-05-11 13:05:38 +02:00
HTM_TxtF ("<input type=\"text\" id=\"%s\" name=\"%s\""
" size=\"40\" maxlength=\"%u\" value=\"%s\"",
Id,Id,Tst_MAX_CHARS_ANSWERS_ONE_QST,
Print->PrintedQuestions[NumQst].StrAnswers);
HTM_TxtF (" onchange=\"updateExamPrint('examprint','%s','%s',"
"'act=%ld&ses=%s&EvtCod=%ld&NumQst=%u');"
" return false;\"", // return false is necessary to not submit form
Id,Id,
Act_GetActCod (ActAnsExaPrn),
Gbl.Session.Id,Print->EvtCod,NumQst);
HTM_Txt (" />");
2020-05-09 23:12:53 +02:00
}
2020-05-09 21:07:50 +02:00
/*****************************************************************************/
/********************** Receive answer to an exam print **********************/
/*****************************************************************************/
void ExaPrn_ReceivePrintAnswer (void)
{
2020-05-11 02:28:38 +02:00
struct ExaPrn_Print Print;
unsigned NumQst;
2020-05-11 13:05:38 +02:00
/***** Reset print *****/
2020-05-11 02:28:38 +02:00
ExaPrn_ResetPrint (&Print);
/***** Get and check parameters *****/
Print.EvtCod = ExaEvt_GetParamEvtCod ();
Print.UsrCod = Gbl.Usrs.Me.UsrDat.UsrCod;
ExaPrn_GetPrintDataByEvtCodAndUsrCod (&Print);
if (Print.PrnCod <= 0)
2020-05-11 14:11:15 +02:00
Lay_WrongExamExit ();
2020-05-11 02:28:38 +02:00
/***** Get questions and answers from database *****/
ExaPrn_GetPrintQuestionsFromDB (&Print);
/***** Get answers from form to assess a test *****/
NumQst = ExaPrn_GetAnswerFromForm (&Print);
/***** Update answer in database *****/
/* Compute question score and store in database */
ExaPrn_ComputeScoreAndStoreQuestionOfPrint (&Print,NumQst);
/* Update exam print in database */
ExaPrn_UpdatePrintInDB (&Print);
/***** Show table with questions to answer *****/
ExaPrn_ShowTableWithQstsToFill (&Print);
}
/*****************************************************************************/
/******** Get questions and answers from form to assess an exam print ********/
/*****************************************************************************/
static unsigned ExaPrn_GetAnswerFromForm (struct ExaPrn_Print *Print)
{
unsigned NumQst;
char AnsName[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x"
/***** Get question index from form *****/
NumQst = ExaPrn_GetParamQstInd ();
/***** Get answers selected by user for this question *****/
snprintf (AnsName,sizeof (AnsName),
"Ans%010u",
NumQst);
Par_GetParMultiToText (AnsName,Print->PrintedQuestions[NumQst].StrAnswers,
Tst_MAX_BYTES_ANSWERS_ONE_QST); /* If answer type == T/F ==> " ", "T", "F"; if choice ==> "0", "2",... */
return NumQst;
}
/*****************************************************************************/
/****************** Write parameter with index of question *******************/
/*****************************************************************************/
/*
static void ExaPrn_PutParamNumQst (unsigned NumQst)
{
Par_PutHiddenParamUnsigned (NULL,"NumQst",NumQst);
}
*/
/*****************************************************************************/
/******************* Get parameter with index of question ********************/
/*****************************************************************************/
static unsigned ExaPrn_GetParamQstInd (void)
{
long NumQst;
NumQst = Par_GetParToLong ("NumQst");
if (NumQst < 0)
Lay_ShowErrorAndExit ("Wrong number of question.");
return (unsigned) NumQst;
}
/*****************************************************************************/
/*********** Compute score of each question and store in database ************/
/*****************************************************************************/
static void ExaPrn_ComputeScoresAndStoreQuestionsOfPrint (struct ExaPrn_Print *Print,
bool UpdateQstScore)
{
unsigned NumQst;
/***** Initialize total score *****/
Print->Score = 0.0;
Print->NumQstsNotBlank = 0;
/***** Compute and store scores of all questions *****/
for (NumQst = 0;
NumQst < Print->NumQsts;
NumQst++)
{
/* Compute question score and store in database */
ExaPrn_ComputeScoreAndStoreQuestionOfPrint (Print,NumQst);
/* Accumulate total score */
Print->Score += Print->PrintedQuestions[NumQst].Score;
if (Print->PrintedQuestions[NumQst].AnswerIsNotBlank)
Print->NumQstsNotBlank++;
/* Update the number of hits and the score of this question in tests database */
if (UpdateQstScore)
Tst_UpdateQstScoreInDB (&Print->PrintedQuestions[NumQst]);
}
}
/*****************************************************************************/
/*********** Compute score of one question and store in database *************/
/*****************************************************************************/
static void ExaPrn_ComputeScoreAndStoreQuestionOfPrint (struct ExaPrn_Print *Print,
unsigned NumQst)
{
struct Tst_Question Question;
/***** Compute question score *****/
Tst_QstConstructor (&Question);
Question.QstCod = Print->PrintedQuestions[NumQst].QstCod;
Question.Answer.Type = Tst_GetQstAnswerType (Question.QstCod);
TstPrn_ComputeAnswerScore (&Print->PrintedQuestions[NumQst],&Question);
Tst_QstDestructor (&Question);
/***** Store test exam question in database *****/
ExaPrn_StoreOneQstOfPrintInDB (Print,
NumQst); // 0, 1, 2, 3...
}
/*****************************************************************************/
/************* Store user's answers of an test exam into database ************/
/*****************************************************************************/
static void ExaPrn_StoreOneQstOfPrintInDB (const struct ExaPrn_Print *Print,
unsigned NumQst)
{
char StrIndexes[Tst_MAX_BYTES_INDEXES_ONE_QST + 1];
char StrAnswers[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1];
/***** Replace each separator of multiple parameters by a comma *****/
/* In database commas are used as separators instead of special chars */
Par_ReplaceSeparatorMultipleByComma (Print->PrintedQuestions[NumQst].StrIndexes,StrIndexes);
Par_ReplaceSeparatorMultipleByComma (Print->PrintedQuestions[NumQst].StrAnswers,StrAnswers);
/***** Insert question and user's answers into database *****/
Str_SetDecimalPointToUS (); // To print the floating point as a dot
DB_QueryREPLACE ("can not update a question in an exam print",
"REPLACE INTO exa_print_questions"
" (PrnCod,QstCod,QstInd,SetCod,Score,Indexes,Answers)"
" VALUES"
" (%ld,%ld,%u,%ld,'%.15lg','%s','%s')",
Print->PrnCod,Print->PrintedQuestions[NumQst].QstCod,
NumQst, // 0, 1, 2, 3...
Print->PrintedQuestions[NumQst].SetCod,
Print->PrintedQuestions[NumQst].Score,
StrIndexes,
StrAnswers);
Str_SetDecimalPointToLocal (); // Return to local system
}
/*****************************************************************************/
/********************** Update exam print in database ************************/
/*****************************************************************************/
static void ExaPrn_UpdatePrintInDB (const struct ExaPrn_Print *Print)
{
/***** Update exam print in database *****/
Str_SetDecimalPointToUS (); // To print the floating point as a dot
DB_QueryUPDATE ("can not update exam print",
"UPDATE exa_prints"
" SET EndTime=NOW(),"
"NumQstsNotBlank=%u,"
"Sent='%c',"
"Score='%.15lg'"
" WHERE PrnCod=%ld"
" AND EvtCod=%ld AND UsrCod=%ld", // Extra checks
Print->NumQstsNotBlank,
Print->Sent ? 'Y' :
'N',
Print->Score,
Print->PrnCod,
Print->EvtCod,
Gbl.Usrs.Me.UsrDat.UsrCod);
Str_SetDecimalPointToLocal (); // Return to local system
2020-05-09 21:07:50 +02:00
}
2020-05-10 15:23:11 +02:00
/*****************************************************************************/
/********************** Receive answer to an exam print **********************/
/*****************************************************************************/
void ExaPrn_EndPrintAnswer (void)
{
Ale_ShowAlert (Ale_INFO,"Terminar de contestar el examen.");
}
2020-05-09 21:07:50 +02:00
/*****************************************************************************/
/***************** Write parameter with code of exam print *******************/
/*****************************************************************************/
2020-05-11 02:28:38 +02:00
// static void ExaPrn_PutParamPrnCod (long ExaCod)
// {
// Par_PutHiddenParamLong (NULL,"PrnCod",ExaCod);
// }
2020-05-09 21:07:50 +02:00
/*****************************************************************************/
/***************** Get parameter with code of exam print *********************/
/*****************************************************************************/
2020-05-09 23:12:53 +02:00
// static long ExaPrn_GetParamPrnCod (void)
// {
// /***** Get code of exam print *****/
// return Par_GetParToLong ("PrnCod");
// }