// 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,"