Version19.160

This commit is contained in:
acanas 2020-04-03 19:13:00 +02:00
parent f690d1ed72
commit 7db18f8aa9
8 changed files with 305 additions and 429 deletions

View File

@ -497,7 +497,7 @@ enscript -2 --landscape --color --file-align=2 --highlight --line-numbers -o - *
En OpenSWAD: En OpenSWAD:
ps2pdf source.ps destination.pdf ps2pdf source.ps destination.pdf
*/ */
#define Log_PLATFORM_VERSION "SWAD 19.159 (2020-04-03)" #define Log_PLATFORM_VERSION "SWAD 19.160 (2020-04-03)"
#define CSS_FILE "swad19.146.css" #define CSS_FILE "swad19.146.css"
#define JS_FILE "swad19.153.js" #define JS_FILE "swad19.153.js"
/* /*
@ -523,8 +523,14 @@ Param
// TODO: Miguel Damas: al principio de los exámenes tendría que poner cuánto resta cada pregunta // TODO: Miguel Damas: al principio de los exámenes tendría que poner cuánto resta cada pregunta
// TODO: Oresti Baños: cambiar ojos por candados en descriptores para prohibir/permitir y dejar los ojos para poder elegir descriptores // TODO: Oresti Baños: cambiar ojos por candados en descriptores para prohibir/permitir y dejar los ojos para poder elegir descriptores
// TODO: Integrar pull requests con traducciones del alemán del usuario eruedin en GitHub // TODO: Integrar pull requests con traducciones del alemán del usuario eruedin en GitHub
// TODO: Comprobar si la puntuación de cada pregunta de un examen se recalcula al mostrarlo o se saca de la base de datos // TODO: Integrar Stem y Feedback en question, creando espacio con malloc como en las respuestas
y qué pasa cuando se edita una pregunta // TODO: Intentar cambiar Tst_WriteChoiceAnsSeeing usando Tst_GetQstDataFromDB y quitar Tst_GetChoiceAns
// TODO: Intentar quitar Tst_GetOneQuestionByCod usando Tst_GetQstDataFromDB
Version 19.160: Apr 03, 2020 The score for each test question displayed in an exam is the one stored in the database instead of being calculated.
New file extension, suggested by Rosa Medina Doménech. (284933 lines)
Copy the following icon to icon public directory:
sudo cp icon/filext32x32/m4a32x32.gif /var/www/html/swad/icon/filext32x32/
Version 19.159: Apr 03, 2020 Code refactoring and bug fixing in tests. (285052 lines) Version 19.159: Apr 03, 2020 Code refactoring and bug fixing in tests. (285052 lines)
Version 19.158: Apr 02, 2020 Lot of code refactoring in tests. (285031 lines) Version 19.158: Apr 02, 2020 Lot of code refactoring in tests. (285031 lines)

View File

@ -75,6 +75,7 @@ const char *Ext_FileExtensionsAllowed[] =
"jpeg" , "jpeg" ,
"latex", "latex",
"m" , "m" ,
"m4a" , // MPEG-4 de audio
"mdb" , "mdb" ,
"mht" , "mht" ,
"mhtml", "mhtml",

View File

@ -1514,7 +1514,7 @@ static void Mch_CreateIndexes (long GamCod,long MchCod)
Lay_ShowErrorAndExit ("Wrong answer type."); Lay_ShowErrorAndExit ("Wrong answer type.");
/* Get shuffle (row[3]) */ /* Get shuffle (row[3]) */
Question.Shuffle = (row[3][0] == 'Y'); Question.Answer.Shuffle = (row[3][0] == 'Y');
/***** Reorder answer *****/ /***** Reorder answer *****/
Mch_ReorderAnswer (MchCod,QstInd,&Question); Mch_ReorderAnswer (MchCod,QstInd,&Question);
@ -1554,8 +1554,8 @@ static void Mch_ReorderAnswer (long MchCod,unsigned QstInd,
" WHERE QstCod=%ld" " WHERE QstCod=%ld"
" ORDER BY %s", " ORDER BY %s",
Question->QstCod, Question->QstCod,
Question->Shuffle ? "RAND()" : // Use RAND() because is really random; RAND(NOW()) repeats order Question->Answer.Shuffle ? "RAND()" : // Use RAND() because is really random; RAND(NOW()) repeats order
"AnsInd"); "AnsInd");
/***** For each answer in question... *****/ /***** For each answer in question... *****/
for (NumAns = 0; for (NumAns = 0;

View File

@ -250,9 +250,6 @@ static void Tst_FreeTextChoiceAnswer (struct Tst_Question *Question,unsigned Num
static void Tst_ResetMediaOfQuestion (struct Tst_Question *Question); static void Tst_ResetMediaOfQuestion (struct Tst_Question *Question);
static void Tst_FreeMediaOfQuestion (struct Tst_Question *Question); static void Tst_FreeMediaOfQuestion (struct Tst_Question *Question);
static void Tst_GetQstDataFromDB (struct Tst_Question *Question,
char Stem[Cns_MAX_BYTES_TEXT + 1],
char Feedback[Cns_MAX_BYTES_TEXT + 1]);
static long Tst_GetMedCodFromDB (long CrsCod,long QstCod,int NumOpt); static long Tst_GetMedCodFromDB (long CrsCod,long QstCod,int NumOpt);
static void Tst_GetMediaFromDB (long CrsCod,long QstCod,int NumOpt, static void Tst_GetMediaFromDB (long CrsCod,long QstCod,int NumOpt,
struct Media *Media); struct Media *Media);
@ -4330,8 +4327,8 @@ static void Tst_PutFormEditOneQst (struct Tst_Question *Question,
HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]); HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
HTM_INPUT_CHECKBOX ("Shuffle",HTM_DONT_SUBMIT_ON_CHANGE, HTM_INPUT_CHECKBOX ("Shuffle",HTM_DONT_SUBMIT_ON_CHANGE,
"value=\"Y\"%s%s", "value=\"Y\"%s%s",
Question->Shuffle ? " checked=\"checked\"" : Question->Answer.Shuffle ? " checked=\"checked\"" :
"", "",
Question->Answer.Type != Tst_ANS_UNIQUE_CHOICE && Question->Answer.Type != Tst_ANS_UNIQUE_CHOICE &&
Question->Answer.Type != Tst_ANS_MULTIPLE_CHOICE ? " disabled=\"disabled\"" : Question->Answer.Type != Tst_ANS_MULTIPLE_CHOICE ? " disabled=\"disabled\"" :
""); "");
@ -4537,19 +4534,23 @@ void Tst_QstConstructor (struct Tst_Question *Question)
Tst_ResetTags (&Question->Tags); Tst_ResetTags (&Question->Tags);
Question->Stem.Text = NULL; Question->EditTime = (time_t) 0;
Question->Stem.Length = 0;
Question->Feedback.Text = NULL; Question->Stem.Text = NULL;
Question->Stem.Length = 0;
Question->Feedback.Text = NULL;
Question->Feedback.Length = 0; Question->Feedback.Length = 0;
Question->Shuffle = false;
Question->Answer.Type = Tst_ANS_UNIQUE_CHOICE; /***** Initialize answers *****/
Question->Answer.Type = Tst_ANS_UNIQUE_CHOICE;
Question->Answer.NumOptions = 0; Question->Answer.NumOptions = 0;
Question->Answer.TF = ' '; Question->Answer.Shuffle = false;
Question->Answer.TF = ' ';
/***** Initialize image attached to stem *****/ /* Initialize image attached to stem */
Med_MediaConstructor (&Question->Media); Med_MediaConstructor (&Question->Media);
/* Initialize options */
for (NumOpt = 0; for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION; NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++) NumOpt++)
@ -4558,7 +4559,7 @@ void Tst_QstConstructor (struct Tst_Question *Question)
Question->Answer.Options[NumOpt].Text = NULL; Question->Answer.Options[NumOpt].Text = NULL;
Question->Answer.Options[NumOpt].Feedback = NULL; Question->Answer.Options[NumOpt].Feedback = NULL;
/***** Initialize image attached to option *****/ /* Initialize image attached to option */
Med_MediaConstructor (&Question->Answer.Options[NumOpt].Media); Med_MediaConstructor (&Question->Answer.Options[NumOpt].Media);
} }
Question->Answer.Integer = 0; Question->Answer.Integer = 0;
@ -4700,132 +4701,152 @@ Tst_AnswerType_t Tst_GetQstAnswerType (long QstCod)
/*****************************************************************************/ /*****************************************************************************/
/****************** Get data of a question from database *********************/ /****************** Get data of a question from database *********************/
/*****************************************************************************/ /*****************************************************************************/
// If question does not exist ==> set question code to -1
static void Tst_GetQstDataFromDB (struct Tst_Question *Question, void Tst_GetQstDataFromDB (struct Tst_Question *Question,
char Stem[Cns_MAX_BYTES_TEXT + 1], char Stem[Cns_MAX_BYTES_TEXT + 1],
char Feedback[Cns_MAX_BYTES_TEXT + 1]) char Feedback[Cns_MAX_BYTES_TEXT + 1])
{ {
MYSQL_RES *mysql_res; MYSQL_RES *mysql_res;
MYSQL_ROW row; MYSQL_ROW row;
bool QuestionExists;
unsigned long NumRows; unsigned long NumRows;
unsigned long NumRow; unsigned long NumRow;
unsigned NumOpt; unsigned NumOpt;
/***** Get question data from database *****/ /***** Get question data from database *****/
if (!DB_QuerySELECT (&mysql_res,"can not get a question", QuestionExists = (DB_QuerySELECT (&mysql_res,"can not get a question",
"SELECT AnsType," // row[0] "SELECT UNIX_TIMESTAMP(EditTime)," // row[0]
"Shuffle," // row[1] "AnsType," // row[1]
"Stem," // row[2] "Shuffle," // row[2]
"Feedback," // row[3] "Stem," // row[3]
"MedCod" // row[4] "Feedback," // row[4]
" FROM tst_questions" "MedCod" // row[5]
" WHERE QstCod=%ld" " FROM tst_questions"
" AND CrsCod=%ld", // Extra check " WHERE QstCod=%ld"
Question->QstCod, " AND CrsCod=%ld", // Extra check
Gbl.Hierarchy.Crs.CrsCod)) Question->QstCod,
Lay_ShowErrorAndExit ("Question does not exist."); Gbl.Hierarchy.Crs.CrsCod) != 0);
row = mysql_fetch_row (mysql_res); if (QuestionExists)
/* Get the type of answer */
Question->Answer.Type = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[0]);
/* Get shuffle (row[1]) */
Question->Shuffle = (row[1][0] == 'Y');
/* Get the stem of the question from the database (row[2]) */
Str_Copy (Stem,row[2],
Cns_MAX_BYTES_TEXT);
/* Get the feedback of the question from the database (row[3]) */
Feedback[0] = '\0';
if (row[3])
if (row[3][0])
Str_Copy (Feedback,row[3],
Cns_MAX_BYTES_TEXT);
/* Get media (row[4]) */
Question->Media.MedCod = Str_ConvertStrCodToLongCod (row[4]);
Med_GetMediaDataByCod (&Question->Media);
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
/***** Get the tags from the database *****/
NumRows = Tst_GetTagsQst (Question->QstCod,&mysql_res);
for (NumRow = 0;
NumRow < NumRows;
NumRow++)
{ {
row = mysql_fetch_row (mysql_res); row = mysql_fetch_row (mysql_res);
Str_Copy (Question->Tags.Txt[NumRow],row[0],
Tst_MAX_BYTES_TAG);
}
/* Free structure that stores the query result */ /* Get edition time (row[0] holds the start UTC time) */
DB_FreeMySQLResult (&mysql_res); Question->EditTime = Dat_GetUNIXTimeFromStr (row[3]);
/***** Get the answers from the database *****/ /* Get the type of answer (row[1]) */
Tst_GetAnswersQst (Question,&mysql_res, Question->Answer.Type = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[1]);
false); // Don't shuffle
/* /* Get shuffle (row[2]) */
row[0] AnsInd Question->Answer.Shuffle = (row[2][0] == 'Y');
row[1] Answer
row[2] Feedback /* Get the stem (row[3]) */
row[3] MedCod Stem[0] = '\0';
row[4] Correct if (row[3])
*/ if (row[3][0])
for (NumOpt = 0; Str_Copy (Stem,row[3],
NumOpt < Question->Answer.NumOptions; Cns_MAX_BYTES_TEXT);
NumOpt++)
{ /* Get the feedback (row[4]) */
row = mysql_fetch_row (mysql_res); Feedback[0] = '\0';
switch (Question->Answer.Type) if (row[4])
if (row[4][0])
Str_Copy (Feedback,row[4],
Cns_MAX_BYTES_TEXT);
/* Get media (row[5]) */
Question->Media.MedCod = Str_ConvertStrCodToLongCod (row[5]);
Med_GetMediaDataByCod (&Question->Media);
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
/***** Get the tags from the database *****/
NumRows = Tst_GetTagsQst (Question->QstCod,&mysql_res);
for (NumRow = 0;
NumRow < NumRows;
NumRow++)
{ {
case Tst_ANS_INT: row = mysql_fetch_row (mysql_res);
if (Question->Answer.NumOptions != 1) Str_Copy (Question->Tags.Txt[NumRow],row[0],
Lay_ShowErrorAndExit ("Wrong answer."); Tst_MAX_BYTES_TAG);
Question->Answer.Integer = Tst_GetIntAnsFromStr (row[1]); }
break;
case Tst_ANS_FLOAT:
if (Question->Answer.NumOptions != 2)
Lay_ShowErrorAndExit ("Wrong answer.");
Question->Answer.FloatingPoint[NumOpt] = Str_GetDoubleFromStr (row[1]);
break;
case Tst_ANS_TRUE_FALSE:
if (Question->Answer.NumOptions != 1)
Lay_ShowErrorAndExit ("Wrong answer.");
Question->Answer.TF = row[1][0];
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
case Tst_ANS_TEXT:
if (Question->Answer.NumOptions > Tst_MAX_OPTIONS_PER_QUESTION)
Lay_ShowErrorAndExit ("Wrong answer.");
if (!Tst_AllocateTextChoiceAnswer (Question,NumOpt))
/* Abort on error */
Ale_ShowAlertsAndExit ();
Str_Copy (Question->Answer.Options[NumOpt].Text,row[1], /* Free structure that stores the query result */
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK); DB_FreeMySQLResult (&mysql_res);
// Feedback (row[2]) is initialized to empty string /***** Get the answers from the database *****/
if (row[2]) Tst_GetAnswersQst (Question,&mysql_res,
if (row[2][0]) false); // Don't shuffle
Str_Copy (Question->Answer.Options[NumOpt].Feedback,row[2], /*
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK); row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
{
row = mysql_fetch_row (mysql_res);
switch (Question->Answer.Type)
{
case Tst_ANS_INT:
Tst_CheckIfNumberOfAnswersIsOne (Question);
Question->Answer.Integer = Tst_GetIntAnsFromStr (row[1]);
break;
case Tst_ANS_FLOAT:
if (Question->Answer.NumOptions != 2)
Lay_ShowErrorAndExit ("Wrong answer.");
Question->Answer.FloatingPoint[NumOpt] = Str_GetDoubleFromStr (row[1]);
break;
case Tst_ANS_TRUE_FALSE:
Tst_CheckIfNumberOfAnswersIsOne (Question);
Question->Answer.TF = row[1][0];
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
case Tst_ANS_TEXT:
/* Check number of options */
if (Question->Answer.NumOptions > Tst_MAX_OPTIONS_PER_QUESTION)
Lay_ShowErrorAndExit ("Wrong answer.");
/* Get media (row[3]) */ /* Allocate space for text and feedback */
Question->Answer.Options[NumOpt].Media.MedCod = Str_ConvertStrCodToLongCod (row[3]); if (!Tst_AllocateTextChoiceAnswer (Question,NumOpt))
Med_GetMediaDataByCod (&Question->Answer.Options[NumOpt].Media); /* Abort on error */
Ale_ShowAlertsAndExit ();
/* Get if this option is correct (row[4]) */ /* Get text (row[1]) */
Question->Answer.Options[NumOpt].Correct = (row[4][0] == 'Y'); Question->Answer.Options[NumOpt].Text[0] = '\0';
break; if (row[1])
default: if (row[1][0])
break; Str_Copy (Question->Answer.Options[NumOpt].Text,row[1],
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
/* Get feedback (row[2]) */
Question->Answer.Options[NumOpt].Feedback[0] = '\0';
if (row[2])
if (row[2][0])
Str_Copy (Question->Answer.Options[NumOpt].Feedback,row[2],
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
/* Get media (row[3]) */
Question->Answer.Options[NumOpt].Media.MedCod = Str_ConvertStrCodToLongCod (row[3]);
Med_GetMediaDataByCod (&Question->Answer.Options[NumOpt].Media);
/* Get if this option is correct (row[4]) */
Question->Answer.Options[NumOpt].Correct = (row[4][0] == 'Y');
break;
default:
break;
}
} }
} }
else
Question->QstCod = -1L;
/* Free structure that stores the query result */ /* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res); DB_FreeMySQLResult (&mysql_res);
} }
@ -5048,7 +5069,7 @@ static void Tst_GetQstFromForm (struct Tst_Question *Question,
Ale_ShowAlerts (NULL); Ale_ShowAlerts (NULL);
/***** Get answers *****/ /***** Get answers *****/
Question->Shuffle = false; Question->Answer.Shuffle = false;
switch (Question->Answer.Type) switch (Question->Answer.Type)
{ {
case Tst_ANS_INT: case Tst_ANS_INT:
@ -5081,7 +5102,7 @@ static void Tst_GetQstFromForm (struct Tst_Question *Question,
case Tst_ANS_UNIQUE_CHOICE: case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE: case Tst_ANS_MULTIPLE_CHOICE:
/* Get shuffle */ /* Get shuffle */
Question->Shuffle = Par_GetParToBool ("Shuffle"); Question->Answer.Shuffle = Par_GetParToBool ("Shuffle");
/* falls through */ /* falls through */
/* no break */ /* no break */
case Tst_ANS_TEXT: case Tst_ANS_TEXT:
@ -5970,8 +5991,8 @@ static void Tst_InsertOrUpdateQstIntoDB (struct Tst_Question *Question)
"0)", // Score "0)", // Score
Gbl.Hierarchy.Crs.CrsCod, Gbl.Hierarchy.Crs.CrsCod,
Tst_StrAnswerTypesDB[Question->Answer.Type], Tst_StrAnswerTypesDB[Question->Answer.Type],
Question->Shuffle ? 'Y' : Question->Answer.Shuffle ? 'Y' :
'N', 'N',
Question->Stem.Text, Question->Stem.Text,
Question->Feedback.Text ? Question->Feedback.Text : Question->Feedback.Text ? Question->Feedback.Text :
"", "",
@ -5991,8 +6012,8 @@ static void Tst_InsertOrUpdateQstIntoDB (struct Tst_Question *Question)
"MedCod=%ld" "MedCod=%ld"
" WHERE QstCod=%ld AND CrsCod=%ld", " WHERE QstCod=%ld AND CrsCod=%ld",
Tst_StrAnswerTypesDB[Question->Answer.Type], Tst_StrAnswerTypesDB[Question->Answer.Type],
Question->Shuffle ? 'Y' : Question->Answer.Shuffle ? 'Y' :
'N', 'N',
Question->Stem.Text, Question->Stem.Text,
Question->Feedback.Text ? Question->Feedback.Text : Question->Feedback.Text ? Question->Feedback.Text :
"", "",

View File

@ -94,17 +94,18 @@ struct Tst_Question
{ {
long QstCod; long QstCod;
struct Tst_Tags Tags; struct Tst_Tags Tags;
time_t EditTime;
struct struct
{ {
char *Text; char *Text;
size_t Length; size_t Length;
} Stem, Feedback; } Stem, Feedback;
struct Media Media; struct Media Media;
bool Shuffle;
struct struct
{ {
Tst_AnswerType_t Type; Tst_AnswerType_t Type;
unsigned NumOptions; unsigned NumOptions;
bool Shuffle;
char TF; char TF;
struct struct
{ {
@ -199,6 +200,9 @@ void Tst_QstDestructor (struct Tst_Question *Question);
bool Tst_AllocateTextChoiceAnswer (struct Tst_Question *Question,unsigned NumOpt); bool Tst_AllocateTextChoiceAnswer (struct Tst_Question *Question,unsigned NumOpt);
Tst_AnswerType_t Tst_GetQstAnswerType (long QstCod); Tst_AnswerType_t Tst_GetQstAnswerType (long QstCod);
void Tst_GetQstDataFromDB (struct Tst_Question *Question,
char Stem[Cns_MAX_BYTES_TEXT + 1],
char Feedback[Cns_MAX_BYTES_TEXT + 1]);
Tst_AnswerType_t Tst_ConvertFromStrAnsTypDBToAnsTyp (const char *StrAnsTypeBD); Tst_AnswerType_t Tst_ConvertFromStrAnsTypDBToAnsTyp (const char *StrAnsTypeBD);
void Tst_ReceiveQst (void); void Tst_ReceiveQst (void);
bool Tst_CheckIfQstFormatIsCorrectAndCountNumOptions (struct Tst_Question *Question); bool Tst_CheckIfQstFormatIsCorrectAndCountNumOptions (struct Tst_Question *Question);

View File

@ -77,6 +77,13 @@ extern struct Globals Gbl;
/***************************** Private prototypes ****************************/ /***************************** Private prototypes ****************************/
/*****************************************************************************/ /*****************************************************************************/
static void TstExa_WriteQstAndAnsExam (struct UsrData *UsrDat,
struct TstExa_Exam *Exam,
unsigned NumQst,
struct Tst_Question *Question,
const char *Stem,
const char *Feedback,
unsigned Visibility);
static void TstExa_ComputeAnswerScore (struct TstExa_Exam *Exam, static void TstExa_ComputeAnswerScore (struct TstExa_Exam *Exam,
unsigned NumQst, unsigned NumQst,
struct Tst_Question *Question); struct Tst_Question *Question);
@ -113,31 +120,26 @@ static void TstExa_WriteIntAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam, const struct TstExa_Exam *Exam,
unsigned NumQst, unsigned NumQst,
const struct Tst_Question *Question, const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility); unsigned Visibility);
static void TstExa_WriteFloatAnsExam (struct UsrData *UsrDat, static void TstExa_WriteFloatAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam, const struct TstExa_Exam *Exam,
unsigned NumQst, unsigned NumQst,
const struct Tst_Question *Question, const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility); unsigned Visibility);
static void TstExa_WriteTFAnsExam (struct UsrData *UsrDat, static void TstExa_WriteTFAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam, const struct TstExa_Exam *Exam,
unsigned NumQst, unsigned NumQst,
const struct Tst_Question *Question, const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility); unsigned Visibility);
static void TstExa_WriteChoiceAnsExam (struct UsrData *UsrDat, static void TstExa_WriteChoiceAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam, const struct TstExa_Exam *Exam,
unsigned NumQst, unsigned NumQst,
struct Tst_Question *Question, struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility); unsigned Visibility);
static void TstExa_WriteTextAnsExam (struct UsrData *UsrDat, static void TstExa_WriteTextAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam, const struct TstExa_Exam *Exam,
unsigned NumQst, unsigned NumQst,
struct Tst_Question *Question, struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility); unsigned Visibility);
static void TstExa_WriteHeadUserCorrect (struct UsrData *UsrDat); static void TstExa_WriteHeadUserCorrect (struct UsrData *UsrDat);
static void TstExa_WriteScoreStart (unsigned ColSpan); static void TstExa_WriteScoreStart (unsigned ColSpan);
@ -222,10 +224,10 @@ void TstExa_UpdateExamInDB (const struct TstExa_Exam *Exam)
void TstExa_ShowExamAfterAssess (struct TstExa_Exam *Exam) void TstExa_ShowExamAfterAssess (struct TstExa_Exam *Exam)
{ {
extern const char *Txt_Question_removed;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumQst; unsigned NumQst;
struct Tst_Question Question;
char Stem[Cns_MAX_BYTES_TEXT + 1];
char Feedback[Cns_MAX_BYTES_TEXT + 1];
/***** Begin table *****/ /***** Begin table *****/
HTM_TABLE_BeginWideMarginPadding (10); HTM_TABLE_BeginWideMarginPadding (10);
@ -240,20 +242,22 @@ void TstExa_ShowExamAfterAssess (struct TstExa_Exam *Exam)
{ {
Gbl.RowEvenOdd = NumQst % 2; Gbl.RowEvenOdd = NumQst % 2;
/***** Query database *****/ /***** Create test question *****/
if (Tst_GetOneQuestionByCod (Exam->Questions[NumQst].QstCod,&mysql_res)) // Question exists Tst_QstConstructor (&Question);
Question.QstCod = Exam->Questions[NumQst].QstCod;
/***** Get question data *****/
Tst_GetQstDataFromDB (&Question,Stem,Feedback);
if (Question.QstCod > 0) // Question exists
{ {
/***** Write question and answers *****/ /***** Write question and answers *****/
row = mysql_fetch_row (mysql_res);
TstExa_WriteQstAndAnsExam (&Gbl.Usrs.Me.UsrDat, TstExa_WriteQstAndAnsExam (&Gbl.Usrs.Me.UsrDat,
Exam, Exam,NumQst,
NumQst, &Question,Stem,Feedback,
row,
TstCfg_GetConfigVisibility ()); TstCfg_GetConfigVisibility ());
/***** Store test exam question in database *****/ /***** Store test exam question in database *****/
TstExa_StoreOneExamQstInDB (Exam, TstExa_StoreOneExamQstInDB (Exam,NumQst);
NumQst); // 0, 1, 2, 3...
/***** Compute total score *****/ /***** Compute total score *****/
Exam->Score += Exam->Questions[NumQst].Score; Exam->Score += Exam->Questions[NumQst].Score;
@ -264,24 +268,9 @@ void TstExa_ShowExamAfterAssess (struct TstExa_Exam *Exam)
if (Gbl.Usrs.Me.Role.Logged == Rol_STD) if (Gbl.Usrs.Me.Role.Logged == Rol_STD)
Tst_UpdateQstScoreInDB (Exam,NumQst); Tst_UpdateQstScoreInDB (Exam,NumQst);
} }
else
{
/***** Question does not exists *****/
HTM_TR_Begin (NULL);
HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd); /***** Destroy test question *****/
Tst_WriteNumQst (NumQst + 1); Tst_QstDestructor (&Question);
HTM_TD_End ();
HTM_TD_Begin ("class=\"DAT_LIGHT LT COLOR%u\"",Gbl.RowEvenOdd);
HTM_Txt (Txt_Question_removed);
HTM_TD_End ();
HTM_TR_End ();
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
} }
/***** End table *****/ /***** End table *****/
@ -292,29 +281,30 @@ void TstExa_ShowExamAfterAssess (struct TstExa_Exam *Exam)
/********** Write a row of a test, with one question and its answer **********/ /********** Write a row of a test, with one question and its answer **********/
/*****************************************************************************/ /*****************************************************************************/
void TstExa_WriteQstAndAnsExam (struct UsrData *UsrDat, static void TstExa_WriteQstAndAnsExam (struct UsrData *UsrDat,
struct TstExa_Exam *Exam, struct TstExa_Exam *Exam,
unsigned NumQst, unsigned NumQst,
MYSQL_ROW row, struct Tst_Question *Question,
unsigned Visibility) const char *Stem,
const char *Feedback,
unsigned Visibility)
{ {
struct Tst_Question Question; extern const char *Txt_Score;
extern const char *Txt_Question_removed;
extern const char *Txt_Question_modified;
bool QuestionExists;
bool QuestionUneditedAfterExam = false;
bool IsVisibleQstAndAnsTxt = TstVis_IsVisibleQstAndAnsTxt (Visibility); bool IsVisibleQstAndAnsTxt = TstVis_IsVisibleQstAndAnsTxt (Visibility);
/*
row[0] UNIX_TIMESTAMP(EditTime)
row[1] AnsType
row[2] Shuffle
row[3] Stem
row[4] Feedback
row[5] MedCod
row[6] NumHits
row[7] NumHitsNotBlank
row[8] Score
*/
/***** Create test question *****/ /***** Does question exist? *****/
Tst_QstConstructor (&Question); QuestionExists = (Question->QstCod > 0);
Question.QstCod = Exam->Questions[NumQst].QstCod;
/***** If this question has been edited later than test time
==> don't show question ****/
if (QuestionExists)
QuestionUneditedAfterExam = (Question->EditTime < Exam->TimeUTC[Dat_START_TIME]);
else
QuestionUneditedAfterExam = false;
/***** Begin row *****/ /***** Begin row *****/
HTM_TR_Begin (NULL); HTM_TR_Begin (NULL);
@ -322,43 +312,56 @@ void TstExa_WriteQstAndAnsExam (struct UsrData *UsrDat,
/***** Number of question and answer type (row[1]) *****/ /***** Number of question and answer type (row[1]) *****/
HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd); HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd);
Tst_WriteNumQst (NumQst + 1); Tst_WriteNumQst (NumQst + 1);
Question.Answer.Type = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[1]); if (QuestionUneditedAfterExam)
Tst_WriteAnswerType (Question.Answer.Type); Tst_WriteAnswerType (Question->Answer.Type);
HTM_TD_End (); HTM_TD_End ();
/***** Stem, media and answers *****/ /***** Stem, media and answers *****/
HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd); HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd);
if (QuestionExists)
/* Stem (row[3]) */
Tst_WriteQstStem (row[3],"TEST_EXA",IsVisibleQstAndAnsTxt);
/* Media (row[5]) */
if (IsVisibleQstAndAnsTxt)
{ {
Question.Media.MedCod = Str_ConvertStrCodToLongCod (row[5]); if (QuestionUneditedAfterExam)
Med_GetMediaDataByCod (&Question.Media); {
Med_ShowMedia (&Question.Media, /* Stem */
"TEST_MED_SHOW_CONT", Tst_WriteQstStem (Stem,"TEST_EXA",IsVisibleQstAndAnsTxt);
"TEST_MED_SHOW");
/* Media */
if (IsVisibleQstAndAnsTxt)
Med_ShowMedia (&Question->Media,
"TEST_MED_SHOW_CONT",
"TEST_MED_SHOW");
/* Answers */
TstExa_ComputeAnswerScore (Exam,NumQst,Question);
TstExa_WriteAnswersExam (UsrDat,Exam,NumQst,Question,Visibility);
}
else
Ale_ShowAlert (Ale_WARNING,Txt_Question_modified);
} }
else
Ale_ShowAlert (Ale_WARNING,Txt_Question_removed);
/* Answers */ /* Write score retrieved from database */
TstExa_ComputeAnswerScore (Exam,NumQst,&Question); HTM_DIV_Begin ("class=\"DAT_SMALL LM\"");
TstExa_WriteAnswersExam (UsrDat, HTM_TxtColonNBSP (Txt_Score);
Exam,NumQst,&Question, HTM_SPAN_Begin ("class=\"%s\"",
Visibility); Exam->Questions[NumQst].StrAnswers[0] ?
(Exam->Questions[NumQst].Score > 0 ? "ANS_OK" : // Correct/semicorrect
"ANS_BAD") : // Wrong
"ANS_0"); // Blank answer
HTM_Double2Decimals (Exam->Questions[NumQst].Score);
HTM_SPAN_End ();
HTM_DIV_End ();
/* Question feedback (row[4]) */ /* Question feedback */
if (TstVis_IsVisibleFeedbackTxt (Visibility)) if (QuestionUneditedAfterExam)
Tst_WriteQstFeedback (row[4],"TEST_EXA_LIGHT"); if (TstVis_IsVisibleFeedbackTxt (Visibility))
Tst_WriteQstFeedback (Feedback,"TEST_EXA_LIGHT");
HTM_TD_End (); HTM_TD_End ();
/***** End row *****/ /***** End row *****/
HTM_TR_End (); HTM_TR_End ();
/***** Destroy test question *****/
Tst_QstDestructor (&Question);
} }
/*****************************************************************************/ /*****************************************************************************/
@ -918,54 +921,28 @@ static void TstExa_WriteAnswersExam (struct UsrData *UsrDat,
struct Tst_Question *Question, struct Tst_Question *Question,
unsigned Visibility) unsigned Visibility)
{ {
MYSQL_RES *mysql_res;
/***** Get answer of a question from database *****/
Tst_GetAnswersQst (Question,&mysql_res,
false); // Don't shuffle
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
/***** Write answer depending on type *****/ /***** Write answer depending on type *****/
switch (Question->Answer.Type) switch (Question->Answer.Type)
{ {
case Tst_ANS_INT: case Tst_ANS_INT:
TstExa_WriteIntAnsExam (UsrDat,Exam, TstExa_WriteIntAnsExam (UsrDat,Exam,NumQst,Question,Visibility);
NumQst,Question,mysql_res,
Visibility);
break; break;
case Tst_ANS_FLOAT: case Tst_ANS_FLOAT:
TstExa_WriteFloatAnsExam (UsrDat,Exam, TstExa_WriteFloatAnsExam (UsrDat,Exam,NumQst,Question,Visibility);
NumQst,Question,mysql_res,
Visibility);
break; break;
case Tst_ANS_TRUE_FALSE: case Tst_ANS_TRUE_FALSE:
TstExa_WriteTFAnsExam (UsrDat,Exam, TstExa_WriteTFAnsExam (UsrDat,Exam,NumQst,Question,Visibility);
NumQst,Question,mysql_res,
Visibility);
break; break;
case Tst_ANS_UNIQUE_CHOICE: case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE: case Tst_ANS_MULTIPLE_CHOICE:
TstExa_WriteChoiceAnsExam (UsrDat,Exam, TstExa_WriteChoiceAnsExam (UsrDat,Exam,NumQst,Question,Visibility);
NumQst,Question,mysql_res,
Visibility);
break; break;
case Tst_ANS_TEXT: case Tst_ANS_TEXT:
TstExa_WriteTextAnsExam (UsrDat,Exam, TstExa_WriteTextAnsExam (UsrDat,Exam,NumQst,Question,Visibility);
NumQst,Question,mysql_res,
Visibility);
break; break;
default: default:
break; break;
} }
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
} }
/*****************************************************************************/ /*****************************************************************************/
@ -976,27 +953,13 @@ static void TstExa_WriteIntAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam, const struct TstExa_Exam *Exam,
unsigned NumQst, unsigned NumQst,
const struct Tst_Question *Question, const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility) unsigned Visibility)
{ {
MYSQL_ROW row;
long IntAnswerUsr; long IntAnswerUsr;
long IntAnswerCorr;
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
/***** Check if number of rows is correct *****/ /***** Check if number of rows is correct *****/
Tst_CheckIfNumberOfAnswersIsOne (Question); Tst_CheckIfNumberOfAnswersIsOne (Question);
/***** Get the numerical value of the correct answer *****/
row = mysql_fetch_row (mysql_res);
if (sscanf (row[1],"%ld",&IntAnswerCorr) != 1)
Lay_ShowErrorAndExit ("Wrong integer answer.");
/***** Header with the title of each column *****/ /***** Header with the title of each column *****/
HTM_TABLE_BeginPadding (2); HTM_TABLE_BeginPadding (2);
HTM_TR_Begin (NULL); HTM_TR_Begin (NULL);
@ -1012,8 +975,8 @@ static void TstExa_WriteIntAnsExam (struct UsrData *UsrDat,
{ {
HTM_TD_Begin ("class=\"%s CM\"", HTM_TD_Begin ("class=\"%s CM\"",
TstVis_IsVisibleCorrectAns (Visibility) ? TstVis_IsVisibleCorrectAns (Visibility) ?
(IntAnswerUsr == IntAnswerCorr ? "ANS_OK" : (IntAnswerUsr == Question->Answer.Integer ? "ANS_OK" :
"ANS_BAD") : "ANS_BAD") :
"ANS_0"); "ANS_0");
HTM_Long (IntAnswerUsr); HTM_Long (IntAnswerUsr);
HTM_TD_End (); HTM_TD_End ();
@ -1032,7 +995,7 @@ static void TstExa_WriteIntAnsExam (struct UsrData *UsrDat,
HTM_TD_Begin ("class=\"ANS_0 CM\""); HTM_TD_Begin ("class=\"ANS_0 CM\"");
if (TstVis_IsVisibleQstAndAnsTxt (Visibility) && if (TstVis_IsVisibleQstAndAnsTxt (Visibility) &&
TstVis_IsVisibleCorrectAns (Visibility)) TstVis_IsVisibleCorrectAns (Visibility))
HTM_Long (IntAnswerCorr); HTM_Long (Question->Answer.Integer);
else else
Ico_PutIconNotVisible (); Ico_PutIconNotVisible ();
HTM_TD_End (); HTM_TD_End ();
@ -1043,17 +1006,17 @@ static void TstExa_WriteIntAnsExam (struct UsrData *UsrDat,
if (TstVis_IsVisibleEachQstScore (Visibility)) if (TstVis_IsVisibleEachQstScore (Visibility))
{ {
TstExa_WriteScoreStart (2); TstExa_WriteScoreStart (2);
if (!Exam->Questions[NumQst].StrAnswers[0]) // If user has omitted the answer if (!Exam->Questions[NumQst].StrAnswers[0]) // If user has omitted the answer
{ {
HTM_SPAN_Begin ("class=\"ANS_0\""); HTM_SPAN_Begin ("class=\"ANS_0\"");
HTM_Double2Decimals (0.0); HTM_Double2Decimals (0.0);
} }
else if (IntAnswerUsr == IntAnswerCorr) // If correct else if (IntAnswerUsr == Question->Answer.Integer) // If correct
{ {
HTM_SPAN_Begin ("class=\"ANS_OK\""); HTM_SPAN_Begin ("class=\"ANS_OK\"");
HTM_Double2Decimals (1.0); HTM_Double2Decimals (1.0);
} }
else // If wrong else // If wrong
{ {
HTM_SPAN_Begin ("class=\"ANS_BAD\""); HTM_SPAN_Begin ("class=\"ANS_BAD\"");
HTM_Double2Decimals (0.0); HTM_Double2Decimals (0.0);
@ -1073,40 +1036,14 @@ static void TstExa_WriteFloatAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam, const struct TstExa_Exam *Exam,
unsigned NumQst, unsigned NumQst,
const struct Tst_Question *Question, const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility) unsigned Visibility)
{ {
MYSQL_ROW row; double FloatAnsUsr = 0.0;
unsigned i;
double FloatAnsUsr = 0.0,Tmp;
double FloatAnsCorr[2];
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
/***** Check if number of rows is correct *****/ /***** Check if number of rows is correct *****/
if (Question->Answer.NumOptions != 2) if (Question->Answer.NumOptions != 2)
Lay_ShowErrorAndExit ("Wrong float range."); Lay_ShowErrorAndExit ("Wrong float range.");
/***** Get the numerical value of the minimum and maximum correct answers *****/
for (i = 0;
i < 2;
i++)
{
row = mysql_fetch_row (mysql_res);
FloatAnsCorr[i] = Str_GetDoubleFromStr (row[1]);
}
if (FloatAnsCorr[0] > FloatAnsCorr[1]) // The maximum and the minimum are swapped
{
/* Swap maximum and minimum */
Tmp = FloatAnsCorr[0];
FloatAnsCorr[0] = FloatAnsCorr[1];
FloatAnsCorr[1] = Tmp;
}
/***** Header with the title of each column *****/ /***** Header with the title of each column *****/
HTM_TABLE_BeginPadding (2); HTM_TABLE_BeginPadding (2);
HTM_TR_Begin (NULL); HTM_TR_Begin (NULL);
@ -1122,9 +1059,9 @@ static void TstExa_WriteFloatAnsExam (struct UsrData *UsrDat,
// A bad formatted floating point answer will interpreted as 0.0 // A bad formatted floating point answer will interpreted as 0.0
HTM_TD_Begin ("class=\"%s CM\"", HTM_TD_Begin ("class=\"%s CM\"",
TstVis_IsVisibleCorrectAns (Visibility) ? TstVis_IsVisibleCorrectAns (Visibility) ?
((FloatAnsUsr >= FloatAnsCorr[0] && ((FloatAnsUsr >= Question->Answer.FloatingPoint[0] &&
FloatAnsUsr <= FloatAnsCorr[1]) ? "ANS_OK" : FloatAnsUsr <= Question->Answer.FloatingPoint[1]) ? "ANS_OK" :
"ANS_BAD") : "ANS_BAD") :
"ANS_0"); "ANS_0");
HTM_Double (FloatAnsUsr); HTM_Double (FloatAnsUsr);
} }
@ -1138,9 +1075,9 @@ static void TstExa_WriteFloatAnsExam (struct UsrData *UsrDat,
TstVis_IsVisibleCorrectAns (Visibility)) TstVis_IsVisibleCorrectAns (Visibility))
{ {
HTM_Txt ("["); HTM_Txt ("[");
HTM_Double (FloatAnsCorr[0]); HTM_Double (Question->Answer.FloatingPoint[0]);
HTM_Txt ("; "); HTM_Txt ("; ");
HTM_Double (FloatAnsCorr[1]); HTM_Double (Question->Answer.FloatingPoint[1]);
HTM_Txt ("]"); HTM_Txt ("]");
} }
else else
@ -1153,18 +1090,18 @@ static void TstExa_WriteFloatAnsExam (struct UsrData *UsrDat,
if (TstVis_IsVisibleEachQstScore (Visibility)) if (TstVis_IsVisibleEachQstScore (Visibility))
{ {
TstExa_WriteScoreStart (2); TstExa_WriteScoreStart (2);
if (!Exam->Questions[NumQst].StrAnswers[0]) // If user has omitted the answer if (!Exam->Questions[NumQst].StrAnswers[0]) // If user has omitted the answer
{ {
HTM_SPAN_Begin ("class=\"ANS_0\""); HTM_SPAN_Begin ("class=\"ANS_0\"");
HTM_Double2Decimals (0.0); HTM_Double2Decimals (0.0);
} }
else if (FloatAnsUsr >= FloatAnsCorr[0] && else if (FloatAnsUsr >= Question->Answer.FloatingPoint[0] &&
FloatAnsUsr <= FloatAnsCorr[1]) // If correct (inside the interval) FloatAnsUsr <= Question->Answer.FloatingPoint[1]) // If correct (inside the interval)
{ {
HTM_SPAN_Begin ("class=\"ANS_OK\""); HTM_SPAN_Begin ("class=\"ANS_OK\"");
HTM_Double2Decimals (1.0); HTM_Double2Decimals (1.0);
} }
else // If wrong (outside the interval) else // If wrong (outside the interval)
{ {
HTM_SPAN_Begin ("class=\"ANS_BAD\""); HTM_SPAN_Begin ("class=\"ANS_BAD\"");
HTM_Double2Decimals (0.0); HTM_Double2Decimals (0.0);
@ -1184,24 +1121,15 @@ static void TstExa_WriteTFAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam, const struct TstExa_Exam *Exam,
unsigned NumQst, unsigned NumQst,
const struct Tst_Question *Question, const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility) unsigned Visibility)
{ {
MYSQL_ROW row; char AnsTFUsr;
char AnsTF;
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
/***** Check if number of rows is correct *****/ /***** Check if number of rows is correct *****/
Tst_CheckIfNumberOfAnswersIsOne (Question); Tst_CheckIfNumberOfAnswersIsOne (Question);
/***** Get answer true or false *****/ /***** Get answer true or false *****/
row = mysql_fetch_row (mysql_res); AnsTFUsr = Exam->Questions[NumQst].StrAnswers[0];
AnsTF = Exam->Questions[NumQst].StrAnswers[0];
/***** Header with the title of each column *****/ /***** Header with the title of each column *****/
HTM_TABLE_BeginPadding (2); HTM_TABLE_BeginPadding (2);
@ -1214,17 +1142,17 @@ static void TstExa_WriteTFAnsExam (struct UsrData *UsrDat,
/***** Write the user answer *****/ /***** Write the user answer *****/
HTM_TD_Begin ("class=\"%s CM\"", HTM_TD_Begin ("class=\"%s CM\"",
TstVis_IsVisibleCorrectAns (Visibility) ? TstVis_IsVisibleCorrectAns (Visibility) ?
(AnsTF == row[1][0] ? "ANS_OK" : (AnsTFUsr == Question->Answer.TF ? "ANS_OK" :
"ANS_BAD") : "ANS_BAD") :
"ANS_0"); "ANS_0");
Tst_WriteAnsTF (AnsTF); Tst_WriteAnsTF (AnsTFUsr);
HTM_TD_End (); HTM_TD_End ();
/***** Write the correct answer *****/ /***** Write the correct answer *****/
HTM_TD_Begin ("class=\"ANS_0 CM\""); HTM_TD_Begin ("class=\"ANS_0 CM\"");
if (TstVis_IsVisibleQstAndAnsTxt (Visibility) && if (TstVis_IsVisibleQstAndAnsTxt (Visibility) &&
TstVis_IsVisibleCorrectAns (Visibility)) TstVis_IsVisibleCorrectAns (Visibility))
Tst_WriteAnsTF (row[1][0]); Tst_WriteAnsTF (Question->Answer.TF);
else else
Ico_PutIconNotVisible (); Ico_PutIconNotVisible ();
HTM_TD_End (); HTM_TD_End ();
@ -1235,17 +1163,17 @@ static void TstExa_WriteTFAnsExam (struct UsrData *UsrDat,
if (TstVis_IsVisibleEachQstScore (Visibility)) if (TstVis_IsVisibleEachQstScore (Visibility))
{ {
TstExa_WriteScoreStart (2); TstExa_WriteScoreStart (2);
if (AnsTF == '\0') // If user has omitted the answer if (AnsTFUsr == '\0') // If user has omitted the answer
{ {
HTM_SPAN_Begin ("class=\"ANS_0\""); HTM_SPAN_Begin ("class=\"ANS_0\"");
HTM_Double2Decimals (0.0); HTM_Double2Decimals (0.0);
} }
else if (AnsTF == row[1][0]) // If correct else if (AnsTFUsr == Question->Answer.TF) // If correct
{ {
HTM_SPAN_Begin ("class=\"ANS_OK\""); HTM_SPAN_Begin ("class=\"ANS_OK\"");
HTM_Double2Decimals (1.0); HTM_Double2Decimals (1.0);
} }
else // If wrong else // If wrong
{ {
HTM_SPAN_Begin ("class=\"ANS_BAD\""); HTM_SPAN_Begin ("class=\"ANS_BAD\"");
HTM_Double2Decimals (-1.0); HTM_Double2Decimals (-1.0);
@ -1265,7 +1193,6 @@ static void TstExa_WriteChoiceAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam, const struct TstExa_Exam *Exam,
unsigned NumQst, unsigned NumQst,
struct Tst_Question *Question, struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility) unsigned Visibility)
{ {
extern const char *Txt_TST_Answer_given_by_the_user; extern const char *Txt_TST_Answer_given_by_the_user;
@ -1279,17 +1206,6 @@ static void TstExa_WriteChoiceAnsExam (struct UsrData *UsrDat,
char *Str; char *Str;
} Ans; } Ans;
/***** Get text and correctness of answers for this question
from database (one row per answer) *****/
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
Tst_GetChoiceAns (Question,mysql_res);
/***** Get indexes for this question from string *****/ /***** Get indexes for this question from string *****/
TstExa_GetIndexesFromStr (Exam->Questions[NumQst].StrIndexes,Indexes); TstExa_GetIndexesFromStr (Exam->Questions[NumQst].StrIndexes,Indexes);
@ -1421,55 +1337,30 @@ static void TstExa_WriteTextAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam, const struct TstExa_Exam *Exam,
unsigned NumQst, unsigned NumQst,
struct Tst_Question *Question, struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility) unsigned Visibility)
{ {
unsigned NumOpt; unsigned NumOpt;
MYSQL_ROW row;
char TextAnsUsr[TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1]; char TextAnsUsr[TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1];
char TextAnsOK[TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1]; char TextAnsOK[TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1];
bool Correct = false; bool Correct = false;
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
/***** Get text and correctness of answers for this question from database (one row per answer) *****/ /***** Get text and correctness of answers for this question from database (one row per answer) *****/
for (NumOpt = 0; for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions; NumOpt < Question->Answer.NumOptions;
NumOpt++) NumOpt++)
{ {
/***** Get next answer *****/ /***** Convert answer text, that is in HTML, to rigorous HTML ******/
row = mysql_fetch_row (mysql_res); if (Question->Answer.Options[NumOpt].Text[0])
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Question->Answer.Options[NumOpt].Text,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false);
/***** Allocate memory for text in this choice answer *****/ /***** Convert answer feedback, that is in HTML, to rigorous HTML ******/
if (!Tst_AllocateTextChoiceAnswer (Question,NumOpt))
/* Abort on error */
Ale_ShowAlertsAndExit ();
/***** Copy answer text (row[1]) and convert it, that is in HTML, to rigorous HTML ******/
Str_Copy (Question->Answer.Options[NumOpt].Text,row[1],
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Question->Answer.Options[NumOpt].Text,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false);
/***** Copy answer feedback (row[2]) and convert it, that is in HTML, to rigorous HTML ******/
if (TstVis_IsVisibleFeedbackTxt (Visibility)) if (TstVis_IsVisibleFeedbackTxt (Visibility))
if (row[2]) if (Question->Answer.Options[NumOpt].Feedback[0])
if (row[2][0]) Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
{ Question->Answer.Options[NumOpt].Feedback,
Str_Copy (Question->Answer.Options[NumOpt].Feedback,row[2], Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false);
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Question->Answer.Options[NumOpt].Feedback,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false);
}
/***** Assign correctness (row[4]) of this answer (this option) *****/
Question->Answer.Options[NumOpt].Correct = (row[4][0] == 'Y');
} }
/***** Header with the title of each column *****/ /***** Header with the title of each column *****/
@ -2437,13 +2328,10 @@ void TstExa_ShowExamAnswers (struct UsrData *UsrDat,
struct TstExa_Exam *Exam, struct TstExa_Exam *Exam,
unsigned Visibility) unsigned Visibility)
{ {
extern const char *Txt_Question_modified;
extern const char *Txt_Question_removed;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumQst; unsigned NumQst;
bool ThisQuestionHasBeenEdited; struct Tst_Question Question;
time_t EditTimeUTC; char Stem[Cns_MAX_BYTES_TEXT + 1];
char Feedback[Cns_MAX_BYTES_TEXT + 1];
for (NumQst = 0; for (NumQst = 0;
NumQst < Exam->NumQsts; NumQst < Exam->NumQsts;
@ -2451,60 +2339,21 @@ void TstExa_ShowExamAnswers (struct UsrData *UsrDat,
{ {
Gbl.RowEvenOdd = NumQst % 2; Gbl.RowEvenOdd = NumQst % 2;
/***** Query database *****/ /***** Create test question *****/
if (Tst_GetOneQuestionByCod (Exam->Questions[NumQst].QstCod,&mysql_res)) // Question exists Tst_QstConstructor (&Question);
{ Question.QstCod = Exam->Questions[NumQst].QstCod;
/***** Get row of the result of the query *****/
row = mysql_fetch_row (mysql_res);
/***** If this question has been edited later than test time /***** Get question data *****/
==> don't show question ****/ Tst_GetQstDataFromDB (&Question,Stem,Feedback);
EditTimeUTC = Dat_GetUNIXTimeFromStr (row[0]);
ThisQuestionHasBeenEdited = false;
if (EditTimeUTC > Exam->TimeUTC[Dat_START_TIME])
ThisQuestionHasBeenEdited = true;
if (ThisQuestionHasBeenEdited) /***** Write questions and answers *****/
{ TstExa_WriteQstAndAnsExam (UsrDat,
/***** Question has been edited *****/ Exam,NumQst,
HTM_TR_Begin (NULL); &Question,Stem,Feedback,
Visibility);
HTM_TD_Begin ("class=\"BIG_INDEX RT COLOR%u\"",Gbl.RowEvenOdd); /***** Destroy test question *****/
HTM_Unsigned (NumQst + 1); Tst_QstDestructor (&Question);
HTM_TD_End ();
HTM_TD_Begin ("class=\"DAT_LIGHT LT COLOR%u\"",Gbl.RowEvenOdd);
HTM_Txt (Txt_Question_modified);
HTM_TD_End ();
HTM_TR_End ();
}
else
/***** Write questions and answers *****/
TstExa_WriteQstAndAnsExam (UsrDat,
Exam,
NumQst,
row,
Visibility);
}
else
{
/***** Question does not exists *****/
HTM_TR_Begin (NULL);
HTM_TD_Begin ("class=\"BIG_INDEX RT COLOR%u\"",Gbl.RowEvenOdd);
HTM_Unsigned (NumQst + 1);
HTM_TD_End ();
HTM_TD_Begin ("class=\"DAT_LIGHT LT COLOR%u\"",Gbl.RowEvenOdd);
HTM_Txt (Txt_Question_removed);
HTM_TD_End ();
HTM_TR_End ();
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
} }
} }

View File

@ -71,11 +71,6 @@ void TstExa_CreateExamInDB (struct TstExa_Exam *Exam);
void TstExa_UpdateExamInDB (const struct TstExa_Exam *Exam); void TstExa_UpdateExamInDB (const struct TstExa_Exam *Exam);
void TstExa_ShowExamAfterAssess (struct TstExa_Exam *Exam); void TstExa_ShowExamAfterAssess (struct TstExa_Exam *Exam);
void TstExa_WriteQstAndAnsExam (struct UsrData *UsrDat,
struct TstExa_Exam *Result,
unsigned NumQst,
MYSQL_ROW row,
unsigned Visibility);
void TstExa_ComputeScoresAndStoreExamQuestions (struct TstExa_Exam *Exam, void TstExa_ComputeScoresAndStoreExamQuestions (struct TstExa_Exam *Exam,
bool UpdateQstScore); bool UpdateQstScore);

View File

@ -675,7 +675,7 @@ static void TsI_ImportQuestionsFromXMLBuffer (const char *XMLBuffer)
} }
/* Get shuffle. By default, shuffle is false. */ /* Get shuffle. By default, shuffle is false. */
Question.Shuffle = false; Question.Answer.Shuffle = false;
for (AnswerElem = QuestionElem->FirstChild; for (AnswerElem = QuestionElem->FirstChild;
AnswerElem != NULL; AnswerElem != NULL;
AnswerElem = AnswerElem->NextBrother) AnswerElem = AnswerElem->NextBrother)
@ -689,7 +689,7 @@ static void TsI_ImportQuestionsFromXMLBuffer (const char *XMLBuffer)
Attribute = Attribute->Next) Attribute = Attribute->Next)
if (!strcmp (Attribute->AttributeName,"shuffle")) if (!strcmp (Attribute->AttributeName,"shuffle"))
{ {
Question.Shuffle = XML_GetAttributteYesNoFromXMLTree (Attribute); Question.Answer.Shuffle = XML_GetAttributteYesNoFromXMLTree (Attribute);
break; // Only first attribute "shuffle" break; // Only first attribute "shuffle"
} }
break; // Only first element "answer" break; // Only first element "answer"
@ -1023,7 +1023,7 @@ static void TsI_WriteRowImportedQst (struct XMLElement *StemElem,
if (Question->Answer.Type == Tst_ANS_UNIQUE_CHOICE || if (Question->Answer.Type == Tst_ANS_UNIQUE_CHOICE ||
Question->Answer.Type == Tst_ANS_MULTIPLE_CHOICE) Question->Answer.Type == Tst_ANS_MULTIPLE_CHOICE)
/* Put an icon that indicates whether shuffle is enabled or not */ /* Put an icon that indicates whether shuffle is enabled or not */
if (Question->Shuffle) if (Question->Answer.Shuffle)
Ico_PutIcon ("check.svg",Txt_TST_Answer_given_by_the_teachers, Ico_PutIcon ("check.svg",Txt_TST_Answer_given_by_the_teachers,
QuestionExists ? "ICO_HIDDEN ICO16x16" : QuestionExists ? "ICO_HIDDEN ICO16x16" :
"ICO16x16"); "ICO16x16");