// swad_test.c: self-assessment tests
/*
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-2019 Antonio Caņ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 .
*/
/*****************************************************************************/
/*********************************** Headers *********************************/
/*****************************************************************************/
#define _GNU_SOURCE // For asprintf
#include // For UINT_MAX
#include // For PATH_MAX
#include // For NULL
#include // To access MySQL databases
#include // For boolean type
#include // For fprintf, asprintf, etc.
#include // For exit, system, malloc, free, etc
#include // For string functions
#include // For mkdir
#include // For mkdir
#include "swad_action.h"
#include "swad_box.h"
#include "swad_database.h"
#include "swad_form.h"
#include "swad_global.h"
#include "swad_HTML.h"
#include "swad_ID.h"
#include "swad_language.h"
#include "swad_match.h"
#include "swad_media.h"
#include "swad_parameter.h"
#include "swad_theme.h"
#include "swad_test.h"
#include "swad_test_import.h"
#include "swad_user.h"
#include "swad_xml.h"
/*****************************************************************************/
/***************************** Public constants ******************************/
/*****************************************************************************/
// strings are limited to Tst_MAX_BYTES_FEEDBACK_TYPE bytes
const char *Tst_FeedbackXML[Tst_NUM_TYPES_FEEDBACK] =
{
"nothing",
"totalResult",
"eachResult",
"eachGoodBad",
"fullFeedback",
};
// strings are limited to Tst_MAX_BYTES_ANSWER_TYPE characters
const char *Tst_StrAnswerTypesXML[Tst_NUM_ANS_TYPES] =
{
"int",
"float",
"TF",
"uniqueChoice",
"multipleChoice",
"text",
};
/*****************************************************************************/
/**************************** Private constants ******************************/
/*****************************************************************************/
#define Tst_MAX_BYTES_TAGS_LIST (16 * 1024)
#define Tst_MAX_BYTES_FLOAT_ANSWER 30 // Maximum length of the strings that store an floating point answer
const char *Tst_PluggableDB[Tst_NUM_OPTIONS_PLUGGABLE] =
{
"unknown",
"N",
"Y",
};
// Feedback to students in tests
const char *Tst_FeedbackDB[Tst_NUM_TYPES_FEEDBACK] =
{
"nothing", // No feedback
"total_result", // Little
"each_result", // Medium
"each_good_bad", // High
"full_feedback", // Maximum
};
const char *Tst_StrAnswerTypesDB[Tst_NUM_ANS_TYPES] =
{
"int",
"float",
"true_false",
"unique_choice",
"multiple_choice",
"text",
};
// Test images will be saved with:
// - maximum width of Tst_IMAGE_SAVED_MAX_HEIGHT
// - maximum height of Tst_IMAGE_SAVED_MAX_HEIGHT
// - maintaining the original aspect ratio (aspect ratio recommended: 3:2)
#define Tst_IMAGE_SAVED_MAX_WIDTH 768
#define Tst_IMAGE_SAVED_MAX_HEIGHT 512
#define Tst_IMAGE_SAVED_QUALITY 75 // 1 to 100
/*****************************************************************************/
/******************************* Internal types ******************************/
/*****************************************************************************/
#define Tst_NUM_STATUS 2
typedef enum
{
Tst_STATUS_SHOWN_BUT_NOT_ASSESSED = 0,
Tst_STATUS_ASSESSED = 1,
Tst_STATUS_ERROR = 2,
} Tst_Status_t;
/*****************************************************************************/
/************** External global variables from others modules ****************/
/*****************************************************************************/
extern struct Globals Gbl;
/*****************************************************************************/
/************************* Internal global variables *************************/
/*****************************************************************************/
/*****************************************************************************/
/***************************** Internal prototypes ***************************/
/*****************************************************************************/
static void Tst_PutFormToViewTstResults (Act_Action_t Action);
static void Tst_GetQuestionsAndAnswersFromForm (void);
static bool Tst_CheckIfNextTstAllowed (void);
static void Tst_SetTstStatus (unsigned NumTst,Tst_Status_t TstStatus);
static Tst_Status_t Tst_GetTstStatus (unsigned NumTst);
static unsigned Tst_GetNumAccessesTst (void);
static void Tst_ShowTestQuestionsWhenSeeing (MYSQL_RES *mysql_res);
static void Tst_ShowTestResultAfterAssess (long TstCod,unsigned *NumQstsNotBlank,double *TotalScore);
static void Tst_WriteQstAndAnsTest (Tst_ActionToDoWithQuestions_t ActionToDoWithQuestions,
struct UsrData *UsrDat,
unsigned NumQst,long QstCod,MYSQL_ROW row,
double *ScoreThisQst,bool *AnswerIsNotBlank);
static void Tst_PutFormToEditQstMedia (struct Media *Media,int NumMediaInForm,
bool OptionsDisabled);
static void Tst_UpdateScoreQst (long QstCod,float ScoreThisQst,bool AnswerIsNotBlank);
static void Tst_UpdateMyNumAccessTst (unsigned NumAccessesTst);
static void Tst_UpdateLastAccTst (void);
static bool Tst_CheckIfICanEditTests (void);
static void Tst_PutIconsTests (void);
static void Tst_PutButtonToAddQuestion (void);
static long Tst_GetParamTagCode (void);
static bool Tst_CheckIfCurrentCrsHasTestTags (void);
static unsigned long Tst_GetAllTagsFromCurrentCrs (MYSQL_RES **mysql_res);
static unsigned long Tst_GetEnabledTagsFromThisCrs (MYSQL_RES **mysql_res);
static void Tst_ShowFormSelTags (unsigned long NumRows,MYSQL_RES *mysql_res,
bool ShowOnlyEnabledTags,unsigned NumCols);
static void Tst_ShowFormEditTags (void);
static void Tst_PutIconEnable (long TagCod,const char *TagTxt);
static void Tst_PutIconDisable (long TagCod,const char *TagTxt);
static void Tst_ShowFormConfigTst (void);
static void Tst_PutInputFieldNumQst (const char *Field,const char *Label,
unsigned Value);
static Tst_Pluggable_t Tst_GetPluggableFromForm (void);
static Tst_Feedback_t Tst_GetFeedbackTypeFromForm (void);
static void Tst_CheckAndCorrectNumbersQst (void);
static void Tst_ShowFormAnswerTypes (unsigned NumCols);
static unsigned long Tst_GetQuestions (MYSQL_RES **mysql_res);
static unsigned long Tst_GetQuestionsForTest (MYSQL_RES **mysql_res);
static void Tst_ListOneQstToEdit (void);
static void Tst_ListOneOrMoreQuestionsForEdition (unsigned long NumRows,
MYSQL_RES *mysql_res);
static void Tst_ListOneOrMoreQuestionsForSelection (unsigned long NumRows,
MYSQL_RES *mysql_res);
static void Tst_WriteAnswersTestToAnswer (unsigned NumQst,long QstCod,bool Shuffle);
static void Tst_WriteAnswersTestResult (struct UsrData *UsrDat,
unsigned NumQst,long QstCod,
double *ScoreThisQst,bool *AnswerIsNotBlank);
static void Tst_WriteTFAnsViewTest (unsigned NumQst);
static void Tst_WriteTFAnsAssessTest (struct UsrData *UsrDat,
unsigned NumQst,MYSQL_RES *mysql_res,
double *ScoreThisQst,bool *AnswerIsNotBlank);
static void Tst_WriteChoiceAnsViewTest (unsigned NumQst,long QstCod,bool Shuffle);
static void Tst_WriteChoiceAnsAssessTest (struct UsrData *UsrDat,
unsigned NumQst,MYSQL_RES *mysql_res,
double *ScoreThisQst,bool *AnswerIsNotBlank);
static void Tst_WriteTextAnsViewTest (unsigned NumQst);
static void Tst_WriteTextAnsAssessTest (struct UsrData *UsrDat,
unsigned NumQst,MYSQL_RES *mysql_res,
double *ScoreThisQst,bool *AnswerIsNotBlank);
static void Tst_WriteIntAnsViewTest (unsigned NumQst);
static void Tst_WriteIntAnsAssessTest (struct UsrData *UsrDat,
unsigned NumQst,MYSQL_RES *mysql_res,
double *ScoreThisQst,bool *AnswerIsNotBlank);
static void Tst_WriteFloatAnsViewTest (unsigned NumQst);
static void Tst_WriteFloatAnsAssessTest (struct UsrData *UsrDat,
unsigned NumQst,MYSQL_RES *mysql_res,
double *ScoreThisQst,bool *AnswerIsNotBlank);
static void Tst_WriteHeadUserCorrect (struct UsrData *UsrDat);
static void Tst_WriteScoreStart (unsigned ColSpan);
static void Tst_WriteScoreEnd (void);
static void Tst_WriteParamQstCod (unsigned NumQst,long QstCod);
static bool Tst_GetParamsTst (Tst_ActionToDoWithQuestions_t ActionToDoWithQuestions);
static unsigned Tst_GetAndCheckParamNumTst (void);
static void Tst_GetParamNumQst (void);
static bool Tst_GetCreateXMLFromForm (void);
static int Tst_CountNumTagsInList (void);
static int Tst_CountNumAnswerTypesInList (void);
static void Tst_PutFormEditOneQst (char Stem[Cns_MAX_BYTES_TEXT + 1],
char Feedback[Cns_MAX_BYTES_TEXT + 1]);
static void Tst_PutFloatInputField (const char *Label,const char *Field,
double Value);
static void Tst_PutTFInputField (const char *Label,char Value);
static void Tst_FreeTextChoiceAnswers (void);
static void Tst_FreeTextChoiceAnswer (unsigned NumOpt);
static void Tst_ResetMediaOfQuestion (void);
static void Tst_FreeMediaOfQuestion (void);
static void Tst_GetQstDataFromDB (char Stem[Cns_MAX_BYTES_TEXT + 1],
char Feedback[Cns_MAX_BYTES_TEXT + 1]);
static long Tst_GetMedCodFromDB (int NumOpt);
static void Tst_GetMediaFromDB (int NumOpt,struct Media *Media);
static Tst_AnswerType_t Tst_ConvertFromUnsignedStrToAnsTyp (const char *UnsignedStr);
static void Tst_GetQstFromForm (char *Stem,char *Feedback);
static void Tst_MoveMediaToDefinitiveDirectories (void);
static long Tst_GetTagCodFromTagTxt (const char *TagTxt);
static long Tst_CreateNewTag (long CrsCod,const char *TagTxt);
static void Tst_EnableOrDisableTag (long TagCod,bool TagHidden);
static void Tst_PutIconToRemoveOneQst (void);
static void Tst_PutParamsRemoveOneQst (void);
static void Tst_PutParamsRemoveQst (void);
static long Tst_GetQstCod (void);
static void Tst_InsertOrUpdateQstIntoDB (void);
static void Tst_InsertTagsIntoDB (void);
static void Tst_InsertAnswersIntoDB (void);
static void Tst_RemAnsFromQst (void);
static void Tst_RemTagsFromQst (void);
static void Tst_RemoveUnusedTagsFromCurrentCrs (void);
static void Tst_RemoveAllMedFilesFromStemOfAllQstsInCrs (long CrsCod);
static void Tst_RemoveMediaFromAllAnsOfQst (long CrsCod,long QstCod);
static void Tst_RemoveAllMedFilesFromAnsOfAllQstsInCrs (long CrsCod);
static unsigned Tst_GetNumTstQuestions (Hie_Level_t Scope,Tst_AnswerType_t AnsType,struct Tst_Stats *Stats);
static unsigned Tst_GetNumCoursesWithTstQuestions (Hie_Level_t Scope,Tst_AnswerType_t AnsType);
static unsigned Tst_GetNumCoursesWithPluggableTstQuestions (Hie_Level_t Scope,Tst_AnswerType_t AnsType);
static long Tst_CreateTestResultInDB (void);
static void Tst_StoreScoreOfTestResultInDB (long TstCod,
unsigned NumQstsNotBlank,double Score);
static void Tst_ShowHeaderTestResults (void);
static void Tst_ShowTstResults (struct UsrData *UsrDat);
static void Tst_PutParamTstCod (long TstCod);
static long Tst_GetParamTstCod (void);
static void Tst_ShowTestResultsSummaryRow (bool ItsMe,
unsigned NumExams,
unsigned NumTotalQsts,
unsigned NumTotalQstsNotBlank,
double TotalScoreOfAllTests);
static void Tst_GetTestResultDataByTstCod (long TstCod,time_t *TstTimeUTC,
unsigned *NumQstsNotBlank,double *Score);
static void Tst_StoreOneTestResultQstInDB (long TstCod,long QstCod,unsigned NumQst,double Score);
static void Tst_GetTestResultQuestionsFromDB (long TstCod);
/*****************************************************************************/
/*************** Show form to generate a self-assessment test ****************/
/*****************************************************************************/
void Tst_ShowFormAskTst (void)
{
extern const char *Hlp_ASSESSMENT_Tests;
extern const char *The_ClassFormInBox[The_NUM_THEMES];
extern const char *Txt_Take_a_test;
extern const char *Txt_No_of_questions;
extern const char *Txt_Generate_test;
extern const char *Txt_No_test_questions;
MYSQL_RES *mysql_res;
unsigned long NumRows;
/***** Read test configuration from database *****/
Tst_GetConfigTstFromDB ();
/***** Put link to view tests results *****/
switch (Gbl.Usrs.Me.Role.Logged)
{
case Rol_STD:
Tst_PutFormToViewTstResults (ActReqSeeMyTstRes);
break;
case Rol_NET:
case Rol_TCH:
case Rol_SYS_ADM:
Tst_PutFormToViewTstResults (ActReqSeeUsrTstRes);
break;
default:
break;
}
/***** Begin box *****/
Box_BoxBegin (NULL,Txt_Take_a_test,Tst_PutIconsTests,
Hlp_ASSESSMENT_Tests,Box_NOT_CLOSABLE);
/***** Get tags *****/
if ((NumRows = Tst_GetEnabledTagsFromThisCrs (&mysql_res)) != 0)
{
/***** Check if minimum date-time of next access to test is older than now *****/
if (Tst_CheckIfNextTstAllowed ())
{
Frm_StartForm (ActSeeTst);
HTM_TABLE_BeginPadding (2);
/***** Selection of tags *****/
Tst_ShowFormSelTags (NumRows,mysql_res,true,1);
/***** Selection of types of answers *****/
Tst_ShowFormAnswerTypes (1);
/***** Number of questions to generate ****/
HTM_TR_Begin (NULL);
HTM_TD_Begin ("class=\"RM\"");
fprintf (Gbl.F.Out,"