swad-core/swad_exam_print.c

1310 lines
49 KiB
C
Raw Normal View History

2020-05-17 02:28:30 +02:00
// swad_exam_print.c: exam prints (each copy of an exam in a session for a student)
2020-05-07 12:57:12 +02:00
/*
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-2023 Antonio Ca<EFBFBD>as Vargas
2020-05-07 12:57:12 +02:00
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 <string.h> // For string functions
#include "swad_action_list.h"
#include "swad_alert.h"
#include "swad_autolink.h"
2020-05-07 12:57:12 +02:00
#include "swad_box.h"
#include "swad_database.h"
#include "swad_error.h"
2020-05-07 12:57:12 +02:00
#include "swad_exam.h"
#include "swad_exam_database.h"
2020-05-23 13:24:08 +02:00
#include "swad_exam_log.h"
2020-05-13 12:53:27 +02:00
#include "swad_exam_print.h"
2020-05-07 12:57:12 +02:00
#include "swad_exam_result.h"
2020-05-17 02:28:30 +02:00
#include "swad_exam_session.h"
2020-05-07 12:57:12 +02:00
#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"
2021-01-20 00:42:59 +01:00
#include "swad_ID.h"
#include "swad_parameter.h"
#include "swad_parameter_code.h"
2021-01-20 00:42:59 +01:00
#include "swad_photo.h"
2020-05-07 12:57:12 +02:00
/*****************************************************************************/
/************** External global variables from others modules ****************/
/*****************************************************************************/
extern struct Globals Gbl;
/*****************************************************************************/
/***************************** Private prototypes ****************************/
/*****************************************************************************/
static void ExaPrn_GetPrintDataFromRow (MYSQL_RES **mysql_res,
struct ExaPrn_Print *Print,
unsigned NumPrints);
2020-05-07 12:57:12 +02:00
2020-05-15 01:07:46 +02:00
static void ExaPrn_GetQuestionsForNewPrintFromDB (struct ExaPrn_Print *Print,long ExaCod);
2020-05-09 01:37:00 +02:00
static unsigned ExaPrn_GetSomeQstsFromSetToPrint (struct ExaPrn_Print *Print,
struct ExaSet_Set *Set,
unsigned *NumQstsInPrint);
2020-05-13 00:28:32 +02:00
static void ExaPrn_GenerateChoiceIndexes (struct TstPrn_PrintedQuestion *PrintedQuestion,
bool Shuffle);
static void ExaPrn_CreatePrint (struct ExaPrn_Print *Print);
2020-05-10 01:42:30 +02:00
2020-05-17 20:12:37 +02:00
static void ExaPrn_ShowExamPrintToFillIt (struct Exa_Exams *Exams,
2021-01-20 00:42:59 +01:00
struct ExaPrn_Print *Print);
2020-05-19 13:12:26 +02:00
static void ExaPrn_GetAndWriteDescription (long ExaCod);
2020-05-25 00:18:09 +02:00
static void ExaPrn_ShowTableWithQstsToFill (struct Exa_Exams *Exams,
const struct ExaPrn_Print *Print);
2020-05-17 20:12:37 +02:00
static void ExaPrn_WriteQstAndAnsToFill (const struct ExaPrn_Print *Print,
unsigned QstInd,
struct Qst_Question *Question);
2020-05-17 20:12:37 +02:00
static void ExaPrn_WriteAnswersToFill (const struct ExaPrn_Print *Print,
unsigned QstInd,
struct Qst_Question *Question);
2020-05-23 13:24:08 +02:00
2020-05-19 15:17:52 +02:00
//-----------------------------------------------------------------------------
2020-05-11 13:05:38 +02:00
static void ExaPrn_WriteIntAnsToFill (const struct ExaPrn_Print *Print,
unsigned QstInd,
__attribute__((unused)) struct Qst_Question *Question);
2020-06-17 02:31:42 +02:00
static void ExaPrn_WriteFltAnsToFill (const struct ExaPrn_Print *Print,
unsigned QstInd,
__attribute__((unused)) struct Qst_Question *Question);
2020-05-19 15:17:52 +02:00
static void ExaPrn_WriteTF_AnsToFill (const struct ExaPrn_Print *Print,
unsigned QstInd,
__attribute__((unused)) struct Qst_Question *Question);
2020-05-19 15:17:52 +02:00
static void ExaPrn_WriteChoAnsToFill (const struct ExaPrn_Print *Print,
unsigned QstInd,
struct Qst_Question *Question);
2020-05-19 15:17:52 +02:00
static void ExaPrn_WriteTxtAnsToFill (const struct ExaPrn_Print *Print,
unsigned QstInd,
__attribute__((unused)) struct Qst_Question *Question);
2020-05-19 15:17:52 +02:00
//-----------------------------------------------------------------------------
2020-05-23 13:24:08 +02:00
2020-05-19 15:17:52 +02:00
static void ExaPrn_WriteJSToUpdateExamPrint (const struct ExaPrn_Print *Print,
unsigned QstInd,
2020-05-19 15:17:52 +02:00
const char *Id,int NumOpt);
2020-05-09 21:07:50 +02:00
2020-05-23 13:24:08 +02:00
static void ExaPrn_GetAnswerFromForm (struct ExaPrn_Print *Print,unsigned QstInd);
2020-05-11 02:28:38 +02:00
static unsigned ExaPrn_GetParQstInd (void);
2020-05-11 02:28:38 +02:00
static void ExaPrn_ComputeScoreAndStoreQuestionOfPrint (struct ExaPrn_Print *Print,
unsigned QstInd);
2020-05-13 12:53:27 +02:00
//-----------------------------------------------------------------------------
static void ExaPrn_GetCorrectAndComputeIntAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
struct Qst_Question *Question);
2020-05-13 12:53:27 +02:00
static void ExaPrn_GetCorrectAndComputeFltAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
struct Qst_Question *Question);
2020-05-13 12:53:27 +02:00
static void ExaPrn_GetCorrectAndComputeTF_AnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
struct Qst_Question *Question);
2020-05-13 12:53:27 +02:00
static void ExaPrn_GetCorrectAndComputeChoAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
struct Qst_Question *Question);
2020-05-13 12:53:27 +02:00
static void ExaPrn_GetCorrectAndComputeTxtAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
struct Qst_Question *Question);
2020-05-23 13:24:08 +02:00
//-----------------------------------------------------------------------------
static void ExaPrn_GetCorrectIntAnswerFromDB (struct Qst_Question *Question);
static void ExaPrn_GetCorrectFltAnswerFromDB (struct Qst_Question *Question);
static void ExaPrn_GetCorrectTF_AnswerFromDB (struct Qst_Question *Question);
static void ExaPrn_GetCorrectChoAnswerFromDB (struct Qst_Question *Question);
static void ExaPrn_GetCorrectTxtAnswerFromDB (struct Qst_Question *Question);
2020-05-13 12:53:27 +02:00
//-----------------------------------------------------------------------------
2020-05-07 18:33:26 +02:00
/*****************************************************************************/
/**************************** Reset exam print *******************************/
/*****************************************************************************/
2020-05-16 02:04:36 +02:00
void ExaPrn_ResetPrint (struct ExaPrn_Print *Print)
2020-05-07 18:33:26 +02:00
{
2020-06-24 20:10:57 +02:00
Print->PrnCod = -1L;
2020-05-17 02:28:30 +02:00
Print->SesCod = -1L;
2020-05-10 01:42:30 +02:00
Print->UsrCod = -1L;
Print->TimeUTC[Dat_STR_TIME] =
Print->TimeUTC[Dat_END_TIME] = (time_t) 0;
2020-05-07 18:33:26 +02:00
Print->Sent = false; // After creating an exam print, it's not sent
2020-06-22 19:27:23 +02:00
Print->NumQsts.All =
Print->NumQsts.NotBlank =
Print->NumQsts.Valid.Correct =
Print->NumQsts.Valid.Wrong.Negative =
Print->NumQsts.Valid.Wrong.Zero =
Print->NumQsts.Valid.Wrong.Positive =
Print->NumQsts.Valid.Blank =
Print->NumQsts.Valid.Total = 0;
Print->Score.All =
Print->Score.Valid = 0.0;
2020-05-07 18:33:26 +02:00
}
2020-05-07 12:57:12 +02:00
/*****************************************************************************/
2020-05-17 02:28:30 +02:00
/********************** Show print of an exam in a session *******************/
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
{
2020-05-17 20:12:37 +02:00
extern const char *Txt_You_dont_have_access_to_the_exam;
2020-05-07 12:57:12 +02:00
struct Exa_Exams Exams;
2020-05-17 02:28:30 +02:00
struct ExaSes_Session Session;
2020-05-07 12:57:12 +02:00
struct ExaPrn_Print Print;
/***** Reset exams context *****/
Exa_ResetExams (&Exams);
Exa_ResetExam (&Exams.Exam);
2020-05-17 02:28:30 +02:00
ExaSes_ResetSession (&Session);
2020-05-07 12:57:12 +02:00
/***** Get and check parameters *****/
ExaSes_GetAndCheckPars (&Exams,&Session);
2020-05-07 12:57:12 +02:00
2020-05-17 02:28:30 +02:00
/***** Check if I can access to this session *****/
if (ExaSes_CheckIfICanAnswerThisSession (&Exams.Exam,&Session))
2020-05-09 01:37:00 +02:00
{
2020-09-27 17:38:51 +02:00
/***** Set basic data of exam print *****/
2020-05-17 02:28:30 +02:00
Print.SesCod = Session.SesCod;
2020-05-15 01:07:46 +02:00
Print.UsrCod = Gbl.Usrs.Me.UsrDat.UsrCod;
2020-09-27 17:45:23 +02:00
/***** Get exam print data from database *****/
ExaPrn_GetPrintDataBySesCodAndUsrCod (&Print);
2020-09-27 17:45:23 +02:00
2021-01-20 00:42:59 +01:00
if (Print.PrnCod <= 0) // Exam print does not exists ==> create it
2020-05-15 01:07:46 +02:00
{
2020-09-27 17:45:23 +02:00
/***** Set again basic data of exam print *****/
Print.SesCod = Session.SesCod;
Print.UsrCod = Gbl.Usrs.Me.UsrDat.UsrCod;
2020-05-15 01:07:46 +02:00
/***** Get questions from database *****/
ExaPrn_GetQuestionsForNewPrintFromDB (&Print,Exams.Exam.ExaCod);
2020-05-15 01:07:46 +02:00
2020-06-18 20:06:17 +02:00
if (Print.NumQsts.All)
2020-05-23 15:53:53 +02:00
{
2020-05-23 19:08:59 +02:00
/***** Create new exam print in database *****/
ExaPrn_CreatePrint (&Print);
2020-05-23 15:53:53 +02:00
/***** Set log print code and action *****/
ExaLog_SetPrnCod (Print.PrnCod);
ExaLog_SetAction (ExaLog_START_EXAM);
2020-05-23 19:08:59 +02:00
ExaLog_SetIfCanAnswer (true);
2020-05-23 15:53:53 +02:00
}
2020-09-27 17:38:51 +02:00
}
2021-01-20 00:42:59 +01:00
else // Exam print exists
2020-09-27 17:38:51 +02:00
{
/***** Get exam print data from database *****/
ExaPrn_GetPrintDataBySesCodAndUsrCod (&Print);
2020-09-27 17:38:51 +02:00
2020-05-23 15:53:53 +02:00
/***** Get questions and current user's answers from database *****/
ExaPrn_GetPrintQuestionsFromDB (&Print);
2020-05-15 01:07:46 +02:00
2020-05-23 15:53:53 +02:00
/***** Set log print code and action *****/
ExaLog_SetPrnCod (Print.PrnCod);
ExaLog_SetAction (ExaLog_RESUME_EXAM);
2020-05-23 19:08:59 +02:00
ExaLog_SetIfCanAnswer (true);
2020-05-23 15:53:53 +02:00
}
2020-05-22 20:10:45 +02:00
/***** Show test to be answered *****/
ExaPrn_ShowExamPrintToFillIt (&Exams,&Print);
2020-05-09 01:37:00 +02:00
}
2020-05-23 15:53:53 +02:00
else // Session not open or accessible
/***** Show warning *****/
2020-05-17 20:12:37 +02:00
Ale_ShowAlert (Ale_INFO,Txt_You_dont_have_access_to_the_exam);
2020-05-07 12:57:12 +02:00
}
2020-06-24 20:10:57 +02:00
/*****************************************************************************/
/**************** Get data of an exam print using print code *****************/
/*****************************************************************************/
void ExaPrn_GetPrintDataByPrnCod (struct ExaPrn_Print *Print)
2020-06-24 20:10:57 +02:00
{
MYSQL_RES *mysql_res;
unsigned NumPrints;
2020-06-24 20:10:57 +02:00
/***** Make database query *****/
NumPrints = Exa_DB_GetPrintDataByPrnCod (&mysql_res,Print->PrnCod);
2020-06-24 20:10:57 +02:00
/***** Get data of print *****/
ExaPrn_GetPrintDataFromRow (&mysql_res,Print,NumPrints);
2020-06-24 20:10:57 +02:00
}
2020-05-09 21:07:50 +02:00
/*****************************************************************************/
2020-05-17 02:28:30 +02:00
/******** Get data of an exam print using session code and user code *********/
2020-05-09 21:07:50 +02:00
/*****************************************************************************/
void ExaPrn_GetPrintDataBySesCodAndUsrCod (struct ExaPrn_Print *Print)
2020-05-09 21:07:50 +02:00
{
2020-05-10 01:42:30 +02:00
MYSQL_RES *mysql_res;
unsigned NumPrints;
2020-05-10 01:42:30 +02:00
/***** Make database query *****/
NumPrints = Exa_DB_GetPrintDataBySesCodAndUsrCod (&mysql_res,
Print->SesCod,
Print->UsrCod);
2020-06-24 20:10:57 +02:00
/***** Get data of print *****/
ExaPrn_GetPrintDataFromRow (&mysql_res,Print,NumPrints);
2020-06-24 20:10:57 +02:00
}
/*****************************************************************************/
/************************* Get assignment data *******************************/
/*****************************************************************************/
static void ExaPrn_GetPrintDataFromRow (MYSQL_RES **mysql_res,
struct ExaPrn_Print *Print,
unsigned NumPrints)
2020-06-24 20:10:57 +02:00
{
MYSQL_ROW row;
if (NumPrints)
2020-05-10 01:42:30 +02:00
{
/* Get next row from result */
2020-06-24 20:10:57 +02:00
row = mysql_fetch_row (*mysql_res);
2020-05-10 01:42:30 +02:00
/* Get print code (row[0]) */
Print->PrnCod = Str_ConvertStrCodToLongCod (row[0]);
2020-06-24 20:10:57 +02:00
/* Get session code (row[1]) */
Print->SesCod = Str_ConvertStrCodToLongCod (row[1]);
2020-05-10 01:42:30 +02:00
2020-06-24 20:10:57 +02:00
/* Get user code (row[2]) */
Print->UsrCod = Str_ConvertStrCodToLongCod (row[2]);
/* Get date-time (row[3] and row[4] hold UTC date-time) */
Print->TimeUTC[Dat_STR_TIME] = Dat_GetUNIXTimeFromStr (row[3]);
Print->TimeUTC[Dat_END_TIME] = Dat_GetUNIXTimeFromStr (row[4]);
2020-06-24 20:10:57 +02:00
/* Get number of questions (row[5]) */
if (sscanf (row[5],"%u",&Print->NumQsts.All) != 1)
2020-06-18 20:06:17 +02:00
Print->NumQsts.All = 0;
2020-05-10 01:42:30 +02:00
2020-06-24 20:10:57 +02:00
/* Get number of questions not blank (row[6]) */
if (sscanf (row[6],"%u",&Print->NumQsts.NotBlank) != 1)
2020-06-18 20:06:17 +02:00
Print->NumQsts.NotBlank = 0;
2020-05-10 01:42:30 +02:00
2020-06-24 20:10:57 +02:00
/* Get if exam has been sent (row[7]) */
Print->Sent = (row[7][0] == 'Y');
2020-05-10 01:42:30 +02:00
2020-06-24 20:10:57 +02:00
/* Get score (row[8]) */
2020-05-10 01:42:30 +02:00
Str_SetDecimalPointToUS (); // To get the decimal point as a dot
2020-06-24 20:10:57 +02:00
if (sscanf (row[8],"%lf",&Print->Score.All) != 1)
2020-06-18 20:06:17 +02:00
Print->Score.All = 0.0;
2020-05-10 01:42:30 +02:00
Str_SetDecimalPointToLocal (); // Return to local system
}
else
2020-06-24 20:10:57 +02:00
ExaPrn_ResetPrint (Print);
2020-05-10 01:42:30 +02:00
/***** Free structure that stores the query result *****/
2020-06-24 20:10:57 +02:00
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-15 01:07:46 +02:00
static void ExaPrn_GetQuestionsForNewPrintFromDB (struct ExaPrn_Print *Print,long ExaCod)
2020-05-07 12:57:12 +02:00
{
MYSQL_RES *mysql_res;
unsigned NumSets;
unsigned NumSet;
struct ExaSet_Set Set;
2020-05-09 01:37:00 +02:00
unsigned NumQstsFromSet;
unsigned NumQstsInPrint = 0;
2020-05-07 12:57:12 +02:00
2020-05-09 01:37:00 +02:00
/***** Get questions from all sets *****/
2020-06-18 20:06:17 +02:00
Print->NumQsts.All = 0;
NumSets = Exa_DB_GetExamSets (&mysql_res,ExaCod);
/***** For each set in exam... *****/
for (NumSet = 0;
NumSet < NumSets;
NumSet++)
{
/***** Create set of questions *****/
ExaSet_ResetSet (&Set);
/***** Get set data *****/
ExaSet_GetSetDataFromRow (mysql_res,&Set);
/***** Questions in this set *****/
NumQstsFromSet = ExaPrn_GetSomeQstsFromSetToPrint (Print,&Set,&NumQstsInPrint);
Print->NumQsts.All += NumQstsFromSet;
}
2020-05-07 12:57:12 +02:00
2020-05-09 01:37:00 +02:00
/***** Check *****/
if (Print->NumQsts.All != NumQstsInPrint)
Err_ShowErrorAndExit ("Wrong number of questions.");
2020-05-09 01:37:00 +02:00
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
/*****************************************************************************/
/********************** Get some questions from a set ************************/
2020-05-07 18:33:26 +02:00
/*****************************************************************************/
2020-05-09 01:37:00 +02:00
static unsigned ExaPrn_GetSomeQstsFromSetToPrint (struct ExaPrn_Print *Print,
struct ExaSet_Set *Set,
unsigned *NumQstsInPrint)
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;
Qst_AnswerType_t AnswerType;
2020-05-07 18:33:26 +02:00
bool Shuffle;
/***** Get questions from database *****/
NumQstsInSet = Exa_DB_GetSomeQstsFromSetToPrint (&mysql_res,
Set->SetCod,
Set->NumQstsToPrint);
2020-05-07 18:33:26 +02:00
/***** Questions in this set *****/
for (NumQstInSet = 0, The_ResetRowColor ();
2020-05-09 01:37:00 +02:00
NumQstInSet < NumQstsInSet;
NumQstInSet++, (*NumQstsInPrint)++, The_ChangeRowColor ())
2020-05-07 18:33:26 +02:00
{
/***** Get question data *****/
row = mysql_fetch_row (mysql_res);
/*
row[0] QstCod
row[1] AnsType
row[2] Shuffle
*/
/* Get question code (row[0]) */
Print->PrintedQuestions[*NumQstsInPrint].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[*NumQstsInPrint].SetCod = Set->SetCod;
2020-05-09 21:07:50 +02:00
2020-05-07 18:33:26 +02:00
/* Get answer type (row[1]) */
AnswerType = Qst_ConvertFromStrAnsTypDBToAnsTyp (row[1]);
2020-05-07 18:33:26 +02:00
/* Get shuffle (row[2]) */
Shuffle = (row[2][0] == 'Y');
/* Set indexes of answers */
switch (AnswerType)
{
case Qst_ANS_INT:
case Qst_ANS_FLOAT:
case Qst_ANS_TRUE_FALSE:
case Qst_ANS_TEXT:
Print->PrintedQuestions[*NumQstsInPrint].StrIndexes[0] = '\0';
2020-05-07 18:33:26 +02:00
break;
case Qst_ANS_UNIQUE_CHOICE:
case Qst_ANS_MULTIPLE_CHOICE:
2020-05-07 18:33:26 +02:00
/* If answer type is unique or multiple option,
generate indexes of answers depending on shuffle */
ExaPrn_GenerateChoiceIndexes (&Print->PrintedQuestions[*NumQstsInPrint],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 answers will be blank.
2020-05-07 18:33:26 +02:00
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. */
Print->PrintedQuestions[*NumQstsInPrint].StrAnswers[0] = '\0';
2020-05-22 20:10:45 +02:00
/* Reset score of this question in print */
Print->PrintedQuestions[*NumQstsInPrint].Score = 0.0;
2020-05-07 18:33:26 +02:00
}
2020-05-09 01:37:00 +02:00
return NumQstsInSet;
}
2020-05-13 00:28:32 +02:00
/*****************************************************************************/
/*************** Generate choice indexes depending on shuffle ****************/
/*****************************************************************************/
static void ExaPrn_GenerateChoiceIndexes (struct TstPrn_PrintedQuestion *PrintedQuestion,
bool Shuffle)
{
struct Qst_Question Question;
2020-05-13 00:28:32 +02:00
unsigned NumOpt;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned Index;
bool ErrorInIndex;
char StrInd[1 + Cns_MAX_DECIMAL_DIGITS_UINT + 1];
/***** Create test question *****/
Qst_QstConstructor (&Question);
2020-05-13 00:28:32 +02:00
Question.QstCod = PrintedQuestion->QstCod;
/***** Get answers of question from database *****/
Question.Answer.NumOptions = Exa_DB_GetQstAnswersFromSet (&mysql_res,
Question.QstCod,
Shuffle);
2020-05-13 00:28:32 +02:00
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
for (NumOpt = 0;
NumOpt < Question.Answer.NumOptions;
NumOpt++)
{
/***** Get next answer *****/
row = mysql_fetch_row (mysql_res);
/***** Assign index (row[0]).
Index is 0,1,2,3... if no shuffle
or 1,3,0,2... (example) if shuffle *****/
ErrorInIndex = false;
if (sscanf (row[0],"%u",&Index) == 1)
{
if (Index >= Qst_MAX_OPTIONS_PER_QUESTION)
2020-05-13 00:28:32 +02:00
ErrorInIndex = true;
}
else
ErrorInIndex = true;
if (ErrorInIndex)
Err_WrongAnswerIndexExit ();
2020-05-13 00:28:32 +02:00
if (NumOpt == 0)
snprintf (StrInd,sizeof (StrInd),"%u",Index);
else
snprintf (StrInd,sizeof (StrInd),",%u",Index);
Str_Concat (PrintedQuestion->StrIndexes,StrInd,
sizeof (PrintedQuestion->StrIndexes) - 1);
2020-05-13 00:28:32 +02:00
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
/***** Destroy test question *****/
Qst_QstDestructor (&Question);
2020-05-13 00:28:32 +02:00
}
2020-05-09 01:37:00 +02:00
/*****************************************************************************/
/***************** Create new blank exam print in database *******************/
/*****************************************************************************/
static void ExaPrn_CreatePrint (struct ExaPrn_Print *Print)
2020-05-09 01:37:00 +02:00
{
unsigned QstInd;
2020-05-22 20:10:45 +02:00
/***** Insert new exam print into database *****/
Print->PrnCod = Exa_DB_CreatePrint (Print);
2020-05-22 20:10:45 +02:00
/***** Store all questions (with blank answers)
of this exam print just generated in database *****/
for (QstInd = 0;
QstInd < Print->NumQsts.All;
QstInd++)
Exa_DB_StoreOneQstOfPrint (Print,QstInd);
2020-05-09 01:37:00 +02:00
}
2020-05-10 01:42:30 +02:00
/*****************************************************************************/
/************* Get the questions of an exam print from database **************/
/*****************************************************************************/
2020-05-16 02:04:36 +02:00
void ExaPrn_GetPrintQuestionsFromDB (struct ExaPrn_Print *Print)
2020-05-10 01:42:30 +02:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned QstInd;
2020-05-10 01:42:30 +02:00
/***** Get questions of an exam print from database *****/
if ((Print->NumQsts.All = Exa_DB_GetPrintQuestions (&mysql_res,Print->PrnCod))
<= ExaPrn_MAX_QUESTIONS_PER_EXAM_PRINT)
for (QstInd = 0;
QstInd < Print->NumQsts.All;
QstInd++)
2020-05-10 01:42:30 +02:00
{
row = mysql_fetch_row (mysql_res);
/* Get question code (row[0])
and set code (row[1]) */
if ((Print->PrintedQuestions[QstInd].QstCod = Str_ConvertStrCodToLongCod (row[0])) <= 0)
Err_WrongQuestionExit ();
if ((Print->PrintedQuestions[QstInd].SetCod = Str_ConvertStrCodToLongCod (row[1])) <= 0)
Err_WrongSetExit ();
2020-05-10 01:42:30 +02:00
2020-05-12 02:45:03 +02:00
/* Get score (row[2]) */
2020-05-11 14:56:49 +02:00
Str_SetDecimalPointToUS (); // To get the decimal point as a dot
if (sscanf (row[2],"%lf",&Print->PrintedQuestions[QstInd].Score) != 1)
Err_ShowErrorAndExit ("Wrong question score.");
2020-05-11 14:56:49 +02:00
Str_SetDecimalPointToLocal (); // Return to local system
/* Get indexes for this question (row[3])
and answers selected by user for this question (row[4]) */
Str_Copy (Print->PrintedQuestions[QstInd].StrIndexes,row[3],
sizeof (Print->PrintedQuestions[QstInd].StrIndexes) - 1);
Str_Copy (Print->PrintedQuestions[QstInd].StrAnswers,row[4],
sizeof (Print->PrintedQuestions[QstInd].StrAnswers) - 1);
2020-05-10 01:42:30 +02:00
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
2020-06-18 20:06:17 +02:00
if (Print->NumQsts.All > ExaPrn_MAX_QUESTIONS_PER_EXAM_PRINT)
Err_ShowErrorAndExit ("Too many questions.");
2020-05-10 01:42:30 +02:00
}
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-17 20:12:37 +02:00
static void ExaPrn_ShowExamPrintToFillIt (struct Exa_Exams *Exams,
2021-01-20 00:42:59 +01:00
struct ExaPrn_Print *Print)
2020-05-09 21:07:50 +02:00
{
2021-01-20 00:42:59 +01:00
extern const char *Hlp_ASSESSMENT_Exams_answer_exam;
2020-05-09 21:07:50 +02:00
/***** Begin box *****/
Box_BoxBegin (NULL,Exams->Exam.Title,
2020-05-09 21:07:50 +02:00
NULL,NULL,
2021-01-20 00:42:59 +01:00
Hlp_ASSESSMENT_Exams_answer_exam,Box_NOT_CLOSABLE);
2020-05-19 13:12:26 +02:00
/***** Heading *****/
/* Institution, degree and course */
Lay_WriteHeaderClassPhoto (false,false,
Gbl.Hierarchy.Ins.InsCod,
Gbl.Hierarchy.Deg.DegCod,
Gbl.Hierarchy.Crs.CrsCod);
2021-01-20 00:42:59 +01:00
/***** Show user and time *****/
HTM_TABLE_BeginWideMarginPadding (10);
ExaRes_ShowExamResultUser (&Gbl.Usrs.Me.UsrDat);
HTM_TABLE_End ();
2021-01-20 00:42:59 +01:00
/***** Exam description *****/
ExaPrn_GetAndWriteDescription (Exams->Exam.ExaCod);
2020-05-09 21:07:50 +02:00
if (Print->NumQsts.All)
{
/***** Show table with questions to answer *****/
HTM_DIV_Begin ("id=\"examprint\""); // Used for AJAX based refresh
ExaPrn_ShowTableWithQstsToFill (Exams,Print);
HTM_DIV_End (); // Used for AJAX based refresh
}
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-19 13:12:26 +02:00
/*****************************************************************************/
/********************* Write description in an exam print ********************/
/*****************************************************************************/
static void ExaPrn_GetAndWriteDescription (long ExaCod)
{
char Txt[Cns_MAX_BYTES_TEXT + 1];
/***** Get description from database *****/
Exa_DB_GetExamTxt (ExaCod,Txt);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Txt,Cns_MAX_BYTES_TEXT,Str_DONT_REMOVE_SPACES);
ALn_InsertLinks (Txt,Cns_MAX_BYTES_TEXT,60); // Insert links
2020-05-19 13:12:26 +02:00
/***** Write description *****/
HTM_DIV_Begin ("class=\"EXA_PRN_DESC DAT_SMALL_%s\"",
The_GetSuffix ());
HTM_Txt (Txt);
2020-05-19 13:12:26 +02:00
HTM_DIV_End ();
}
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-25 00:18:09 +02:00
static void ExaPrn_ShowTableWithQstsToFill (struct Exa_Exams *Exams,
const struct ExaPrn_Print *Print)
2020-05-10 14:03:40 +02:00
{
2020-05-25 00:18:09 +02:00
extern const char *Txt_I_have_finished;
unsigned QstInd;
struct Qst_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
/***** Write one row for each question *****/
for (QstInd = 0;
QstInd < Print->NumQsts.All;
QstInd++)
{
/* Create test question */
Qst_QstConstructor (&Question);
Question.QstCod = Print->PrintedQuestions[QstInd].QstCod;
2020-05-10 14:03:40 +02:00
/* Get question from database */
ExaSet_GetQstDataFromDB (&Question);
2020-05-10 14:03:40 +02:00
/* Write question and answers */
ExaPrn_WriteQstAndAnsToFill (Print,QstInd,&Question);
2020-05-10 14:03:40 +02:00
/* Destroy test question */
Qst_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-25 00:18:09 +02:00
/***** Form to end/close this exam print *****/
Frm_BeginForm (ActEndExaPrn);
ExaSes_PutParsEdit (Exams);
Btn_PutCreateButton (Txt_I_have_finished);
2020-05-25 00:18:09 +02:00
Frm_EndForm ();
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-17 20:12:37 +02:00
static void ExaPrn_WriteQstAndAnsToFill (const struct ExaPrn_Print *Print,
unsigned QstInd,
struct Qst_Question *Question)
2020-05-09 23:12:53 +02:00
{
static struct ExaSet_Set CurrentSet =
{
.ExaCod = -1L,
.SetCod = -1L,
.SetInd = 0,
.NumQstsToPrint = 0,
.Title[0] = '\0'
};
if (Print->PrintedQuestions[QstInd].SetCod != CurrentSet.SetCod)
2020-05-09 23:12:53 +02:00
{
/***** Get data of this set *****/
CurrentSet.SetCod = Print->PrintedQuestions[QstInd].SetCod;
ExaSet_GetSetDataByCod (&CurrentSet);
2020-05-09 23:12:53 +02:00
/***** Title for this set *****/
HTM_TR_Begin (NULL);
HTM_TD_Begin ("colspan=\"2\" class=\"%s\"",The_GetColorRows ());
ExaSet_WriteSetTitle (&CurrentSet);
HTM_TD_End ();
2020-05-09 23:12:53 +02:00
HTM_TR_End ();
}
/***** Begin row *****/
HTM_TR_Begin (NULL);
/***** Number of question and answer type *****/
HTM_TD_Begin ("class=\"RT\"");
Lay_WriteIndex (QstInd + 1,"BIG_INDEX");
Qst_WriteAnswerType (Question->Answer.Type,"DAT_SMALL");
HTM_TD_End ();
2020-05-09 23:12:53 +02:00
/***** Stem, media and answers *****/
HTM_TD_Begin ("class=\"LT\"");
2020-05-09 23:12:53 +02:00
/* Stem */
Qst_WriteQstStem (Question->Stem,"Qst_TXT",true);
2020-05-09 23:12:53 +02:00
/* Media */
Med_ShowMedia (&Question->Media,
"Tst_MED_SHOW_CONT",
"Tst_MED_SHOW");
2020-05-09 23:12:53 +02:00
/* Answers */
Frm_BeginFormNoAction (); // Form that can not be submitted, to avoid enter key to send it
ExaPrn_WriteAnswersToFill (Print,QstInd,Question);
Frm_EndForm ();
2020-05-09 23:12:53 +02:00
HTM_TD_End ();
2020-05-09 23:12:53 +02:00
/***** End row *****/
HTM_TR_End ();
}
/*****************************************************************************/
/***************** Write answers of a question to fill them ******************/
/*****************************************************************************/
2020-05-17 20:12:37 +02:00
static void ExaPrn_WriteAnswersToFill (const struct ExaPrn_Print *Print,
unsigned QstInd,
struct Qst_Question *Question)
2020-05-09 23:12:53 +02:00
{
void (*ExaPrn_WriteAnsToFill[Qst_NUM_ANS_TYPES]) (const struct ExaPrn_Print *Print,
unsigned QstInd,
struct Qst_Question *Question) =
2020-06-17 02:31:42 +02:00
{
[Qst_ANS_INT ] = ExaPrn_WriteIntAnsToFill,
[Qst_ANS_FLOAT ] = ExaPrn_WriteFltAnsToFill,
[Qst_ANS_TRUE_FALSE ] = ExaPrn_WriteTF_AnsToFill,
[Qst_ANS_UNIQUE_CHOICE ] = ExaPrn_WriteChoAnsToFill,
[Qst_ANS_MULTIPLE_CHOICE] = ExaPrn_WriteChoAnsToFill,
[Qst_ANS_TEXT ] = ExaPrn_WriteTxtAnsToFill,
2020-06-17 02:31:42 +02:00
};
/***** Write answers *****/
ExaPrn_WriteAnsToFill[Question->Answer.Type] (Print,QstInd,Question);
2020-05-09 23:12:53 +02:00
}
/*****************************************************************************/
/****************** Write integer answer when seeing a test ******************/
/*****************************************************************************/
2020-05-11 13:05:38 +02:00
static void ExaPrn_WriteIntAnsToFill (const struct ExaPrn_Print *Print,
unsigned QstInd,
__attribute__((unused)) struct Qst_Question *Question)
2020-05-09 23:12:53 +02:00
{
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 *****/
snprintf (Id,sizeof (Id),"Ans%010u",QstInd);
2020-05-12 02:45:03 +02:00
HTM_TxtF ("<input type=\"text\" id=\"%s\" name=\"Ans\""
2020-05-11 02:28:38 +02:00
" size=\"11\" maxlength=\"11\" value=\"%s\"",
Id,Print->PrintedQuestions[QstInd].StrAnswers);
ExaPrn_WriteJSToUpdateExamPrint (Print,QstInd,Id,-1);
2020-05-19 15:17:52 +02:00
HTM_Txt (" />");
2020-05-09 23:12:53 +02:00
}
/*****************************************************************************/
/****************** Write float answer when seeing a test ********************/
/*****************************************************************************/
2020-06-17 02:31:42 +02:00
static void ExaPrn_WriteFltAnsToFill (const struct ExaPrn_Print *Print,
unsigned QstInd,
__attribute__((unused)) struct Qst_Question *Question)
2020-05-09 23:12:53 +02:00
{
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 *****/
snprintf (Id,sizeof (Id),"Ans%010u",QstInd);
2020-05-12 02:45:03 +02:00
HTM_TxtF ("<input type=\"text\" id=\"%s\" name=\"Ans\""
2020-05-11 13:05:38 +02:00
" size=\"11\" maxlength=\"%u\" value=\"%s\"",
Id,Qst_MAX_BYTES_FLOAT_ANSWER,
Print->PrintedQuestions[QstInd].StrAnswers);
ExaPrn_WriteJSToUpdateExamPrint (Print,QstInd,Id,-1);
2020-05-19 15:17:52 +02:00
HTM_Txt (" />");
2020-05-09 23:12:53 +02:00
}
/*****************************************************************************/
/************** Write false / true answer when seeing a test ****************/
/*****************************************************************************/
2020-05-19 15:17:52 +02:00
static void ExaPrn_WriteTF_AnsToFill (const struct ExaPrn_Print *Print,
unsigned QstInd,
__attribute__((unused)) struct Qst_Question *Question)
2020-05-09 23:12:53 +02:00
{
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 answers will be blank.
2020-05-09 23:12:53 +02:00
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. */
snprintf (Id,sizeof (Id),"Ans%010u",QstInd);
2020-05-12 02:45:03 +02:00
HTM_TxtF ("<select id=\"%s\" name=\"Ans\"",Id);
ExaPrn_WriteJSToUpdateExamPrint (Print,QstInd,Id,-1);
2020-05-19 15:17:52 +02:00
HTM_Txt (" />");
HTM_OPTION (HTM_Type_STRING,"" ,
Print->PrintedQuestions[QstInd].StrAnswers[0] == '\0' ? HTM_OPTION_SELECTED :
HTM_OPTION_UNSELECTED,
HTM_OPTION_ENABLED,
"&nbsp;");
HTM_OPTION (HTM_Type_STRING,"T",
Print->PrintedQuestions[QstInd].StrAnswers[0] == 'T' ? HTM_OPTION_SELECTED :
HTM_OPTION_UNSELECTED,
HTM_OPTION_ENABLED,
"%s",Txt_TF_QST[0]);
HTM_OPTION (HTM_Type_STRING,"F",
Print->PrintedQuestions[QstInd].StrAnswers[0] == 'F' ? HTM_OPTION_SELECTED :
HTM_OPTION_UNSELECTED,
HTM_OPTION_ENABLED,
"%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-19 15:17:52 +02:00
static void ExaPrn_WriteChoAnsToFill (const struct ExaPrn_Print *Print,
unsigned QstInd,
struct Qst_Question *Question)
2020-05-09 23:12:53 +02:00
{
unsigned NumOpt;
unsigned Indexes[Qst_MAX_OPTIONS_PER_QUESTION]; // Indexes of all answers of this question
bool UsrAnswers[Qst_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
2020-06-17 02:31:42 +02:00
/***** Change format of answers text *****/
Qst_ChangeFormatAnswersText (Question);
2020-06-17 02:31:42 +02:00
2020-05-09 23:12:53 +02:00
/***** Get indexes for this question from string *****/
TstPrn_GetIndexesFromStr (Print->PrintedQuestions[QstInd].StrIndexes,Indexes);
2020-05-09 23:12:53 +02:00
/***** Get the user's answers for this question from string *****/
TstPrn_GetAnswersFromStr (Print->PrintedQuestions[QstInd].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 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\"");
snprintf (Id,sizeof (Id),"Ans%010u",QstInd);
HTM_TxtF ("<input type=\"%s\" id=\"%s_%u\" name=\"Ans\" value=\"%u\"%s",
Question->Answer.Type == Qst_ANS_UNIQUE_CHOICE ? "radio" :
"checkbox",
Id,NumOpt,Indexes[NumOpt],
UsrAnswers[Indexes[NumOpt]] ? " checked=\"checked\"" :
"");
ExaPrn_WriteJSToUpdateExamPrint (Print,QstInd,Id,(int) NumOpt);
HTM_Txt (" />");
HTM_TD_End ();
HTM_TD_Begin ("class=\"LT\"");
HTM_LABEL_Begin ("for=\"Ans%010u_%u\" class=\"Qst_TXT_%s\"",
QstInd,NumOpt,The_GetSuffix ());
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=\"Qst_TXT_%s\"",
QstInd,NumOpt,The_GetSuffix ());
HTM_Txt (Question->Answer.Options[Indexes[NumOpt]].Text);
HTM_LABEL_End ();
Med_ShowMedia (&Question->Answer.Options[Indexes[NumOpt]].Media,
"Tst_MED_SHOW_CONT",
"Tst_MED_SHOW");
HTM_TD_End ();
HTM_TR_End ();
}
2020-05-09 23:12:53 +02:00
/***** End table *****/
HTM_TABLE_End ();
}
/*****************************************************************************/
/******************** Write text answer when seeing a test *******************/
/*****************************************************************************/
2020-05-19 15:17:52 +02:00
static void ExaPrn_WriteTxtAnsToFill (const struct ExaPrn_Print *Print,
unsigned QstInd,
__attribute__((unused)) struct Qst_Question *Question)
2020-05-09 23:12:53 +02:00
{
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 *****/
snprintf (Id,sizeof (Id),"Ans%010u",QstInd);
2020-05-12 02:45:03 +02:00
HTM_TxtF ("<input type=\"text\" id=\"%s\" name=\"Ans\""
2020-05-11 13:05:38 +02:00
" size=\"40\" maxlength=\"%u\" value=\"%s\"",
Id,Qst_MAX_CHARS_ANSWERS_ONE_QST,
Print->PrintedQuestions[QstInd].StrAnswers);
ExaPrn_WriteJSToUpdateExamPrint (Print,QstInd,Id,-1);
2020-05-19 15:17:52 +02:00
HTM_Txt (" />");
}
/*****************************************************************************/
/********************** Receive answer to an exam print **********************/
/*****************************************************************************/
static void ExaPrn_WriteJSToUpdateExamPrint (const struct ExaPrn_Print *Print,
unsigned QstInd,
2020-05-19 15:17:52 +02:00
const char *Id,int NumOpt)
{
if (NumOpt < 0)
HTM_TxtF (" onchange=\"updateExamPrint('examprint','%s','Ans',"
"'act=%ld&ses=%s&SesCod=%ld&QstInd=%u',%u);",
2020-05-19 15:17:52 +02:00
Id,
Act_GetActCod (ActAnsExaPrn),Gbl.Session.Id,Print->SesCod,QstInd,
2020-06-22 22:47:54 +02:00
(unsigned) Gbl.Prefs.Language);
2020-05-19 15:17:52 +02:00
else // NumOpt >= 0
HTM_TxtF (" onclick=\"updateExamPrint('examprint','%s_%d','Ans',"
"'act=%ld&ses=%s&SesCod=%ld&QstInd=%u',%u);",
2020-05-19 15:17:52 +02:00
Id,NumOpt,
Act_GetActCod (ActAnsExaPrn),Gbl.Session.Id,Print->SesCod,QstInd,
2020-06-22 22:47:54 +02:00
(unsigned) Gbl.Prefs.Language);
2020-05-19 15:17:52 +02:00
HTM_Txt (" return false;\""); // return false is necessary to not submit form
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-17 20:12:37 +02:00
extern const char *Txt_You_dont_have_access_to_the_exam;
2020-05-25 00:18:09 +02:00
extern const char *Txt_Continue;
struct Exa_Exams Exams;
2020-05-23 19:08:59 +02:00
struct ExaSes_Session Session;
2020-05-11 02:28:38 +02:00
struct ExaPrn_Print Print;
2020-05-23 13:24:08 +02:00
unsigned QstInd;
2020-05-11 02:28:38 +02:00
2020-05-25 00:18:09 +02:00
/***** Reset exams context *****/
Exa_ResetExams (&Exams);
Exa_ResetExam (&Exams.Exam);
2020-05-23 19:08:59 +02:00
ExaSes_ResetSession (&Session);
2020-05-11 02:28:38 +02:00
2020-05-17 02:28:30 +02:00
/***** Get session code *****/
Print.SesCod = ParCod_GetAndCheckPar (ParCod_Ses);
2020-05-11 02:28:38 +02:00
2020-05-23 13:24:08 +02:00
/***** Get print data *****/
Print.UsrCod = Gbl.Usrs.Me.UsrDat.UsrCod;
ExaPrn_GetPrintDataBySesCodAndUsrCod (&Print);
2020-05-23 13:24:08 +02:00
if (Print.PrnCod <= 0)
Err_WrongExamExit ();
2020-05-11 02:28:38 +02:00
2020-05-23 19:08:59 +02:00
/***** Get session data *****/
Session.SesCod = Print.SesCod;
ExaSes_GetSessionDataByCod (&Session);
2020-05-23 19:08:59 +02:00
if (Session.SesCod <= 0)
Err_WrongExamExit ();
2020-05-25 00:18:09 +02:00
Exams.SesCod = Session.SesCod;
2020-05-23 19:08:59 +02:00
/***** Get exam data *****/
Exams.Exam.ExaCod = Session.ExaCod;
Exa_GetExamDataByCod (&Exams.Exam);
if (Exams.Exam.ExaCod <= 0)
Err_WrongExamExit ();
if (Exams.Exam.CrsCod != Gbl.Hierarchy.Crs.CrsCod)
Err_WrongExamExit ();
2020-05-23 13:24:08 +02:00
/***** Get question index from form *****/
QstInd = ExaPrn_GetParQstInd ();
2020-05-22 20:10:45 +02:00
2020-05-23 15:53:53 +02:00
/***** Set log print code, action and question index *****/
ExaLog_SetPrnCod (Print.PrnCod);
ExaLog_SetAction (ExaLog_ANSWER_QUESTION);
ExaLog_SetQstInd (QstInd);
2020-05-15 01:07:46 +02:00
2020-05-23 13:24:08 +02:00
/***** Check if session if visible and open *****/
if (ExaSes_CheckIfICanAnswerThisSession (&Exams.Exam,&Session))
2020-05-23 13:24:08 +02:00
{
2020-05-23 19:08:59 +02:00
/***** Set log open to true ****/
ExaLog_SetIfCanAnswer (true);
/***** Get questions and current user's answers of exam print from database *****/
ExaPrn_GetPrintQuestionsFromDB (&Print);
2020-05-23 15:53:53 +02:00
2020-05-15 01:07:46 +02:00
/***** Get answers from form to assess a test *****/
2020-05-23 13:24:08 +02:00
ExaPrn_GetAnswerFromForm (&Print,QstInd);
2020-05-11 02:28:38 +02:00
2020-05-15 01:07:46 +02:00
/***** Update answer in database *****/
/* Compute question score and store in database */
2020-05-23 13:24:08 +02:00
ExaPrn_ComputeScoreAndStoreQuestionOfPrint (&Print,QstInd);
2020-05-11 02:28:38 +02:00
2020-05-15 01:07:46 +02:00
/* Update exam print in database */
Print.NumQsts.NotBlank = Exa_DB_GetNumQstsNotBlankInPrint (Print.PrnCod);
Print.Score.All = Exa_DB_ComputeTotalScoreOfPrint (Print.PrnCod);
Exa_DB_UpdatePrint (&Print);
2020-05-11 02:28:38 +02:00
2020-05-15 01:07:46 +02:00
/***** Show table with questions to answer *****/
2020-05-25 00:18:09 +02:00
ExaPrn_ShowTableWithQstsToFill (&Exams,&Print);
2020-05-15 01:07:46 +02:00
}
2020-05-23 15:53:53 +02:00
else // Not accessible to answer
{
2020-05-23 19:08:59 +02:00
/***** Set log open to false ****/
ExaLog_SetIfCanAnswer (false);
2020-05-23 15:53:53 +02:00
/***** Show warning *****/
2020-05-17 20:12:37 +02:00
Ale_ShowAlert (Ale_INFO,Txt_You_dont_have_access_to_the_exam);
2020-05-25 00:18:09 +02:00
/***** Form to end/close this exam print *****/
Frm_BeginForm (ActEndExaPrn);
ExaSes_PutParsEdit (&Exams);
Btn_PutCreateButton (Txt_Continue);
2020-05-25 00:18:09 +02:00
Frm_EndForm ();
2020-05-23 15:53:53 +02:00
}
2020-05-11 02:28:38 +02:00
}
/*****************************************************************************/
/******** Get questions and answers from form to assess an exam print ********/
/*****************************************************************************/
2020-05-23 13:24:08 +02:00
static void ExaPrn_GetAnswerFromForm (struct ExaPrn_Print *Print,unsigned QstInd)
2020-05-11 02:28:38 +02:00
{
/***** Get answers selected by user for this question *****/
Par_GetParText ("Ans",Print->PrintedQuestions[QstInd].StrAnswers,
Qst_MAX_BYTES_ANSWERS_ONE_QST); /* If answer type == T/F ==> " ", "T", "F"; if choice ==> "0", "2",... */
2020-05-11 02:28:38 +02:00
}
/*****************************************************************************/
/********************* Get parameter with question index *********************/
2020-05-11 02:28:38 +02:00
/*****************************************************************************/
static unsigned ExaPrn_GetParQstInd (void)
2020-05-11 02:28:38 +02:00
{
long QstInd;
2020-05-11 02:28:38 +02:00
if ((QstInd = Par_GetParLong ("QstInd")) < 0) // In exams, question index should be 0, 1, 2, 3...
Err_WrongQuestionIndexExit ();
2020-05-11 02:28:38 +02:00
return (unsigned) QstInd;
2020-05-11 02:28:38 +02:00
}
/*****************************************************************************/
/*********** Compute score of one question and store in database *************/
/*****************************************************************************/
static void ExaPrn_ComputeScoreAndStoreQuestionOfPrint (struct ExaPrn_Print *Print,
unsigned QstInd)
2020-05-11 02:28:38 +02:00
{
struct Qst_Question Question;
char CurrentStrAnswersInDB[Qst_MAX_BYTES_ANSWERS_ONE_QST + 1]; // Answers selected by user
2020-05-11 02:28:38 +02:00
/***** Compute question score *****/
Qst_QstConstructor (&Question);
Question.QstCod = Print->PrintedQuestions[QstInd].QstCod;
Question.Answer.Type = ExaSet_GetAnswerType (Question.QstCod);
ExaPrn_ComputeAnswerScore (&Print->PrintedQuestions[QstInd],&Question);
Qst_QstDestructor (&Question);
2020-05-11 02:28:38 +02:00
2020-05-12 13:24:43 +02:00
/***** If type is unique choice and the option (radio button) is checked
==> uncheck it by deleting answer *****/
if (Question.Answer.Type == Qst_ANS_UNIQUE_CHOICE)
2020-05-12 13:24:43 +02:00
{
Exa_DB_GetAnswersFromQstInPrint (Print->PrnCod,Print->PrintedQuestions[QstInd].QstCod,
CurrentStrAnswersInDB);
if (!strcmp (Print->PrintedQuestions[QstInd].StrAnswers,CurrentStrAnswersInDB))
2020-05-22 20:10:45 +02:00
{
/* The answer just clicked by user
is the same as the last one checked and stored in database */
Print->PrintedQuestions[QstInd].StrAnswers[0] = '\0'; // Uncheck option
Print->PrintedQuestions[QstInd].Score = 0; // Clear question score
2020-05-22 20:10:45 +02:00
}
2020-05-12 13:24:43 +02:00
}
/***** Store test question in database *****/
Exa_DB_StoreOneQstOfPrint (Print,
QstInd); // 0, 1, 2, 3...
2020-05-11 02:28:38 +02:00
}
2020-05-13 12:53:27 +02:00
/*****************************************************************************/
/************* Write answers of a question when assessing a test *************/
/*****************************************************************************/
void ExaPrn_ComputeAnswerScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
struct Qst_Question *Question)
2020-05-13 12:53:27 +02:00
{
void (*ExaPrn_GetCorrectAndComputeAnsScore[Qst_NUM_ANS_TYPES]) (struct TstPrn_PrintedQuestion *PrintedQuestion,
struct Qst_Question *Question) =
2020-05-13 13:13:03 +02:00
{
[Qst_ANS_INT ] = ExaPrn_GetCorrectAndComputeIntAnsScore,
[Qst_ANS_FLOAT ] = ExaPrn_GetCorrectAndComputeFltAnsScore,
[Qst_ANS_TRUE_FALSE ] = ExaPrn_GetCorrectAndComputeTF_AnsScore,
[Qst_ANS_UNIQUE_CHOICE ] = ExaPrn_GetCorrectAndComputeChoAnsScore,
[Qst_ANS_MULTIPLE_CHOICE] = ExaPrn_GetCorrectAndComputeChoAnsScore,
[Qst_ANS_TEXT ] = ExaPrn_GetCorrectAndComputeTxtAnsScore,
2020-05-13 13:13:03 +02:00
};
/***** Get correct answer and compute answer score depending on type *****/
ExaPrn_GetCorrectAndComputeAnsScore[Question->Answer.Type] (PrintedQuestion,Question);
2020-05-13 12:53:27 +02:00
}
/*****************************************************************************/
/******* Get correct answer and compute score for each type of answer ********/
/*****************************************************************************/
static void ExaPrn_GetCorrectAndComputeIntAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
struct Qst_Question *Question)
2020-05-13 12:53:27 +02:00
{
2020-05-13 13:37:15 +02:00
/***** Get the numerical value of the correct answer,
and compute score *****/
2020-05-13 12:53:27 +02:00
ExaPrn_GetCorrectIntAnswerFromDB (Question);
TstPrn_ComputeIntAnsScore (PrintedQuestion,Question);
}
static void ExaPrn_GetCorrectAndComputeFltAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
struct Qst_Question *Question)
2020-05-13 12:53:27 +02:00
{
2020-05-13 13:37:15 +02:00
/***** Get the numerical value of the minimum and maximum correct answers,
and compute score *****/
2020-05-13 12:53:27 +02:00
ExaPrn_GetCorrectFltAnswerFromDB (Question);
TstPrn_ComputeFltAnsScore (PrintedQuestion,Question);
}
static void ExaPrn_GetCorrectAndComputeTF_AnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
struct Qst_Question *Question)
2020-05-13 12:53:27 +02:00
{
2020-05-13 13:37:15 +02:00
/***** Get answer true or false,
and compute score *****/
2020-05-13 12:53:27 +02:00
ExaPrn_GetCorrectTF_AnswerFromDB (Question);
TstPrn_ComputeTF_AnsScore (PrintedQuestion,Question);
}
static void ExaPrn_GetCorrectAndComputeChoAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
struct Qst_Question *Question)
2020-05-13 12:53:27 +02:00
{
2020-05-13 13:37:15 +02:00
/***** Get correct options of test question from database,
and compute score *****/
2020-05-13 12:53:27 +02:00
ExaPrn_GetCorrectChoAnswerFromDB (Question);
TstPrn_ComputeChoAnsScore (PrintedQuestion,Question);
}
static void ExaPrn_GetCorrectAndComputeTxtAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
struct Qst_Question *Question)
2020-05-13 12:53:27 +02:00
{
2020-05-13 13:37:15 +02:00
/***** Get correct text answers for this question from database,
and compute score *****/
2020-05-13 12:53:27 +02:00
ExaPrn_GetCorrectTxtAnswerFromDB (Question);
TstPrn_ComputeTxtAnsScore (PrintedQuestion,Question);
}
/*****************************************************************************/
/***************** Get correct answer for each type of answer ****************/
/*****************************************************************************/
static void ExaPrn_GetCorrectIntAnswerFromDB (struct Qst_Question *Question)
2020-05-13 12:53:27 +02:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
/***** Query database *****/
Question->Answer.NumOptions = Exa_DB_GetQstAnswersTextFromSet (&mysql_res,Question->QstCod);
2020-05-13 12:53:27 +02:00
/***** Check if number of rows is correct *****/
Qst_CheckIfNumberOfAnswersIsOne (Question);
2020-05-13 12:53:27 +02:00
/***** Get correct answer *****/
row = mysql_fetch_row (mysql_res);
if (sscanf (row[0],"%ld",&Question->Answer.Integer) != 1)
Err_WrongAnswerExit ();
2020-05-13 12:53:27 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
static void ExaPrn_GetCorrectFltAnswerFromDB (struct Qst_Question *Question)
2020-05-13 12:53:27 +02:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumOpt;
double Tmp;
/***** Query database *****/
Question->Answer.NumOptions = Exa_DB_GetQstAnswersTextFromSet (&mysql_res,Question->QstCod);
2020-05-13 12:53:27 +02:00
/***** Check if number of rows is correct *****/
if (Question->Answer.NumOptions != 2)
Err_WrongAnswerExit ();
2020-05-13 12:53:27 +02:00
/***** Get float range *****/
for (NumOpt = 0;
NumOpt < 2;
NumOpt++)
{
row = mysql_fetch_row (mysql_res);
Question->Answer.FloatingPoint[NumOpt] = Str_GetDoubleFromStr (row[0]);
}
if (Question->Answer.FloatingPoint[0] >
Question->Answer.FloatingPoint[1]) // The maximum and the minimum are swapped
{
/* Swap maximum and minimum */
Tmp = Question->Answer.FloatingPoint[0];
Question->Answer.FloatingPoint[0] = Question->Answer.FloatingPoint[1];
Question->Answer.FloatingPoint[1] = Tmp;
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
static void ExaPrn_GetCorrectTF_AnswerFromDB (struct Qst_Question *Question)
2020-05-13 12:53:27 +02:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
/***** Query database *****/
Question->Answer.NumOptions = Exa_DB_GetQstAnswersTextFromSet (&mysql_res,Question->QstCod);
2020-05-13 12:53:27 +02:00
/***** Check if number of rows is correct *****/
Qst_CheckIfNumberOfAnswersIsOne (Question);
2020-05-13 12:53:27 +02:00
/***** Get answer *****/
row = mysql_fetch_row (mysql_res);
Question->Answer.TF = row[0][0];
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
static void ExaPrn_GetCorrectChoAnswerFromDB (struct Qst_Question *Question)
2020-05-13 12:53:27 +02:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumOpt;
/***** Query database *****/
Question->Answer.NumOptions = Exa_DB_GetQstAnswersCorrFromSet (&mysql_res,Question->QstCod);
2020-05-13 12:53:27 +02:00
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
{
/* Get next answer */
row = mysql_fetch_row (mysql_res);
/* Assign correctness (row[0]) of this answer (this option) */
Question->Answer.Options[NumOpt].Correct = (row[0][0] == 'Y');
}
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
}
static void ExaPrn_GetCorrectTxtAnswerFromDB (struct Qst_Question *Question)
2020-05-13 12:53:27 +02:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumOpt;
/***** Query database *****/
Question->Answer.NumOptions = Exa_DB_GetQstAnswersTextFromSet (&mysql_res,Question->QstCod);
2020-05-13 12:53:27 +02:00
/***** Get text and correctness of answers for this question from database (one row per answer) *****/
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
{
/***** Get next answer *****/
row = mysql_fetch_row (mysql_res);
/***** Allocate memory for text in this choice answer *****/
if (!Qst_AllocateTextChoiceAnswer (Question,NumOpt))
2020-05-13 12:53:27 +02:00
/* Abort on error */
Ale_ShowAlertsAndExit ();
2020-06-17 02:31:42 +02:00
/***** Copy answer text (row[0]) ******/
2020-05-13 12:53:27 +02:00
Str_Copy (Question->Answer.Options[NumOpt].Text,row[0],
Qst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2020-05-13 12:53:27 +02:00
}
2020-06-17 02:31:42 +02:00
/***** Change format of answers text *****/
Qst_ChangeFormatAnswersText (Question);
2020-06-17 02:31:42 +02:00
2020-05-13 12:53:27 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}