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:
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 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: 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: Comprobar si la puntuación de cada pregunta de un examen se recalcula al mostrarlo o se saca de la base de datos
y qué pasa cuando se edita una pregunta
// TODO: Integrar Stem y Feedback en question, creando espacio con malloc como en las respuestas
// 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.158: Apr 02, 2020 Lot of code refactoring in tests. (285031 lines)

View File

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

View File

@ -1514,7 +1514,7 @@ static void Mch_CreateIndexes (long GamCod,long MchCod)
Lay_ShowErrorAndExit ("Wrong answer type.");
/* Get shuffle (row[3]) */
Question.Shuffle = (row[3][0] == 'Y');
Question.Answer.Shuffle = (row[3][0] == 'Y');
/***** Reorder answer *****/
Mch_ReorderAnswer (MchCod,QstInd,&Question);
@ -1554,8 +1554,8 @@ static void Mch_ReorderAnswer (long MchCod,unsigned QstInd,
" WHERE QstCod=%ld"
" ORDER BY %s",
Question->QstCod,
Question->Shuffle ? "RAND()" : // Use RAND() because is really random; RAND(NOW()) repeats order
"AnsInd");
Question->Answer.Shuffle ? "RAND()" : // Use RAND() because is really random; RAND(NOW()) repeats order
"AnsInd");
/***** For each answer in question... *****/
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_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 void Tst_GetMediaFromDB (long CrsCod,long QstCod,int NumOpt,
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_INPUT_CHECKBOX ("Shuffle",HTM_DONT_SUBMIT_ON_CHANGE,
"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_MULTIPLE_CHOICE ? " disabled=\"disabled\"" :
"");
@ -4537,19 +4534,23 @@ void Tst_QstConstructor (struct Tst_Question *Question)
Tst_ResetTags (&Question->Tags);
Question->Stem.Text = NULL;
Question->Stem.Length = 0;
Question->Feedback.Text = NULL;
Question->EditTime = (time_t) 0;
Question->Stem.Text = NULL;
Question->Stem.Length = 0;
Question->Feedback.Text = NULL;
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.TF = ' ';
Question->Answer.Shuffle = false;
Question->Answer.TF = ' ';
/***** Initialize image attached to stem *****/
/* Initialize image attached to stem */
Med_MediaConstructor (&Question->Media);
/* Initialize options */
for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
@ -4558,7 +4559,7 @@ void Tst_QstConstructor (struct Tst_Question *Question)
Question->Answer.Options[NumOpt].Text = NULL;
Question->Answer.Options[NumOpt].Feedback = NULL;
/***** Initialize image attached to option *****/
/* Initialize image attached to option */
Med_MediaConstructor (&Question->Answer.Options[NumOpt].Media);
}
Question->Answer.Integer = 0;
@ -4700,132 +4701,152 @@ Tst_AnswerType_t Tst_GetQstAnswerType (long QstCod)
/*****************************************************************************/
/****************** 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,
char Stem[Cns_MAX_BYTES_TEXT + 1],
char Feedback[Cns_MAX_BYTES_TEXT + 1])
void Tst_GetQstDataFromDB (struct Tst_Question *Question,
char Stem[Cns_MAX_BYTES_TEXT + 1],
char Feedback[Cns_MAX_BYTES_TEXT + 1])
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
bool QuestionExists;
unsigned long NumRows;
unsigned long NumRow;
unsigned NumOpt;
/***** Get question data from database *****/
if (!DB_QuerySELECT (&mysql_res,"can not get a question",
"SELECT AnsType," // row[0]
"Shuffle," // row[1]
"Stem," // row[2]
"Feedback," // row[3]
"MedCod" // row[4]
" FROM tst_questions"
" WHERE QstCod=%ld"
" AND CrsCod=%ld", // Extra check
Question->QstCod,
Gbl.Hierarchy.Crs.CrsCod))
Lay_ShowErrorAndExit ("Question does not exist.");
QuestionExists = (DB_QuerySELECT (&mysql_res,"can not get a question",
"SELECT UNIX_TIMESTAMP(EditTime)," // row[0]
"AnsType," // row[1]
"Shuffle," // row[2]
"Stem," // row[3]
"Feedback," // row[4]
"MedCod" // row[5]
" FROM tst_questions"
" WHERE QstCod=%ld"
" AND CrsCod=%ld", // Extra check
Question->QstCod,
Gbl.Hierarchy.Crs.CrsCod) != 0);
row = mysql_fetch_row (mysql_res);
/* 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++)
if (QuestionExists)
{
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 */
DB_FreeMySQLResult (&mysql_res);
/* Get edition time (row[0] holds the start UTC time) */
Question->EditTime = Dat_GetUNIXTimeFromStr (row[3]);
/***** Get the answers from the 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
*/
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
{
row = mysql_fetch_row (mysql_res);
switch (Question->Answer.Type)
/* Get the type of answer (row[1]) */
Question->Answer.Type = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[1]);
/* Get shuffle (row[2]) */
Question->Answer.Shuffle = (row[2][0] == 'Y');
/* Get the stem (row[3]) */
Stem[0] = '\0';
if (row[3])
if (row[3][0])
Str_Copy (Stem,row[3],
Cns_MAX_BYTES_TEXT);
/* Get the feedback (row[4]) */
Feedback[0] = '\0';
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:
if (Question->Answer.NumOptions != 1)
Lay_ShowErrorAndExit ("Wrong answer.");
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 ();
row = mysql_fetch_row (mysql_res);
Str_Copy (Question->Tags.Txt[NumRow],row[0],
Tst_MAX_BYTES_TAG);
}
Str_Copy (Question->Answer.Options[NumOpt].Text,row[1],
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
// Feedback (row[2]) is initialized to empty string
if (row[2])
if (row[2][0])
Str_Copy (Question->Answer.Options[NumOpt].Feedback,row[2],
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
/***** Get the answers from the 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
*/
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]) */
Question->Answer.Options[NumOpt].Media.MedCod = Str_ConvertStrCodToLongCod (row[3]);
Med_GetMediaDataByCod (&Question->Answer.Options[NumOpt].Media);
/* Allocate space for text and feedback */
if (!Tst_AllocateTextChoiceAnswer (Question,NumOpt))
/* Abort on error */
Ale_ShowAlertsAndExit ();
/* Get if this option is correct (row[4]) */
Question->Answer.Options[NumOpt].Correct = (row[4][0] == 'Y');
break;
default:
break;
/* Get text (row[1]) */
Question->Answer.Options[NumOpt].Text[0] = '\0';
if (row[1])
if (row[1][0])
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 */
DB_FreeMySQLResult (&mysql_res);
}
@ -5048,7 +5069,7 @@ static void Tst_GetQstFromForm (struct Tst_Question *Question,
Ale_ShowAlerts (NULL);
/***** Get answers *****/
Question->Shuffle = false;
Question->Answer.Shuffle = false;
switch (Question->Answer.Type)
{
case Tst_ANS_INT:
@ -5081,7 +5102,7 @@ static void Tst_GetQstFromForm (struct Tst_Question *Question,
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
/* Get shuffle */
Question->Shuffle = Par_GetParToBool ("Shuffle");
Question->Answer.Shuffle = Par_GetParToBool ("Shuffle");
/* falls through */
/* no break */
case Tst_ANS_TEXT:
@ -5970,8 +5991,8 @@ static void Tst_InsertOrUpdateQstIntoDB (struct Tst_Question *Question)
"0)", // Score
Gbl.Hierarchy.Crs.CrsCod,
Tst_StrAnswerTypesDB[Question->Answer.Type],
Question->Shuffle ? 'Y' :
'N',
Question->Answer.Shuffle ? 'Y' :
'N',
Question->Stem.Text,
Question->Feedback.Text ? Question->Feedback.Text :
"",
@ -5991,8 +6012,8 @@ static void Tst_InsertOrUpdateQstIntoDB (struct Tst_Question *Question)
"MedCod=%ld"
" WHERE QstCod=%ld AND CrsCod=%ld",
Tst_StrAnswerTypesDB[Question->Answer.Type],
Question->Shuffle ? 'Y' :
'N',
Question->Answer.Shuffle ? 'Y' :
'N',
Question->Stem.Text,
Question->Feedback.Text ? Question->Feedback.Text :
"",

View File

@ -94,17 +94,18 @@ struct Tst_Question
{
long QstCod;
struct Tst_Tags Tags;
time_t EditTime;
struct
{
char *Text;
size_t Length;
} Stem, Feedback;
struct Media Media;
bool Shuffle;
struct
{
Tst_AnswerType_t Type;
unsigned NumOptions;
bool Shuffle;
char TF;
struct
{
@ -199,6 +200,9 @@ void Tst_QstDestructor (struct Tst_Question *Question);
bool Tst_AllocateTextChoiceAnswer (struct Tst_Question *Question,unsigned NumOpt);
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);
void Tst_ReceiveQst (void);
bool Tst_CheckIfQstFormatIsCorrectAndCountNumOptions (struct Tst_Question *Question);

View File

@ -77,6 +77,13 @@ extern struct Globals Gbl;
/***************************** 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,
unsigned NumQst,
struct Tst_Question *Question);
@ -113,31 +120,26 @@ static void TstExa_WriteIntAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam,
unsigned NumQst,
const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility);
static void TstExa_WriteFloatAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam,
unsigned NumQst,
const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility);
static void TstExa_WriteTFAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam,
unsigned NumQst,
const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility);
static void TstExa_WriteChoiceAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam,
unsigned NumQst,
struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility);
static void TstExa_WriteTextAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam,
unsigned NumQst,
struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility);
static void TstExa_WriteHeadUserCorrect (struct UsrData *UsrDat);
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)
{
extern const char *Txt_Question_removed;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumQst;
struct Tst_Question Question;
char Stem[Cns_MAX_BYTES_TEXT + 1];
char Feedback[Cns_MAX_BYTES_TEXT + 1];
/***** Begin table *****/
HTM_TABLE_BeginWideMarginPadding (10);
@ -240,20 +242,22 @@ void TstExa_ShowExamAfterAssess (struct TstExa_Exam *Exam)
{
Gbl.RowEvenOdd = NumQst % 2;
/***** Query database *****/
if (Tst_GetOneQuestionByCod (Exam->Questions[NumQst].QstCod,&mysql_res)) // Question exists
/***** Create test question *****/
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 *****/
row = mysql_fetch_row (mysql_res);
TstExa_WriteQstAndAnsExam (&Gbl.Usrs.Me.UsrDat,
Exam,
NumQst,
row,
Exam,NumQst,
&Question,Stem,Feedback,
TstCfg_GetConfigVisibility ());
/***** Store test exam question in database *****/
TstExa_StoreOneExamQstInDB (Exam,
NumQst); // 0, 1, 2, 3...
TstExa_StoreOneExamQstInDB (Exam,NumQst);
/***** Compute total 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)
Tst_UpdateQstScoreInDB (Exam,NumQst);
}
else
{
/***** Question does not exists *****/
HTM_TR_Begin (NULL);
HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd);
Tst_WriteNumQst (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);
/***** Destroy test question *****/
Tst_QstDestructor (&Question);
}
/***** 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 **********/
/*****************************************************************************/
void TstExa_WriteQstAndAnsExam (struct UsrData *UsrDat,
struct TstExa_Exam *Exam,
unsigned NumQst,
MYSQL_ROW row,
unsigned Visibility)
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)
{
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);
/*
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 *****/
Tst_QstConstructor (&Question);
Question.QstCod = Exam->Questions[NumQst].QstCod;
/***** Does question exist? *****/
QuestionExists = (Question->QstCod > 0);
/***** 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 *****/
HTM_TR_Begin (NULL);
@ -322,43 +312,56 @@ void TstExa_WriteQstAndAnsExam (struct UsrData *UsrDat,
/***** Number of question and answer type (row[1]) *****/
HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd);
Tst_WriteNumQst (NumQst + 1);
Question.Answer.Type = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[1]);
Tst_WriteAnswerType (Question.Answer.Type);
if (QuestionUneditedAfterExam)
Tst_WriteAnswerType (Question->Answer.Type);
HTM_TD_End ();
/***** Stem, media and answers *****/
HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd);
/* Stem (row[3]) */
Tst_WriteQstStem (row[3],"TEST_EXA",IsVisibleQstAndAnsTxt);
/* Media (row[5]) */
if (IsVisibleQstAndAnsTxt)
if (QuestionExists)
{
Question.Media.MedCod = Str_ConvertStrCodToLongCod (row[5]);
Med_GetMediaDataByCod (&Question.Media);
Med_ShowMedia (&Question.Media,
"TEST_MED_SHOW_CONT",
"TEST_MED_SHOW");
if (QuestionUneditedAfterExam)
{
/* Stem */
Tst_WriteQstStem (Stem,"TEST_EXA",IsVisibleQstAndAnsTxt);
/* 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 */
TstExa_ComputeAnswerScore (Exam,NumQst,&Question);
TstExa_WriteAnswersExam (UsrDat,
Exam,NumQst,&Question,
Visibility);
/* Write score retrieved from database */
HTM_DIV_Begin ("class=\"DAT_SMALL LM\"");
HTM_TxtColonNBSP (Txt_Score);
HTM_SPAN_Begin ("class=\"%s\"",
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]) */
if (TstVis_IsVisibleFeedbackTxt (Visibility))
Tst_WriteQstFeedback (row[4],"TEST_EXA_LIGHT");
/* Question feedback */
if (QuestionUneditedAfterExam)
if (TstVis_IsVisibleFeedbackTxt (Visibility))
Tst_WriteQstFeedback (Feedback,"TEST_EXA_LIGHT");
HTM_TD_End ();
/***** End row *****/
HTM_TR_End ();
/***** Destroy test question *****/
Tst_QstDestructor (&Question);
}
/*****************************************************************************/
@ -918,54 +921,28 @@ static void TstExa_WriteAnswersExam (struct UsrData *UsrDat,
struct Tst_Question *Question,
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 *****/
switch (Question->Answer.Type)
{
case Tst_ANS_INT:
TstExa_WriteIntAnsExam (UsrDat,Exam,
NumQst,Question,mysql_res,
Visibility);
TstExa_WriteIntAnsExam (UsrDat,Exam,NumQst,Question,Visibility);
break;
case Tst_ANS_FLOAT:
TstExa_WriteFloatAnsExam (UsrDat,Exam,
NumQst,Question,mysql_res,
Visibility);
TstExa_WriteFloatAnsExam (UsrDat,Exam,NumQst,Question,Visibility);
break;
case Tst_ANS_TRUE_FALSE:
TstExa_WriteTFAnsExam (UsrDat,Exam,
NumQst,Question,mysql_res,
Visibility);
TstExa_WriteTFAnsExam (UsrDat,Exam,NumQst,Question,Visibility);
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
TstExa_WriteChoiceAnsExam (UsrDat,Exam,
NumQst,Question,mysql_res,
Visibility);
TstExa_WriteChoiceAnsExam (UsrDat,Exam,NumQst,Question,Visibility);
break;
case Tst_ANS_TEXT:
TstExa_WriteTextAnsExam (UsrDat,Exam,
NumQst,Question,mysql_res,
Visibility);
TstExa_WriteTextAnsExam (UsrDat,Exam,NumQst,Question,Visibility);
break;
default:
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,
unsigned NumQst,
const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility)
{
MYSQL_ROW row;
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 *****/
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 *****/
HTM_TABLE_BeginPadding (2);
HTM_TR_Begin (NULL);
@ -1012,8 +975,8 @@ static void TstExa_WriteIntAnsExam (struct UsrData *UsrDat,
{
HTM_TD_Begin ("class=\"%s CM\"",
TstVis_IsVisibleCorrectAns (Visibility) ?
(IntAnswerUsr == IntAnswerCorr ? "ANS_OK" :
"ANS_BAD") :
(IntAnswerUsr == Question->Answer.Integer ? "ANS_OK" :
"ANS_BAD") :
"ANS_0");
HTM_Long (IntAnswerUsr);
HTM_TD_End ();
@ -1032,7 +995,7 @@ static void TstExa_WriteIntAnsExam (struct UsrData *UsrDat,
HTM_TD_Begin ("class=\"ANS_0 CM\"");
if (TstVis_IsVisibleQstAndAnsTxt (Visibility) &&
TstVis_IsVisibleCorrectAns (Visibility))
HTM_Long (IntAnswerCorr);
HTM_Long (Question->Answer.Integer);
else
Ico_PutIconNotVisible ();
HTM_TD_End ();
@ -1043,17 +1006,17 @@ static void TstExa_WriteIntAnsExam (struct UsrData *UsrDat,
if (TstVis_IsVisibleEachQstScore (Visibility))
{
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_Double2Decimals (0.0);
}
else if (IntAnswerUsr == IntAnswerCorr) // If correct
else if (IntAnswerUsr == Question->Answer.Integer) // If correct
{
HTM_SPAN_Begin ("class=\"ANS_OK\"");
HTM_Double2Decimals (1.0);
}
else // If wrong
else // If wrong
{
HTM_SPAN_Begin ("class=\"ANS_BAD\"");
HTM_Double2Decimals (0.0);
@ -1073,40 +1036,14 @@ static void TstExa_WriteFloatAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam,
unsigned NumQst,
const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility)
{
MYSQL_ROW row;
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
*/
double FloatAnsUsr = 0.0;
/***** Check if number of rows is correct *****/
if (Question->Answer.NumOptions != 2)
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 *****/
HTM_TABLE_BeginPadding (2);
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
HTM_TD_Begin ("class=\"%s CM\"",
TstVis_IsVisibleCorrectAns (Visibility) ?
((FloatAnsUsr >= FloatAnsCorr[0] &&
FloatAnsUsr <= FloatAnsCorr[1]) ? "ANS_OK" :
"ANS_BAD") :
((FloatAnsUsr >= Question->Answer.FloatingPoint[0] &&
FloatAnsUsr <= Question->Answer.FloatingPoint[1]) ? "ANS_OK" :
"ANS_BAD") :
"ANS_0");
HTM_Double (FloatAnsUsr);
}
@ -1138,9 +1075,9 @@ static void TstExa_WriteFloatAnsExam (struct UsrData *UsrDat,
TstVis_IsVisibleCorrectAns (Visibility))
{
HTM_Txt ("[");
HTM_Double (FloatAnsCorr[0]);
HTM_Double (Question->Answer.FloatingPoint[0]);
HTM_Txt ("; ");
HTM_Double (FloatAnsCorr[1]);
HTM_Double (Question->Answer.FloatingPoint[1]);
HTM_Txt ("]");
}
else
@ -1153,18 +1090,18 @@ static void TstExa_WriteFloatAnsExam (struct UsrData *UsrDat,
if (TstVis_IsVisibleEachQstScore (Visibility))
{
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_Double2Decimals (0.0);
}
else if (FloatAnsUsr >= FloatAnsCorr[0] &&
FloatAnsUsr <= FloatAnsCorr[1]) // If correct (inside the interval)
else if (FloatAnsUsr >= Question->Answer.FloatingPoint[0] &&
FloatAnsUsr <= Question->Answer.FloatingPoint[1]) // If correct (inside the interval)
{
HTM_SPAN_Begin ("class=\"ANS_OK\"");
HTM_Double2Decimals (1.0);
}
else // If wrong (outside the interval)
else // If wrong (outside the interval)
{
HTM_SPAN_Begin ("class=\"ANS_BAD\"");
HTM_Double2Decimals (0.0);
@ -1184,24 +1121,15 @@ static void TstExa_WriteTFAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam,
unsigned NumQst,
const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility)
{
MYSQL_ROW row;
char AnsTF;
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
char AnsTFUsr;
/***** Check if number of rows is correct *****/
Tst_CheckIfNumberOfAnswersIsOne (Question);
/***** Get answer true or false *****/
row = mysql_fetch_row (mysql_res);
AnsTF = Exam->Questions[NumQst].StrAnswers[0];
AnsTFUsr = Exam->Questions[NumQst].StrAnswers[0];
/***** Header with the title of each column *****/
HTM_TABLE_BeginPadding (2);
@ -1214,17 +1142,17 @@ static void TstExa_WriteTFAnsExam (struct UsrData *UsrDat,
/***** Write the user answer *****/
HTM_TD_Begin ("class=\"%s CM\"",
TstVis_IsVisibleCorrectAns (Visibility) ?
(AnsTF == row[1][0] ? "ANS_OK" :
"ANS_BAD") :
(AnsTFUsr == Question->Answer.TF ? "ANS_OK" :
"ANS_BAD") :
"ANS_0");
Tst_WriteAnsTF (AnsTF);
Tst_WriteAnsTF (AnsTFUsr);
HTM_TD_End ();
/***** Write the correct answer *****/
HTM_TD_Begin ("class=\"ANS_0 CM\"");
if (TstVis_IsVisibleQstAndAnsTxt (Visibility) &&
TstVis_IsVisibleCorrectAns (Visibility))
Tst_WriteAnsTF (row[1][0]);
Tst_WriteAnsTF (Question->Answer.TF);
else
Ico_PutIconNotVisible ();
HTM_TD_End ();
@ -1235,17 +1163,17 @@ static void TstExa_WriteTFAnsExam (struct UsrData *UsrDat,
if (TstVis_IsVisibleEachQstScore (Visibility))
{
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_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_Double2Decimals (1.0);
}
else // If wrong
else // If wrong
{
HTM_SPAN_Begin ("class=\"ANS_BAD\"");
HTM_Double2Decimals (-1.0);
@ -1265,7 +1193,6 @@ static void TstExa_WriteChoiceAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam,
unsigned NumQst,
struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility)
{
extern const char *Txt_TST_Answer_given_by_the_user;
@ -1279,17 +1206,6 @@ static void TstExa_WriteChoiceAnsExam (struct UsrData *UsrDat,
char *Str;
} 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 *****/
TstExa_GetIndexesFromStr (Exam->Questions[NumQst].StrIndexes,Indexes);
@ -1421,55 +1337,30 @@ static void TstExa_WriteTextAnsExam (struct UsrData *UsrDat,
const struct TstExa_Exam *Exam,
unsigned NumQst,
struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility)
{
unsigned NumOpt;
MYSQL_ROW row;
char TextAnsUsr[TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1];
char TextAnsOK[TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1];
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) *****/
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
{
/***** Get next answer *****/
row = mysql_fetch_row (mysql_res);
/***** Convert answer text, that is in HTML, to rigorous HTML ******/
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 *****/
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 ******/
/***** Convert answer feedback, that is in HTML, to rigorous HTML ******/
if (TstVis_IsVisibleFeedbackTxt (Visibility))
if (row[2])
if (row[2][0])
{
Str_Copy (Question->Answer.Options[NumOpt].Feedback,row[2],
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');
if (Question->Answer.Options[NumOpt].Feedback[0])
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Question->Answer.Options[NumOpt].Feedback,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false);
}
/***** Header with the title of each column *****/
@ -2437,13 +2328,10 @@ void TstExa_ShowExamAnswers (struct UsrData *UsrDat,
struct TstExa_Exam *Exam,
unsigned Visibility)
{
extern const char *Txt_Question_modified;
extern const char *Txt_Question_removed;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumQst;
bool ThisQuestionHasBeenEdited;
time_t EditTimeUTC;
struct Tst_Question Question;
char Stem[Cns_MAX_BYTES_TEXT + 1];
char Feedback[Cns_MAX_BYTES_TEXT + 1];
for (NumQst = 0;
NumQst < Exam->NumQsts;
@ -2451,60 +2339,21 @@ void TstExa_ShowExamAnswers (struct UsrData *UsrDat,
{
Gbl.RowEvenOdd = NumQst % 2;
/***** Query database *****/
if (Tst_GetOneQuestionByCod (Exam->Questions[NumQst].QstCod,&mysql_res)) // Question exists
{
/***** Get row of the result of the query *****/
row = mysql_fetch_row (mysql_res);
/***** Create test question *****/
Tst_QstConstructor (&Question);
Question.QstCod = Exam->Questions[NumQst].QstCod;
/***** If this question has been edited later than test time
==> don't show question ****/
EditTimeUTC = Dat_GetUNIXTimeFromStr (row[0]);
ThisQuestionHasBeenEdited = false;
if (EditTimeUTC > Exam->TimeUTC[Dat_START_TIME])
ThisQuestionHasBeenEdited = true;
/***** Get question data *****/
Tst_GetQstDataFromDB (&Question,Stem,Feedback);
if (ThisQuestionHasBeenEdited)
{
/***** Question has been edited *****/
HTM_TR_Begin (NULL);
/***** Write questions and answers *****/
TstExa_WriteQstAndAnsExam (UsrDat,
Exam,NumQst,
&Question,Stem,Feedback,
Visibility);
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_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);
/***** Destroy test question *****/
Tst_QstDestructor (&Question);
}
}

View File

@ -71,11 +71,6 @@ void TstExa_CreateExamInDB (struct TstExa_Exam *Exam);
void TstExa_UpdateExamInDB (const 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,
bool UpdateQstScore);

View File

@ -675,7 +675,7 @@ static void TsI_ImportQuestionsFromXMLBuffer (const char *XMLBuffer)
}
/* Get shuffle. By default, shuffle is false. */
Question.Shuffle = false;
Question.Answer.Shuffle = false;
for (AnswerElem = QuestionElem->FirstChild;
AnswerElem != NULL;
AnswerElem = AnswerElem->NextBrother)
@ -689,7 +689,7 @@ static void TsI_ImportQuestionsFromXMLBuffer (const char *XMLBuffer)
Attribute = Attribute->Next)
if (!strcmp (Attribute->AttributeName,"shuffle"))
{
Question.Shuffle = XML_GetAttributteYesNoFromXMLTree (Attribute);
Question.Answer.Shuffle = XML_GetAttributteYesNoFromXMLTree (Attribute);
break; // Only first attribute "shuffle"
}
break; // Only first element "answer"
@ -1023,7 +1023,7 @@ static void TsI_WriteRowImportedQst (struct XMLElement *StemElem,
if (Question->Answer.Type == Tst_ANS_UNIQUE_CHOICE ||
Question->Answer.Type == Tst_ANS_MULTIPLE_CHOICE)
/* 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,
QuestionExists ? "ICO_HIDDEN ICO16x16" :
"ICO16x16");