swad-core/swad_test_print.c

2385 lines
82 KiB
C
Raw Normal View History

2020-05-07 18:33:26 +02:00
// swad_test_print.c: test exam prints made by users
2020-04-02 03:28:08 +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-2020 Antonio Ca<EFBFBD>as Vargas
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*****************************************************************************/
/*********************************** Headers *********************************/
/*****************************************************************************/
#define _GNU_SOURCE // For asprintf
#include <stdbool.h> // For boolean type
#include <stddef.h> // For NULL
#include <stdio.h> // For asprintf
#include <stdlib.h> // For free
#include <string.h> // For string functions
#include "swad_action.h"
#include "swad_database.h"
#include "swad_form.h"
#include "swad_global.h"
#include "swad_HTML.h"
#include "swad_ID.h"
2020-04-14 17:15:17 +02:00
#include "swad_photo.h"
2020-04-02 03:28:08 +02:00
#include "swad_test.h"
2020-05-07 18:33:26 +02:00
#include "swad_test_print.h"
2020-04-02 03:28:08 +02:00
#include "swad_test_visibility.h"
#include "swad_user.h"
/*****************************************************************************/
/***************************** Public constants ******************************/
/*****************************************************************************/
/*****************************************************************************/
/**************************** Private constants ******************************/
/*****************************************************************************/
/*****************************************************************************/
/******************************* Private types *******************************/
/*****************************************************************************/
/*****************************************************************************/
/************** External global variables from others modules ****************/
/*****************************************************************************/
extern struct Globals Gbl;
/*****************************************************************************/
/************************* Private global variables **************************/
/*****************************************************************************/
/*****************************************************************************/
/***************************** Private prototypes ****************************/
/*****************************************************************************/
2020-05-11 02:28:38 +02:00
static void TstPrn_ResetPrintExceptPrnCod (struct TstPrn_Print *Print);
2020-04-16 21:03:22 +02:00
2020-05-07 18:33:26 +02:00
static void TstPrn_WriteQstAndAnsExam (struct UsrData *UsrDat,
struct TstPrn_Print *Print,
2020-04-03 19:13:00 +02:00
unsigned NumQst,
struct Tst_Question *Question,
2020-05-07 19:54:24 +02:00
bool QuestionExists,
2020-04-03 19:13:00 +02:00
unsigned Visibility);
2020-05-09 01:37:00 +02:00
static void TstPrn_ComputeIntAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
2020-04-02 03:28:08 +02:00
struct Tst_Question *Question);
2020-05-07 18:33:26 +02:00
static void TstPrn_GetCorrectIntAnswerFromDB (struct Tst_Question *Question);
2020-05-09 01:37:00 +02:00
static void TstPrn_ComputeFloatAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
2020-04-02 03:28:08 +02:00
struct Tst_Question *Question);
2020-05-07 18:33:26 +02:00
static void TstPrn_GetCorrectFloatAnswerFromDB (struct Tst_Question *Question);
2020-05-09 01:37:00 +02:00
static void TstPrn_ComputeTFAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
2020-04-02 03:28:08 +02:00
struct Tst_Question *Question);
2020-05-07 18:33:26 +02:00
static void TstPrn_GetCorrectTFAnswerFromDB (struct Tst_Question *Question);
static void TstPrn_GetCorrectChoiceAnswerFromDB (struct Tst_Question *Question);
2020-04-02 03:28:08 +02:00
2020-05-09 01:37:00 +02:00
static void TstPrn_ComputeScoreQst (struct TstPrn_PrintedQuestion *PrintedQuestion,
2020-04-02 03:28:08 +02:00
const struct Tst_Question *Question,
unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION], // Indexes of all answers of this question
bool UsrAnswers[Tst_MAX_OPTIONS_PER_QUESTION]);
2020-05-09 01:37:00 +02:00
static void TstPrn_ComputeTextAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
2020-04-02 03:28:08 +02:00
struct Tst_Question *Question);
2020-05-07 18:33:26 +02:00
static void TstPrn_GetCorrectTextAnswerFromDB (struct Tst_Question *Question);
2020-04-02 03:28:08 +02:00
2020-05-07 18:33:26 +02:00
static void TstPrn_WriteAnswersExam (struct UsrData *UsrDat,
const struct TstPrn_Print *Print,
2020-04-02 03:28:08 +02:00
unsigned NumQst,
struct Tst_Question *Question,
unsigned Visibility);
2020-05-07 18:33:26 +02:00
static void TstPrn_WriteIntAnsExam (struct UsrData *UsrDat,
const struct TstPrn_Print *Print,
2020-04-02 03:28:08 +02:00
unsigned NumQst,
const struct Tst_Question *Question,
unsigned Visibility);
2020-05-07 18:33:26 +02:00
static void TstPrn_WriteFloatAnsExam (struct UsrData *UsrDat,
const struct TstPrn_Print *Print,
2020-04-02 03:28:08 +02:00
unsigned NumQst,
const struct Tst_Question *Question,
unsigned Visibility);
2020-05-07 18:33:26 +02:00
static void TstPrn_WriteTFAnsExam (struct UsrData *UsrDat,
const struct TstPrn_Print *Print,
2020-04-02 03:28:08 +02:00
unsigned NumQst,
const struct Tst_Question *Question,
unsigned Visibility);
2020-05-07 18:33:26 +02:00
static void TstPrn_WriteChoiceAnsExam (struct UsrData *UsrDat,
const struct TstPrn_Print *Print,
2020-04-02 03:28:08 +02:00
unsigned NumQst,
struct Tst_Question *Question,
unsigned Visibility);
2020-05-07 18:33:26 +02:00
static void TstPrn_WriteTextAnsExam (struct UsrData *UsrDat,
const struct TstPrn_Print *Print,
2020-04-02 03:28:08 +02:00
unsigned NumQst,
struct Tst_Question *Question,
unsigned Visibility);
2020-05-07 18:33:26 +02:00
static void TstPrn_WriteHeadUserCorrect (struct UsrData *UsrDat);
2020-04-02 03:28:08 +02:00
2020-05-09 01:37:00 +02:00
static void TstPrn_StoreOneQstOfPrintInDB (const struct TstPrn_Print *Print,
unsigned NumQst);
2020-04-02 03:28:08 +02:00
2020-05-07 18:33:26 +02:00
static void TstPrn_PutFormToSelectUsrsToViewUsrsExams (__attribute__((unused)) void *Args);
2020-04-09 21:36:21 +02:00
2020-05-07 18:33:26 +02:00
static void TstPrn_ShowUsrsExams (__attribute__((unused)) void *Args);
static void TstPrn_ShowHeaderExams (void);
static void TstPrn_ShowExams (struct UsrData *UsrDat);
static void TstPrn_ShowExamsSummaryRow (bool ItsMe,
2020-04-02 03:28:08 +02:00
unsigned NumExams,
unsigned NumTotalQsts,
unsigned NumTotalQstsNotBlank,
double TotalScoreOfAllTests);
2020-05-07 18:33:26 +02:00
static void TstPrn_ShowTagsPresentInAnExam (long ResCod);
2020-04-02 03:28:08 +02:00
2020-04-03 01:55:39 +02:00
/*****************************************************************************/
/******************************** Reset exam *********************************/
/*****************************************************************************/
2020-05-11 02:28:38 +02:00
void TstPrn_ResetPrint (struct TstPrn_Print *Print)
2020-04-03 01:55:39 +02:00
{
2020-05-07 18:33:26 +02:00
Print->PrnCod = -1L;
2020-05-11 02:28:38 +02:00
TstPrn_ResetPrintExceptPrnCod (Print);
2020-04-16 21:03:22 +02:00
}
2020-05-11 02:28:38 +02:00
static void TstPrn_ResetPrintExceptPrnCod (struct TstPrn_Print *Print)
2020-04-16 21:03:22 +02:00
{
2020-05-07 18:33:26 +02:00
Print->TimeUTC[Dat_START_TIME] =
Print->TimeUTC[Dat_END_TIME ] = (time_t) 0;
Print->NumQsts =
Print->NumQstsNotBlank = 0;
Print->Sent = false; // After creating an exam, it's not sent
Print->AllowTeachers = false; // Teachers can't seen the exam if student don't allow it
Print->Score = 0.0;
2020-04-03 01:55:39 +02:00
}
2020-04-02 03:28:08 +02:00
/*****************************************************************************/
2020-05-09 01:37:00 +02:00
/************** Create new blank test exam print in database *****************/
2020-04-02 03:28:08 +02:00
/*****************************************************************************/
2020-05-09 01:37:00 +02:00
void TstPrn_CreatePrintInDB (struct TstPrn_Print *Print)
2020-04-02 03:28:08 +02:00
{
2020-05-09 01:37:00 +02:00
/***** Insert new test exam print into table *****/
2020-05-07 18:33:26 +02:00
Print->PrnCod =
2020-05-09 01:37:00 +02:00
DB_QueryINSERTandReturnCode ("can not create new test exam print",
2020-04-02 03:28:08 +02:00
"INSERT INTO tst_exams"
2020-05-09 01:37:00 +02:00
" (CrsCod,UsrCod,StartTime,EndTime,NumQsts,NumQstsNotBlank,Sent,AllowTeachers,Score)"
2020-04-02 03:28:08 +02:00
" VALUES"
2020-05-09 01:37:00 +02:00
" (%ld,%ld,NOW(),NOW(),%u,0,'N','N',0)",
2020-04-02 03:28:08 +02:00
Gbl.Hierarchy.Crs.CrsCod,
Gbl.Usrs.Me.UsrDat.UsrCod,
2020-05-07 18:33:26 +02:00
Print->NumQsts);
2020-04-02 03:28:08 +02:00
}
/*****************************************************************************/
2020-05-11 02:28:38 +02:00
/******************** Update test exam print in database *********************/
2020-04-02 03:28:08 +02:00
/*****************************************************************************/
2020-05-11 02:28:38 +02:00
void TstPrn_UpdatePrintInDB (const struct TstPrn_Print *Print)
2020-04-02 03:28:08 +02:00
{
2020-05-11 02:28:38 +02:00
/***** Update test exam print in database *****/
2020-04-02 03:28:08 +02:00
Str_SetDecimalPointToUS (); // To print the floating point as a dot
DB_QueryUPDATE ("can not update test exam",
"UPDATE tst_exams"
" SET EndTime=NOW(),"
"NumQstsNotBlank=%u,"
2020-04-16 21:03:22 +02:00
"Sent='%c',"
2020-04-03 01:55:39 +02:00
"AllowTeachers='%c',"
2020-04-02 03:28:08 +02:00
"Score='%.15lg'"
" WHERE ExaCod=%ld"
" AND CrsCod=%ld AND UsrCod=%ld", // Extra checks
2020-05-07 18:33:26 +02:00
Print->NumQstsNotBlank,
Print->Sent ? 'Y' :
'N',
Print->AllowTeachers ? 'Y' :
'N',
Print->Score,
Print->PrnCod,
2020-04-02 03:28:08 +02:00
Gbl.Hierarchy.Crs.CrsCod,
Gbl.Usrs.Me.UsrDat.UsrCod);
Str_SetDecimalPointToLocal (); // Return to local system
}
/*****************************************************************************/
/********************* Show test exam after assessing it *********************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
void TstPrn_ShowExamAfterAssess (struct TstPrn_Print *Print)
2020-04-02 03:28:08 +02:00
{
unsigned NumQst;
2020-04-03 19:13:00 +02:00
struct Tst_Question Question;
2020-05-07 19:54:24 +02:00
bool QuestionExists;
2020-04-02 03:28:08 +02:00
/***** Begin table *****/
HTM_TABLE_BeginWideMarginPadding (10);
/***** Initialize score and number of questions not blank *****/
2020-05-07 18:33:26 +02:00
Print->NumQstsNotBlank = 0;
Print->Score = 0.0;
2020-04-02 03:28:08 +02:00
for (NumQst = 0;
2020-05-07 18:33:26 +02:00
NumQst < Print->NumQsts;
2020-04-02 03:28:08 +02:00
NumQst++)
{
Gbl.RowEvenOdd = NumQst % 2;
2020-04-03 19:13:00 +02:00
/***** Create test question *****/
Tst_QstConstructor (&Question);
2020-05-07 18:33:26 +02:00
Question.QstCod = Print->PrintedQuestions[NumQst].QstCod;
2020-04-03 19:13:00 +02:00
/***** Get question data *****/
2020-05-07 19:54:24 +02:00
QuestionExists = Tst_GetQstDataFromDB (&Question);
/***** Write question and answers *****/
TstPrn_WriteQstAndAnsExam (&Gbl.Usrs.Me.UsrDat,Print,
NumQst,&Question,QuestionExists,
TstCfg_GetConfigVisibility ());
/***** Store test exam question in database *****/
2020-05-09 01:37:00 +02:00
TstPrn_StoreOneQstOfPrintInDB (Print,NumQst);
2020-05-07 19:54:24 +02:00
/***** Compute total score *****/
Print->Score += Print->PrintedQuestions[NumQst].Score;
if (Print->PrintedQuestions[NumQst].AnswerIsNotBlank)
Print->NumQstsNotBlank++;
/***** Update the number of accesses and the score of this question *****/
if (Gbl.Usrs.Me.Role.Logged == Rol_STD)
2020-05-09 01:37:00 +02:00
Tst_UpdateQstScoreInDB (&Print->PrintedQuestions[NumQst]);
2020-04-02 03:28:08 +02:00
2020-04-03 19:13:00 +02:00
/***** Destroy test question *****/
Tst_QstDestructor (&Question);
2020-04-02 03:28:08 +02:00
}
/***** End table *****/
HTM_TABLE_End ();
}
/*****************************************************************************/
/********** Write a row of a test, with one question and its answer **********/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
static void TstPrn_WriteQstAndAnsExam (struct UsrData *UsrDat,
struct TstPrn_Print *Print,
2020-04-03 19:13:00 +02:00
unsigned NumQst,
struct Tst_Question *Question,
2020-05-07 19:54:24 +02:00
bool QuestionExists,
2020-04-03 19:13:00 +02:00
unsigned Visibility)
2020-04-02 03:28:08 +02:00
{
2020-04-03 19:13:00 +02:00
extern const char *Txt_Score;
extern const char *Txt_Question_removed;
extern const char *Txt_Question_modified;
bool QuestionUneditedAfterExam = false;
2020-04-02 03:28:08 +02:00
bool IsVisibleQstAndAnsTxt = TstVis_IsVisibleQstAndAnsTxt (Visibility);
2020-04-03 19:13:00 +02:00
/***** If this question has been edited later than test time
==> don't show question ****/
if (QuestionExists)
2020-05-07 18:33:26 +02:00
QuestionUneditedAfterExam = (Question->EditTime < Print->TimeUTC[Dat_START_TIME]);
2020-04-03 19:13:00 +02:00
else
QuestionUneditedAfterExam = false;
2020-04-02 03:28:08 +02:00
/***** Begin row *****/
HTM_TR_Begin (NULL);
/***** Number of question and answer type (row[1]) *****/
HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd);
Tst_WriteNumQst (NumQst + 1);
2020-04-03 19:13:00 +02:00
if (QuestionUneditedAfterExam)
Tst_WriteAnswerType (Question->Answer.Type);
2020-04-02 03:28:08 +02:00
HTM_TD_End ();
/***** Stem, media and answers *****/
HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd);
2020-04-03 19:13:00 +02:00
if (QuestionExists)
2020-04-02 03:28:08 +02:00
{
2020-04-03 19:13:00 +02:00
if (QuestionUneditedAfterExam)
{
/* Stem */
2020-04-04 19:20:50 +02:00
Tst_WriteQstStem (Question->Stem,"TEST_EXA",IsVisibleQstAndAnsTxt);
2020-04-03 19:13:00 +02:00
/* Media */
if (IsVisibleQstAndAnsTxt)
Med_ShowMedia (&Question->Media,
"TEST_MED_SHOW_CONT",
"TEST_MED_SHOW");
/* Answers */
2020-05-09 01:37:00 +02:00
TstPrn_ComputeAnswerScore (&Print->PrintedQuestions[NumQst],Question);
2020-05-07 18:33:26 +02:00
TstPrn_WriteAnswersExam (UsrDat,Print,NumQst,Question,Visibility);
2020-04-03 19:13:00 +02:00
}
else
Ale_ShowAlert (Ale_WARNING,Txt_Question_modified);
2020-04-02 03:28:08 +02:00
}
2020-04-03 19:13:00 +02:00
else
Ale_ShowAlert (Ale_WARNING,Txt_Question_removed);
2020-04-02 03:28:08 +02:00
2020-04-03 19:13:00 +02:00
/* Write score retrieved from database */
2020-04-03 19:19:41 +02:00
if (TstVis_IsVisibleEachQstScore (Visibility))
{
HTM_DIV_Begin ("class=\"DAT_SMALL LM\"");
HTM_TxtColonNBSP (Txt_Score);
HTM_SPAN_Begin ("class=\"%s\"",
2020-05-07 18:33:26 +02:00
Print->PrintedQuestions[NumQst].StrAnswers[0] ?
2020-05-07 19:54:24 +02:00
(Print->PrintedQuestions[NumQst].Score > 0 ? "ANS_OK" : // Correct/semicorrect
"ANS_BAD") : // Wrong
"ANS_0"); // Blank answer
2020-05-07 18:33:26 +02:00
HTM_Double2Decimals (Print->PrintedQuestions[NumQst].Score);
2020-04-03 19:19:41 +02:00
HTM_SPAN_End ();
HTM_DIV_End ();
}
2020-04-03 19:13:00 +02:00
/* Question feedback */
if (QuestionUneditedAfterExam)
if (TstVis_IsVisibleFeedbackTxt (Visibility))
2020-04-04 19:20:50 +02:00
Tst_WriteQstFeedback (Question->Feedback,"TEST_EXA_LIGHT");
2020-04-02 03:28:08 +02:00
HTM_TD_End ();
/***** End row *****/
HTM_TR_End ();
}
/*****************************************************************************/
/*********** Compute score of each question and store in database ************/
/*****************************************************************************/
2020-05-09 01:37:00 +02:00
void TstPrn_ComputeScoresAndStoreQuestionsOfPrint (struct TstPrn_Print *Print,
bool UpdateQstScore)
2020-04-02 03:28:08 +02:00
{
unsigned NumQst;
struct Tst_Question Question;
/***** Initialize total score *****/
2020-05-07 18:33:26 +02:00
Print->Score = 0.0;
Print->NumQstsNotBlank = 0;
2020-04-02 03:28:08 +02:00
/***** Compute and store scores of all questions *****/
for (NumQst = 0;
2020-05-07 18:33:26 +02:00
NumQst < Print->NumQsts;
2020-04-02 03:28:08 +02:00
NumQst++)
{
/* Compute question score */
Tst_QstConstructor (&Question);
2020-05-07 18:33:26 +02:00
Question.QstCod = Print->PrintedQuestions[NumQst].QstCod;
2020-04-02 03:28:08 +02:00
Question.Answer.Type = Tst_GetQstAnswerType (Question.QstCod);
2020-05-09 01:37:00 +02:00
TstPrn_ComputeAnswerScore (&Print->PrintedQuestions[NumQst],&Question);
2020-04-02 03:28:08 +02:00
Tst_QstDestructor (&Question);
/* Store test exam question in database */
2020-05-09 01:37:00 +02:00
TstPrn_StoreOneQstOfPrintInDB (Print,
NumQst); // 0, 1, 2, 3...
2020-04-02 03:28:08 +02:00
/* Accumulate total score */
2020-05-07 18:33:26 +02:00
Print->Score += Print->PrintedQuestions[NumQst].Score;
if (Print->PrintedQuestions[NumQst].AnswerIsNotBlank)
Print->NumQstsNotBlank++;
2020-04-02 03:28:08 +02:00
/* Update the number of hits and the score of this question in tests database */
if (UpdateQstScore)
2020-05-09 01:37:00 +02:00
Tst_UpdateQstScoreInDB (&Print->PrintedQuestions[NumQst]);
2020-04-02 03:28:08 +02:00
}
}
/*****************************************************************************/
/************* Write answers of a question when assessing a test *************/
/*****************************************************************************/
2020-05-09 01:37:00 +02:00
void TstPrn_ComputeAnswerScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
struct Tst_Question *Question)
2020-04-02 03:28:08 +02:00
{
/***** Write answer depending on type *****/
switch (Question->Answer.Type)
{
case Tst_ANS_INT:
2020-05-09 01:37:00 +02:00
TstPrn_ComputeIntAnsScore (PrintedQuestion,Question);
2020-04-02 03:28:08 +02:00
break;
case Tst_ANS_FLOAT:
2020-05-09 01:37:00 +02:00
TstPrn_ComputeFloatAnsScore (PrintedQuestion,Question);
2020-04-02 03:28:08 +02:00
break;
case Tst_ANS_TRUE_FALSE:
2020-05-09 01:37:00 +02:00
TstPrn_ComputeTFAnsScore (PrintedQuestion,Question);
2020-04-02 03:28:08 +02:00
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
2020-05-09 01:37:00 +02:00
TstPrn_ComputeChoiceAnsScore (PrintedQuestion,Question);
2020-04-02 03:28:08 +02:00
break;
case Tst_ANS_TEXT:
2020-05-09 01:37:00 +02:00
TstPrn_ComputeTextAnsScore (PrintedQuestion,Question);
2020-04-02 03:28:08 +02:00
break;
default:
break;
}
}
/*****************************************************************************/
/**************** Write integer answer when assessing a test *****************/
/*****************************************************************************/
2020-05-09 01:37:00 +02:00
static void TstPrn_ComputeIntAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
2020-04-02 03:28:08 +02:00
struct Tst_Question *Question)
{
long AnswerUsr;
/***** Get the numerical value of the correct answer *****/
2020-05-07 18:33:26 +02:00
TstPrn_GetCorrectIntAnswerFromDB (Question);
2020-04-02 03:28:08 +02:00
/***** Compute score *****/
2020-05-09 01:37:00 +02:00
PrintedQuestion->Score = 0.0; // Default score for blank or wrong answer
PrintedQuestion->AnswerIsNotBlank = (PrintedQuestion->StrAnswers[0] != '\0');
if (PrintedQuestion->AnswerIsNotBlank) // If user has answered the answer
if (sscanf (PrintedQuestion->StrAnswers,"%ld",&AnswerUsr) == 1)
2020-04-02 03:28:08 +02:00
if (AnswerUsr == Question->Answer.Integer) // Correct answer
2020-05-09 01:37:00 +02:00
PrintedQuestion->Score = 1.0;
2020-04-02 03:28:08 +02:00
}
2020-05-07 18:33:26 +02:00
static void TstPrn_GetCorrectIntAnswerFromDB (struct Tst_Question *Question)
2020-04-02 03:28:08 +02:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
/***** Query database *****/
Question->Answer.NumOptions =
(unsigned) DB_QuerySELECT (&mysql_res,"can not get answers of a question",
"SELECT Answer" // row[0]
" FROM tst_answers"
" WHERE QstCod=%ld",
Question->QstCod);
/***** Check if number of rows is correct *****/
Tst_CheckIfNumberOfAnswersIsOne (Question);
/***** Get correct answer *****/
row = mysql_fetch_row (mysql_res);
if (sscanf (row[0],"%ld",&Question->Answer.Integer) != 1)
Lay_ShowErrorAndExit ("Wrong integer answer.");
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
/***************** Write float answer when assessing a test ******************/
/*****************************************************************************/
2020-05-09 01:37:00 +02:00
static void TstPrn_ComputeFloatAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
2020-04-02 03:28:08 +02:00
struct Tst_Question *Question)
{
double AnswerUsr;
/***** Get the numerical value of the minimum and maximum correct answers *****/
2020-05-07 18:33:26 +02:00
TstPrn_GetCorrectFloatAnswerFromDB (Question);
2020-04-02 03:28:08 +02:00
/***** Compute score *****/
2020-05-09 01:37:00 +02:00
PrintedQuestion->Score = 0.0; // Default score for blank or wrong answer
PrintedQuestion->AnswerIsNotBlank = (PrintedQuestion->StrAnswers[0] != '\0');
if (PrintedQuestion->AnswerIsNotBlank) // If user has answered the answer
2020-04-02 03:28:08 +02:00
{
2020-05-09 01:37:00 +02:00
AnswerUsr = Str_GetDoubleFromStr (PrintedQuestion->StrAnswers);
2020-04-30 20:15:21 +02:00
2020-04-02 03:28:08 +02:00
// A bad formatted floating point answer will interpreted as 0.0
2020-05-09 01:37:00 +02:00
PrintedQuestion->Score = (AnswerUsr >= Question->Answer.FloatingPoint[0] &&
AnswerUsr <= Question->Answer.FloatingPoint[1]) ? 1.0 : // If correct (inside the interval)
0.0; // If wrong (outside the interval)
2020-04-02 03:28:08 +02:00
}
}
2020-05-07 18:33:26 +02:00
static void TstPrn_GetCorrectFloatAnswerFromDB (struct Tst_Question *Question)
2020-04-02 03:28:08 +02:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumOpt;
double Tmp;
/***** Query database *****/
Question->Answer.NumOptions =
(unsigned) DB_QuerySELECT (&mysql_res,"can not get answers of a question",
"SELECT Answer" // row[0]
" FROM tst_answers"
" WHERE QstCod=%ld",
Question->QstCod);
/***** Check if number of rows is correct *****/
if (Question->Answer.NumOptions != 2)
Lay_ShowErrorAndExit ("Wrong float range.");
/***** 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);
}
/*****************************************************************************/
/************** Write false / true answer when assessing a test **************/
/*****************************************************************************/
2020-05-09 01:37:00 +02:00
static void TstPrn_ComputeTFAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
2020-04-02 03:28:08 +02:00
struct Tst_Question *Question)
{
/***** Get answer true or false *****/
2020-05-07 18:33:26 +02:00
TstPrn_GetCorrectTFAnswerFromDB (Question);
2020-04-02 03:28:08 +02:00
/***** Compute score *****/
2020-05-09 01:37:00 +02:00
PrintedQuestion->AnswerIsNotBlank = (PrintedQuestion->StrAnswers[0] != '\0');
if (PrintedQuestion->AnswerIsNotBlank) // User has selected T or F
PrintedQuestion->Score = (PrintedQuestion->StrAnswers[0] == Question->Answer.TF) ? 1.0 : // Correct
-1.0; // Wrong
2020-04-02 03:28:08 +02:00
else
2020-05-09 01:37:00 +02:00
PrintedQuestion->Score = 0.0;
2020-04-02 03:28:08 +02:00
}
2020-05-07 18:33:26 +02:00
static void TstPrn_GetCorrectTFAnswerFromDB (struct Tst_Question *Question)
2020-04-02 03:28:08 +02:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
/***** Query database *****/
Question->Answer.NumOptions =
(unsigned) DB_QuerySELECT (&mysql_res,"can not get answers of a question",
"SELECT Answer" // row[0]
" FROM tst_answers"
" WHERE QstCod=%ld",
Question->QstCod);
/***** Check if number of rows is correct *****/
Tst_CheckIfNumberOfAnswersIsOne (Question);
/***** 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);
}
/*****************************************************************************/
/************ Compute score for single or multiple choice answer *************/
/*****************************************************************************/
2020-05-09 01:37:00 +02:00
void TstPrn_ComputeChoiceAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
2020-04-02 03:28:08 +02:00
struct Tst_Question *Question)
{
unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION]; // Indexes of all answers of this question
bool UsrAnswers[Tst_MAX_OPTIONS_PER_QUESTION];
/***** Get correct options of test question from database *****/
2020-05-07 18:33:26 +02:00
TstPrn_GetCorrectChoiceAnswerFromDB (Question);
2020-04-02 03:28:08 +02:00
/***** Get indexes for this question from string *****/
2020-05-09 01:37:00 +02:00
TstPrn_GetIndexesFromStr (PrintedQuestion->StrIndexes,Indexes);
2020-04-02 03:28:08 +02:00
/***** Get the user's answers for this question from string *****/
2020-05-09 01:37:00 +02:00
TstPrn_GetAnswersFromStr (PrintedQuestion->StrAnswers,UsrAnswers);
2020-04-02 03:28:08 +02:00
/***** Compute the total score of this question *****/
2020-05-09 01:37:00 +02:00
TstPrn_ComputeScoreQst (PrintedQuestion,Question,Indexes,UsrAnswers);
2020-04-02 03:28:08 +02:00
}
2020-05-07 18:33:26 +02:00
static void TstPrn_GetCorrectChoiceAnswerFromDB (struct Tst_Question *Question)
2020-04-02 03:28:08 +02:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumOpt;
/***** Query database *****/
Question->Answer.NumOptions =
(unsigned) DB_QuerySELECT (&mysql_res,"can not get answers of a question",
"SELECT Correct" // row[0]
" FROM tst_answers"
" WHERE QstCod=%ld"
" ORDER BY AnsInd",
Question->QstCod);
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);
}
/*****************************************************************************/
/********************* Get vector of indexes from string *********************/
/*****************************************************************************/
2020-05-09 01:37:00 +02:00
void TstPrn_GetIndexesFromStr (const char StrIndexesOneQst[Tst_MAX_BYTES_INDEXES_ONE_QST + 1], // 0 1 2 3, 3 0 2 1, etc.
unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION])
2020-04-02 03:28:08 +02:00
{
unsigned NumOpt;
const char *Ptr;
char StrOneIndex[Cns_MAX_DECIMAL_DIGITS_UINT + 1];
/***** Get indexes from string *****/
for (NumOpt = 0, Ptr = StrIndexesOneQst;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION && *Ptr;
NumOpt++)
{
Par_GetNextStrUntilSeparParamMult (&Ptr,StrOneIndex,Cns_MAX_DECIMAL_DIGITS_UINT);
if (sscanf (StrOneIndex,"%u",&(Indexes[NumOpt])) != 1)
Lay_ShowErrorAndExit ("Wrong index of answer.");
if (Indexes[NumOpt] >= Tst_MAX_OPTIONS_PER_QUESTION)
Lay_ShowErrorAndExit ("Wrong index of answer.");
}
/***** Initialize remaining to 0 *****/
for (;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
Indexes[NumOpt] = 0;
}
/*****************************************************************************/
/****************** Get vector of user's answers from string *****************/
/*****************************************************************************/
2020-05-09 01:37:00 +02:00
void TstPrn_GetAnswersFromStr (const char StrAnswersOneQst[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1],
2020-04-02 03:28:08 +02:00
bool UsrAnswers[Tst_MAX_OPTIONS_PER_QUESTION])
{
unsigned NumOpt;
const char *Ptr;
char StrOneAnswer[Cns_MAX_DECIMAL_DIGITS_UINT + 1];
unsigned AnsUsr;
/***** Initialize all answers to false *****/
for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
UsrAnswers[NumOpt] = false;
/***** Set selected answers to true *****/
for (NumOpt = 0, Ptr = StrAnswersOneQst;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION && *Ptr;
NumOpt++)
{
Par_GetNextStrUntilSeparParamMult (&Ptr,StrOneAnswer,Cns_MAX_DECIMAL_DIGITS_UINT);
if (sscanf (StrOneAnswer,"%u",&AnsUsr) != 1)
Lay_ShowErrorAndExit ("Bad user's answer.");
if (AnsUsr >= Tst_MAX_OPTIONS_PER_QUESTION)
Lay_ShowErrorAndExit ("Bad user's answer.");
UsrAnswers[AnsUsr] = true;
}
}
/*****************************************************************************/
/*********************** Compute the score of a question *********************/
/*****************************************************************************/
2020-05-09 01:37:00 +02:00
static void TstPrn_ComputeScoreQst (struct TstPrn_PrintedQuestion *PrintedQuestion,
2020-04-02 03:28:08 +02:00
const struct Tst_Question *Question,
unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION], // Indexes of all answers of this question
bool UsrAnswers[Tst_MAX_OPTIONS_PER_QUESTION])
{
unsigned NumOpt;
unsigned NumOptTotInQst = 0;
unsigned NumOptCorrInQst = 0;
unsigned NumAnsGood = 0;
unsigned NumAnsBad = 0;
/***** Compute the total score of this question *****/
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
{
NumOptTotInQst++;
if (Question->Answer.Options[Indexes[NumOpt]].Correct)
NumOptCorrInQst++;
if (UsrAnswers[Indexes[NumOpt]]) // This answer has been selected by the user
{
if (Question->Answer.Options[Indexes[NumOpt]].Correct)
NumAnsGood++;
else
NumAnsBad++;
}
}
/* The answer is blank? */
2020-05-09 01:37:00 +02:00
PrintedQuestion->AnswerIsNotBlank = NumAnsGood != 0 || NumAnsBad != 0;
if (PrintedQuestion->AnswerIsNotBlank)
2020-04-02 03:28:08 +02:00
{
/* Compute the score */
if (Question->Answer.Type == Tst_ANS_UNIQUE_CHOICE)
{
if (NumOptTotInQst >= 2) // It should be 2 options at least
2020-05-09 01:37:00 +02:00
PrintedQuestion->Score = (double) NumAnsGood -
(double) NumAnsBad / (double) (NumOptTotInQst - 1);
2020-04-22 03:15:04 +02:00
else // 0 or 1 options (impossible)
2020-05-09 01:37:00 +02:00
PrintedQuestion->Score = (double) NumAnsGood;
2020-04-02 03:28:08 +02:00
}
else // AnswerType == Tst_ANS_MULTIPLE_CHOICE
{
if (NumOptCorrInQst) // There are correct options in the question
{
if (NumOptCorrInQst < NumOptTotInQst) // If there are correct options and wrong options (typical case)
2020-05-09 01:37:00 +02:00
PrintedQuestion->Score = (double) NumAnsGood / (double) NumOptCorrInQst -
(double) NumAnsBad / (double) (NumOptTotInQst - NumOptCorrInQst);
2020-04-22 03:15:04 +02:00
else // If all options are correct (extrange case)
2020-05-09 01:37:00 +02:00
PrintedQuestion->Score = (double) NumAnsGood / (double) NumOptCorrInQst;
2020-04-02 03:28:08 +02:00
}
else
{
if (NumOptTotInQst) // There are options but none is correct (extrange case)
2020-05-09 01:37:00 +02:00
PrintedQuestion->Score = - (double) NumAnsBad / (double) NumOptTotInQst;
2020-04-22 03:15:04 +02:00
else // There are no options (impossible!)
2020-05-09 01:37:00 +02:00
PrintedQuestion->Score = 0.0;
2020-04-02 03:28:08 +02:00
}
}
}
else // Answer is blank
2020-05-09 01:37:00 +02:00
PrintedQuestion->Score = 0.0;
2020-04-02 03:28:08 +02:00
}
/*****************************************************************************/
/********************* Compute score for text answer *************************/
/*****************************************************************************/
2020-05-09 01:37:00 +02:00
static void TstPrn_ComputeTextAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion,
2020-04-02 03:28:08 +02:00
struct Tst_Question *Question)
{
unsigned NumOpt;
2020-05-09 01:37:00 +02:00
char TextAnsUsr[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1];
char TextAnsOK[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1];
2020-04-02 03:28:08 +02:00
/***** Get correct answers for this question from database *****/
2020-05-07 18:33:26 +02:00
TstPrn_GetCorrectTextAnswerFromDB (Question);
2020-04-02 03:28:08 +02:00
/***** Compute score *****/
2020-05-09 01:37:00 +02:00
PrintedQuestion->Score = 0.0; // Default score for blank or wrong answer
PrintedQuestion->AnswerIsNotBlank = (PrintedQuestion->StrAnswers[0] != '\0');
if (PrintedQuestion->AnswerIsNotBlank) // If user has answered the answer
2020-04-02 03:28:08 +02:00
{
/* Filter the user answer */
2020-05-09 01:37:00 +02:00
Str_Copy (TextAnsUsr,PrintedQuestion->StrAnswers,
Tst_MAX_BYTES_ANSWERS_ONE_QST);
2020-04-02 03:28:08 +02:00
/* In order to compare student answer to stored answer,
the text answers are stored avoiding two or more consecurive spaces */
Str_ReplaceSeveralSpacesForOne (TextAnsUsr);
Str_ConvertToComparable (TextAnsUsr);
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
{
/* Filter this correct answer */
Str_Copy (TextAnsOK,Question->Answer.Options[NumOpt].Text,
2020-05-09 01:37:00 +02:00
Tst_MAX_BYTES_ANSWERS_ONE_QST);
2020-04-02 03:28:08 +02:00
Str_ConvertToComparable (TextAnsOK);
/* Check is user answer is correct */
if (!strcoll (TextAnsUsr,TextAnsOK))
2020-05-09 01:37:00 +02:00
PrintedQuestion->Score = 1.0; // Correct answer
2020-04-02 03:28:08 +02:00
}
}
}
2020-05-07 18:33:26 +02:00
static void TstPrn_GetCorrectTextAnswerFromDB (struct Tst_Question *Question)
2020-04-02 03:28:08 +02:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumOpt;
/***** Query database *****/
Question->Answer.NumOptions =
(unsigned) DB_QuerySELECT (&mysql_res,"can not get answers of a question",
"SELECT Answer" // row[0]
" FROM tst_answers"
" WHERE QstCod=%ld",
Question->QstCod);
/***** 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 (!Tst_AllocateTextChoiceAnswer (Question,NumOpt))
/* Abort on error */
Ale_ShowAlertsAndExit ();
2020-04-03 01:55:39 +02:00
/***** Copy answer text (row[0]) and convert it, that is in HTML, to rigorous HTML ******/
Str_Copy (Question->Answer.Options[NumOpt].Text,row[0],
2020-04-02 03:28:08 +02:00
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);
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
/************ Compute and show total grade out of maximum grade **************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
void TstPrn_ComputeAndShowGrade (unsigned NumQsts,double Score,double MaxGrade)
2020-04-02 03:28:08 +02:00
{
2020-05-07 18:33:26 +02:00
TstPrn_ShowGrade (TstPrn_ComputeGrade (NumQsts,Score,MaxGrade),MaxGrade);
2020-04-02 03:28:08 +02:00
}
/*****************************************************************************/
/**************** Compute total grade out of maximum grade *******************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
double TstPrn_ComputeGrade (unsigned NumQsts,double Score,double MaxGrade)
2020-04-02 03:28:08 +02:00
{
double MaxScore;
double Grade;
/***** Compute grade *****/
if (NumQsts)
{
MaxScore = (double) NumQsts;
Grade = Score * MaxGrade / MaxScore;
}
else
Grade = 0.0;
return Grade;
}
/*****************************************************************************/
/****************** Show total grade out of maximum grade ********************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
void TstPrn_ShowGrade (double Grade,double MaxGrade)
2020-04-02 03:28:08 +02:00
{
/***** Write grade over maximum grade *****/
HTM_Double2Decimals (Grade);
HTM_Txt ("/");
HTM_Double2Decimals (MaxGrade);
}
/*****************************************************************************/
/************* Write answers of a question when assessing a test *************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
static void TstPrn_WriteAnswersExam (struct UsrData *UsrDat,
const struct TstPrn_Print *Print,
2020-04-02 03:28:08 +02:00
unsigned NumQst,
struct Tst_Question *Question,
unsigned Visibility)
{
/***** Write answer depending on type *****/
switch (Question->Answer.Type)
{
case Tst_ANS_INT:
2020-05-07 18:33:26 +02:00
TstPrn_WriteIntAnsExam (UsrDat,Print,NumQst,Question,Visibility);
2020-04-02 03:28:08 +02:00
break;
case Tst_ANS_FLOAT:
2020-05-07 18:33:26 +02:00
TstPrn_WriteFloatAnsExam (UsrDat,Print,NumQst,Question,Visibility);
2020-04-02 03:28:08 +02:00
break;
case Tst_ANS_TRUE_FALSE:
2020-05-07 18:33:26 +02:00
TstPrn_WriteTFAnsExam (UsrDat,Print,NumQst,Question,Visibility);
2020-04-02 03:28:08 +02:00
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
2020-05-07 18:33:26 +02:00
TstPrn_WriteChoiceAnsExam (UsrDat,Print,NumQst,Question,Visibility);
2020-04-02 03:28:08 +02:00
break;
case Tst_ANS_TEXT:
2020-05-07 18:33:26 +02:00
TstPrn_WriteTextAnsExam (UsrDat,Print,NumQst,Question,Visibility);
2020-04-02 03:28:08 +02:00
break;
default:
break;
}
}
/*****************************************************************************/
/******************* Write integer answer in a test exam *********************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
static void TstPrn_WriteIntAnsExam (struct UsrData *UsrDat,
const struct TstPrn_Print *Print,
2020-04-02 03:28:08 +02:00
unsigned NumQst,
const struct Tst_Question *Question,
unsigned Visibility)
{
long IntAnswerUsr;
2020-04-03 19:13:00 +02:00
2020-04-02 03:28:08 +02:00
/***** Check if number of rows is correct *****/
Tst_CheckIfNumberOfAnswersIsOne (Question);
/***** Header with the title of each column *****/
HTM_TABLE_BeginPadding (2);
HTM_TR_Begin (NULL);
2020-05-07 18:33:26 +02:00
TstPrn_WriteHeadUserCorrect (UsrDat);
2020-04-02 03:28:08 +02:00
HTM_TR_End ();
HTM_TR_Begin (NULL);
/***** Write the user answer *****/
2020-05-07 18:33:26 +02:00
if (Print->PrintedQuestions[NumQst].StrAnswers[0]) // If user has answered the question
2020-04-02 03:28:08 +02:00
{
2020-05-07 18:33:26 +02:00
if (sscanf (Print->PrintedQuestions[NumQst].StrAnswers,"%ld",&IntAnswerUsr) == 1)
2020-04-02 03:28:08 +02:00
{
HTM_TD_Begin ("class=\"%s CM\"",
TstVis_IsVisibleCorrectAns (Visibility) ?
2020-04-03 19:13:00 +02:00
(IntAnswerUsr == Question->Answer.Integer ? "ANS_OK" :
"ANS_BAD") :
2020-04-02 03:28:08 +02:00
"ANS_0");
HTM_Long (IntAnswerUsr);
HTM_TD_End ();
}
else
{
HTM_TD_Begin ("class=\"ANS_0 CM\"");
HTM_Txt ("?");
HTM_TD_End ();
}
}
else // If user has omitted the answer
HTM_TD_Empty (1);
/***** Write the correct answer *****/
HTM_TD_Begin ("class=\"ANS_0 CM\"");
if (TstVis_IsVisibleQstAndAnsTxt (Visibility) &&
TstVis_IsVisibleCorrectAns (Visibility))
2020-04-03 19:13:00 +02:00
HTM_Long (Question->Answer.Integer);
2020-04-02 03:28:08 +02:00
else
Ico_PutIconNotVisible ();
HTM_TD_End ();
HTM_TR_End ();
HTM_TABLE_End ();
}
/*****************************************************************************/
/******************** Write float answer in an test exam *********************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
static void TstPrn_WriteFloatAnsExam (struct UsrData *UsrDat,
const struct TstPrn_Print *Print,
2020-04-02 03:28:08 +02:00
unsigned NumQst,
const struct Tst_Question *Question,
unsigned Visibility)
{
2020-04-03 19:13:00 +02:00
double FloatAnsUsr = 0.0;
2020-04-02 03:28:08 +02:00
/***** Check if number of rows is correct *****/
if (Question->Answer.NumOptions != 2)
Lay_ShowErrorAndExit ("Wrong float range.");
/***** Header with the title of each column *****/
HTM_TABLE_BeginPadding (2);
HTM_TR_Begin (NULL);
2020-05-07 18:33:26 +02:00
TstPrn_WriteHeadUserCorrect (UsrDat);
2020-04-02 03:28:08 +02:00
HTM_TR_End ();
HTM_TR_Begin (NULL);
/***** Write the user answer *****/
2020-05-07 18:33:26 +02:00
if (Print->PrintedQuestions[NumQst].StrAnswers[0]) // If user has answered the question
2020-04-02 03:28:08 +02:00
{
2020-05-07 18:33:26 +02:00
FloatAnsUsr = Str_GetDoubleFromStr (Print->PrintedQuestions[NumQst].StrAnswers);
2020-04-02 03:28:08 +02:00
// A bad formatted floating point answer will interpreted as 0.0
HTM_TD_Begin ("class=\"%s CM\"",
TstVis_IsVisibleCorrectAns (Visibility) ?
2020-04-03 19:13:00 +02:00
((FloatAnsUsr >= Question->Answer.FloatingPoint[0] &&
FloatAnsUsr <= Question->Answer.FloatingPoint[1]) ? "ANS_OK" :
"ANS_BAD") :
2020-04-02 03:28:08 +02:00
"ANS_0");
HTM_Double (FloatAnsUsr);
}
else // If user has omitted the answer
HTM_TD_Begin (NULL);
HTM_TD_End ();
/***** Write the correct answer *****/
HTM_TD_Begin ("class=\"ANS_0 CM\"");
if (TstVis_IsVisibleQstAndAnsTxt (Visibility) &&
TstVis_IsVisibleCorrectAns (Visibility))
{
HTM_Txt ("[");
2020-04-03 19:13:00 +02:00
HTM_Double (Question->Answer.FloatingPoint[0]);
2020-04-02 03:28:08 +02:00
HTM_Txt ("; ");
2020-04-03 19:13:00 +02:00
HTM_Double (Question->Answer.FloatingPoint[1]);
2020-04-02 03:28:08 +02:00
HTM_Txt ("]");
}
else
Ico_PutIconNotVisible ();
HTM_TD_End ();
HTM_TR_End ();
HTM_TABLE_End ();
}
/*****************************************************************************/
/***************** Write false / true answer in a test exam ******************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
static void TstPrn_WriteTFAnsExam (struct UsrData *UsrDat,
const struct TstPrn_Print *Print,
2020-04-02 03:28:08 +02:00
unsigned NumQst,
const struct Tst_Question *Question,
unsigned Visibility)
{
2020-04-03 19:13:00 +02:00
char AnsTFUsr;
2020-04-02 03:28:08 +02:00
/***** Check if number of rows is correct *****/
Tst_CheckIfNumberOfAnswersIsOne (Question);
/***** Get answer true or false *****/
2020-05-07 18:33:26 +02:00
AnsTFUsr = Print->PrintedQuestions[NumQst].StrAnswers[0];
2020-04-02 03:28:08 +02:00
/***** Header with the title of each column *****/
HTM_TABLE_BeginPadding (2);
HTM_TR_Begin (NULL);
2020-05-07 18:33:26 +02:00
TstPrn_WriteHeadUserCorrect (UsrDat);
2020-04-02 03:28:08 +02:00
HTM_TR_End ();
HTM_TR_Begin (NULL);
/***** Write the user answer *****/
HTM_TD_Begin ("class=\"%s CM\"",
TstVis_IsVisibleCorrectAns (Visibility) ?
2020-04-03 19:13:00 +02:00
(AnsTFUsr == Question->Answer.TF ? "ANS_OK" :
"ANS_BAD") :
2020-04-02 03:28:08 +02:00
"ANS_0");
2020-04-03 19:13:00 +02:00
Tst_WriteAnsTF (AnsTFUsr);
2020-04-02 03:28:08 +02:00
HTM_TD_End ();
/***** Write the correct answer *****/
HTM_TD_Begin ("class=\"ANS_0 CM\"");
if (TstVis_IsVisibleQstAndAnsTxt (Visibility) &&
TstVis_IsVisibleCorrectAns (Visibility))
2020-04-03 19:13:00 +02:00
Tst_WriteAnsTF (Question->Answer.TF);
2020-04-02 03:28:08 +02:00
else
Ico_PutIconNotVisible ();
HTM_TD_End ();
HTM_TR_End ();
HTM_TABLE_End ();
}
/*****************************************************************************/
/********** Write single or multiple choice answer in a test exam ************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
static void TstPrn_WriteChoiceAnsExam (struct UsrData *UsrDat,
const struct TstPrn_Print *Print,
2020-04-02 03:28:08 +02:00
unsigned NumQst,
struct Tst_Question *Question,
unsigned Visibility)
{
extern const char *Txt_TST_Answer_given_by_the_user;
extern const char *Txt_TST_Answer_given_by_the_teachers;
unsigned NumOpt;
unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION]; // Indexes of all answers of this question
bool UsrAnswers[Tst_MAX_OPTIONS_PER_QUESTION];
struct
{
char *Class;
char *Str;
} Ans;
/***** Get indexes for this question from string *****/
2020-05-07 18:33:26 +02:00
TstPrn_GetIndexesFromStr (Print->PrintedQuestions[NumQst].StrIndexes,Indexes);
2020-04-02 03:28:08 +02:00
/***** Get the user's answers for this question from string *****/
2020-05-07 18:33:26 +02:00
TstPrn_GetAnswersFromStr (Print->PrintedQuestions[NumQst].StrAnswers,UsrAnswers);
2020-04-02 03:28:08 +02:00
/***** Begin table *****/
HTM_TABLE_BeginPadding (2);
HTM_TR_Begin (NULL);
2020-05-07 18:33:26 +02:00
TstPrn_WriteHeadUserCorrect (UsrDat);
2020-04-02 03:28:08 +02:00
HTM_TD_Empty (2);
HTM_TR_End ();
/***** Write answers (one row per answer) *****/
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
{
HTM_TR_Begin (NULL);
/* Draw icon depending on user's answer */
if (UsrAnswers[Indexes[NumOpt]] == true) // This answer has been selected by the user
{
if (TstVis_IsVisibleCorrectAns (Visibility))
{
if (Question->Answer.Options[Indexes[NumOpt]].Correct)
{
Ans.Class = "ANS_OK";
Ans.Str = "&check;";
}
else
{
Ans.Class = "ANS_BAD";
Ans.Str = "&cross;";
}
}
else
{
Ans.Class = "ANS_0";
Ans.Str = "&bull;";
}
HTM_TD_Begin ("class=\"%s CT\" title=\"%s\"",
Ans.Class,Txt_TST_Answer_given_by_the_user);
HTM_Txt (Ans.Str);
HTM_TD_End ();
}
else // This answer has NOT been selected by the user
HTM_TD_Empty (1);
/* Draw icon that indicates whether the answer is correct */
if (TstVis_IsVisibleCorrectAns (Visibility))
{
if (Question->Answer.Options[Indexes[NumOpt]].Correct)
{
HTM_TD_Begin ("class=\"ANS_0 CT\" title=\"%s\"",
Txt_TST_Answer_given_by_the_teachers);
HTM_Txt ("&bull;");
HTM_TD_End ();
}
else
HTM_TD_Empty (1);
}
else
{
HTM_TD_Begin ("class=\"ANS_0 CT\"");
Ico_PutIconNotVisible ();
HTM_TD_End ();
}
/* Answer letter (a, b, c,...) */
HTM_TD_Begin ("class=\"ANS_TXT LT\"");
HTM_TxtF ("%c)&nbsp;",'a' + (char) NumOpt);
HTM_TD_End ();
/* Answer text and feedback */
HTM_TD_Begin ("class=\"LT\"");
HTM_DIV_Begin ("class=\"ANS_TXT\"");
if (TstVis_IsVisibleQstAndAnsTxt (Visibility))
{
HTM_Txt (Question->Answer.Options[Indexes[NumOpt]].Text);
Med_ShowMedia (&Question->Answer.Options[Indexes[NumOpt]].Media,
"TEST_MED_SHOW_CONT",
"TEST_MED_SHOW");
}
else
Ico_PutIconNotVisible ();
HTM_DIV_End ();
if (TstVis_IsVisibleCorrectAns (Visibility))
if (Question->Answer.Options[Indexes[NumOpt]].Feedback)
if (Question->Answer.Options[Indexes[NumOpt]].Feedback[0])
{
HTM_DIV_Begin ("class=\"TEST_EXA_LIGHT\"");
HTM_Txt (Question->Answer.Options[Indexes[NumOpt]].Feedback);
HTM_DIV_End ();
}
HTM_TD_End ();
HTM_TR_End ();
}
/***** End table *****/
HTM_TABLE_End ();
}
/*****************************************************************************/
/***************** Write text answer when assessing a test *******************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
static void TstPrn_WriteTextAnsExam (struct UsrData *UsrDat,
const struct TstPrn_Print *Print,
2020-04-02 03:28:08 +02:00
unsigned NumQst,
struct Tst_Question *Question,
unsigned Visibility)
{
unsigned NumOpt;
2020-05-09 01:37:00 +02:00
char TextAnsUsr[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1];
char TextAnsOK[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1];
2020-04-02 03:28:08 +02:00
bool Correct = false;
2020-04-03 19:13:00 +02:00
2020-04-02 03:28:08 +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++)
{
2020-04-03 19:13:00 +02:00
/***** 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);
2020-04-02 03:28:08 +02:00
2020-04-03 19:13:00 +02:00
/***** Convert answer feedback, that is in HTML, to rigorous HTML ******/
2020-04-02 03:28:08 +02:00
if (TstVis_IsVisibleFeedbackTxt (Visibility))
2020-04-03 19:13:00 +02:00
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);
2020-04-02 03:28:08 +02:00
}
/***** Header with the title of each column *****/
HTM_TABLE_BeginPadding (2);
HTM_TR_Begin (NULL);
2020-05-07 18:33:26 +02:00
TstPrn_WriteHeadUserCorrect (UsrDat);
2020-04-02 03:28:08 +02:00
HTM_TR_End ();
HTM_TR_Begin (NULL);
/***** Write the user answer *****/
2020-05-07 18:33:26 +02:00
if (Print->PrintedQuestions[NumQst].StrAnswers[0]) // If user has answered the question
2020-04-02 03:28:08 +02:00
{
/* Filter the user answer */
2020-05-07 18:33:26 +02:00
Str_Copy (TextAnsUsr,Print->PrintedQuestions[NumQst].StrAnswers,
2020-05-09 01:37:00 +02:00
Tst_MAX_BYTES_ANSWERS_ONE_QST);
2020-04-02 03:28:08 +02:00
/* In order to compare student answer to stored answer,
the text answers are stored avoiding two or more consecurive spaces */
Str_ReplaceSeveralSpacesForOne (TextAnsUsr);
Str_ConvertToComparable (TextAnsUsr);
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
{
/* Filter this correct answer */
Str_Copy (TextAnsOK,Question->Answer.Options[NumOpt].Text,
2020-05-09 01:37:00 +02:00
Tst_MAX_BYTES_ANSWERS_ONE_QST);
2020-04-02 03:28:08 +02:00
Str_ConvertToComparable (TextAnsOK);
/* Check is user answer is correct */
if (!strcoll (TextAnsUsr,TextAnsOK))
{
Correct = true;
break;
}
}
HTM_TD_Begin ("class=\"%s CT\"",
TstVis_IsVisibleCorrectAns (Visibility) ?
(Correct ? "ANS_OK" :
"ANS_BAD") :
"ANS_0");
2020-05-07 18:33:26 +02:00
HTM_Txt (Print->PrintedQuestions[NumQst].StrAnswers);
2020-04-02 03:28:08 +02:00
}
else // If user has omitted the answer
HTM_TD_Begin (NULL);
HTM_TD_End ();
/***** Write the correct answers *****/
if (TstVis_IsVisibleQstAndAnsTxt (Visibility) &&
TstVis_IsVisibleCorrectAns (Visibility))
{
HTM_TD_Begin ("class=\"CT\"");
HTM_TABLE_BeginPadding (2);
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
{
HTM_TR_Begin (NULL);
/* Answer letter (a, b, c,...) */
HTM_TD_Begin ("class=\"ANS_0 LT\"");
HTM_TxtF ("%c)&nbsp;",'a' + (char) NumOpt);
HTM_TD_End ();
/* Answer text and feedback */
HTM_TD_Begin ("class=\"LT\"");
HTM_DIV_Begin ("class=\"ANS_0\"");
HTM_Txt (Question->Answer.Options[NumOpt].Text);
HTM_DIV_End ();
if (TstVis_IsVisibleFeedbackTxt (Visibility))
if (Question->Answer.Options[NumOpt].Feedback)
if (Question->Answer.Options[NumOpt].Feedback[0])
{
HTM_DIV_Begin ("class=\"TEST_EXA_LIGHT\"");
HTM_Txt (Question->Answer.Options[NumOpt].Feedback);
HTM_DIV_End ();
}
HTM_TD_End ();
HTM_TR_End ();
}
HTM_TABLE_End ();
HTM_TD_End ();
}
else
{
HTM_TD_Begin ("class=\"ANS_0 CT\"");
Ico_PutIconNotVisible ();
HTM_TD_End ();
}
HTM_TR_End ();
HTM_TABLE_End ();
}
/*****************************************************************************/
/********* Write head with two columns: ********/
/********* one for the user's answer and other for the correct answer ********/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
static void TstPrn_WriteHeadUserCorrect (struct UsrData *UsrDat)
2020-04-02 03:28:08 +02:00
{
extern const char *Txt_User[Usr_NUM_SEXS];
extern const char *Txt_ROLES_PLURAL_Abc[Rol_NUM_ROLES][Usr_NUM_SEXS];
HTM_TD_Begin ("class=\"DAT_SMALL CM\"");
HTM_Txt (Txt_User[UsrDat->Sex]);
HTM_TD_End ();
HTM_TD_Begin ("class=\"DAT_SMALL CM\"");
HTM_Txt (Txt_ROLES_PLURAL_Abc[Rol_TCH][Usr_SEX_UNKNOWN]);
HTM_TD_End ();
}
/*****************************************************************************/
2020-05-09 01:37:00 +02:00
/************ Store user's answers of an test print into database ************/
2020-04-02 03:28:08 +02:00
/*****************************************************************************/
2020-05-09 01:37:00 +02:00
static void TstPrn_StoreOneQstOfPrintInDB (const struct TstPrn_Print *Print,
unsigned NumQst)
2020-04-02 03:28:08 +02:00
{
2020-05-09 01:37:00 +02:00
char StrIndexes[Tst_MAX_BYTES_INDEXES_ONE_QST + 1];
char StrAnswers[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1];
2020-04-02 03:28:08 +02:00
/***** Replace each separator of multiple parameters by a comma *****/
/* In database commas are used as separators instead of special chars */
2020-05-07 18:33:26 +02:00
Par_ReplaceSeparatorMultipleByComma (Print->PrintedQuestions[NumQst].StrIndexes,StrIndexes);
Par_ReplaceSeparatorMultipleByComma (Print->PrintedQuestions[NumQst].StrAnswers,StrAnswers);
2020-04-02 03:28:08 +02:00
/***** Insert question and user's answers into database *****/
Str_SetDecimalPointToUS (); // To print the floating point as a dot
DB_QueryREPLACE ("can not update a question of a test exam",
"REPLACE INTO tst_exam_questions"
" (ExaCod,QstCod,QstInd,Score,Indexes,Answers)"
" VALUES"
" (%ld,%ld,%u,'%.15lg','%s','%s')",
2020-05-07 18:33:26 +02:00
Print->PrnCod,Print->PrintedQuestions[NumQst].QstCod,
2020-04-02 03:28:08 +02:00
NumQst, // 0, 1, 2, 3...
2020-05-07 18:33:26 +02:00
Print->PrintedQuestions[NumQst].Score,
2020-04-02 03:28:08 +02:00
StrIndexes,
StrAnswers);
Str_SetDecimalPointToLocal (); // Return to local system
}
/*****************************************************************************/
/************* Select users and dates to show their test exams ***************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
void TstPrn_SelUsrsToViewUsrsExams (void)
2020-04-09 21:36:21 +02:00
{
2020-05-07 18:33:26 +02:00
TstPrn_PutFormToSelectUsrsToViewUsrsExams (NULL);
2020-04-09 21:36:21 +02:00
}
2020-05-07 18:33:26 +02:00
static void TstPrn_PutFormToSelectUsrsToViewUsrsExams (__attribute__((unused)) void *Args)
2020-04-02 03:28:08 +02:00
{
extern const char *Hlp_ASSESSMENT_Tests_results;
extern const char *Txt_Results;
extern const char *Txt_View_test_results;
2020-04-09 21:36:21 +02:00
Usr_PutFormToSelectUsrsToGoToAct (&Gbl.Usrs.Selected,
ActSeeUsrTstRes,
NULL,NULL,
Txt_Results,
Hlp_ASSESSMENT_Tests_results,
Txt_View_test_results,
true); // Put form with date range
2020-04-02 03:28:08 +02:00
}
/*****************************************************************************/
/******************** Select dates to show my test exams *********************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
void TstPrn_SelDatesToSeeMyExams (void)
2020-04-02 03:28:08 +02:00
{
extern const char *Hlp_ASSESSMENT_Tests_results;
extern const char *Txt_Results;
extern const char *Txt_View_test_results;
static const Dat_SetHMS SetHMS[Dat_NUM_START_END_TIME] =
{
2020-04-13 16:39:15 +02:00
[Dat_START_TIME] = Dat_HMS_DO_NOT_SET,
[Dat_END_TIME ] = Dat_HMS_DO_NOT_SET
2020-04-02 03:28:08 +02:00
};
/***** Begin form *****/
Frm_StartForm (ActSeeMyTstRes);
/***** Begin box and table *****/
Box_BoxTableBegin (NULL,Txt_Results,
NULL,NULL,
Hlp_ASSESSMENT_Tests_results,Box_NOT_CLOSABLE,2);
Dat_PutFormStartEndClientLocalDateTimesWithYesterdayToday (SetHMS);
/***** End table, send button and end box *****/
Box_BoxTableWithButtonEnd (Btn_CONFIRM_BUTTON,Txt_View_test_results);
/***** End form *****/
Frm_EndForm ();
}
/*****************************************************************************/
/***************************** Show my test exams ****************************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
void TstPrn_ShowMyExams (void)
2020-04-02 03:28:08 +02:00
{
extern const char *Hlp_ASSESSMENT_Tests_results;
extern const char *Txt_Results;
/***** Get starting and ending dates *****/
Dat_GetIniEndDatesFromForm ();
/***** Begin box and table *****/
Box_BoxTableBegin (NULL,Txt_Results,
NULL,NULL,
Hlp_ASSESSMENT_Tests_results,Box_NOT_CLOSABLE,2);
/***** Header of the table with the list of users *****/
2020-05-07 18:33:26 +02:00
TstPrn_ShowHeaderExams ();
2020-04-02 03:28:08 +02:00
/***** List my test exams *****/
TstCfg_GetConfigFromDB (); // To get feedback type
2020-05-07 18:33:26 +02:00
TstPrn_ShowExams (&Gbl.Usrs.Me.UsrDat);
2020-04-02 03:28:08 +02:00
/***** End table and box *****/
Box_BoxTableEnd ();
}
/*****************************************************************************/
/******************** Get users and show their test exams ********************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
void TstPrn_GetUsrsAndShowExams (void)
2020-04-02 03:28:08 +02:00
{
Usr_GetSelectedUsrsAndGoToAct (&Gbl.Usrs.Selected,
2020-05-07 18:33:26 +02:00
TstPrn_ShowUsrsExams,NULL,
TstPrn_PutFormToSelectUsrsToViewUsrsExams,NULL);
2020-04-02 03:28:08 +02:00
}
/*****************************************************************************/
/********************* Show test exams for several users *********************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
static void TstPrn_ShowUsrsExams (__attribute__((unused)) void *Args)
2020-04-02 03:28:08 +02:00
{
extern const char *Hlp_ASSESSMENT_Tests_results;
extern const char *Txt_Results;
const char *Ptr;
/***** Get starting and ending dates *****/
Dat_GetIniEndDatesFromForm ();
/***** Begin box and table *****/
Box_BoxTableBegin (NULL,Txt_Results,
NULL,NULL,
Hlp_ASSESSMENT_Tests_results,Box_NOT_CLOSABLE,2);
/***** Header of the table with the list of users *****/
2020-05-07 18:33:26 +02:00
TstPrn_ShowHeaderExams ();
2020-04-02 03:28:08 +02:00
/***** List the test exams of the selected users *****/
Ptr = Gbl.Usrs.Selected.List[Rol_UNK];
while (*Ptr)
{
Par_GetNextStrUntilSeparParamMult (&Ptr,Gbl.Usrs.Other.UsrDat.EncryptedUsrCod,
Cry_BYTES_ENCRYPTED_STR_SHA256_BASE64);
Usr_GetUsrCodFromEncryptedUsrCod (&Gbl.Usrs.Other.UsrDat);
if (Usr_ChkUsrCodAndGetAllUsrDataFromUsrCod (&Gbl.Usrs.Other.UsrDat,Usr_DONT_GET_PREFS))
2020-04-22 03:15:04 +02:00
if (Usr_CheckIfICanViewTstExaMchResult (&Gbl.Usrs.Other.UsrDat))
2020-04-02 03:28:08 +02:00
{
/***** Show test exams *****/
Gbl.Usrs.Other.UsrDat.Accepted = Usr_CheckIfUsrHasAcceptedInCurrentCrs (&Gbl.Usrs.Other.UsrDat);
2020-05-07 18:33:26 +02:00
TstPrn_ShowExams (&Gbl.Usrs.Other.UsrDat);
2020-04-02 03:28:08 +02:00
}
}
/***** End table and box *****/
Box_BoxTableEnd ();
}
/*****************************************************************************/
/************************ Show header of my test exams ***********************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
static void TstPrn_ShowHeaderExams (void)
2020-04-02 03:28:08 +02:00
{
extern const char *Txt_User[Usr_NUM_SEXS];
extern const char *Txt_START_END_TIME[Dat_NUM_START_END_TIME];
extern const char *Txt_Questions;
extern const char *Txt_Non_blank_BR_questions;
extern const char *Txt_Score;
extern const char *Txt_Average_BR_score_BR_per_question_BR_from_0_to_1;
extern const char *Txt_Grade;
HTM_TR_Begin (NULL);
HTM_TH (1,2,"CT",Txt_User[Usr_SEX_UNKNOWN]);
HTM_TH (1,1,"LT",Txt_START_END_TIME[Dat_START_TIME]);
HTM_TH (1,1,"LT",Txt_START_END_TIME[Dat_END_TIME ]);
HTM_TH (1,1,"RT",Txt_Questions);
HTM_TH (1,1,"RT",Txt_Non_blank_BR_questions);
HTM_TH (1,1,"RT",Txt_Score);
HTM_TH (1,1,"RT",Txt_Average_BR_score_BR_per_question_BR_from_0_to_1);
HTM_TH (1,1,"RT",Txt_Grade);
HTM_TH_Empty (1);
HTM_TR_End ();
}
/*****************************************************************************/
/************ Show the test exams of a user in the current course ************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
static void TstPrn_ShowExams (struct UsrData *UsrDat)
2020-04-02 03:28:08 +02:00
{
extern const char *Txt_View_test;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumExams;
unsigned NumExam;
static unsigned UniqueId = 0;
Dat_StartEndTime_t StartEndTime;
char *Id;
2020-05-07 18:33:26 +02:00
struct TstPrn_Print Print;
2020-04-02 03:28:08 +02:00
unsigned NumTotalQsts = 0;
unsigned NumTotalQstsNotBlank = 0;
double TotalScoreOfAllTests = 0.0;
unsigned NumExamsVisibleByTchs = 0;
bool ItsMe = Usr_ItsMe (UsrDat->UsrCod);
2020-04-16 21:03:22 +02:00
struct
{
bool NumQsts;
bool Score;
bool Exam;
} ICanView;
2020-04-02 03:28:08 +02:00
char *ClassDat;
/***** Make database query *****/
/* From here... ...to here
___________|_____ _____|___________
-----|______Exam_|_____|-----------------|_____|_Exam______|-----> time
Start | End Start | End
*/
NumExams =
(unsigned) DB_QuerySELECT (&mysql_res,"can not get test exams of a user",
"SELECT ExaCod," // row[0]
"UNIX_TIMESTAMP(StartTime)," // row[1]
"UNIX_TIMESTAMP(EndTime)," // row[2]
"NumQsts," // row[3]
"NumQstsNotBlank," // row[4]
2020-04-16 21:03:22 +02:00
"Sent," // row[5]
"AllowTeachers," // row[6]
"Score" // row[7]
2020-04-02 03:28:08 +02:00
" FROM tst_exams"
" WHERE CrsCod=%ld AND UsrCod=%ld"
" AND EndTime>=FROM_UNIXTIME(%ld)"
" AND StartTime<=FROM_UNIXTIME(%ld)"
" ORDER BY ExaCod",
Gbl.Hierarchy.Crs.CrsCod,
UsrDat->UsrCod,
(long) Gbl.DateRange.TimeUTC[Dat_START_TIME],
(long) Gbl.DateRange.TimeUTC[Dat_END_TIME ]);
/***** Show user's data *****/
HTM_TR_Begin (NULL);
Usr_ShowTableCellWithUsrData (UsrDat,NumExams);
/***** Get and print test exams *****/
if (NumExams)
{
for (NumExam = 0;
NumExam < NumExams;
NumExam++)
{
row = mysql_fetch_row (mysql_res);
/* Get test code (row[0]) */
2020-05-11 02:28:38 +02:00
TstPrn_ResetPrint (&Print);
2020-05-07 18:33:26 +02:00
if ((Print.PrnCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
2020-04-02 03:28:08 +02:00
Lay_ShowErrorAndExit ("Wrong code of test exam.");
2020-04-16 21:03:22 +02:00
/* Get if exam has been sent (row[5]) */
2020-05-07 18:33:26 +02:00
Print.Sent = (row[5][0] == 'Y');
2020-04-16 21:03:22 +02:00
/* Get if teachers are allowed to see this test exam (row[6]) */
2020-05-07 18:33:26 +02:00
Print.AllowTeachers = (row[6][0] == 'Y');
ClassDat = Print.AllowTeachers ? "DAT" :
2020-04-16 21:03:22 +02:00
"DAT_LIGHT";
2020-04-02 03:28:08 +02:00
switch (Gbl.Usrs.Me.Role.Logged)
{
case Rol_STD:
2020-05-07 18:33:26 +02:00
ICanView.NumQsts = Print.Sent && ItsMe;
ICanView.Score = Print.Sent && ItsMe &&
2020-04-16 21:03:22 +02:00
TstVis_IsVisibleTotalScore (TstCfg_GetConfigVisibility ());
2020-05-07 18:33:26 +02:00
ICanView.Exam = Print.Sent && ItsMe;
2020-04-02 03:28:08 +02:00
break;
case Rol_NET:
case Rol_TCH:
case Rol_DEG_ADM:
case Rol_CTR_ADM:
case Rol_INS_ADM:
2020-05-07 18:33:26 +02:00
ICanView.NumQsts = Print.Sent; // If the exam has been sent,
2020-04-16 21:03:22 +02:00
// teachers can see the number of questions
ICanView.Score =
2020-05-07 18:33:26 +02:00
ICanView.Exam = Print.Sent && (ItsMe || Print.AllowTeachers);
2020-04-02 03:28:08 +02:00
break;
case Rol_SYS_ADM:
2020-04-16 21:03:22 +02:00
ICanView.NumQsts =
ICanView.Score =
ICanView.Exam = true;
2020-04-02 03:28:08 +02:00
break;
default:
2020-04-16 21:03:22 +02:00
ICanView.NumQsts =
ICanView.Score =
ICanView.Exam = false;
2020-04-02 03:28:08 +02:00
break;
}
if (NumExam)
HTM_TR_Begin (NULL);
/* Write date and time (row[1] and row[2] hold UTC date-times) */
2020-05-07 18:33:26 +02:00
Print.TimeUTC[Dat_START_TIME] = Dat_GetUNIXTimeFromStr (row[1]);
Print.TimeUTC[Dat_END_TIME ] = Dat_GetUNIXTimeFromStr (row[2]);
2020-04-02 03:28:08 +02:00
UniqueId++;
for (StartEndTime = (Dat_StartEndTime_t) 0;
StartEndTime <= (Dat_StartEndTime_t) (Dat_NUM_START_END_TIME - 1);
StartEndTime++)
{
if (asprintf (&Id,"tst_date_%u_%u",(unsigned) StartEndTime,UniqueId) < 0)
Lay_NotEnoughMemoryExit ();
HTM_TD_Begin ("id=\"%s\" class=\"%s LT COLOR%u\"",
Id,ClassDat,Gbl.RowEvenOdd);
2020-05-07 18:33:26 +02:00
Dat_WriteLocalDateHMSFromUTC (Id,Print.TimeUTC[StartEndTime],
2020-04-02 03:28:08 +02:00
Gbl.Prefs.DateFormat,Dat_SEPARATOR_BREAK,
true,true,false,0x7);
HTM_TD_End ();
free (Id);
}
/* Get number of questions (row[3]) */
2020-05-07 18:33:26 +02:00
if (sscanf (row[3],"%u",&Print.NumQsts) != 1)
Print.NumQsts = 0;
if (Print.AllowTeachers)
NumTotalQsts += Print.NumQsts;
2020-04-02 03:28:08 +02:00
/* Get number of questions not blank (row[4]) */
2020-05-07 18:33:26 +02:00
if (sscanf (row[4],"%u",&Print.NumQstsNotBlank) != 1)
Print.NumQstsNotBlank = 0;
if (Print.AllowTeachers)
NumTotalQstsNotBlank += Print.NumQstsNotBlank;
2020-04-02 03:28:08 +02:00
2020-04-16 21:03:22 +02:00
/* Get score (row[7]) */
2020-04-02 03:28:08 +02:00
Str_SetDecimalPointToUS (); // To get the decimal point as a dot
2020-05-07 18:33:26 +02:00
if (sscanf (row[7],"%lf",&Print.Score) != 1)
Print.Score = 0.0;
2020-04-02 03:28:08 +02:00
Str_SetDecimalPointToLocal (); // Return to local system
2020-05-07 18:33:26 +02:00
if (Print.AllowTeachers)
TotalScoreOfAllTests += Print.Score;
2020-04-02 03:28:08 +02:00
/* Write number of questions */
HTM_TD_Begin ("class=\"%s RT COLOR%u\"",ClassDat,Gbl.RowEvenOdd);
2020-04-16 21:03:22 +02:00
if (ICanView.NumQsts)
2020-05-07 18:33:26 +02:00
HTM_Unsigned (Print.NumQsts);
2020-04-02 03:28:08 +02:00
HTM_TD_End ();
/* Write number of questions not blank */
HTM_TD_Begin ("class=\"%s RT COLOR%u\"",ClassDat,Gbl.RowEvenOdd);
2020-04-16 21:03:22 +02:00
if (ICanView.NumQsts)
2020-05-07 18:33:26 +02:00
HTM_Unsigned (Print.NumQstsNotBlank);
2020-04-02 03:28:08 +02:00
HTM_TD_End ();
/* Write score */
HTM_TD_Begin ("class=\"%s RT COLOR%u\"",ClassDat,Gbl.RowEvenOdd);
2020-04-16 21:03:22 +02:00
if (ICanView.Score)
2020-05-07 18:33:26 +02:00
HTM_Double2Decimals (Print.Score);
2020-04-02 03:28:08 +02:00
HTM_TD_End ();
/* Write average score per question */
HTM_TD_Begin ("class=\"%s RT COLOR%u\"",ClassDat,Gbl.RowEvenOdd);
2020-04-16 21:03:22 +02:00
if (ICanView.Score)
2020-05-07 18:33:26 +02:00
HTM_Double2Decimals (Print.NumQsts ? Print.Score /
(double) Print.NumQsts :
0.0);
2020-04-02 03:28:08 +02:00
HTM_TD_End ();
/* Write grade */
HTM_TD_Begin ("class=\"%s RT COLOR%u\"",ClassDat,Gbl.RowEvenOdd);
2020-04-16 21:03:22 +02:00
if (ICanView.Score)
2020-05-07 18:33:26 +02:00
TstPrn_ComputeAndShowGrade (Print.NumQsts,Print.Score,
2020-05-09 01:37:00 +02:00
Tst_SCORE_MAX);
2020-04-02 03:28:08 +02:00
HTM_TD_End ();
/* Link to show this test exam */
HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd);
2020-04-16 21:03:22 +02:00
if (ICanView.Exam)
2020-04-02 03:28:08 +02:00
{
Frm_StartForm (Gbl.Action.Act == ActSeeMyTstRes ? ActSeeOneTstResMe :
ActSeeOneTstResOth);
2020-05-09 21:07:50 +02:00
TstPrn_PutParamPrnCod (Print.PrnCod);
2020-04-02 03:28:08 +02:00
Ico_PutIconLink ("tasks.svg",Txt_View_test);
Frm_EndForm ();
}
HTM_TD_End ();
HTM_TR_End ();
2020-05-07 18:33:26 +02:00
if (Print.AllowTeachers)
2020-04-02 03:28:08 +02:00
NumExamsVisibleByTchs++;
}
/***** Write totals for this user *****/
2020-05-07 18:33:26 +02:00
TstPrn_ShowExamsSummaryRow (ItsMe,NumExamsVisibleByTchs,
2020-04-16 21:03:22 +02:00
NumTotalQsts,NumTotalQstsNotBlank,
TotalScoreOfAllTests);
2020-04-02 03:28:08 +02:00
}
else
{
2020-04-14 02:37:24 +02:00
HTM_TD_ColouredEmpty (8);
2020-04-02 03:28:08 +02:00
HTM_TR_End ();
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
Gbl.RowEvenOdd = 1 - Gbl.RowEvenOdd;
}
/*****************************************************************************/
2020-05-09 21:07:50 +02:00
/*************** Write parameter with code of test exam print ****************/
2020-04-02 03:28:08 +02:00
/*****************************************************************************/
2020-05-09 21:07:50 +02:00
void TstPrn_PutParamPrnCod (long ExaCod)
2020-04-02 03:28:08 +02:00
{
2020-05-09 21:07:50 +02:00
Par_PutHiddenParamLong (NULL,"PrnCod",ExaCod);
2020-04-02 03:28:08 +02:00
}
/*****************************************************************************/
2020-05-09 21:07:50 +02:00
/*************** Get parameter with code of test exam print ******************/
2020-04-02 03:28:08 +02:00
/*****************************************************************************/
2020-05-09 21:07:50 +02:00
long TstPrn_GetParamPrnCod (void)
2020-04-02 03:28:08 +02:00
{
2020-05-09 21:07:50 +02:00
/***** Get code of test exam print *****/
return Par_GetParToLong ("PrnCod");
2020-04-02 03:28:08 +02:00
}
/*****************************************************************************/
/**************** Show row with summary of user's test exams *****************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
static void TstPrn_ShowExamsSummaryRow (bool ItsMe,
2020-04-02 03:28:08 +02:00
unsigned NumExams,
unsigned NumTotalQsts,
unsigned NumTotalQstsNotBlank,
double TotalScoreOfAllTests)
{
extern const char *Txt_Visible_tests;
bool ICanViewTotalScore;
switch (Gbl.Usrs.Me.Role.Logged)
{
case Rol_STD:
ICanViewTotalScore = ItsMe &&
TstVis_IsVisibleTotalScore (TstCfg_GetConfigVisibility ());
break;
case Rol_NET:
case Rol_TCH:
case Rol_DEG_ADM:
case Rol_CTR_ADM:
case Rol_INS_ADM:
ICanViewTotalScore = ItsMe ||
NumExams;
break;
case Rol_SYS_ADM:
ICanViewTotalScore = true;
break;
default:
ICanViewTotalScore = false;
break;
}
/***** Start row *****/
HTM_TR_Begin (NULL);
/***** Row title *****/
HTM_TD_Begin ("colspan=\"2\" class=\"DAT_N_LINE_TOP RM COLOR%u\"",Gbl.RowEvenOdd);
HTM_TxtColonNBSP (Txt_Visible_tests);
HTM_Unsigned (NumExams);
HTM_TD_End ();
/***** Write total number of questions *****/
HTM_TD_Begin ("class=\"DAT_N_LINE_TOP RM COLOR%u\"",Gbl.RowEvenOdd);
if (NumExams)
HTM_Unsigned (NumTotalQsts);
HTM_TD_End ();
/***** Write total number of questions not blank *****/
HTM_TD_Begin ("class=\"DAT_N_LINE_TOP RM COLOR%u\"",Gbl.RowEvenOdd);
if (NumExams)
HTM_Unsigned (NumTotalQstsNotBlank);
HTM_TD_End ();
/***** Write total score *****/
HTM_TD_Begin ("class=\"DAT_N_LINE_TOP RM COLOR%u\"",Gbl.RowEvenOdd);
if (ICanViewTotalScore)
HTM_Double2Decimals (TotalScoreOfAllTests);
HTM_TD_End ();
/***** Write average score per question *****/
HTM_TD_Begin ("class=\"DAT_N_LINE_TOP RM COLOR%u\"",Gbl.RowEvenOdd);
if (ICanViewTotalScore)
HTM_Double2Decimals (NumTotalQsts ? TotalScoreOfAllTests / (double) NumTotalQsts :
0.0);
HTM_TD_End ();
/***** Write score over Tst_SCORE_MAX *****/
HTM_TD_Begin ("class=\"DAT_N_LINE_TOP RM COLOR%u\"",Gbl.RowEvenOdd);
if (ICanViewTotalScore)
2020-05-07 18:33:26 +02:00
TstPrn_ComputeAndShowGrade (NumTotalQsts,TotalScoreOfAllTests,
2020-05-09 01:37:00 +02:00
Tst_SCORE_MAX);
2020-04-02 03:28:08 +02:00
HTM_TD_End ();
/***** Last cell *****/
HTM_TD_Begin ("class=\"DAT_N_LINE_TOP COLOR%u\"",Gbl.RowEvenOdd);
HTM_TD_End ();
/***** End row *****/
HTM_TR_End ();
}
/*****************************************************************************/
/******************** Show one test exam of another user *********************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
void TstPrn_ShowOneExam (void)
2020-04-02 03:28:08 +02:00
{
extern const char *Hlp_ASSESSMENT_Tests_results;
extern const char *Txt_Test_result;
extern const char *Txt_The_user_does_not_exist;
extern const char *Txt_ROLES_SINGUL_Abc[Rol_NUM_ROLES][Usr_NUM_SEXS];
extern const char *Txt_START_END_TIME[Dat_NUM_START_END_TIME];
extern const char *Txt_Questions;
extern const char *Txt_non_blank_QUESTIONS;
extern const char *Txt_Score;
extern const char *Txt_Grade;
extern const char *Txt_Tags;
2020-05-07 18:33:26 +02:00
struct TstPrn_Print Print;
2020-04-02 03:28:08 +02:00
bool ShowPhoto;
char PhotoURL[PATH_MAX + 1];
Dat_StartEndTime_t StartEndTime;
char *Id;
bool ItsMe;
bool ICanViewTest;
bool ICanViewScore;
/***** Get the code of the test *****/
2020-05-11 02:28:38 +02:00
TstPrn_ResetPrint (&Print);
2020-05-09 21:07:50 +02:00
if ((Print.PrnCod = TstPrn_GetParamPrnCod ()) == -1L)
2020-04-02 03:28:08 +02:00
Lay_ShowErrorAndExit ("Code of test is missing.");
/***** Get test exam data *****/
2020-05-10 01:42:30 +02:00
TstPrn_GetPrintDataByPrnCod (&Print);
2020-04-02 03:28:08 +02:00
TstCfg_SetConfigVisibility (TstVis_MAX_VISIBILITY);
/***** Check if I can view this test exam *****/
ItsMe = Usr_ItsMe (Gbl.Usrs.Other.UsrDat.UsrCod);
switch (Gbl.Usrs.Me.Role.Logged)
{
case Rol_STD:
ICanViewTest = ItsMe;
if (ItsMe)
{
TstCfg_GetConfigFromDB (); // To get feedback type
ICanViewScore = TstVis_IsVisibleTotalScore (TstCfg_GetConfigVisibility ());
}
else
ICanViewScore = false;
break;
case Rol_TCH:
case Rol_DEG_ADM:
case Rol_CTR_ADM:
case Rol_INS_ADM:
switch (Gbl.Action.Act)
{
case ActSeeOneTstResMe:
ICanViewTest =
ICanViewScore = ItsMe;
break;
case ActSeeOneTstResOth:
ICanViewTest =
ICanViewScore = ItsMe ||
2020-05-07 18:33:26 +02:00
Print.AllowTeachers;
2020-04-02 03:28:08 +02:00
break;
default:
ICanViewTest =
ICanViewScore = false;
break;
}
break;
case Rol_SYS_ADM:
ICanViewTest =
ICanViewScore = true;
break;
default:
ICanViewTest =
ICanViewScore = false;
break;
}
if (ICanViewTest) // I am allowed to view this test exam
{
/***** Get questions and user's answers of the test exam from database *****/
2020-05-10 01:42:30 +02:00
TstPrn_GetPrintQuestionsFromDB (&Print);
2020-04-02 03:28:08 +02:00
/***** Begin box *****/
Box_BoxBegin (NULL,Txt_Test_result,
NULL,NULL,
Hlp_ASSESSMENT_Tests_results,Box_NOT_CLOSABLE);
Lay_WriteHeaderClassPhoto (false,false,
Gbl.Hierarchy.Ins.InsCod,
Gbl.Hierarchy.Deg.DegCod,
Gbl.Hierarchy.Crs.CrsCod);
/***** Begin table *****/
HTM_TABLE_BeginWideMarginPadding (5);
/***** Header row *****/
/* Get data of the user who made the test */
if (!Usr_ChkUsrCodAndGetAllUsrDataFromUsrCod (&Gbl.Usrs.Other.UsrDat,Usr_DONT_GET_PREFS))
Lay_ShowErrorAndExit (Txt_The_user_does_not_exist);
2020-04-22 03:15:04 +02:00
if (!Usr_CheckIfICanViewTstExaMchResult (&Gbl.Usrs.Other.UsrDat))
2020-04-02 03:28:08 +02:00
Lay_NoPermissionExit ();
/* User */
HTM_TR_Begin (NULL);
HTM_TD_Begin ("class=\"DAT_N RT\"");
HTM_TxtF ("%s:",Txt_ROLES_SINGUL_Abc[Gbl.Usrs.Other.UsrDat.Roles.InCurrentCrs.Role][Gbl.Usrs.Other.UsrDat.Sex]);
HTM_TD_End ();
HTM_TD_Begin ("class=\"DAT LT\"");
ID_WriteUsrIDs (&Gbl.Usrs.Other.UsrDat,NULL);
HTM_TxtF ("&nbsp;%s",Gbl.Usrs.Other.UsrDat.Surname1);
if (Gbl.Usrs.Other.UsrDat.Surname2[0])
HTM_TxtF ("&nbsp;%s",Gbl.Usrs.Other.UsrDat.Surname2);
if (Gbl.Usrs.Other.UsrDat.FirstName[0])
HTM_TxtF (", %s",Gbl.Usrs.Other.UsrDat.FirstName);
HTM_BR ();
ShowPhoto = Pho_ShowingUsrPhotoIsAllowed (&Gbl.Usrs.Other.UsrDat,PhotoURL);
Pho_ShowUsrPhoto (&Gbl.Usrs.Other.UsrDat,ShowPhoto ? PhotoURL :
NULL,
"PHOTO45x60",Pho_ZOOM,false);
HTM_TD_End ();
HTM_TR_End ();
/* Test date */
for (StartEndTime = (Dat_StartEndTime_t) 0;
StartEndTime <= (Dat_StartEndTime_t) (Dat_NUM_START_END_TIME - 1);
StartEndTime++)
{
if (asprintf (&Id,"tst_date_%u",(unsigned) StartEndTime) < 0)
Lay_NotEnoughMemoryExit ();
HTM_TR_Begin (NULL);
HTM_TD_Begin ("class=\"DAT_N RT\"");
HTM_TxtF ("%s:",Txt_START_END_TIME[StartEndTime]);
HTM_TD_End ();
HTM_TD_Begin ("id=\"%s\" class=\"DAT LT\"",Id);
2020-05-07 18:33:26 +02:00
Dat_WriteLocalDateHMSFromUTC (Id,Print.TimeUTC[StartEndTime],
2020-04-02 03:28:08 +02:00
Gbl.Prefs.DateFormat,Dat_SEPARATOR_COMMA,
true,true,true,0x7);
HTM_TD_End ();
HTM_TR_End ();
free (Id);
}
/* Number of questions */
HTM_TR_Begin (NULL);
HTM_TD_Begin ("class=\"DAT_N RT\"");
HTM_TxtF ("%s:",Txt_Questions);
HTM_TD_End ();
HTM_TD_Begin ("class=\"DAT LT\"");
HTM_TxtF ("%u (%u %s)",
2020-05-07 18:33:26 +02:00
Print.NumQsts,
Print.NumQstsNotBlank,Txt_non_blank_QUESTIONS);
2020-04-02 03:28:08 +02:00
HTM_TD_End ();
HTM_TR_End ();
/* Score */
HTM_TR_Begin (NULL);
HTM_TD_Begin ("class=\"DAT_N RT\"");
HTM_TxtF ("%s:",Txt_Score);
HTM_TD_End ();
HTM_TD_Begin ("class=\"DAT LT\"");
if (ICanViewScore)
2020-05-07 18:33:26 +02:00
HTM_Double2Decimals (Print.Score);
2020-04-02 03:28:08 +02:00
else
Ico_PutIconNotVisible ();
HTM_TD_End ();
/* Grade */
HTM_TR_Begin (NULL);
HTM_TD_Begin ("class=\"DAT_N RT\"");
HTM_TxtF ("%s:",Txt_Grade);
HTM_TD_End ();
HTM_TD_Begin ("class=\"DAT LT\"");
if (ICanViewScore)
2020-05-07 18:33:26 +02:00
TstPrn_ComputeAndShowGrade (Print.NumQsts,Print.Score,
2020-05-09 01:37:00 +02:00
Tst_SCORE_MAX);
2020-04-02 03:28:08 +02:00
else
Ico_PutIconNotVisible ();
HTM_TD_End ();
HTM_TR_End ();
/* Tags present in this test */
HTM_TR_Begin (NULL);
HTM_TD_Begin ("class=\"DAT_N RT\"");
HTM_TxtF ("%s:",Txt_Tags);
HTM_TD_End ();
HTM_TD_Begin ("class=\"DAT LT\"");
2020-05-07 18:33:26 +02:00
TstPrn_ShowTagsPresentInAnExam (Print.PrnCod);
2020-04-02 03:28:08 +02:00
HTM_TD_End ();
HTM_TR_End ();
/***** Write answers and solutions *****/
2020-05-07 18:33:26 +02:00
TstPrn_ShowExamAnswers (&Gbl.Usrs.Other.UsrDat,&Print,
2020-04-02 03:28:08 +02:00
TstCfg_GetConfigVisibility ());
/***** End table *****/
HTM_TABLE_End ();
/***** Write total mark of test *****/
if (ICanViewScore)
{
HTM_DIV_Begin ("class=\"DAT_N_BOLD CM\"");
HTM_TxtColonNBSP (Txt_Score);
2020-05-07 18:33:26 +02:00
HTM_Double2Decimals (Print.Score);
2020-04-02 03:28:08 +02:00
HTM_BR ();
HTM_TxtColonNBSP (Txt_Grade);
2020-05-07 18:33:26 +02:00
TstPrn_ComputeAndShowGrade (Print.NumQsts,Print.Score,
2020-05-09 01:37:00 +02:00
Tst_SCORE_MAX);
2020-04-02 03:28:08 +02:00
HTM_DIV_End ();
}
/***** End box *****/
Box_BoxEnd ();
}
else // I am not allowed to view this test exam
Lay_NoPermissionExit ();
}
/*****************************************************************************/
/********************* Show test tags in this test exam **********************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
static void TstPrn_ShowTagsPresentInAnExam (long ResCod)
2020-04-02 03:28:08 +02:00
{
MYSQL_RES *mysql_res;
unsigned NumTags;
/***** Get all tags of questions in this test *****/
NumTags = (unsigned)
DB_QuerySELECT (&mysql_res,"can not get tags"
" present in a test exam",
"SELECT tst_tags.TagTxt" // row[0]
" FROM"
" (SELECT DISTINCT(tst_question_tags.TagCod)"
" FROM tst_question_tags,tst_exam_questions"
" WHERE tst_exam_questions.ExaCod=%ld"
" AND tst_exam_questions.QstCod=tst_question_tags.QstCod)"
" AS TagsCods,tst_tags"
" WHERE TagsCods.TagCod=tst_tags.TagCod"
" ORDER BY tst_tags.TagTxt",
2020-04-22 03:15:04 +02:00
ResCod);
2020-04-02 03:28:08 +02:00
Tst_ShowTagList (NumTags,mysql_res);
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
/************** Show user's and correct answers of a test exam ***************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
void TstPrn_ShowExamAnswers (struct UsrData *UsrDat,
struct TstPrn_Print *Print,
2020-04-02 03:28:08 +02:00
unsigned Visibility)
{
unsigned NumQst;
2020-04-03 19:13:00 +02:00
struct Tst_Question Question;
2020-05-07 19:54:24 +02:00
bool QuestionExists;
2020-04-02 03:28:08 +02:00
for (NumQst = 0;
2020-05-07 18:33:26 +02:00
NumQst < Print->NumQsts;
2020-04-02 03:28:08 +02:00
NumQst++)
{
Gbl.RowEvenOdd = NumQst % 2;
2020-04-03 19:13:00 +02:00
/***** Create test question *****/
Tst_QstConstructor (&Question);
2020-05-07 18:33:26 +02:00
Question.QstCod = Print->PrintedQuestions[NumQst].QstCod;
2020-04-02 03:28:08 +02:00
2020-04-03 19:13:00 +02:00
/***** Get question data *****/
2020-05-07 19:54:24 +02:00
QuestionExists = Tst_GetQstDataFromDB (&Question);
/***** Write questions and answers *****/
TstPrn_WriteQstAndAnsExam (UsrDat,Print,
NumQst,&Question,QuestionExists,
Visibility);
2020-04-02 03:28:08 +02:00
2020-04-03 19:13:00 +02:00
/***** Destroy test question *****/
Tst_QstDestructor (&Question);
2020-04-02 03:28:08 +02:00
}
}
/*****************************************************************************/
/************ Get data of a test exam using its test exam code ***************/
/*****************************************************************************/
2020-05-10 01:42:30 +02:00
void TstPrn_GetPrintDataByPrnCod (struct TstPrn_Print *Print)
2020-04-02 03:28:08 +02:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
/***** Make database query *****/
if (DB_QuerySELECT (&mysql_res,"can not get data of a test exam",
"SELECT UsrCod," // row[0]
"UNIX_TIMESTAMP(StartTime)," // row[1]
"UNIX_TIMESTAMP(EndTime)," // row[2]
"NumQsts," // row[3]
"NumQstsNotBlank," // row[4]
2020-04-16 21:03:22 +02:00
"Sent," // row[5]
"AllowTeachers," // row[6]
"Score" // row[7]
2020-04-02 03:28:08 +02:00
" FROM tst_exams"
" WHERE ExaCod=%ld AND CrsCod=%ld",
2020-05-07 18:33:26 +02:00
Print->PrnCod,
2020-04-02 03:28:08 +02:00
Gbl.Hierarchy.Crs.CrsCod) == 1)
{
row = mysql_fetch_row (mysql_res);
/* Get user code (row[0]) */
Gbl.Usrs.Other.UsrDat.UsrCod = Str_ConvertStrCodToLongCod (row[0]);
/* Get date-time (row[1] and row[2] hold UTC date-time) */
2020-05-07 18:33:26 +02:00
Print->TimeUTC[Dat_START_TIME] = Dat_GetUNIXTimeFromStr (row[1]);
Print->TimeUTC[Dat_END_TIME ] = Dat_GetUNIXTimeFromStr (row[2]);
2020-04-02 03:28:08 +02:00
/* Get number of questions (row[3]) */
2020-05-07 18:33:26 +02:00
if (sscanf (row[3],"%u",&Print->NumQsts) != 1)
Print->NumQsts = 0;
2020-04-02 03:28:08 +02:00
/* Get number of questions not blank (row[4]) */
2020-05-07 18:33:26 +02:00
if (sscanf (row[4],"%u",&Print->NumQstsNotBlank) != 1)
Print->NumQstsNotBlank = 0;
2020-04-02 03:28:08 +02:00
2020-04-16 21:03:22 +02:00
/* Get if exam has been sent (row[5]) */
2020-05-07 18:33:26 +02:00
Print->Sent = (row[5][0] == 'Y');
2020-04-16 21:03:22 +02:00
/* Get if teachers are allowed to see this test exam (row[6]) */
2020-05-07 18:33:26 +02:00
Print->AllowTeachers = (row[6][0] == 'Y');
2020-04-02 03:28:08 +02:00
2020-04-16 21:03:22 +02:00
/* Get score (row[7]) */
2020-04-02 03:28:08 +02:00
Str_SetDecimalPointToUS (); // To get the decimal point as a dot
2020-05-07 18:33:26 +02:00
if (sscanf (row[7],"%lf",&Print->Score) != 1)
Print->Score = 0.0;
2020-04-02 03:28:08 +02:00
Str_SetDecimalPointToLocal (); // Return to local system
}
else
2020-05-11 02:28:38 +02:00
TstPrn_ResetPrintExceptPrnCod (Print);
2020-04-02 03:28:08 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
2020-05-10 01:42:30 +02:00
/*********** Get the questions of a test exam print from database ************/
2020-04-02 03:28:08 +02:00
/*****************************************************************************/
2020-05-10 01:42:30 +02:00
void TstPrn_GetPrintQuestionsFromDB (struct TstPrn_Print *Print)
2020-04-02 03:28:08 +02:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
2020-04-14 02:37:24 +02:00
unsigned NumQsts;
2020-04-02 03:28:08 +02:00
unsigned NumQst;
2020-04-30 20:15:21 +02:00
Tst_AnswerType_t AnswerType;
2020-04-02 03:28:08 +02:00
2020-05-10 01:42:30 +02:00
/***** Get questions of a test exam print from database *****/
2020-04-14 02:37:24 +02:00
NumQsts =
2020-04-02 03:28:08 +02:00
(unsigned) DB_QuerySELECT (&mysql_res,"can not get questions"
" of a test exam",
2020-04-30 20:15:21 +02:00
"SELECT tst_exam_questions.QstCod," // row[0]
"tst_questions.AnsType," // row[1]
"tst_exam_questions.Indexes," // row[2]
"tst_exam_questions.Answers" // row[3]
" FROM tst_exam_questions,tst_questions"
" WHERE tst_exam_questions.ExaCod=%ld"
" AND tst_exam_questions.QstCod=tst_questions.QstCod"
" ORDER BY tst_exam_questions.QstInd",
2020-05-07 18:33:26 +02:00
Print->PrnCod);
2020-04-02 03:28:08 +02:00
2020-05-10 01:42:30 +02:00
/***** Get questions *****/
2020-05-11 14:11:15 +02:00
// Some questions may be deleted, so the number of questions retrieved
// could be lower than the original number of questions in the exam print
if (NumQsts <= Print->NumQsts)
2020-04-14 02:37:24 +02:00
for (NumQst = 0;
NumQst < NumQsts;
NumQst++)
{
row = mysql_fetch_row (mysql_res);
2020-04-02 03:28:08 +02:00
2020-04-30 20:15:21 +02:00
/* Get question code (row[0]) */
2020-05-07 18:33:26 +02:00
if ((Print->PrintedQuestions[NumQst].QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
2020-04-14 02:37:24 +02:00
Lay_ShowErrorAndExit ("Wrong code of question.");
2020-04-02 03:28:08 +02:00
2020-04-30 20:15:21 +02:00
/* Get answer type (row[1]) */
AnswerType = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[1]);
/* Get indexes for this question (row[2]) */
2020-05-07 18:33:26 +02:00
Str_Copy (Print->PrintedQuestions[NumQst].StrIndexes,row[2],
2020-05-09 01:37:00 +02:00
Tst_MAX_BYTES_INDEXES_ONE_QST);
2020-04-02 03:28:08 +02:00
2020-04-30 20:15:21 +02:00
/* Get answers selected by user for this question (row[3]) */
2020-05-07 18:33:26 +02:00
Str_Copy (Print->PrintedQuestions[NumQst].StrAnswers,row[3],
2020-05-09 01:37:00 +02:00
Tst_MAX_BYTES_ANSWERS_ONE_QST);
2020-04-02 03:28:08 +02:00
2020-04-14 02:37:24 +02:00
/* Replace each comma by a separator of multiple parameters */
/* In database commas are used as separators instead of special chars */
2020-05-07 18:33:26 +02:00
Par_ReplaceCommaBySeparatorMultiple (Print->PrintedQuestions[NumQst].StrIndexes);
2020-04-30 20:15:21 +02:00
if (AnswerType == Tst_ANS_MULTIPLE_CHOICE)
// Only multiple choice questions have multiple answers separated by commas
// Other types of questions have a unique answer, and comma may be part of that answer
2020-05-07 18:33:26 +02:00
Par_ReplaceCommaBySeparatorMultiple (Print->PrintedQuestions[NumQst].StrAnswers);
2020-04-14 02:37:24 +02:00
}
2020-04-02 03:28:08 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
2020-04-14 02:37:24 +02:00
2020-05-11 14:11:15 +02:00
if (NumQsts > Print->NumQsts)
2020-04-25 01:36:53 +02:00
Lay_WrongExamExit ();
2020-04-02 03:28:08 +02:00
}
/*****************************************************************************/
/********************** Remove test exams made by a user *********************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
void TstPrn_RemoveExamsMadeByUsrInAllCrss (long UsrCod)
2020-04-02 03:28:08 +02:00
{
/***** Remove test exams made by the specified user *****/
DB_QueryDELETE ("can not remove test exams made by a user",
"DELETE FROM tst_exam_questions"
" USING tst_exams,tst_exam_questions"
" WHERE tst_exams.UsrCod=%ld"
" AND tst_exams.ExaCod=tst_exam_questions.ExaCod",
UsrCod);
DB_QueryDELETE ("can not remove test exams made by a user",
"DELETE FROM tst_exams"
" WHERE UsrCod=%ld",
UsrCod);
}
/*****************************************************************************/
/*************** Remove test exams made by a user in a course ****************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
void TstPrn_RemoveExamsMadeByUsrInCrs (long UsrCod,long CrsCod)
2020-04-02 03:28:08 +02:00
{
/***** Remove test exams made by the given user *****/
DB_QueryDELETE ("can not remove test exams made by a user in a course",
"DELETE FROM tst_exam_questions"
" USING tst_exams,tst_exam_questions"
" WHERE tst_exams.CrsCod=%ld AND tst_exams.UsrCod=%ld"
" AND tst_exams.ExaCod=tst_exam_questions.ExaCod",
CrsCod,UsrCod);
DB_QueryDELETE ("can not remove test exams made by a user in a course",
"DELETE FROM tst_exams"
" WHERE CrsCod=%ld AND UsrCod=%ld",
CrsCod,UsrCod);
}
/*****************************************************************************/
/******************* Remove all test exams made in a course ******************/
/*****************************************************************************/
2020-05-07 18:33:26 +02:00
void TstPrn_RemoveCrsExams (long CrsCod)
2020-04-02 03:28:08 +02:00
{
/***** Remove questions of test exams made in the course *****/
DB_QueryDELETE ("can not remove test exams made in a course",
"DELETE FROM tst_exam_questions"
" USING tst_exams,tst_exam_questions"
" WHERE tst_exams.CrsCod=%ld"
" AND tst_exams.ExaCod=tst_exam_questions.ExaCod",
CrsCod);
/***** Remove test exams made in the course *****/
DB_QueryDELETE ("can not remove test exams made in a course",
"DELETE FROM tst_exams WHERE CrsCod=%ld",
CrsCod);
}