From 38bb6cbd69533ddd95c745c7d395b72d46c67cd8 Mon Sep 17 00:00:00 2001 From: acanas Date: Thu, 2 Apr 2020 03:28:08 +0200 Subject: [PATCH] Version19.158 --- .settings/language.settings.xml | 2 +- Makefile | 6 +- sql/swad.sql | 8 +- swad_API.c | 28 +- swad_account.c | 3 +- swad_action.c | 30 +- swad_changelog.h | 20 +- swad_course.c | 3 +- swad_database.c | 20 +- swad_enrolment.c | 3 +- swad_game.c | 14 +- swad_match.c | 26 +- swad_match_result.c | 280 ++-- swad_match_result.h | 30 +- swad_test.c | 2028 +++-------------------- swad_test.h | 21 +- swad_test_config.c | 6 +- swad_test_exam.c | 2676 +++++++++++++++++++++++++++++++ swad_test_exam.h | 110 ++ swad_test_result.c | 1157 ------------- swad_test_result.h | 84 - swad_test_visibility.c | 94 +- swad_test_visibility.h | 42 +- swad_text.c | 12 +- 24 files changed, 3361 insertions(+), 3342 deletions(-) create mode 100644 swad_test_exam.c create mode 100644 swad_test_exam.h delete mode 100644 swad_test_result.c delete mode 100644 swad_test_result.h diff --git a/.settings/language.settings.xml b/.settings/language.settings.xml index 549daa6a..53f6545d 100644 --- a/.settings/language.settings.xml +++ b/.settings/language.settings.xml @@ -11,7 +11,7 @@ - + diff --git a/Makefile b/Makefile index 401ded7f..12295a84 100644 --- a/Makefile +++ b/Makefile @@ -57,9 +57,9 @@ OBJS = swad_account.o swad_action.o swad_agenda.o swad_alert.o \ swad_scope.o swad_search.o swad_session.o swad_setting.o \ swad_statistic.o swad_string.o swad_survey.o swad_syllabus.o \ swad_system_config.o \ - swad_tab.o swad_test.o swad_test_config.o swad_test_import.o \ - swad_test_result.o \ - swad_test_visibility.o swad_theme.o swad_timeline.o swad_timetable.o \ + swad_tab.o swad_test.o swad_test_config.o swad_test_exam.o \ + swad_test_import.o swad_test_visibility.o swad_theme.o swad_timeline.o \ + swad_timetable.o \ swad_user.o \ swad_xml.o \ swad_zip.o diff --git a/sql/swad.sql b/sql/swad.sql index c9463f43..84c3f18a 100644 --- a/sql/swad.sql +++ b/sql/swad.sql @@ -1304,18 +1304,18 @@ CREATE TABLE IF NOT EXISTS tst_config ( -- Table tst_exam_questions: stores the questions and answers in test exams made by users -- CREATE TABLE IF NOT EXISTS tst_exam_questions ( - TstCod INT NOT NULL, + ExaCod INT NOT NULL, QstCod INT NOT NULL, QstInd INT NOT NULL, Score DOUBLE PRECISION NOT NULL DEFAULT 0, Indexes TEXT NOT NULL, Answers TEXT NOT NULL, - INDEX(TstCod,QstCod)); + UNIQUE INDEX(ExaCod,QstCod)); -- -- Table tst_exams: stores the test exams made by users -- CREATE TABLE IF NOT EXISTS tst_exams ( - TstCod INT NOT NULL AUTO_INCREMENT, + ExaCod INT NOT NULL AUTO_INCREMENT, CrsCod INT NOT NULL, UsrCod INT NOT NULL, StartTime DATETIME NOT NULL, @@ -1324,7 +1324,7 @@ CREATE TABLE IF NOT EXISTS tst_exams ( NumQstsNotBlank INT NOT NULL DEFAULT 0, AllowTeachers ENUM('N','Y') NOT NULL DEFAULT 'N', Score DOUBLE PRECISION NOT NULL DEFAULT 0, - UNIQUE INDEX(TstCod), + UNIQUE INDEX(ExaCod), INDEX(CrsCod,UsrCod)); -- -- Table tst_question_tags: stores the tags associated to each test question diff --git a/swad_API.c b/swad_API.c index 75ff457f..afee6c47 100644 --- a/swad_API.c +++ b/swad_API.c @@ -3949,7 +3949,7 @@ int swad__sendNotice (struct soap *soap, /****************** Return test configuration in a course ********************/ /*****************************************************************************/ -#define TsR_MAX_BYTES_FEEDBACK_TYPE 32 +#define TstExa_MAX_BYTES_FEEDBACK_TYPE 32 int swad__getTestConfig (struct soap *soap, char *wsKey,int courseCode, // input @@ -4001,10 +4001,10 @@ int swad__getTestConfig (struct soap *soap, getTestConfigOut->minQuestions = getTestConfigOut->defQuestions = getTestConfigOut->maxQuestions = 0; - getTestConfigOut->visibility = TsV_MIN_VISIBILITY; + getTestConfigOut->visibility = TstVis_MIN_VISIBILITY; /* TODO: Remove these lines in 2021 */ - getTestConfigOut->feedback = (char *) soap_malloc (soap,TsR_MAX_BYTES_FEEDBACK_TYPE + 1); + getTestConfigOut->feedback = (char *) soap_malloc (soap,TstExa_MAX_BYTES_FEEDBACK_TYPE + 1); getTestConfigOut->feedback[0] = '\0'; /***** Get test configuration *****/ @@ -4019,26 +4019,26 @@ int swad__getTestConfig (struct soap *soap, /* Convert from visibility to old feedback */ /* TODO: Remove these lines in 2021 */ - if (!TsV_IsVisibleTotalScore (TstCfg_GetConfigVisibility ())) + if (!TstVis_IsVisibleTotalScore (TstCfg_GetConfigVisibility ())) Str_Copy (getTestConfigOut->feedback, "nothing", - TsR_MAX_BYTES_FEEDBACK_TYPE); - else if (!TsV_IsVisibleEachQstScore (TstCfg_GetConfigVisibility ())) + TstExa_MAX_BYTES_FEEDBACK_TYPE); + else if (!TstVis_IsVisibleEachQstScore (TstCfg_GetConfigVisibility ())) Str_Copy (getTestConfigOut->feedback, "totalResult", - TsR_MAX_BYTES_FEEDBACK_TYPE); - else if (!TsV_IsVisibleCorrectAns (TstCfg_GetConfigVisibility ())) + TstExa_MAX_BYTES_FEEDBACK_TYPE); + else if (!TstVis_IsVisibleCorrectAns (TstCfg_GetConfigVisibility ())) Str_Copy (getTestConfigOut->feedback, "eachResult", - TsR_MAX_BYTES_FEEDBACK_TYPE); - else if (!TsV_IsVisibleFeedbackTxt (TstCfg_GetConfigVisibility ())) + TstExa_MAX_BYTES_FEEDBACK_TYPE); + else if (!TstVis_IsVisibleFeedbackTxt (TstCfg_GetConfigVisibility ())) Str_Copy (getTestConfigOut->feedback, "eachGoodBad", - TsR_MAX_BYTES_FEEDBACK_TYPE); + TstExa_MAX_BYTES_FEEDBACK_TYPE); else Str_Copy (getTestConfigOut->feedback, "fullFeedback", - TsR_MAX_BYTES_FEEDBACK_TYPE); + TstExa_MAX_BYTES_FEEDBACK_TYPE); /***** Get number of tests *****/ if (TstCfg_GetConfigPluggable () == TstCfg_PLUGGABLE_YES && @@ -4073,7 +4073,7 @@ static int API_GetTstConfig (long CrsCod) TstCfg_SetConfigMin (0); TstCfg_SetConfigDef (0); TstCfg_SetConfigMax (0); - TstCfg_SetConfigVisibility (TsV_VISIBILITY_DEFAULT); + TstCfg_SetConfigVisibility (TstVis_VISIBILITY_DEFAULT); } /***** Free structure that stores the query result *****/ @@ -4906,7 +4906,7 @@ int swad__getGames (struct soap *soap, getGamesOut->gamesArray.__ptr[NumGame].maxGrade = 0.0; /* Get visibility (row[5]) */ - getGamesOut->gamesArray.__ptr[NumGame].visibility = TsV_GetVisibilityFromStr (row[5]); + getGamesOut->gamesArray.__ptr[NumGame].visibility = TstVis_GetVisibilityFromStr (row[5]); /* Get title of the game (row[6]) */ Length = strlen (row[6]); diff --git a/swad_account.c b/swad_account.c index 187d4444..e0eaf61e 100644 --- a/swad_account.c +++ b/swad_account.c @@ -45,6 +45,7 @@ #include "swad_parameter.h" #include "swad_profile.h" #include "swad_report.h" +#include "swad_test_exam.h" #include "swad_timeline.h" /*****************************************************************************/ @@ -1076,7 +1077,7 @@ void Acc_CompletelyEliminateAccount (struct UsrData *UsrDat, UsrDat->FullName); /***** Remove test results made by user in all courses *****/ - TsR_RemoveTestResultsMadeByUsrInAllCrss (UsrDat->UsrCod); + TstExa_RemoveExamsMadeByUsrInAllCrss (UsrDat->UsrCod); /***** Remove user's notifications *****/ Ntf_RemoveUsrNtfs (UsrDat->UsrCod); diff --git a/swad_action.c b/swad_action.c index 0ab04afe..dcc894b4 100644 --- a/swad_action.c +++ b/swad_action.c @@ -644,12 +644,12 @@ const struct Act_Actions Act_Actions[Act_NUM_ACTIONS] = [ActRenTag ] = { 143,-1,TabUnk,ActReqTst ,0x220,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Tst_RenameTag ,NULL}, [ActRcvCfgTst ] = { 454,-1,TabUnk,ActReqTst ,0x220,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,TstCfg_ReceiveConfigTst ,NULL}, - [ActReqSeeMyTstRes ] = {1083,-1,TabUnk,ActReqTst ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,Dat_SetIniEndDates ,TsR_SelDatesToSeeMyTstResults ,NULL}, - [ActSeeMyTstRes ] = {1084,-1,TabUnk,ActReqTst ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,TsR_ShowMyTstResults ,NULL}, - [ActSeeOneTstResMe ] = {1085,-1,TabUnk,ActReqTst ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,TsR_ShowOneTstResult ,NULL}, - [ActReqSeeUsrTstRes ] = {1080,-1,TabUnk,ActReqTst ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,Dat_SetIniEndDates ,TsR_SelUsrsToViewUsrsTstResults,NULL}, - [ActSeeUsrTstRes ] = {1081,-1,TabUnk,ActReqTst ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,TsR_GetUsrsAndShowTstResults ,NULL}, - [ActSeeOneTstResOth ] = {1082,-1,TabUnk,ActReqTst ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,TsR_ShowOneTstResult ,NULL}, + [ActReqSeeMyTstRes ] = {1083,-1,TabUnk,ActReqTst ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,Dat_SetIniEndDates ,TstExa_SelDatesToSeeMyExams ,NULL}, + [ActSeeMyTstRes ] = {1084,-1,TabUnk,ActReqTst ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,TstExa_ShowMyExams ,NULL}, + [ActSeeOneTstResMe ] = {1085,-1,TabUnk,ActReqTst ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,TstExa_ShowOneExam ,NULL}, + [ActReqSeeUsrTstRes ] = {1080,-1,TabUnk,ActReqTst ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,Dat_SetIniEndDates ,TstExa_SelUsrsToViewUsrsExams ,NULL}, + [ActSeeUsrTstRes ] = {1081,-1,TabUnk,ActReqTst ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,TstExa_GetUsrsAndShowExams ,NULL}, + [ActSeeOneTstResOth ] = {1082,-1,TabUnk,ActReqTst ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,TstExa_ShowOneExam ,NULL}, [ActSeeGam ] = {1650,-1,TabUnk,ActSeeAllGam ,0x238,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Gam_SeeOneGame ,NULL}, @@ -672,16 +672,16 @@ const struct Act_Actions Act_Actions[Act_NUM_ACTIONS] = [ActAnsMchQstStd ] = {1651,-1,TabUnk,ActSeeAllGam ,0x008, 0, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Mch_GetMatchBeingPlayed ,Mch_ReceiveQuestionAnswer ,NULL}, [ActRefMchStd ] = {1782,-1,TabUnk,ActSeeAllGam ,0x008, 0, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_AJAX_RFRESH,Mch_GetMatchBeingPlayed ,Mch_RefreshMatchStd ,NULL}, - [ActSeeMyMchResCrs ] = {1796,-1,TabUnk,ActSeeAllGam ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,McR_ShowMyMchResultsInCrs ,NULL}, - [ActSeeMyMchResGam ] = {1810,-1,TabUnk,ActSeeAllGam ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,McR_ShowMyMchResultsInGam ,NULL}, - [ActSeeMyMchResMch ] = {1812,-1,TabUnk,ActSeeAllGam ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,McR_ShowMyMchResultsInMch ,NULL}, - [ActSeeOneMchResMe ] = {1797,-1,TabUnk,ActSeeAllGam ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,McR_ShowOneMchResult ,NULL}, + [ActSeeMyMchResCrs ] = {1796,-1,TabUnk,ActSeeAllGam ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,MchRes_ShowMyMchResultsInCrs ,NULL}, + [ActSeeMyMchResGam ] = {1810,-1,TabUnk,ActSeeAllGam ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,MchRes_ShowMyMchResultsInGam ,NULL}, + [ActSeeMyMchResMch ] = {1812,-1,TabUnk,ActSeeAllGam ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,MchRes_ShowMyMchResultsInMch ,NULL}, + [ActSeeOneMchResMe ] = {1797,-1,TabUnk,ActSeeAllGam ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,MchRes_ShowOneMchResult ,NULL}, - [ActReqSeeAllMchRes ] = {1798,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,McR_SelUsrsToViewMchResults ,NULL}, - [ActSeeAllMchResCrs ] = {1799,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,McR_ShowAllMchResultsInCrs ,NULL}, - [ActSeeAllMchResGam ] = {1811,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,McR_ShowAllMchResultsInGam ,NULL}, - [ActSeeAllMchResMch ] = {1813,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,McR_ShowAllMchResultsInMch ,NULL}, - [ActSeeOneMchResOth ] = {1800,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,McR_ShowOneMchResult ,NULL}, + [ActReqSeeAllMchRes ] = {1798,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,MchRes_SelUsrsToViewMchResults ,NULL}, + [ActSeeAllMchResCrs ] = {1799,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,MchRes_ShowAllMchResultsInCrs ,NULL}, + [ActSeeAllMchResGam ] = {1811,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,MchRes_ShowAllMchResultsInGam ,NULL}, + [ActSeeAllMchResMch ] = {1813,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,MchRes_ShowAllMchResultsInMch ,NULL}, + [ActSeeOneMchResOth ] = {1800,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,MchRes_ShowOneMchResult ,NULL}, [ActChgVisResMchUsr ] = {1801,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Mch_ToggleVisibilResultsMchUsr ,NULL}, diff --git a/swad_changelog.h b/swad_changelog.h index 02121190..301a6f56 100644 --- a/swad_changelog.h +++ b/swad_changelog.h @@ -497,7 +497,7 @@ enscript -2 --landscape --color --file-align=2 --highlight --line-numbers -o - * En OpenSWAD: ps2pdf source.ps destination.pdf */ -#define Log_PLATFORM_VERSION "SWAD 19.157 (2020-04-01)" +#define Log_PLATFORM_VERSION "SWAD 19.158 (2020-04-02)" #define CSS_FILE "swad19.146.css" #define JS_FILE "swad19.153.js" /* @@ -522,11 +522,19 @@ Param // TODO: En la lista de conectados central, poner el logo de la institución a la que pertenece el usuario // TODO: Miguel Damas: al principio de los exámenes tendría que poner cuánto resta cada pregunta // TODO: Oresti Baños: cambiar ojos por candados en descriptores para prohibir/permitir y dejar los ojos para poder elegir descriptores -// TODO: Si el alumno ha marcado "Permitir que los profesores...", entonces pedir confirmación al pulsar el botón azul, para evitar que se envíe por error antes de tiempo -// TODO: Tener en cuenta en los resultados de test (exámenes) el tiempo de inicio y el tiempo de fin - Cuando el alumno ve un test, se crea un examen (en la base de datos), aunque no se conteste, a partir de los datos del formulario. - El examen se muestra en pantalla tomándolo del examen en la base de datos, no del formulario. - Cuando el alumno pulsa en "He terminado" se le pregunta si está seguro y se vuelve a mostrar el examen cogiéndolo de la base de datos. +// TODO: Los exámenes de test que no se han confirmado + deben aparecer en la base de datos con un código especial, + no contando en la nota y no mostrándose la fecha de finalización. + El botón de confirmar envío de examen debería ser verde. + + Version 19.158: Apr 02, 2020 Lot of code refactoring in tests. (285031 lines) + 5 changes necessary in database: +ALTER TABLE tst_exams RENAME INDEX TstCod TO ExaCod; +ALTER TABLE tst_exams CHANGE COLUMN TstCod ExaCod INT NOT NULL AUTO_INCREMENT; + +DROP INDEX TstCod ON tst_exam_questions; +ALTER TABLE tst_exam_questions CHANGE COLUMN TstCod ExaCod INT NOT NULL; +ALTER TABLE tst_exam_questions ADD UNIQUE INDEX(ExaCod,QstCod); Version 19.157: Apr 01, 2020 Code refactoring in tests. Test exam is stored in database when it's generated. Not tested. (285023 lines) diff --git a/swad_course.c b/swad_course.c index 0aa6ab6c..08e97512 100644 --- a/swad_course.c +++ b/swad_course.c @@ -40,6 +40,7 @@ #include "swad_HTML.h" #include "swad_info.h" #include "swad_logo.h" +#include "swad_test_exam.h" /*****************************************************************************/ /************** External global variables from others modules ****************/ @@ -1891,7 +1892,7 @@ static void Crs_EmptyCourseCompletely (long CrsCod) Svy_RemoveSurveys (Hie_CRS,CrsCod); /***** Remove all test exams made in the course *****/ - TsR_RemoveCrsTestResults (CrsCod); + TstExa_RemoveCrsExams (CrsCod); /***** Remove all tests questions in the course *****/ Tst_RemoveCrsTests (CrsCod); diff --git a/swad_database.c b/swad_database.c index d7232b58..7f6c6cf7 100644 --- a/swad_database.c +++ b/swad_database.c @@ -1465,7 +1465,7 @@ mysql> DESCRIBE mch_indexes; DB_CreateTable ("CREATE TABLE IF NOT EXISTS mch_indexes (" "MchCod INT NOT NULL," "QstInd INT NOT NULL," - "Indexes TEXT NOT NULL," // Tst_MAX_BYTES_INDEXES_ONE_QST + "Indexes TEXT NOT NULL," // TstExa_MAX_BYTES_INDEXES_ONE_QST "UNIQUE INDEX(MchCod,QstInd))"); /***** Table mch_results *****/ @@ -2768,8 +2768,8 @@ mysql> DESCRIBE tst_exam_questions; +---------+---------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------+---------+------+-----+---------+-------+ -| TstCod | int(11) | NO | MUL | NULL | | -| QstCod | int(11) | NO | | NULL | | +| ExaCod | int(11) | NO | PRI | NULL | | +| QstCod | int(11) | NO | PRI | NULL | | | QstInd | int(11) | NO | | NULL | | | Score | double | NO | | 0 | | | Indexes | text | NO | | NULL | | @@ -2778,13 +2778,13 @@ mysql> DESCRIBE tst_exam_questions; 6 rows in set (0.00 sec) */ DB_CreateTable ("CREATE TABLE IF NOT EXISTS tst_exam_questions (" - "TstCod INT NOT NULL," + "ExaCod INT NOT NULL," "QstCod INT NOT NULL," "QstInd INT NOT NULL," "Score DOUBLE PRECISION NOT NULL DEFAULT 0," - "Indexes TEXT NOT NULL," // Tst_MAX_BYTES_INDEXES_ONE_QST - "Answers TEXT NOT NULL," // Tst_MAX_BYTES_ANSWERS_ONE_QST - "INDEX(TstCod,QstCod))"); + "Indexes TEXT NOT NULL," // TstExa_MAX_BYTES_INDEXES_ONE_QST + "Answers TEXT NOT NULL," // TstExa_MAX_BYTES_ANSWERS_ONE_QST + "UNIQUE INDEX(ExaCod,QstCod))"); /***** Table tst_exams *****/ /* @@ -2792,7 +2792,7 @@ mysql> DESCRIBE tst_exams; +-----------------+---------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------------+---------------+------+-----+---------+----------------+ -| TstCod | int(11) | NO | PRI | NULL | auto_increment | +| ExaCod | int(11) | NO | PRI | NULL | auto_increment | | CrsCod | int(11) | NO | MUL | NULL | | | UsrCod | int(11) | NO | | NULL | | | StartTime | datetime | NO | | NULL | | @@ -2805,7 +2805,7 @@ mysql> DESCRIBE tst_exams; 9 rows in set (0.00 sec) */ DB_CreateTable ("CREATE TABLE IF NOT EXISTS tst_exams (" - "TstCod INT NOT NULL AUTO_INCREMENT," + "ExaCod INT NOT NULL AUTO_INCREMENT," "CrsCod INT NOT NULL," "UsrCod INT NOT NULL," "StartTime DATETIME NOT NULL," @@ -2814,7 +2814,7 @@ mysql> DESCRIBE tst_exams; "NumQstsNotBlank INT NOT NULL DEFAULT 0," "AllowTeachers ENUM('N','Y') NOT NULL DEFAULT 'N'," "Score DOUBLE PRECISION NOT NULL DEFAULT 0," - "UNIQUE INDEX(TstCod)," + "UNIQUE INDEX(ExaCod)," "INDEX(CrsCod,UsrCod))"); /***** Table tst_question_tags *****/ diff --git a/swad_enrolment.c b/swad_enrolment.c index d0d91874..f5a914e6 100644 --- a/swad_enrolment.c +++ b/swad_enrolment.c @@ -44,6 +44,7 @@ #include "swad_notification.h" #include "swad_parameter.h" #include "swad_role.h" +#include "swad_test_exam.h" #include "swad_user.h" /*****************************************************************************/ @@ -4112,7 +4113,7 @@ static void Enr_EffectivelyRemUsrFromCrs (struct UsrData *UsrDat, Brw_RemoveSomeInfoAboutCrsUsrFilesFromDB (UsrDat->UsrCod,Crs->CrsCod); /***** Remove test results made by user in course *****/ - TsR_RemoveTestResultsMadeByUsrInCrs (UsrDat->UsrCod,Crs->CrsCod); + TstExa_RemoveExamsMadeByUsrInCrs (UsrDat->UsrCod,Crs->CrsCod); /***** Set all the notifications for this user in this course as removed, except notifications about new messages *****/ diff --git a/swad_game.c b/swad_game.c index b5997398..c536b10d 100644 --- a/swad_game.c +++ b/swad_game.c @@ -541,7 +541,7 @@ static void Gam_ShowOneGame (struct Game *Game,bool ShowOnlyThisGame) HTM_Double (Game->MaxGrade); HTM_BR (); HTM_TxtColonNBSP (Txt_Result_visibility); - TsV_ShowVisibilityIcons (Game->Visibility,Game->Hidden); + TstVis_ShowVisibilityIcons (Game->Visibility,Game->Hidden); HTM_DIV_End (); /***** Number of matches in game *****/ @@ -619,13 +619,13 @@ static void Gam_PutIconToShowResultsOfGame (void *Args) switch (Gbl.Usrs.Me.Role.Logged) { case Rol_STD: - Ico_PutContextualIconToShowResults (ActSeeMyMchResGam,McR_RESULTS_BOX_ID, + Ico_PutContextualIconToShowResults (ActSeeMyMchResGam,MchRes_RESULTS_BOX_ID, Gam_PutParams,&Gbl); break; case Rol_NET: case Rol_TCH: case Rol_SYS_ADM: - Ico_PutContextualIconToShowResults (ActSeeAllMchResGam,McR_RESULTS_BOX_ID, + Ico_PutContextualIconToShowResults (ActSeeAllMchResGam,MchRes_RESULTS_BOX_ID, Gam_PutParams,&Gbl); break; default: @@ -990,7 +990,7 @@ void Gam_GetDataOfGameByCod (struct Game *Game) Game->MaxGrade = 0.0; /* Get visibility (row[5]) */ - Game->Visibility = TsV_GetVisibilityFromStr (row[5]); + Game->Visibility = TstVis_GetVisibilityFromStr (row[5]); /* Get the title of the game (row[6]) */ Str_Copy (Game->Title,row[6], @@ -1054,7 +1054,7 @@ static void Gam_ResetGame (struct Game *Game) Game->CrsCod = -1L; Game->UsrCod = -1L; Game->MaxGrade = Gam_MAX_GRADE_DEFAULT; - Game->Visibility = TsV_VISIBILITY_DEFAULT; + Game->Visibility = TstVis_VISIBILITY_DEFAULT; Game->TimeUTC[Dat_START_TIME] = (time_t) 0; Game->TimeUTC[Dat_END_TIME ] = (time_t) 0; Game->Title[0] = '\0'; @@ -1403,7 +1403,7 @@ static void Gam_PutFormsEditionGame (struct Game *Game, HTM_TD_End (); HTM_TD_Begin ("class=\"LB\""); - TsV_PutVisibilityCheckboxes (Game->Visibility); + TstVis_PutVisibilityCheckboxes (Game->Visibility); HTM_TD_End (); HTM_TR_End (); @@ -1503,7 +1503,7 @@ static void Gam_ReceiveGameFieldsFromForm (struct Game *Game, Game->MaxGrade = 0.0; /***** Get visibility *****/ - Game->Visibility = TsV_GetVisibilityFromForm (); + Game->Visibility = TstVis_GetVisibilityFromForm (); /***** Get game text *****/ Par_GetParToHTML ("Txt",Txt,Cns_MAX_BYTES_TEXT); // Store in HTML format (not rigorous) diff --git a/swad_match.c b/swad_match.c index 3bca5d3f..1405cfbc 100644 --- a/swad_match.c +++ b/swad_match.c @@ -222,7 +222,7 @@ static void Mch_GetNumPlayers (struct Match *Match); static void Mch_RemoveMyAnswerToMatchQuestion (const struct Match *Match); -static void Mch_ComputeScore (struct TsR_Result *Result); +static void Mch_ComputeScore (struct TstExa_Exam *Result); static unsigned Mch_GetNumUsrsWhoHaveAnswerMch (long MchCod); @@ -784,7 +784,7 @@ static void Mch_ListOneOrMoreMatchesResultStd (const struct Match *Match) /* Result is visible by me */ Gam_SetCurrentGamCod (Match->GamCod); // Used to pass parameter Mch_SetCurrentMchCod (Match->MchCod); // Used to pass parameter - Lay_PutContextualLinkOnlyIcon (ActSeeMyMchResMch,McR_RESULTS_BOX_ID, + Lay_PutContextualLinkOnlyIcon (ActSeeMyMchResMch,MchRes_RESULTS_BOX_ID, Mch_PutParamsEdit,&Gbl, "trophy.svg", Txt_Results); @@ -807,7 +807,7 @@ static void Mch_ListOneOrMoreMatchesResultTch (const struct Match *Match) Mch_SetCurrentMchCod (Match->MchCod); // Used to pass parameter /* Show match results */ - Lay_PutContextualLinkOnlyIcon (ActSeeAllMchResMch,McR_RESULTS_BOX_ID, + Lay_PutContextualLinkOnlyIcon (ActSeeAllMchResMch,MchRes_RESULTS_BOX_ID, Mch_PutParamsEdit,&Gbl, "trophy.svg", Txt_Results); @@ -1541,7 +1541,7 @@ static void Mch_ReorderAnswer (long MchCod,unsigned QstInd, long LongNum; unsigned AnsInd; char StrOneAnswer[Cns_MAX_DECIMAL_DIGITS_UINT + 1]; - char StrAnswersOneQst[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1]; + char StrAnswersOneQst[TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1]; /***** Initialize list of answers to empty string *****/ StrAnswersOneQst[0] = '\0'; @@ -1574,9 +1574,9 @@ static void Mch_ReorderAnswer (long MchCod,unsigned QstInd, /* Concatenate answer index to list of answers */ if (NumAns) Str_Concat (StrAnswersOneQst,",", - Tst_MAX_BYTES_ANSWERS_ONE_QST); + TstExa_MAX_BYTES_ANSWERS_ONE_QST); Str_Concat (StrAnswersOneQst,StrOneAnswer, - Tst_MAX_BYTES_ANSWERS_ONE_QST); + TstExa_MAX_BYTES_ANSWERS_ONE_QST); } /***** Free structure that stores the query result *****/ @@ -1600,7 +1600,7 @@ void Mch_GetIndexes (long MchCod,unsigned QstInd, { MYSQL_RES *mysql_res; MYSQL_ROW row; - char StrIndexesOneQst[Tst_MAX_BYTES_INDEXES_ONE_QST + 1]; + char StrIndexesOneQst[TstExa_MAX_BYTES_INDEXES_ONE_QST + 1]; /***** Get indexes for a question from database *****/ if (!DB_QuerySELECT (&mysql_res,"can not get data of a question", @@ -1613,14 +1613,14 @@ void Mch_GetIndexes (long MchCod,unsigned QstInd, /* Get indexes (row[0]) */ Str_Copy (StrIndexesOneQst,row[0], - Tst_MAX_BYTES_INDEXES_ONE_QST); + TstExa_MAX_BYTES_INDEXES_ONE_QST); /***** Free structure that stores the query result *****/ DB_FreeMySQLResult (&mysql_res); /***** Get indexes from string *****/ Par_ReplaceCommaBySeparatorMultiple (StrIndexesOneQst); - Tst_GetIndexesFromStr (StrIndexesOneQst,Indexes); + TstExa_GetIndexesFromStr (StrIndexesOneQst,Indexes); } /*****************************************************************************/ @@ -3834,7 +3834,7 @@ void Mch_ReceiveQuestionAnswer (void) unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION]; struct Mch_UsrAnswer PreviousUsrAnswer; struct Mch_UsrAnswer UsrAnswer; - struct TsR_Result Result; + struct TstExa_Exam Result; /***** Get data of the match from database *****/ Match.MchCod = Gbl.Games.MchCodBeingPlayed; @@ -3889,7 +3889,7 @@ void Mch_ReceiveQuestionAnswer (void) UsrAnswer.AnsInd); /***** Update student's match result *****/ - McR_GetMatchResultQuestionsFromDB (Match.MchCod,Gbl.Usrs.Me.UsrDat.UsrCod, + MchRes_GetMatchResultQuestionsFromDB (Match.MchCod,Gbl.Usrs.Me.UsrDat.UsrCod, &Result); Mch_ComputeScore (&Result); @@ -3952,7 +3952,7 @@ static void Mch_RemoveMyAnswerToMatchQuestion (const struct Match *Match) /******************** Compute match score for a student **********************/ /*****************************************************************************/ -static void Mch_ComputeScore (struct TsR_Result *Result) +static void Mch_ComputeScore (struct TstExa_Exam *Result) { unsigned NumQst; struct Tst_Question Question; @@ -3967,7 +3967,7 @@ static void Mch_ComputeScore (struct TsR_Result *Result) Question.Answer.Type = Tst_ANS_UNIQUE_CHOICE; /***** Compute score for this answer ******/ - Tst_ComputeChoiceAnsScore (Result,NumQst,&Question); + TstExa_ComputeChoiceAnsScore (Result,NumQst,&Question); /***** Update total score *****/ Result->Score += Result->Questions[NumQst].Score; diff --git a/swad_match_result.c b/swad_match_result.c index 56f0b0f7..eb2fcc82 100644 --- a/swad_match_result.c +++ b/swad_match_result.c @@ -70,41 +70,41 @@ extern struct Globals Gbl; /***************************** Private prototypes ****************************/ /*****************************************************************************/ -static void McR_ListMyMchResultsInCrs (void); -static void McR_ListMyMchResultsInGam (long GamCod); -static void McR_ListMyMchResultsInMch (long MchCod); -static void McR_ShowAllMchResultsInSelectedGames (void); -static void McR_ListAllMchResultsInSelectedGames (void); -static void McR_ListAllMchResultsInGam (long GamCod); -static void McR_ListAllMchResultsInMch (long MchCod); +static void MchRes_ListMyMchResultsInCrs (void); +static void MchRes_ListMyMchResultsInGam (long GamCod); +static void MchRes_ListMyMchResultsInMch (long MchCod); +static void MchRes_ShowAllMchResultsInSelectedGames (void); +static void MchRes_ListAllMchResultsInSelectedGames (void); +static void MchRes_ListAllMchResultsInGam (long GamCod); +static void MchRes_ListAllMchResultsInMch (long MchCod); -static void McR_ShowResultsBegin (const char *Title,bool ListGamesToSelect); -static void McR_ShowResultsEnd (void); +static void MchRes_ShowResultsBegin (const char *Title,bool ListGamesToSelect); +static void MchRes_ShowResultsEnd (void); -static void McR_ListGamesToSelect (void); -static void McR_ShowHeaderMchResults (Usr_MeOrOther_t MeOrOther); +static void MchRes_ListGamesToSelect (void); +static void MchRes_ShowHeaderMchResults (Usr_MeOrOther_t MeOrOther); -static void McR_BuildGamesSelectedCommas (char **GamesSelectedCommas); -static void McR_ShowMchResults (Usr_MeOrOther_t MeOrOther, +static void MchRes_BuildGamesSelectedCommas (char **GamesSelectedCommas); +static void MchRes_ShowMchResults (Usr_MeOrOther_t MeOrOther, long MchCod, // <= 0 ==> any long GamCod, // <= 0 ==> any const char *GamesSelectedCommas); -static void McR_ShowMchResultsSummaryRow (unsigned NumResults, +static void MchRes_ShowMchResultsSummaryRow (unsigned NumResults, unsigned NumTotalQsts, unsigned NumTotalQstsNotBlank, double TotalScoreOfAllResults, double TotalGrade); -static void McR_GetMatchResultDataByMchCod (long MchCod,long UsrCod, - struct TsR_Result *Result); +static void MchRes_GetMatchResultDataByMchCod (long MchCod,long UsrCod, + struct TstExa_Exam *Exam); -static bool McR_CheckIfICanSeeMatchResult (struct Match *Match,long UsrCod); -static bool McR_CheckIfICanViewScore (bool ICanViewResult,unsigned Visibility); +static bool MchRes_CheckIfICanSeeMatchResult (struct Match *Match,long UsrCod); +static bool MchRes_CheckIfICanViewScore (bool ICanViewResult,unsigned Visibility); /*****************************************************************************/ /*********** Select users and dates to show their matches results ************/ /*****************************************************************************/ -void McR_SelUsrsToViewMchResults (void) +void MchRes_SelUsrsToViewMchResults (void) { extern const char *Hlp_ASSESSMENT_Games_results; extern const char *Txt_Results; @@ -123,7 +123,7 @@ void McR_SelUsrsToViewMchResults (void) /*************************** Show my matches results *************************/ /*****************************************************************************/ -void McR_ShowMyMchResultsInCrs (void) +void MchRes_ShowMyMchResultsInCrs (void) { extern const char *Txt_Results; @@ -132,26 +132,26 @@ void McR_ShowMyMchResultsInCrs (void) Gam_GetListSelectedGamCods (); /***** List my matches results in the current course *****/ - McR_ShowResultsBegin (Txt_Results,true); // List games to select - McR_ListMyMchResultsInCrs (); - McR_ShowResultsEnd (); + MchRes_ShowResultsBegin (Txt_Results,true); // List games to select + MchRes_ListMyMchResultsInCrs (); + MchRes_ShowResultsEnd (); /***** Free list of games *****/ free (Gbl.Games.GamCodsSelected); Gam_FreeListGames (); } -static void McR_ListMyMchResultsInCrs (void) +static void MchRes_ListMyMchResultsInCrs (void) { char *GamesSelectedCommas = NULL; // Initialized to avoid warning /***** Table header *****/ - McR_ShowHeaderMchResults (Usr_ME); + MchRes_ShowHeaderMchResults (Usr_ME); /***** List my matches results in the current course *****/ TstCfg_GetConfigFromDB (); // Get feedback type - McR_BuildGamesSelectedCommas (&GamesSelectedCommas); - McR_ShowMchResults (Usr_ME,-1L,-1L,GamesSelectedCommas); + MchRes_BuildGamesSelectedCommas (&GamesSelectedCommas); + MchRes_ShowMchResults (Usr_ME,-1L,-1L,GamesSelectedCommas); free (GamesSelectedCommas); } @@ -159,7 +159,7 @@ static void McR_ListMyMchResultsInCrs (void) /***************** Show my matches results in a given game *******************/ /*****************************************************************************/ -void McR_ShowMyMchResultsInGam (void) +void MchRes_ShowMyMchResultsInGam (void) { extern const char *Txt_Results_of_game_X; struct Game Game; @@ -175,31 +175,31 @@ void McR_ShowMyMchResultsInGam (void) false); // Do not put form to start new match /***** List my matches results in game *****/ - McR_ShowResultsBegin (Str_BuildStringStr (Txt_Results_of_game_X,Game.Title), + MchRes_ShowResultsBegin (Str_BuildStringStr (Txt_Results_of_game_X,Game.Title), false); // Do not list games to select Str_FreeString (); - McR_ListMyMchResultsInGam (Game.GamCod); - McR_ShowResultsEnd (); + MchRes_ListMyMchResultsInGam (Game.GamCod); + MchRes_ShowResultsEnd (); /***** Game end *****/ Gam_ShowOnlyOneGameEnd (); } -static void McR_ListMyMchResultsInGam (long GamCod) +static void MchRes_ListMyMchResultsInGam (long GamCod) { /***** Table header *****/ - McR_ShowHeaderMchResults (Usr_ME); + MchRes_ShowHeaderMchResults (Usr_ME); /***** List my matches results in game *****/ TstCfg_GetConfigFromDB (); // Get feedback type - McR_ShowMchResults (Usr_ME,-1L,GamCod,NULL); + MchRes_ShowMchResults (Usr_ME,-1L,GamCod,NULL); } /*****************************************************************************/ /***************** Show my matches results in a given match ******************/ /*****************************************************************************/ -void McR_ShowMyMchResultsInMch (void) +void MchRes_ShowMyMchResultsInMch (void) { extern const char *Txt_Results_of_match_X; struct Game Game; @@ -219,42 +219,42 @@ void McR_ShowMyMchResultsInMch (void) false); // Do not put form to start new match /***** List my matches results in match *****/ - McR_ShowResultsBegin (Str_BuildStringStr (Txt_Results_of_match_X,Match.Title), + MchRes_ShowResultsBegin (Str_BuildStringStr (Txt_Results_of_match_X,Match.Title), false); // Do not list games to select Str_FreeString (); - McR_ListMyMchResultsInMch (Match.MchCod); - McR_ShowResultsEnd (); + MchRes_ListMyMchResultsInMch (Match.MchCod); + MchRes_ShowResultsEnd (); /***** Game end *****/ Gam_ShowOnlyOneGameEnd (); } -static void McR_ListMyMchResultsInMch (long MchCod) +static void MchRes_ListMyMchResultsInMch (long MchCod) { /***** Table header *****/ - McR_ShowHeaderMchResults (Usr_ME); + MchRes_ShowHeaderMchResults (Usr_ME); /***** List my matches results in game *****/ TstCfg_GetConfigFromDB (); // Get feedback type - McR_ShowMchResults (Usr_ME,MchCod,-1L,NULL); + MchRes_ShowMchResults (Usr_ME,MchCod,-1L,NULL); } /*****************************************************************************/ /****************** Get users and show their matches results *****************/ /*****************************************************************************/ -void McR_ShowAllMchResultsInCrs (void) +void MchRes_ShowAllMchResultsInCrs (void) { Usr_GetSelectedUsrsAndGoToAct (&Gbl.Usrs.Selected, - McR_ShowAllMchResultsInSelectedGames, - McR_SelUsrsToViewMchResults); + MchRes_ShowAllMchResultsInSelectedGames, + MchRes_SelUsrsToViewMchResults); } /*****************************************************************************/ /****************** Show matches results for several users *******************/ /*****************************************************************************/ -static void McR_ShowAllMchResultsInSelectedGames (void) +static void MchRes_ShowAllMchResultsInSelectedGames (void) { extern const char *Txt_Results; @@ -263,25 +263,25 @@ static void McR_ShowAllMchResultsInSelectedGames (void) Gam_GetListSelectedGamCods (); /***** List the matches results of the selected users *****/ - McR_ShowResultsBegin (Txt_Results,true); // List games to select - McR_ListAllMchResultsInSelectedGames (); - McR_ShowResultsEnd (); + MchRes_ShowResultsBegin (Txt_Results,true); // List games to select + MchRes_ListAllMchResultsInSelectedGames (); + MchRes_ShowResultsEnd (); /***** Free list of games *****/ free (Gbl.Games.GamCodsSelected); Gam_FreeListGames (); } -static void McR_ListAllMchResultsInSelectedGames (void) +static void MchRes_ListAllMchResultsInSelectedGames (void) { char *GamesSelectedCommas = NULL; // Initialized to avoid warning const char *Ptr; /***** Table head *****/ - McR_ShowHeaderMchResults (Usr_OTHER); + MchRes_ShowHeaderMchResults (Usr_OTHER); /***** List the matches results of the selected users *****/ - McR_BuildGamesSelectedCommas (&GamesSelectedCommas); + MchRes_BuildGamesSelectedCommas (&GamesSelectedCommas); Ptr = Gbl.Usrs.Selected.List[Rol_UNK]; while (*Ptr) { @@ -293,7 +293,7 @@ static void McR_ListAllMchResultsInSelectedGames (void) { /***** Show matches results *****/ Gbl.Usrs.Other.UsrDat.Accepted = Usr_CheckIfUsrHasAcceptedInCurrentCrs (&Gbl.Usrs.Other.UsrDat); - McR_ShowMchResults (Usr_OTHER,-1L,-1L,GamesSelectedCommas); + MchRes_ShowMchResults (Usr_OTHER,-1L,-1L,GamesSelectedCommas); } } free (GamesSelectedCommas); @@ -303,7 +303,7 @@ static void McR_ListAllMchResultsInSelectedGames (void) /*** Show matches results of a game for the users who answered in that game **/ /*****************************************************************************/ -void McR_ShowAllMchResultsInGam (void) +void MchRes_ShowAllMchResultsInGam (void) { extern const char *Txt_Results_of_game_X; struct Game Game; @@ -319,17 +319,17 @@ void McR_ShowAllMchResultsInGam (void) false); // Do not put form to start new match /***** List matches results in game *****/ - McR_ShowResultsBegin (Str_BuildStringStr (Txt_Results_of_game_X,Game.Title), + MchRes_ShowResultsBegin (Str_BuildStringStr (Txt_Results_of_game_X,Game.Title), false); // Do not list games to select Str_FreeString (); - McR_ListAllMchResultsInGam (Game.GamCod); - McR_ShowResultsEnd (); + MchRes_ListAllMchResultsInGam (Game.GamCod); + MchRes_ShowResultsEnd (); /***** Game end *****/ Gam_ShowOnlyOneGameEnd (); } -static void McR_ListAllMchResultsInGam (long GamCod) +static void MchRes_ListAllMchResultsInGam (long GamCod) { MYSQL_RES *mysql_res; MYSQL_ROW row; @@ -337,7 +337,7 @@ static void McR_ListAllMchResultsInGam (long GamCod) unsigned long NumUsr; /***** Table head *****/ - McR_ShowHeaderMchResults (Usr_OTHER); + MchRes_ShowHeaderMchResults (Usr_OTHER); /***** Get all users who have answered any match question in this game *****/ NumUsrs = DB_QuerySELECT (&mysql_res,"can not get users in game", @@ -371,7 +371,7 @@ static void McR_ListAllMchResultsInGam (long GamCod) { /***** Show matches results *****/ Gbl.Usrs.Other.UsrDat.Accepted = Usr_CheckIfUsrHasAcceptedInCurrentCrs (&Gbl.Usrs.Other.UsrDat); - McR_ShowMchResults (Usr_OTHER,-1L,GamCod,NULL); + MchRes_ShowMchResults (Usr_OTHER,-1L,GamCod,NULL); } } } @@ -384,7 +384,7 @@ static void McR_ListAllMchResultsInGam (long GamCod) /** Show matches results of a match for the users who answered in that match */ /*****************************************************************************/ -void McR_ShowAllMchResultsInMch (void) +void MchRes_ShowAllMchResultsInMch (void) { extern const char *Txt_Results_of_match_X; struct Game Game; @@ -404,17 +404,17 @@ void McR_ShowAllMchResultsInMch (void) false); // Do not put form to start new match /***** List matches results in match *****/ - McR_ShowResultsBegin (Str_BuildStringStr (Txt_Results_of_match_X,Match.Title), + MchRes_ShowResultsBegin (Str_BuildStringStr (Txt_Results_of_match_X,Match.Title), false); // Do not list games to select Str_FreeString (); - McR_ListAllMchResultsInMch (Match.MchCod); - McR_ShowResultsEnd (); + MchRes_ListAllMchResultsInMch (Match.MchCod); + MchRes_ShowResultsEnd (); /***** Game end *****/ Gam_ShowOnlyOneGameEnd (); } -static void McR_ListAllMchResultsInMch (long MchCod) +static void MchRes_ListAllMchResultsInMch (long MchCod) { MYSQL_RES *mysql_res; MYSQL_ROW row; @@ -422,7 +422,7 @@ static void McR_ListAllMchResultsInMch (long MchCod) unsigned long NumUsr; /***** Table head *****/ - McR_ShowHeaderMchResults (Usr_OTHER); + MchRes_ShowHeaderMchResults (Usr_OTHER); /***** Get all users who have answered any match question in this game *****/ NumUsrs = DB_QuerySELECT (&mysql_res,"can not get users in match", @@ -456,7 +456,7 @@ static void McR_ListAllMchResultsInMch (long MchCod) { /***** Show matches results *****/ Gbl.Usrs.Other.UsrDat.Accepted = Usr_CheckIfUsrHasAcceptedInCurrentCrs (&Gbl.Usrs.Other.UsrDat); - McR_ShowMchResults (Usr_OTHER,MchCod,-1L,NULL); + MchRes_ShowMchResults (Usr_OTHER,MchCod,-1L,NULL); } } } @@ -469,26 +469,26 @@ static void McR_ListAllMchResultsInMch (long MchCod) /************************ Show results (begin / end) *************************/ /*****************************************************************************/ -static void McR_ShowResultsBegin (const char *Title,bool ListGamesToSelect) +static void MchRes_ShowResultsBegin (const char *Title,bool ListGamesToSelect) { extern const char *Hlp_ASSESSMENT_Games_results; /***** Begin box *****/ - HTM_SECTION_Begin (McR_RESULTS_BOX_ID); + HTM_SECTION_Begin (MchRes_RESULTS_BOX_ID); Box_BoxBegin ("100%",Title, NULL,NULL, Hlp_ASSESSMENT_Games_results,Box_NOT_CLOSABLE); /***** List games to select *****/ if (ListGamesToSelect) - McR_ListGamesToSelect (); + MchRes_ListGamesToSelect (); /***** Begin match results table *****/ - HTM_SECTION_Begin (McR_RESULTS_TABLE_ID); + HTM_SECTION_Begin (MchRes_RESULTS_TABLE_ID); HTM_TABLE_BeginWidePadding (2); } -static void McR_ShowResultsEnd (void) +static void MchRes_ShowResultsEnd (void) { /***** End match results table *****/ HTM_TABLE_End (); @@ -503,7 +503,7 @@ static void McR_ShowResultsEnd (void) /********** Write list of those attendance events that have students *********/ /*****************************************************************************/ -static void McR_ListGamesToSelect (void) +static void MchRes_ListGamesToSelect (void) { extern const char *Hlp_ASSESSMENT_Games_results; extern const char *The_ClassFormLinkInBoxBold[The_NUM_THEMES]; @@ -521,7 +521,7 @@ static void McR_ListGamesToSelect (void) /***** Begin form to update the results depending on the games selected *****/ - Frm_StartFormAnchor (Gbl.Action.Act,McR_RESULTS_TABLE_ID); + Frm_StartFormAnchor (Gbl.Action.Act,MchRes_RESULTS_TABLE_ID); Grp_PutParamsCodGrps (); Usr_PutHiddenParSelectedUsrsCods (&Gbl.Usrs.Selected); @@ -596,7 +596,7 @@ static void McR_ListGamesToSelect (void) /********************* Show header of my matches results *********************/ /*****************************************************************************/ -static void McR_ShowHeaderMchResults (Usr_MeOrOther_t MeOrOther) +static void MchRes_ShowHeaderMchResults (Usr_MeOrOther_t MeOrOther) { extern const char *Txt_User[Usr_NUM_SEXS]; extern const char *Txt_Match; @@ -629,7 +629,7 @@ static void McR_ShowHeaderMchResults (Usr_MeOrOther_t MeOrOther) /******* from list of selected games ********/ /*****************************************************************************/ -static void McR_BuildGamesSelectedCommas (char **GamesSelectedCommas) +static void MchRes_BuildGamesSelectedCommas (char **GamesSelectedCommas) { size_t MaxLength; unsigned NumGame; @@ -658,7 +658,7 @@ static void McR_BuildGamesSelectedCommas (char **GamesSelectedCommas) /********* Show the matches results of a user in the current course **********/ /*****************************************************************************/ -static void McR_ShowMchResults (Usr_MeOrOther_t MeOrOther, +static void MchRes_ShowMchResults (Usr_MeOrOther_t MeOrOther, long MchCod, // <= 0 ==> any long GamCod, // <= 0 ==> any const char *GamesSelectedCommas) @@ -776,11 +776,11 @@ static void McR_ShowMchResults (Usr_MeOrOther_t MeOrOther, Mch_GetDataOfMatchByCod (&Match); /* Get visibility (row[7]) */ - Visibility = TsV_GetVisibilityFromStr (row[7]); + Visibility = TstVis_GetVisibilityFromStr (row[7]); /* Show match result? */ - ICanViewResult = McR_CheckIfICanSeeMatchResult (&Match,UsrDat->UsrCod); - ICanViewScore = McR_CheckIfICanViewScore (ICanViewResult,Visibility); + ICanViewResult = MchRes_CheckIfICanSeeMatchResult (&Match,UsrDat->UsrCod); + ICanViewScore = MchRes_CheckIfICanViewScore (ICanViewResult,Visibility); if (NumResult) HTM_TR_Begin (NULL); @@ -872,8 +872,8 @@ static void McR_ShowMchResults (Usr_MeOrOther_t MeOrOther, HTM_TD_Begin ("class=\"DAT RT COLOR%u\"",Gbl.RowEvenOdd); if (ICanViewScore) { - Grade = Tst_ComputeGrade (NumQstsInThisResult,ScoreInThisResult,MaxGrade); - Tst_ShowGrade (Grade,MaxGrade); + Grade = TstExa_ComputeGrade (NumQstsInThisResult,ScoreInThisResult,MaxGrade); + TstExa_ShowGrade (Grade,MaxGrade); TotalGrade += Grade; } else @@ -909,7 +909,7 @@ static void McR_ShowMchResults (Usr_MeOrOther_t MeOrOther, } /***** Write totals for this user *****/ - McR_ShowMchResultsSummaryRow (NumResults, + MchRes_ShowMchResultsSummaryRow (NumResults, NumTotalQsts,NumTotalQstsNotBlank, TotalScoreOfAllResults, TotalGrade); @@ -930,7 +930,7 @@ static void McR_ShowMchResults (Usr_MeOrOther_t MeOrOther, /************** Show row with summary of user's matches results **************/ /*****************************************************************************/ -static void McR_ShowMchResultsSummaryRow (unsigned NumResults, +static void MchRes_ShowMchResultsSummaryRow (unsigned NumResults, unsigned NumTotalQsts, unsigned NumTotalQstsNotBlank, double TotalScoreOfAllResults, @@ -988,7 +988,7 @@ static void McR_ShowMchResultsSummaryRow (unsigned NumResults, /******************* Show one match result of another user *******************/ /*****************************************************************************/ -void McR_ShowOneMchResult (void) +void MchRes_ShowOneMchResult (void) { extern const char *Hlp_ASSESSMENT_Games_results; extern const char *Txt_The_user_does_not_exist; @@ -1005,7 +1005,7 @@ void McR_ShowOneMchResult (void) struct UsrData *UsrDat; Dat_StartEndTime_t StartEndTime; char *Id; - struct TsR_Result Result; + struct TstExa_Exam Exam; bool ShowPhoto; char PhotoURL[PATH_MAX + 1]; bool ICanViewResult; @@ -1030,16 +1030,16 @@ void McR_ShowOneMchResult (void) } /***** Get match result data *****/ - McR_GetMatchResultDataByMchCod (Match.MchCod,UsrDat->UsrCod, - &Result); + MchRes_GetMatchResultDataByMchCod (Match.MchCod,UsrDat->UsrCod, + &Exam); /***** Check if I can view this match result *****/ switch (Gbl.Usrs.Me.Role.Logged) { case Rol_STD: - ICanViewResult = McR_CheckIfICanSeeMatchResult (&Match,UsrDat->UsrCod); + ICanViewResult = MchRes_CheckIfICanSeeMatchResult (&Match,UsrDat->UsrCod); if (ICanViewResult) - ICanViewScore = TsV_IsVisibleTotalScore (Game.Visibility); + ICanViewScore = TstVis_IsVisibleTotalScore (Game.Visibility); else ICanViewScore = false; break; @@ -1061,8 +1061,8 @@ void McR_ShowOneMchResult (void) if (ICanViewResult) // I am allowed to view this match result { /***** Get questions and user's answers of the match result from database *****/ - McR_GetMatchResultQuestionsFromDB (Match.MchCod,UsrDat->UsrCod, - &Result); + MchRes_GetMatchResultQuestionsFromDB (Match.MchCod,UsrDat->UsrCod, + &Exam); /***** Begin box *****/ Box_BoxBegin (NULL,Match.Title, @@ -1120,7 +1120,7 @@ void McR_ShowOneMchResult (void) if (asprintf (&Id,"match_%u",(unsigned) StartEndTime) < 0) Lay_NotEnoughMemoryExit (); HTM_TD_Begin ("id=\"%s\" class=\"DAT LT\"",Id); - Dat_WriteLocalDateHMSFromUTC (Id,Result.TimeUTC[StartEndTime], + Dat_WriteLocalDateHMSFromUTC (Id,Exam.TimeUTC[StartEndTime], Gbl.Prefs.DateFormat,Dat_SEPARATOR_COMMA, true,true,true,0x7); HTM_TD_End (); @@ -1138,8 +1138,8 @@ void McR_ShowOneMchResult (void) HTM_TD_Begin ("class=\"DAT LT\""); HTM_TxtF ("%u (%u %s)", - Result.NumQsts, - Result.NumQstsNotBlank,Txt_non_blank_QUESTIONS); + Exam.NumQsts, + Exam.NumQstsNotBlank,Txt_non_blank_QUESTIONS); HTM_TD_End (); HTM_TR_End (); @@ -1153,7 +1153,7 @@ void McR_ShowOneMchResult (void) HTM_TD_Begin ("class=\"DAT LT\""); if (ICanViewScore) - HTM_Double2Decimals (Result.Score); + HTM_Double2Decimals (Exam.Score); else Ico_PutIconNotVisible (); HTM_TD_End (); @@ -1169,8 +1169,8 @@ void McR_ShowOneMchResult (void) HTM_TD_Begin ("class=\"DAT LT\""); if (ICanViewScore) - Tst_ComputeAndShowGrade (Result.NumQsts, - Result.Score, + TstExa_ComputeAndShowGrade (Exam.NumQsts, + Exam.Score, Game.MaxGrade); else Ico_PutIconNotVisible (); @@ -1192,7 +1192,7 @@ void McR_ShowOneMchResult (void) HTM_TR_End (); /***** Write answers and solutions *****/ - TsR_ShowTestResult (UsrDat,&Result,Game.Visibility); + TstExa_ShowExamAnswers (UsrDat,&Exam,Game.Visibility); /***** End table *****/ HTM_TABLE_End (); @@ -1202,10 +1202,10 @@ void McR_ShowOneMchResult (void) { HTM_DIV_Begin ("class=\"DAT_N_BOLD CM\""); HTM_TxtColonNBSP (Txt_Score); - HTM_Double2Decimals (Result.Score); + HTM_Double2Decimals (Exam.Score); HTM_BR (); HTM_TxtColonNBSP (Txt_Grade); - Tst_ComputeAndShowGrade (Result.NumQsts,Result.Score,Game.MaxGrade); + TstExa_ComputeAndShowGrade (Exam.NumQsts,Exam.Score,Game.MaxGrade); HTM_DIV_End (); } @@ -1220,8 +1220,8 @@ void McR_ShowOneMchResult (void) /************ Get the questions of a match result from database **************/ /*****************************************************************************/ -void McR_GetMatchResultQuestionsFromDB (long MchCod,long UsrCod, - struct TsR_Result *Result) +void MchRes_GetMatchResultQuestionsFromDB (long MchCod,long UsrCod, + struct TstExa_Exam *Exam) { MYSQL_RES *mysql_res; MYSQL_ROW row; @@ -1231,27 +1231,27 @@ void McR_GetMatchResultQuestionsFromDB (long MchCod,long UsrCod, struct Mch_UsrAnswer UsrAnswer; /***** Get questions and answers of a match result *****/ - Result->NumQsts = (unsigned) - DB_QuerySELECT (&mysql_res,"can not get questions and answers" - " of a match result", - "SELECT gam_questions.QstCod," // row[0] - "gam_questions.QstInd," // row[1] - "mch_indexes.Indexes" // row[2] - " FROM mch_matches,gam_questions,mch_indexes" - " WHERE mch_matches.MchCod=%ld" - " AND mch_matches.GamCod=gam_questions.GamCod" - " AND mch_matches.MchCod=mch_indexes.MchCod" - " AND gam_questions.QstInd=mch_indexes.QstInd" - " ORDER BY gam_questions.QstInd", - MchCod); - for (NumQst = 0, Result->NumQstsNotBlank = 0; - NumQst < Result->NumQsts; + Exam->NumQsts = (unsigned) + DB_QuerySELECT (&mysql_res,"can not get questions and answers" + " of a match result", + "SELECT gam_questions.QstCod," // row[0] + "gam_questions.QstInd," // row[1] + "mch_indexes.Indexes" // row[2] + " FROM mch_matches,gam_questions,mch_indexes" + " WHERE mch_matches.MchCod=%ld" + " AND mch_matches.GamCod=gam_questions.GamCod" + " AND mch_matches.MchCod=mch_indexes.MchCod" + " AND gam_questions.QstInd=mch_indexes.QstInd" + " ORDER BY gam_questions.QstInd", + MchCod); + for (NumQst = 0, Exam->NumQstsNotBlank = 0; + NumQst < Exam->NumQsts; NumQst++) { row = mysql_fetch_row (mysql_res); /* Get question code (row[0]) */ - if ((Result->Questions[NumQst].QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0) + if ((Exam->Questions[NumQst].QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0) Lay_ShowErrorAndExit ("Wrong code of question."); /* Get question index (row[1]) */ @@ -1260,24 +1260,24 @@ void McR_GetMatchResultQuestionsFromDB (long MchCod,long UsrCod, QstInd = (unsigned) LongNum; /* Get indexes for this question (row[2]) */ - Str_Copy (Result->Questions[NumQst].StrIndexes,row[2], - Tst_MAX_BYTES_INDEXES_ONE_QST); + Str_Copy (Exam->Questions[NumQst].StrIndexes,row[2], + TstExa_MAX_BYTES_INDEXES_ONE_QST); /* Get answers selected by user for this question */ Mch_GetQstAnsFromDB (MchCod,UsrCod,QstInd,&UsrAnswer); if (UsrAnswer.AnsInd >= 0) // UsrAnswer.AnsInd >= 0 ==> answer selected { - snprintf (Result->Questions[NumQst].StrAnswers,Tst_MAX_BYTES_ANSWERS_ONE_QST + 1, + snprintf (Exam->Questions[NumQst].StrAnswers,TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1, "%d",UsrAnswer.AnsInd); - Result->NumQstsNotBlank++; + Exam->NumQstsNotBlank++; } else // UsrAnswer.AnsInd < 0 ==> no answer selected - Result->Questions[NumQst].StrAnswers[0] = '\0'; // Empty answer + Exam->Questions[NumQst].StrAnswers[0] = '\0'; // Empty answer /* Replace each comma by a separator of multiple parameters */ /* In database commas are used as separators instead of special chars */ - Par_ReplaceCommaBySeparatorMultiple (Result->Questions[NumQst].StrIndexes); - Par_ReplaceCommaBySeparatorMultiple (Result->Questions[NumQst].StrAnswers); + Par_ReplaceCommaBySeparatorMultiple (Exam->Questions[NumQst].StrIndexes); + Par_ReplaceCommaBySeparatorMultiple (Exam->Questions[NumQst].StrAnswers); } /***** Free structure that stores the query result *****/ @@ -1288,8 +1288,8 @@ void McR_GetMatchResultQuestionsFromDB (long MchCod,long UsrCod, /************* Get data of a match result using its match code ***************/ /*****************************************************************************/ -static void McR_GetMatchResultDataByMchCod (long MchCod,long UsrCod, - struct TsR_Result *Result) +static void MchRes_GetMatchResultDataByMchCod (long MchCod,long UsrCod, + struct TstExa_Exam *Exam) { MYSQL_RES *mysql_res; MYSQL_ROW row; @@ -1318,27 +1318,27 @@ static void McR_GetMatchResultDataByMchCod (long MchCod,long UsrCod, for (StartEndTime = (Dat_StartEndTime_t) 0; StartEndTime <= (Dat_StartEndTime_t) (Dat_NUM_START_END_TIME - 1); StartEndTime++) - Result->TimeUTC[StartEndTime] = Dat_GetUNIXTimeFromStr (row[StartEndTime]); + Exam->TimeUTC[StartEndTime] = Dat_GetUNIXTimeFromStr (row[StartEndTime]); /* Get number of questions (row[2]) */ - if (sscanf (row[2],"%u",&Result->NumQsts) != 1) - Result->NumQsts = 0; + if (sscanf (row[2],"%u",&Exam->NumQsts) != 1) + Exam->NumQsts = 0; /* Get number of questions not blank (row[3]) */ - if (sscanf (row[3],"%u",&Result->NumQstsNotBlank) != 1) - Result->NumQstsNotBlank = 0; + if (sscanf (row[3],"%u",&Exam->NumQstsNotBlank) != 1) + Exam->NumQstsNotBlank = 0; /* Get score (row[4]) */ Str_SetDecimalPointToUS (); // To get the decimal point as a dot - if (sscanf (row[4],"%lf",&Result->Score) != 1) - Result->Score = 0.0; + if (sscanf (row[4],"%lf",&Exam->Score) != 1) + Exam->Score = 0.0; Str_SetDecimalPointToLocal (); // Return to local system } else { - Result->NumQsts = 0; - Result->NumQstsNotBlank = 0; - Result->Score = 0.0; + Exam->NumQsts = 0; + Exam->NumQstsNotBlank = 0; + Exam->Score = 0.0; } /***** Free structure that stores the query result *****/ @@ -1349,7 +1349,7 @@ static void McR_GetMatchResultDataByMchCod (long MchCod,long UsrCod, /********************** Get if I can see match result ************************/ /*****************************************************************************/ -static bool McR_CheckIfICanSeeMatchResult (struct Match *Match,long UsrCod) +static bool MchRes_CheckIfICanSeeMatchResult (struct Match *Match,long UsrCod) { bool ItsMe; @@ -1376,13 +1376,13 @@ static bool McR_CheckIfICanSeeMatchResult (struct Match *Match,long UsrCod) /********************** Get if I can see match result ************************/ /*****************************************************************************/ -static bool McR_CheckIfICanViewScore (bool ICanViewResult,unsigned Visibility) +static bool MchRes_CheckIfICanViewScore (bool ICanViewResult,unsigned Visibility) { switch (Gbl.Usrs.Me.Role.Logged) { case Rol_STD: if (ICanViewResult) - return TsV_IsVisibleTotalScore (Visibility); + return TstVis_IsVisibleTotalScore (Visibility); return false; break; case Rol_NET: diff --git a/swad_match_result.h b/swad_match_result.h index 47c08373..7321564f 100644 --- a/swad_match_result.h +++ b/swad_match_result.h @@ -1,7 +1,7 @@ // swad_match_result.h: matches results in games using remote control -#ifndef _SWAD_MCR -#define _SWAD_MCR +#ifndef _SWAD_MCH_RES +#define _SWAD_MCH_RES /* SWAD (Shared Workspace At a Distance in Spanish), is a web platform developed at the University of Granada (Spain), @@ -27,29 +27,31 @@ /********************************* Headers ***********************************/ /*****************************************************************************/ +#include "swad_test_exam.h" + /*****************************************************************************/ /************************** Public types and constants ***********************/ /*****************************************************************************/ -#define McR_RESULTS_BOX_ID "mcr_box" -#define McR_RESULTS_TABLE_ID "mcr_table" +#define MchRes_RESULTS_BOX_ID "mcr_box" +#define MchRes_RESULTS_TABLE_ID "mcr_table" /*****************************************************************************/ /***************************** Public prototypes *****************************/ /*****************************************************************************/ -void McR_SelUsrsToViewMchResults (void); +void MchRes_SelUsrsToViewMchResults (void); -void McR_ShowMyMchResultsInCrs (void); -void McR_ShowMyMchResultsInGam (void); -void McR_ShowMyMchResultsInMch (void); +void MchRes_ShowMyMchResultsInCrs (void); +void MchRes_ShowMyMchResultsInGam (void); +void MchRes_ShowMyMchResultsInMch (void); -void McR_ShowAllMchResultsInCrs (void); -void McR_ShowAllMchResultsInGam (void); -void McR_ShowAllMchResultsInMch (void); +void MchRes_ShowAllMchResultsInCrs (void); +void MchRes_ShowAllMchResultsInGam (void); +void MchRes_ShowAllMchResultsInMch (void); -void McR_ShowOneMchResult (void); -void McR_GetMatchResultQuestionsFromDB (long MchCod,long UsrCod, - struct TsR_Result *Result); +void MchRes_ShowOneMchResult (void); +void MchRes_GetMatchResultQuestionsFromDB (long MchCod,long UsrCod, + struct TstExa_Exam *Exam); #endif diff --git a/swad_test.c b/swad_test.c index ea7b4c4d..a83bf59d 100644 --- a/swad_test.c +++ b/swad_test.c @@ -50,6 +50,7 @@ #include "swad_parameter.h" #include "swad_theme.h" #include "swad_test.h" +#include "swad_test_exam.h" #include "swad_test_config.h" #include "swad_test_import.h" #include "swad_test_visibility.h" @@ -108,6 +109,13 @@ typedef enum Tst_STATUS_ERROR = 2, } Tst_Status_t; +#define Tst_NUM_REQUEST_OR_CONFIRM 2 +typedef enum + { + Tst_REQUEST, + Tst_CONFIRM, + } Tst_RequestOrConfirm_t; + /*****************************************************************************/ /************** External global variables from others modules ****************/ /*****************************************************************************/ @@ -131,54 +139,24 @@ static void Tst_ShowFormRequestTest (struct Tst_Test *Test); static void Tst_PutCheckBoxAllowTeachers (bool AllowTeachers); -static void Tst_GetQuestionsAndAnswersFromForm (struct TsR_Result *Result); - -static void Tst_ComputeAndStoreResultScore (struct TsR_Result *Result, - bool UpdateQstScore); -static void Tst_ComputeAnswerScore (struct TsR_Result *Result, - unsigned NumQst, - struct Tst_Question *Question); -static void Tst_ComputeIntAnsScore (struct TsR_Result *Result, - unsigned NumQst, - struct Tst_Question *Question); -static void Tst_GetCorrectIntAnswerFromDB (struct Tst_Question *Question); -static void Tst_ComputeFloatAnsScore (struct TsR_Result *Result, - unsigned NumQst, - struct Tst_Question *Question); -static void Tst_GetCorrectFloatAnswerFromDB (struct Tst_Question *Question); -static void Tst_ComputeTFAnsScore (struct TsR_Result *Result, - unsigned NumQst, - struct Tst_Question *Question); -static void Tst_GetCorrectTFAnswerFromDB (struct Tst_Question *Question); -static void Tst_GetCorrectChoiceAnswerFromDB (struct Tst_Question *Question); -static void Tst_GetAnswersFromStr (const char StrAnswersOneQst[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1], - bool UsrAnswers[Tst_MAX_OPTIONS_PER_QUESTION]); -static void Tst_ComputeScoreQst (struct TsR_Result *Result, - unsigned NumQst, - 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]); -static void Tst_ComputeTextAnsScore (struct TsR_Result *Result, - unsigned NumQst, - struct Tst_Question *Question); -static void Tst_GetCorrectTextAnswerFromDB (struct Tst_Question *Question); +static void Tst_GetAnswersFromForm (struct TstExa_Exam *Exam); 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 (struct TsR_Result *Result); +static unsigned Tst_GetNumExamsGeneratedByMe (void); +static void Tst_ShowTestExamToFillIt (struct TstExa_Exam *Exam, + unsigned NumExamsGeneratedByMe, + Tst_RequestOrConfirm_t RequestOrConfirm); -static void Tst_ShowTestResultAfterAssess (struct TsR_Result *Result); -static void Tst_WriteQstAndAnsSeeing (struct TsR_Result *Result, +static void Tst_WriteQstAndAnsSeeing (struct TstExa_Exam *Exam, unsigned NumQst, struct Tst_Question *Question, MYSQL_ROW row); static void Tst_PutFormToEditQstMedia (const struct Media *Media,int NumMediaInForm, bool OptionsDisabled); -static void Tst_UpdateQstScore (const struct TsR_Result *Result,unsigned NumQst); -static void Tst_UpdateMyNumAccessTst (unsigned NumAccessesTst); +static void Tst_IncreaseMyNumAccessTst (void); static void Tst_UpdateLastAccTst (unsigned NumQsts); static void Tst_ShowFormRequestEditTests (struct Tst_Test *Test); @@ -205,8 +183,8 @@ static void Tst_PutInputFieldNumQst (const char *Field,const char *Label, static void Tst_ShowFormAnswerTypes (const struct Tst_AnswerTypes *AnswerTypes); static void Tst_GetQuestions (struct Tst_Test *Test,MYSQL_RES **mysql_res); static void Tst_GetQuestionsForNewTestFromDB (struct Tst_Test *Test, - struct TsR_Result *Result); -static void Tst_GetChoiceAnsSeeing (struct TsR_Result *Result, + struct TstExa_Exam *Exam); +static void Tst_GenerateChoiceIndexesDependingOnShuffle (struct TstExa_Exam *Exam, unsigned NumQst, bool Shuffle); @@ -220,78 +198,39 @@ static void Tst_ListOneOrMoreQuestionsForSelection (unsigned NumQsts, static void Tst_WriteQuestionRowForSelection (unsigned NumQst, struct Tst_Question *Question); -static void Tst_WriteAnswersSeeing (struct TsR_Result *Result, +static void Tst_WriteAnswersSeeing (struct TstExa_Exam *Exam, unsigned NumQst, struct Tst_Question *Question); -static void Tst_WriteAnswersResult (struct UsrData *UsrDat, - const struct TsR_Result *Result, - unsigned NumQst, - struct Tst_Question *Question, - unsigned Visibility); static void Tst_WriteIntAnsListing (const struct Tst_Question *Question, MYSQL_RES *mysql_res); -static void Tst_WriteIntAnsSeeing (const struct TsR_Result *Result, +static void Tst_WriteIntAnsSeeing (const struct TstExa_Exam *Exam, unsigned NumQst); -static void Tst_WriteIntAnsResult (struct UsrData *UsrDat, - const struct TsR_Result *Result, - unsigned NumQst, - const struct Tst_Question *Question, - MYSQL_RES *mysql_res, - unsigned Visibility); static void Tst_WriteFloatAnsEdit (const struct Tst_Question *Question, MYSQL_RES *mysql_res); -static void Tst_WriteFloatAnsSeeing (const struct TsR_Result *Result, +static void Tst_WriteFloatAnsSeeing (const struct TstExa_Exam *Exam, unsigned NumQst); -static void Tst_WriteFloatAnsResult (struct UsrData *UsrDat, - const struct TsR_Result *Result, - unsigned NumQst, - const struct Tst_Question *Question, - MYSQL_RES *mysql_res, - unsigned Visibility); static void Tst_WriteTFAnsListing (const struct Tst_Question *Question, MYSQL_RES *mysql_res); -static void Tst_WriteTFAnsSeeing (const struct TsR_Result *Result, +static void Tst_WriteTFAnsSeeing (const struct TstExa_Exam *Exam, unsigned NumQst); -static void Tst_WriteTFAnsResult (struct UsrData *UsrDat, - const struct TsR_Result *Result, - unsigned NumQst, - const struct Tst_Question *Question, - MYSQL_RES *mysql_res, - unsigned Visibility); static void Tst_WriteChoiceAnsListing (struct Tst_Question *Question, MYSQL_RES *mysql_res); -static void Tst_WriteChoiceAnsSeeing (const struct TsR_Result *Result, +static void Tst_WriteChoiceAnsSeeing (const struct TstExa_Exam *Exam, unsigned NumQst, struct Tst_Question *Question, MYSQL_RES *mysql_res); -static void Tst_WriteChoiceAnsResult (struct UsrData *UsrDat, - const struct TsR_Result *Result, - unsigned NumQst, - struct Tst_Question *Question, - MYSQL_RES *mysql_res, - unsigned Visibility); -static void Tst_GetChoiceAns (struct Tst_Question *Question,MYSQL_RES *mysql_res); -static void Tst_WriteTextAnsSeeing (const struct TsR_Result *Result, +static void Tst_WriteTextAnsSeeing (const struct TstExa_Exam *Exam, unsigned NumQst); -static void Tst_WriteTextAnsResult (struct UsrData *UsrDat, - const struct TsR_Result *Result, - unsigned NumQst, - struct Tst_Question *Question, - MYSQL_RES *mysql_res, - unsigned Visibility); -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 (struct Tst_Test *Test, Tst_ActionToDoWithQuestions_t ActionToDoWithQuestions); -static unsigned Tst_GetAndCheckParamNumTst (void); +static unsigned Tst_GetParamNumTst (void); static unsigned Tst_GetParamNumQsts (void); static unsigned Tst_CountNumTagsInList (const struct Tst_Tags *Tags); static int Tst_CountNumAnswerTypesInList (const struct Tst_AnswerTypes *AnswerTypes); @@ -311,7 +250,6 @@ static void Tst_FreeTextChoiceAnswer (struct Tst_Question *Question,unsigned Num static void Tst_ResetMediaOfQuestion (struct Tst_Question *Question); static void Tst_FreeMediaOfQuestion (struct Tst_Question *Question); -static Tst_AnswerType_t Tst_GetQstAnswerType (long QstCod); static void Tst_GetQstDataFromDB (struct Tst_Question *Question, char Stem[Cns_MAX_BYTES_TEXT + 1], char Feedback[Cns_MAX_BYTES_TEXT + 1]); @@ -512,13 +450,10 @@ static void Tst_ShowFormRequestTest (struct Tst_Test *Test) void Tst_ShowNewTest (void) { - extern const char *Hlp_ASSESSMENT_Tests; extern const char *Txt_No_questions_found_matching_your_search_criteria; - extern const char *Txt_Test; - extern const char *Txt_Done_assess_test; struct Tst_Test Test; - struct TsR_Result Result; - unsigned NumAccessesTst; + struct TstExa_Exam Exam; + unsigned NumExamsGeneratedByMe; /***** Create test *****/ Tst_TstConstructor (&Test); @@ -532,47 +467,23 @@ void Tst_ShowNewTest (void) if (Tst_GetParamsTst (&Test,Tst_SHOW_TEST_TO_ANSWER)) // Get parameters from form { /***** Get questions *****/ - Tst_GetQuestionsForNewTestFromDB (&Test,&Result); - if (Result.NumQsts) + Tst_GetQuestionsForNewTestFromDB (&Test,&Exam); + if (Exam.NumQsts) { - /***** Get and update number of hits *****/ - NumAccessesTst = Tst_GetNumAccessesTst () + 1; - if (Gbl.Usrs.Me.IBelongToCurrentCrs) - Tst_UpdateMyNumAccessTst (NumAccessesTst); + /***** Increase number of exams generated (answered or not) by me *****/ + Tst_IncreaseMyNumAccessTst (); + NumExamsGeneratedByMe = Tst_GetNumExamsGeneratedByMe (); - /***** Create new test in database to store the result *****/ - TsR_CreateTestResultInDB (&Result); + /***** Create new test exam in database *****/ + TstExa_CreateExamInDB (&Exam); + TstExa_ComputeScoresAndStoreExamQuestions (&Exam, + false); // Don't update question score - /***** Begin box *****/ - Box_BoxBegin (NULL,Txt_Test, - NULL,NULL, - Hlp_ASSESSMENT_Tests,Box_NOT_CLOSABLE); - Lay_WriteHeaderClassPhoto (false,false, - Gbl.Hierarchy.Ins.InsCod, - Gbl.Hierarchy.Deg.DegCod, - Gbl.Hierarchy.Crs.CrsCod); - - /***** Begin form *****/ - Frm_StartForm (ActAssTst); - Par_PutHiddenParamLong (NULL,"TstCod",Result.TstCod); - Par_PutHiddenParamUnsigned (NULL,"NumTst",NumAccessesTst); - Par_PutHiddenParamUnsigned (NULL,"NumQst",Test.NumQsts); - - /***** List the questions *****/ - Tst_ShowTestQuestionsWhenSeeing (&Result); - - /***** Test result will be saved? *****/ - Tst_PutCheckBoxAllowTeachers (true); - - /***** End form *****/ - Btn_PutConfirmButton (Txt_Done_assess_test); - Frm_EndForm (); - - /***** End box *****/ - Box_BoxEnd (); + /***** Show test exam to be answered *****/ + Tst_ShowTestExamToFillIt (&Exam,NumExamsGeneratedByMe,Tst_REQUEST); /***** Set test status *****/ - Tst_SetTstStatus (NumAccessesTst,Tst_STATUS_SHOWN_BUT_NOT_ASSESSED); + Tst_SetTstStatus (NumExamsGeneratedByMe,Tst_STATUS_SHOWN_BUT_NOT_ASSESSED); /***** Update date-time of my next allowed access to test *****/ if (Gbl.Usrs.Me.Role.Logged == Rol_STD) @@ -593,7 +504,7 @@ void Tst_ShowNewTest (void) } /*****************************************************************************/ -/************ Put checkbox to allow teachers to see test result **************/ +/************* Put checkbox to allow teachers to see test exam ***************/ /*****************************************************************************/ static void Tst_PutCheckBoxAllowTeachers (bool AllowTeachers) @@ -601,12 +512,12 @@ static void Tst_PutCheckBoxAllowTeachers (bool AllowTeachers) extern const char *The_ClassFormInBox[The_NUM_THEMES]; extern const char *Txt_Allow_teachers_to_consult_this_test; - /***** Test result will be available for teachers? *****/ + /***** Test exam will be available for teachers? *****/ HTM_DIV_Begin ("class=\"CM\""); HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]); HTM_INPUT_CHECKBOX ("AllowTchs",HTM_DONT_SUBMIT_ON_CHANGE, "value=\"Y\"%s", - AllowTeachers ? " checked=\"checked\"" : // Teachers can see test result + AllowTeachers ? " checked=\"checked\"" : // Teachers can see test exam ""); HTM_TxtF (" %s",Txt_Allow_teachers_to_consult_this_test); HTM_LABEL_End (); @@ -622,34 +533,40 @@ void Tst_RequestAssessTest (void) extern const char *Txt_The_test_X_has_already_been_assessed_previously; extern const char *Txt_There_was_an_error_in_assessing_the_test_X; unsigned NumTst; - // long TstCod = -1L; // Initialized to avoid warning - struct TsR_Result Result; + struct TstExa_Exam Exam; /***** Read test configuration from database *****/ TstCfg_GetConfigFromDB (); - /***** Get number of this test from form *****/ - NumTst = Tst_GetAndCheckParamNumTst (); + /***** Get basic parameters of the exam *****/ + /* Get test exam code from form */ + if ((Exam.ExaCod = TstExa_GetParamExaCod ()) <= 0) + Lay_ShowErrorAndExit ("Wrong test exam."); + + /* Get number of this test from form */ + NumTst = Tst_GetParamNumTst (); /****** Get test status in database for this session-course-num.test *****/ switch (Tst_GetTstStatus (NumTst)) { case Tst_STATUS_SHOWN_BUT_NOT_ASSESSED: - /***** Get parameters from the form *****/ - /* Get questions and answers from form to assess a test */ - Tst_GetQuestionsAndAnswersFromForm (&Result); + /***** Get test exam from database *****/ + TstExa_GetExamDataByExaCod (&Exam); + TstExa_GetExamQuestionsFromDB (&Exam); + + /***** Get answers from form to assess a test *****/ + Tst_GetAnswersFromForm (&Exam); + + /***** Update test exam in database *****/ + TstExa_ComputeScoresAndStoreExamQuestions (&Exam, + false); // Don't update question score /***** Show question and button to send the test *****/ /* Start alert */ - Ale_ShowAlertAndButton1 (Ale_INFO,"Por favor, revise el test antes de enviarlo"); + Ale_ShowAlert (Ale_INFO,"Por favor, revise el test antes de enviarlo."); // TODO: Need translation!!! - /* Show test again */ - - - /* End alert */ - Ale_ShowAlertAndButton2 (ActAssTst,NULL,NULL, - NULL,NULL, - Btn_CONFIRM_BUTTON,"Enviar test"); + /* Show the same test exam to be answered */ + Tst_ShowTestExamToFillIt (&Exam,NumTst,Tst_CONFIRM); break; case Tst_STATUS_ASSESSED: Ale_ShowAlert (Ale_WARNING,Txt_The_test_X_has_already_been_assessed_previously, @@ -676,30 +593,33 @@ void Tst_AssessTest (void) extern const char *Txt_The_test_X_has_already_been_assessed_previously; extern const char *Txt_There_was_an_error_in_assessing_the_test_X; unsigned NumTst; - struct TsR_Result Result; + struct TstExa_Exam Exam; /***** Read test configuration from database *****/ TstCfg_GetConfigFromDB (); /***** Get basic parameters of the exam *****/ - /* Get test result code from form */ - if ((Result.TstCod = Par_GetParToLong ("TstCod")) <= 0) - Lay_ShowErrorAndExit ("Wrong test result."); + /* Get test exam code from form */ + if ((Exam.ExaCod = TstExa_GetParamExaCod ()) <= 0) + Lay_ShowErrorAndExit ("Wrong test exam."); /* Get number of this test from form */ - NumTst = Tst_GetAndCheckParamNumTst (); + NumTst = Tst_GetParamNumTst (); /****** Get test status in database for this session-course-num.test *****/ switch (Tst_GetTstStatus (NumTst)) { case Tst_STATUS_SHOWN_BUT_NOT_ASSESSED: - /***** Get parameters from the form *****/ - /* Get questions and answers from form to assess a test */ - Tst_GetQuestionsAndAnswersFromForm (&Result); + /***** Get test exam from database *****/ + TstExa_GetExamDataByExaCod (&Exam); + TstExa_GetExamQuestionsFromDB (&Exam); - /***** Update test result in database *****/ - Tst_ComputeAndStoreResultScore (&Result, - Gbl.Usrs.Me.Role.Logged == Rol_STD); // Update question score? + /***** Get answers from form to assess a test *****/ + Tst_GetAnswersFromForm (&Exam); + + /***** Update test exam in database *****/ + TstExa_ComputeScoresAndStoreExamQuestions (&Exam, + Gbl.Usrs.Me.Role.Logged == Rol_STD); // Update question score? /***** Begin box *****/ Box_BoxBegin (NULL,Txt_Test_result, @@ -719,21 +639,19 @@ void Tst_AssessTest (void) } /***** Write answers and solutions *****/ - HTM_TABLE_BeginWideMarginPadding (10); - Tst_ShowTestResultAfterAssess (&Result); - HTM_TABLE_End (); + TstExa_ShowExamAfterAssess (&Exam); /***** Write total score and grade *****/ - if (TsV_IsVisibleTotalScore (TstCfg_GetConfigVisibility ())) + if (TstVis_IsVisibleTotalScore (TstCfg_GetConfigVisibility ())) { HTM_DIV_Begin ("class=\"DAT_N_BOLD CM\""); HTM_TxtColonNBSP (Txt_Score); - HTM_Double2Decimals (Result.Score); + HTM_Double2Decimals (Exam.Score); HTM_BR (); HTM_TxtColonNBSP (Txt_Grade); - Tst_ComputeAndShowGrade (Result.NumQsts, - Result.Score, - TsR_SCORE_MAX); + TstExa_ComputeAndShowGrade (Exam.NumQsts, + Exam.Score, + TstExa_SCORE_MAX); HTM_DIV_End (); } @@ -758,623 +676,26 @@ void Tst_AssessTest (void) /*********** Get questions and answers from form to assess a test ************/ /*****************************************************************************/ -static void Tst_GetQuestionsAndAnswersFromForm (struct TsR_Result *Result) +static void Tst_GetAnswersFromForm (struct TstExa_Exam *Exam) { unsigned NumQst; - char StrQstIndOrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Qstxx...x", "Indxx...x" or "Ansxx...x" + char StrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x" - /***** Get number of questions *****/ - Result->NumQsts = Tst_GetParamNumQsts (); - - /***** Loop for every question computing the score *****/ + /***** Loop for every question getting user's answers *****/ for (NumQst = 0; - NumQst < Result->NumQsts; + NumQst < Exam->NumQsts; NumQst++) { - /***** Get question codes and user's answers from form *****/ - /* Get question code */ - snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns), - "Qst%010u", - NumQst); - if ((Result->Questions[NumQst].QstCod = Par_GetParToLong (StrQstIndOrAns)) <= 0) - Lay_ShowErrorAndExit ("Code of question is missing."); - - /* Get indexes for this question */ // TODO: Get indexes from tst_exam_questions in database instead of form - snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns), - "Ind%010u", - NumQst); - Par_GetParMultiToText (StrQstIndOrAns,Result->Questions[NumQst].StrIndexes, - Tst_MAX_BYTES_INDEXES_ONE_QST); /* If choice ==> "0", "1", "2",... */ - /* Get answers selected by user for this question */ - snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns), + snprintf (StrAns,sizeof (StrAns), "Ans%010u", NumQst); - Par_GetParMultiToText (StrQstIndOrAns,Result->Questions[NumQst].StrAnswers, - Tst_MAX_BYTES_ANSWERS_ONE_QST); /* If answer type == T/F ==> " ", "T", "F"; if choice ==> "0", "2",... */ + Par_GetParMultiToText (StrAns,Exam->Questions[NumQst].StrAnswers, + TstExa_MAX_BYTES_ANSWERS_ONE_QST); /* If answer type == T/F ==> " ", "T", "F"; if choice ==> "0", "2",... */ } - /***** Get if test result will be visible by teachers *****/ - Result->AllowTeachers = Par_GetParToBool ("AllowTchs"); - } - -/*****************************************************************************/ -/*********** Compute score of each question and store in database ************/ -/*****************************************************************************/ - -static void Tst_ComputeAndStoreResultScore (struct TsR_Result *Result, - bool UpdateQstScore) - { - unsigned NumQst; - struct Tst_Question Question; - - /***** Initialize total score *****/ - Result->Score = 0.0; - Result->NumQstsNotBlank = 0; - - /***** Compute and store scores of all questions *****/ - for (NumQst = 0; - NumQst < Result->NumQsts; - NumQst++) - { - /* Compute question score */ - Tst_QstConstructor (&Question); - Question.QstCod = Result->Questions[NumQst].QstCod; - Question.Answer.Type = Tst_GetQstAnswerType (Question.QstCod); - Tst_ComputeAnswerScore (Result,NumQst,&Question); - Tst_QstDestructor (&Question); - - /* Store test result question in database */ - TsR_StoreOneTestResultQstInDB (Result, - NumQst); // 0, 1, 2, 3... - - /* Accumulate total score */ - Result->Score += Result->Questions[NumQst].Score; - if (Result->Questions[NumQst].AnswerIsNotBlank) - Result->NumQstsNotBlank++; - - /* Update the number of hits and the score of this question in tests database */ - if (UpdateQstScore) - Tst_UpdateQstScore (Result,NumQst); - } - - /***** Store test result in database *****/ - TsR_UpdateScoreOfTestResultInDB (Result); - } - -/*****************************************************************************/ -/************* Write answers of a question when assessing a test *************/ -/*****************************************************************************/ - -static void Tst_ComputeAnswerScore (struct TsR_Result *Result, - unsigned NumQst, - struct Tst_Question *Question) - { - /***** Write answer depending on type *****/ - switch (Question->Answer.Type) - { - case Tst_ANS_INT: - Tst_ComputeIntAnsScore (Result,NumQst,Question); - break; - case Tst_ANS_FLOAT: - Tst_ComputeFloatAnsScore (Result,NumQst,Question); - break; - case Tst_ANS_TRUE_FALSE: - Tst_ComputeTFAnsScore (Result,NumQst,Question); - break; - case Tst_ANS_UNIQUE_CHOICE: - case Tst_ANS_MULTIPLE_CHOICE: - Tst_ComputeChoiceAnsScore (Result,NumQst,Question); - break; - case Tst_ANS_TEXT: - Tst_ComputeTextAnsScore (Result,NumQst,Question); - break; - default: - break; - } - } - -/*****************************************************************************/ -/**************** Write integer answer when assessing a test *****************/ -/*****************************************************************************/ - -static void Tst_ComputeIntAnsScore (struct TsR_Result *Result, - unsigned NumQst, - struct Tst_Question *Question) - { - long AnswerUsr; - - /***** Get the numerical value of the correct answer *****/ - Tst_GetCorrectIntAnswerFromDB (Question); - - /***** Compute score *****/ - Result->Questions[NumQst].Score = 0.0; // Default score for blank or wrong answer - Result->Questions[NumQst].AnswerIsNotBlank = (Result->Questions[NumQst].StrAnswers[0] != '\0'); - if (Result->Questions[NumQst].AnswerIsNotBlank) // If user has answered the answer - if (sscanf (Result->Questions[NumQst].StrAnswers,"%ld",&AnswerUsr) == 1) - if (AnswerUsr == Question->Answer.Integer) // Correct answer - Result->Questions[NumQst].Score = 1.0; - } - -static void Tst_GetCorrectIntAnswerFromDB (struct Tst_Question *Question) - { - 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 ******************/ -/*****************************************************************************/ - -static void Tst_ComputeFloatAnsScore (struct TsR_Result *Result, - unsigned NumQst, - struct Tst_Question *Question) - { - double AnswerUsr; - - /***** Check if number of rows is correct *****/ - if (Question->Answer.NumOptions != 2) - Lay_ShowErrorAndExit ("Wrong float range."); - - /***** Get the numerical value of the minimum and maximum correct answers *****/ - Tst_GetCorrectFloatAnswerFromDB (Question); - - /***** Compute score *****/ - Result->Questions[NumQst].Score = 0.0; // Default score for blank or wrong answer - Result->Questions[NumQst].AnswerIsNotBlank = (Result->Questions[NumQst].StrAnswers[0] != '\0'); - if (Result->Questions[NumQst].AnswerIsNotBlank) // If user has answered the answer - { - AnswerUsr = Str_GetDoubleFromStr (Result->Questions[NumQst].StrAnswers); - // A bad formatted floating point answer will interpreted as 0.0 - Result->Questions[NumQst].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) - } - } - -static void Tst_GetCorrectFloatAnswerFromDB (struct Tst_Question *Question) - { - 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 **************/ -/*****************************************************************************/ - -static void Tst_ComputeTFAnsScore (struct TsR_Result *Result, - unsigned NumQst, - struct Tst_Question *Question) - { - /***** Get answer true or false *****/ - Tst_GetCorrectTFAnswerFromDB (Question); - - /***** Compute score *****/ - Result->Questions[NumQst].AnswerIsNotBlank = (Result->Questions[NumQst].StrAnswers[0] != '\0'); - if (Result->Questions[NumQst].AnswerIsNotBlank) // User has selected T or F - Result->Questions[NumQst].Score = (Result->Questions[NumQst].StrAnswers[0] == Question->Answer.TF) ? 1.0 : // Correct - -1.0; // Wrong - else - Result->Questions[NumQst].Score = 0.0; - } - -static void Tst_GetCorrectTFAnswerFromDB (struct Tst_Question *Question) - { - 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 *************/ -/*****************************************************************************/ - -void Tst_ComputeChoiceAnsScore (struct TsR_Result *Result, - unsigned NumQst, - 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 *****/ - Tst_GetCorrectChoiceAnswerFromDB (Question); - - /***** Get indexes for this question from string *****/ - Tst_GetIndexesFromStr (Result->Questions[NumQst].StrIndexes,Indexes); - - /***** Get the user's answers for this question from string *****/ - Tst_GetAnswersFromStr (Result->Questions[NumQst].StrAnswers,UsrAnswers); - - /***** Compute the total score of this question *****/ - Tst_ComputeScoreQst (Result,NumQst,Question,Indexes,UsrAnswers); - } - -static void Tst_GetCorrectChoiceAnswerFromDB (struct Tst_Question *Question) - { - 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 *********************/ -/*****************************************************************************/ - -void Tst_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]) - { - 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 *****************/ -/*****************************************************************************/ - -static void Tst_GetAnswersFromStr (const char StrAnswersOneQst[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1], - 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 *********************/ -/*****************************************************************************/ - -static void Tst_ComputeScoreQst (struct TsR_Result *Result, - unsigned NumQst, - 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? */ - Result->Questions[NumQst].AnswerIsNotBlank = NumAnsGood != 0 || NumAnsBad != 0; - if (Result->Questions[NumQst].AnswerIsNotBlank) - { - /* Compute the score */ - if (Question->Answer.Type == Tst_ANS_UNIQUE_CHOICE) - { - if (NumOptTotInQst >= 2) // It should be 2 options at least - Result->Questions[NumQst].Score = (double) NumAnsGood - - (double) NumAnsBad / (double) (NumOptTotInQst - 1); - else // 0 or 1 options (impossible) - Result->Questions[NumQst].Score = (double) NumAnsGood; - } - 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) - Result->Questions[NumQst].Score = (double) NumAnsGood / (double) NumOptCorrInQst - - (double) NumAnsBad / (double) (NumOptTotInQst - NumOptCorrInQst); - else // Si todas the opciones son correctas (caso raro) - Result->Questions[NumQst].Score = (double) NumAnsGood / (double) NumOptCorrInQst; - } - else - { - if (NumOptTotInQst) // There are options but none is correct (extrange case) - Result->Questions[NumQst].Score = - (double) NumAnsBad / (double) NumOptTotInQst; - else // There are no options (impossible!) - Result->Questions[NumQst].Score = 0.0; - } - } - } - else // Answer is blank - Result->Questions[NumQst].Score = 0.0; - } - -/*****************************************************************************/ -/********************* Compute score for text answer *************************/ -/*****************************************************************************/ - -static void Tst_ComputeTextAnsScore (struct TsR_Result *Result, - unsigned NumQst, - struct Tst_Question *Question) - { - unsigned NumOpt; - char TextAnsUsr[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1]; - char TextAnsOK[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1]; - bool Correct = false; - - /***** Get correct answers for this question from database *****/ - Tst_GetCorrectTextAnswerFromDB (Question); - - /***** Compute score *****/ - Result->Questions[NumQst].Score = 0.0; // Default score for blank or wrong answer - Result->Questions[NumQst].AnswerIsNotBlank = (Result->Questions[NumQst].StrAnswers[0] != '\0'); - if (Result->Questions[NumQst].AnswerIsNotBlank) // If user has answered the answer - { - /* Filter the user answer */ - Str_Copy (TextAnsUsr,Result->Questions[NumQst].StrAnswers, - Tst_MAX_BYTES_ANSWERS_ONE_QST); - - /* 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, - Tst_MAX_BYTES_ANSWERS_ONE_QST); - Str_ConvertToComparable (TextAnsOK); - - /* Check is user answer is correct */ - if (!strcoll (TextAnsUsr,TextAnsOK)) - { - Correct = true; - break; - } - } - - if (Correct) - Result->Questions[NumQst].Score = 1.0; // Correct answer - } - } - -static void Tst_GetCorrectTextAnswerFromDB (struct Tst_Question *Question) - { - 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); - - /***** 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 (); - - /***** Copy answer text (row[1]) and convert it, that is in HTML, to rigorous HTML ******/ - Str_Copy (Question->Answer.Options[NumOpt].Text,row[1], - Tst_MAX_BYTES_ANSWER_OR_FEEDBACK); - Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, - Question->Answer.Options[NumOpt].Text, - Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false); - } - - /***** 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); - } - -/*****************************************************************************/ -/************ Compute and show total grade out of maximum grade **************/ -/*****************************************************************************/ - -void Tst_ComputeAndShowGrade (unsigned NumQsts,double Score,double MaxGrade) - { - Tst_ShowGrade (Tst_ComputeGrade (NumQsts,Score,MaxGrade),MaxGrade); - } - -/*****************************************************************************/ -/**************** Compute total grade out of maximum grade *******************/ -/*****************************************************************************/ - -double Tst_ComputeGrade (unsigned NumQsts,double Score,double MaxGrade) - { - 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 ********************/ -/*****************************************************************************/ - -void Tst_ShowGrade (double Grade,double MaxGrade) - { - /***** Write grade over maximum grade *****/ - HTM_Double2Decimals (Grade); - HTM_Txt ("/"); - HTM_Double2Decimals (MaxGrade); + /***** Get if test exam will be visible by teachers *****/ + Exam->AllowTeachers = Par_GetParToBool ("AllowTchs"); } /*****************************************************************************/ @@ -1385,7 +706,6 @@ void Tst_ShowGrade (double Grade,double MaxGrade) static bool Tst_CheckIfNextTstAllowed (void) { extern const char *Hlp_ASSESSMENT_Tests; - extern const char *Txt_Test; extern const char *Txt_You_can_not_take_a_new_test_until; extern const char *Txt_Today; MYSQL_RES *mysql_res; @@ -1497,20 +817,20 @@ static Tst_Status_t Tst_GetTstStatus (unsigned NumTst) } /*****************************************************************************/ -/************************* Get number of hits to test ************************/ +/***************** Get number of test exams generated by me ******************/ /*****************************************************************************/ -static unsigned Tst_GetNumAccessesTst (void) +static unsigned Tst_GetNumExamsGeneratedByMe (void) { MYSQL_RES *mysql_res; MYSQL_ROW row; unsigned long NumRows; - unsigned NumAccessesTst = 0; + unsigned NumExamsGeneratedByMe = 0; if (Gbl.Usrs.Me.IBelongToCurrentCrs) { - /***** Get number of hits to test from database *****/ - NumRows = DB_QuerySELECT (&mysql_res,"can not get number of hits to test", + /***** Get number of test exams generated by me from database *****/ + NumRows = DB_QuerySELECT (&mysql_res,"can not get number of test exams generated", "SELECT NumAccTst" // row[0] " FROM crs_usr" " WHERE CrsCod=%ld AND UsrCod=%ld", @@ -1518,15 +838,15 @@ static unsigned Tst_GetNumAccessesTst (void) Gbl.Usrs.Me.UsrDat.UsrCod); if (NumRows == 0) - NumAccessesTst = 0; + NumExamsGeneratedByMe = 0; else if (NumRows == 1) { /* Get number of hits */ row = mysql_fetch_row (mysql_res); if (row[0] == NULL) - NumAccessesTst = 0; - else if (sscanf (row[0],"%u",&NumAccessesTst) != 1) - NumAccessesTst = 0; + NumExamsGeneratedByMe = 0; + else if (sscanf (row[0],"%u",&NumExamsGeneratedByMe) != 1) + NumExamsGeneratedByMe = 0; } else Lay_ShowErrorAndExit ("Error when getting number of hits to test."); @@ -1535,56 +855,94 @@ static unsigned Tst_GetNumAccessesTst (void) DB_FreeMySQLResult (&mysql_res); } - return NumAccessesTst; + return NumExamsGeneratedByMe; } /*****************************************************************************/ -/*** Write the test questions when showing a new test exam to be answered ****/ +/************************ Show a test exam to be answered ********************/ /*****************************************************************************/ -static void Tst_ShowTestQuestionsWhenSeeing (struct TsR_Result *Result) +static void Tst_ShowTestExamToFillIt (struct TstExa_Exam *Exam, + unsigned NumExamsGeneratedByMe, + Tst_RequestOrConfirm_t RequestOrConfirm) { + extern const char *Hlp_ASSESSMENT_Tests; + extern const char *Txt_Test; + // extern const char *Txt_Done_assess_test; MYSQL_RES *mysql_res; MYSQL_ROW row; unsigned NumQst; struct Tst_Question Question; - - /***** Trivial check *****/ - if (Result->NumQsts == 0) - return; - - /***** Begin table *****/ - HTM_TABLE_BeginWideMarginPadding (10); - - /***** Write one row for each question *****/ - for (NumQst = 0; - NumQst < Result->NumQsts; - NumQst++) + static const Act_Action_t Action[Tst_NUM_REQUEST_OR_CONFIRM] = { - Gbl.RowEvenOdd = NumQst % 2; + [Tst_REQUEST] = ActReqAssTst, + [Tst_CONFIRM] = ActAssTst, + }; + static char *TxtButton[Tst_NUM_REQUEST_OR_CONFIRM] = + { + [Tst_REQUEST] = "He terminado", // TODO: Need translation!!! + [Tst_CONFIRM] = "Enviar", // TODO: Need translation!!! + }; - /* Create test question */ - Tst_QstConstructor (&Question); - Question.QstCod = Result->Questions[NumQst].QstCod; + /***** Begin box *****/ + Box_BoxBegin (NULL,Txt_Test, + NULL,NULL, + Hlp_ASSESSMENT_Tests,Box_NOT_CLOSABLE); + Lay_WriteHeaderClassPhoto (false,false, + Gbl.Hierarchy.Ins.InsCod, + Gbl.Hierarchy.Deg.DegCod, + Gbl.Hierarchy.Crs.CrsCod); - /* Show question */ - if (Tst_GetOneQuestionByCod (Question.QstCod,&mysql_res)) // Question exists + if (Exam->NumQsts) + { + /***** Begin form *****/ + Frm_StartForm (Action[RequestOrConfirm]); + TstExa_PutParamExaCod (Exam->ExaCod); + Par_PutHiddenParamUnsigned (NULL,"NumTst",NumExamsGeneratedByMe); + + /***** Begin table *****/ + HTM_TABLE_BeginWideMarginPadding (10); + + /***** Write one row for each question *****/ + for (NumQst = 0; + NumQst < Exam->NumQsts; + NumQst++) { - /* Get row of the result of the query */ - row = mysql_fetch_row (mysql_res); + Gbl.RowEvenOdd = NumQst % 2; - /* Write question and answers */ - Tst_WriteQstAndAnsSeeing (Result,NumQst,&Question,row); + /* Create test question */ + Tst_QstConstructor (&Question); + Question.QstCod = Exam->Questions[NumQst].QstCod; + + /* Show question */ + if (Tst_GetOneQuestionByCod (Question.QstCod,&mysql_res)) // Question exists + { + /* Get row of the result of the query */ + row = mysql_fetch_row (mysql_res); + + /* Write question and answers */ + Tst_WriteQstAndAnsSeeing (Exam,NumQst,&Question,row); + } + else + Lay_ShowErrorAndExit ("Wrong question."); + + /* Destroy test question */ + Tst_QstDestructor (&Question); } - else - Lay_ShowErrorAndExit ("Wrong question."); - /* Destroy test question */ - Tst_QstDestructor (&Question); + /***** End table *****/ + HTM_TABLE_End (); + + /***** Test exam will be visible by teachers *****/ + Tst_PutCheckBoxAllowTeachers (true); + + /***** End form *****/ + Btn_PutConfirmButton (TxtButton[RequestOrConfirm]); + Frm_EndForm (); } - /***** End table *****/ - HTM_TABLE_End (); + /***** End box *****/ + Box_BoxEnd (); } /*****************************************************************************/ @@ -1616,77 +974,11 @@ void Tst_ShowTagList (unsigned NumTags,MYSQL_RES *mysql_res) HTM_Txt (Txt_no_tags); } -/*****************************************************************************/ -/******************* Show the result of assessing a test *********************/ -/*****************************************************************************/ - -static void Tst_ShowTestResultAfterAssess (struct TsR_Result *Result) - { - extern const char *Txt_Question_removed; - MYSQL_RES *mysql_res; - MYSQL_ROW row; - unsigned NumQst; - - /***** Initialize score and number of questions not blank *****/ - Result->NumQstsNotBlank = 0; - Result->Score = 0.0; - - for (NumQst = 0; - NumQst < Result->NumQsts; - NumQst++) - { - Gbl.RowEvenOdd = NumQst % 2; - - /***** Query database *****/ - if (Tst_GetOneQuestionByCod (Result->Questions[NumQst].QstCod,&mysql_res)) // Question exists - { - /***** Write question and answers *****/ - row = mysql_fetch_row (mysql_res); - Tst_WriteQstAndAnsTestResult (&Gbl.Usrs.Me.UsrDat, - Result, - NumQst, - row, - TstCfg_GetConfigVisibility ()); - - /***** Store test result question in database *****/ - TsR_StoreOneTestResultQstInDB (Result, - NumQst); // 0, 1, 2, 3... - - /***** Compute total score *****/ - Result->Score += Result->Questions[NumQst].Score; - if (Result->Questions[NumQst].AnswerIsNotBlank) - Result->NumQstsNotBlank++; - - /***** Update the number of accesses and the score of this question *****/ - if (Gbl.Usrs.Me.Role.Logged == Rol_STD) - Tst_UpdateQstScore (Result,NumQst); - } - else - { - /***** Question does not exists *****/ - HTM_TR_Begin (NULL); - - HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd); - Tst_WriteNumQst (NumQst + 1); - HTM_TD_End (); - - HTM_TD_Begin ("class=\"DAT_LIGHT LT COLOR%u\"",Gbl.RowEvenOdd); - HTM_Txt (Txt_Question_removed); - HTM_TD_End (); - - HTM_TR_End (); - } - - /***** Free structure that stores the query result *****/ - DB_FreeMySQLResult (&mysql_res); - } - } - /*****************************************************************************/ /********** Write a row of a test, with one question and its answer **********/ /*****************************************************************************/ -static void Tst_WriteQstAndAnsSeeing (struct TsR_Result *Result, +static void Tst_WriteQstAndAnsSeeing (struct TstExa_Exam *Exam, unsigned NumQst, struct Tst_Question *Question, MYSQL_ROW row) @@ -1730,7 +1022,7 @@ static void Tst_WriteQstAndAnsSeeing (struct TsR_Result *Result, "TEST_MED_SHOW"); /* Answers */ - Tst_WriteAnswersSeeing (Result,NumQst,Question); + Tst_WriteAnswersSeeing (Exam,NumQst,Question); HTM_TD_End (); @@ -1738,79 +1030,6 @@ static void Tst_WriteQstAndAnsSeeing (struct TsR_Result *Result, HTM_TR_End (); } -/*****************************************************************************/ -/********** Write a row of a test, with one question and its answer **********/ -/*****************************************************************************/ - -void Tst_WriteQstAndAnsTestResult (struct UsrData *UsrDat, - struct TsR_Result *Result, - unsigned NumQst, - MYSQL_ROW row, - unsigned Visibility) - { - struct Tst_Question Question; - bool IsVisibleQstAndAnsTxt = TsV_IsVisibleQstAndAnsTxt (Visibility); - /* - row[0] UNIX_TIMESTAMP(EditTime) - row[1] AnsType - row[2] Shuffle - row[3] Stem - row[4] Feedback - row[5] MedCod - row[6] NumHits - row[7] NumHitsNotBlank - row[8] Score - */ - - /***** Create test question *****/ - Tst_QstConstructor (&Question); - Question.QstCod = Result->Questions[NumQst].QstCod; - - /***** 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); - Question.Answer.Type = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[1]); - Tst_WriteAnswerType (Question.Answer.Type); - HTM_TD_End (); - - /***** Stem, media and answers *****/ - HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd); - - /* Stem (row[3]) */ - Tst_WriteQstStem (row[3],"TEST_EXA",IsVisibleQstAndAnsTxt); - - /* Media (row[5]) */ - if (IsVisibleQstAndAnsTxt) - { - Question.Media.MedCod = Str_ConvertStrCodToLongCod (row[5]); - Med_GetMediaDataByCod (&Question.Media); - Med_ShowMedia (&Question.Media, - "TEST_MED_SHOW_CONT", - "TEST_MED_SHOW"); - } - - /* Answers */ - Tst_ComputeAnswerScore (Result,NumQst,&Question); - Tst_WriteAnswersResult (UsrDat, - Result,NumQst,&Question, - Visibility); - - /* Question feedback (row[4]) */ - if (TsV_IsVisibleFeedbackTxt (Visibility)) - Tst_WriteQstFeedback (row[4],"TEST_EXA_LIGHT"); - - HTM_TD_End (); - - /***** End row *****/ - HTM_TR_End (); - - /***** Destroy test question *****/ - Tst_QstDestructor (&Question); - } - /*****************************************************************************/ /********************* Write the number of a test question *******************/ /*****************************************************************************/ @@ -1969,42 +1188,20 @@ void Tst_WriteQstFeedback (const char *Feedback,const char *ClassFeedback) } } -/*****************************************************************************/ -/*********************** Update the score of a question **********************/ -/*****************************************************************************/ - -static void Tst_UpdateQstScore (const struct TsR_Result *Result,unsigned NumQst) - { - /***** Update number of clicks and score of the question *****/ - Str_SetDecimalPointToUS (); // To print the floating point as a dot - if (Result->Questions[NumQst].AnswerIsNotBlank) - DB_QueryUPDATE ("can not update the score of a question", - "UPDATE tst_questions" - " SET NumHits=NumHits+1,NumHitsNotBlank=NumHitsNotBlank+1," - "Score=Score+(%.15lg)" - " WHERE QstCod=%ld", - Result->Questions[NumQst].Score, - Result->Questions[NumQst].QstCod); - else // The answer is blank - DB_QueryUPDATE ("can not update the score of a question", - "UPDATE tst_questions" - " SET NumHits=NumHits+1" - " WHERE QstCod=%ld", - Result->Questions[NumQst].QstCod); - Str_SetDecimalPointToLocal (); // Return to local system - } - /*****************************************************************************/ /*********** Update my number of accesses to test in this course *************/ /*****************************************************************************/ -static void Tst_UpdateMyNumAccessTst (unsigned NumAccessesTst) +static void Tst_IncreaseMyNumAccessTst (void) { + /***** Trivial check *****/ + if (!Gbl.Usrs.Me.IBelongToCurrentCrs) + return; + /***** Update my number of accesses to test in this course *****/ DB_QueryUPDATE ("can not update the number of accesses to test", - "UPDATE crs_usr SET NumAccTst=%u" + "UPDATE crs_usr SET NumAccTst=NumAccTst+1" " WHERE CrsCod=%ld AND UsrCod=%ld", - NumAccessesTst, Gbl.Hierarchy.Crs.CrsCod,Gbl.Usrs.Me.UsrDat.UsrCod); } @@ -2236,7 +1433,7 @@ static void Tst_PutIconsTests (void *TestPtr) NULL,NULL); } - /***** Put icon to view tests results *****/ + /***** Put icon to view test exams *****/ switch (Gbl.Usrs.Me.Role.Logged) { case Rol_STD: @@ -2861,7 +2058,7 @@ static void Tst_ShowFormConfigTst (void) HTM_TR_End (); - /***** Visibility of results *****/ + /***** Visibility of test exams *****/ HTM_TR_Begin (NULL); HTM_TD_Begin ("class=\"%s RT\"",The_ClassFormInBox[Gbl.Prefs.Theme]); @@ -2869,7 +2066,7 @@ static void Tst_ShowFormConfigTst (void) HTM_TD_End (); HTM_TD_Begin ("class=\"LB\""); - TsV_PutVisibilityCheckboxes (TstCfg_GetConfigVisibility ()); + TstVis_PutVisibilityCheckboxes (TstCfg_GetConfigVisibility ()); HTM_TD_End (); HTM_TR_End (); @@ -3233,7 +2430,7 @@ static void Tst_GetQuestions (struct Tst_Test *Test,MYSQL_RES **mysql_res) /*****************************************************************************/ static void Tst_GetQuestionsForNewTestFromDB (struct Tst_Test *Test, - struct TsR_Result *Result) + struct TstExa_Exam *Exam) { MYSQL_RES *mysql_res; MYSQL_ROW row; @@ -3346,14 +2543,14 @@ static void Tst_GetQuestionsForNewTestFromDB (struct Tst_Test *Test, Lay_ShowAlert (Lay_INFO,Query); */ /* Make the query */ - Result->NumQsts = - Test->NumQsts = (unsigned) DB_QuerySELECT (&mysql_res,"can not get questions", - "%s", - Query); + Exam->NumQsts = + Test->NumQsts = (unsigned) DB_QuerySELECT (&mysql_res,"can not get questions", + "%s", + Query); - /***** Get questions and answers from database and store them in result *****/ + /***** Get questions and answers from database *****/ for (NumQst = 0; - NumQst < Result->NumQsts; + NumQst < Exam->NumQsts; NumQst++) { /* Get question row */ @@ -3365,7 +2562,7 @@ static void Tst_GetQuestionsForNewTestFromDB (struct Tst_Test *Test, */ /* Get question code (row[0]) */ - if ((Result->Questions[NumQst].QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0) + if ((Exam->Questions[NumQst].QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0) Lay_ShowErrorAndExit ("Wrong code of question."); /* Get answer type (row[1]) */ @@ -3381,13 +2578,13 @@ static void Tst_GetQuestionsForNewTestFromDB (struct Tst_Test *Test, case Tst_ANS_FLOAT: case Tst_ANS_TRUE_FALSE: case Tst_ANS_TEXT: - Result->Questions[NumQst].StrIndexes[0] = '\0'; + Exam->Questions[NumQst].StrIndexes[0] = '\0'; break; case Tst_ANS_UNIQUE_CHOICE: case Tst_ANS_MULTIPLE_CHOICE: /* If answer type is unique or multiple option, - get indexes of answers depending on shuffle */ - Tst_GetChoiceAnsSeeing (Result,NumQst,Shuffle); + generate indexes of answers depending on shuffle */ + Tst_GenerateChoiceIndexesDependingOnShuffle (Exam,NumQst,Shuffle); break; default: break; @@ -3397,32 +2594,33 @@ static void Tst_GetQuestionsForNewTestFromDB (struct Tst_Test *Test, Initially user has not answered the question ==> initially all the answers will be blank. If the user does not confirm the submission of their exam ==> ==> the exam may be half filled ==> the answers displayed will be those selected by the user. */ - Result->Questions[NumQst].StrAnswers[0] = '\0'; + Exam->Questions[NumQst].StrAnswers[0] = '\0'; } - /***** Get if test result will be visible by teachers *****/ - Result->AllowTeachers = Par_GetParToBool ("AllowTchs"); + /***** Get if test exam will be visible by teachers *****/ + Exam->AllowTeachers = Par_GetParToBool ("AllowTchs"); } /*****************************************************************************/ /********* Get single or multiple choice answer when seeing a test ***********/ /*****************************************************************************/ -static void Tst_GetChoiceAnsSeeing (struct TsR_Result *Result, +static void Tst_GenerateChoiceIndexesDependingOnShuffle (struct TstExa_Exam *Exam, unsigned NumQst, bool Shuffle) { + extern const char *Par_SEPARATOR_PARAM_MULTIPLE; struct Tst_Question Question; unsigned NumOpt; MYSQL_RES *mysql_res; MYSQL_ROW row; unsigned Index; bool ErrorInIndex; - char StrInd[Cns_MAX_DECIMAL_DIGITS_UINT + 1]; + char StrInd[1 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; /***** Create test question *****/ Tst_QstConstructor (&Question); - Question.QstCod = Result->Questions[NumQst].QstCod; + Question.QstCod = Exam->Questions[NumQst].QstCod; /***** Get answers of question from database *****/ Tst_GetAnswersQst (&Question,&mysql_res,Shuffle); @@ -3455,10 +2653,12 @@ static void Tst_GetChoiceAnsSeeing (struct TsR_Result *Result, if (ErrorInIndex) Lay_ShowErrorAndExit ("Wrong index of answer."); - snprintf (StrInd,sizeof (StrInd),NumOpt == 0 ? "%u" : - ",%u",Index); - Str_Concat (Result->Questions[NumQst].StrIndexes,StrInd, - Tst_MAX_BYTES_INDEXES_ONE_QST); + if (NumOpt == 0) + snprintf (StrInd,sizeof (StrInd),"%u",Index); + else + snprintf (StrInd,sizeof (StrInd),"%s%u",Par_SEPARATOR_PARAM_MULTIPLE,Index); + Str_Concat (Exam->Questions[NumQst].StrIndexes,StrInd, + TstExa_MAX_BYTES_INDEXES_ONE_QST); } /***** Free structure that stores the query result *****/ @@ -4097,7 +3297,7 @@ void Tst_WriteAnswersListing (struct Tst_Question *Question) /************** Write answers of a question when seeing a test ***************/ /*****************************************************************************/ -static void Tst_WriteAnswersSeeing (struct TsR_Result *Result, +static void Tst_WriteAnswersSeeing (struct TstExa_Exam *Exam, unsigned NumQst, struct Tst_Question *Question) { @@ -4107,13 +3307,13 @@ static void Tst_WriteAnswersSeeing (struct TsR_Result *Result, switch (Question->Answer.Type) { case Tst_ANS_INT: - Tst_WriteIntAnsSeeing (Result,NumQst); + Tst_WriteIntAnsSeeing (Exam,NumQst); break; case Tst_ANS_FLOAT: - Tst_WriteFloatAnsSeeing (Result,NumQst); + Tst_WriteFloatAnsSeeing (Exam,NumQst); break; case Tst_ANS_TRUE_FALSE: - Tst_WriteTFAnsSeeing (Result,NumQst); + Tst_WriteTFAnsSeeing (Exam,NumQst); break; case Tst_ANS_UNIQUE_CHOICE: case Tst_ANS_MULTIPLE_CHOICE: @@ -4122,79 +3322,19 @@ static void Tst_WriteAnswersSeeing (struct TsR_Result *Result, false); // Don't shuffle /* Write answer */ - Tst_WriteChoiceAnsSeeing (Result,NumQst,Question,mysql_res); + Tst_WriteChoiceAnsSeeing (Exam,NumQst,Question,mysql_res); /* Free structure that stores the query result */ DB_FreeMySQLResult (&mysql_res); break; case Tst_ANS_TEXT: - Tst_WriteTextAnsSeeing (Result,NumQst); + Tst_WriteTextAnsSeeing (Exam,NumQst); break; default: break; } } -/*****************************************************************************/ -/************* Write answers of a question when assessing a test *************/ -/*****************************************************************************/ - -static void Tst_WriteAnswersResult (struct UsrData *UsrDat, - const struct TsR_Result *Result, - unsigned NumQst, - struct Tst_Question *Question, - unsigned Visibility) - { - MYSQL_RES *mysql_res; - - /***** Get answer of a question from database *****/ - Tst_GetAnswersQst (Question,&mysql_res, - false); // Don't shuffle - /* - row[0] AnsInd - row[1] Answer - row[2] Feedback - row[3] MedCod - row[4] Correct - */ - - /***** Write answer depending on type *****/ - switch (Question->Answer.Type) - { - case Tst_ANS_INT: - Tst_WriteIntAnsResult (UsrDat,Result, - NumQst,Question,mysql_res, - Visibility); - break; - case Tst_ANS_FLOAT: - Tst_WriteFloatAnsResult (UsrDat,Result, - NumQst,Question,mysql_res, - Visibility); - break; - case Tst_ANS_TRUE_FALSE: - Tst_WriteTFAnsResult (UsrDat,Result, - NumQst,Question,mysql_res, - Visibility); - break; - case Tst_ANS_UNIQUE_CHOICE: - case Tst_ANS_MULTIPLE_CHOICE: - Tst_WriteChoiceAnsResult (UsrDat,Result, - NumQst,Question,mysql_res, - Visibility); - break; - case Tst_ANS_TEXT: - Tst_WriteTextAnsResult (UsrDat,Result, - NumQst,Question,mysql_res, - Visibility); - break; - default: - break; - } - - /***** Free structure that stores the query result *****/ - DB_FreeMySQLResult (&mysql_res); - } - /*****************************************************************************/ /***************** Check if a question is valid for a game *******************/ /*****************************************************************************/ @@ -4236,116 +3376,19 @@ static void Tst_WriteIntAnsListing (const struct Tst_Question *Question, /****************** Write integer answer when seeing a test ******************/ /*****************************************************************************/ -static void Tst_WriteIntAnsSeeing (const struct TsR_Result *Result, +static void Tst_WriteIntAnsSeeing (const struct TstExa_Exam *Exam, unsigned NumQst) { - char StrQstIndOrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Qstxx...x", "Indxx...x" or "Ansxx...x" + char StrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x" /***** Write input field for the answer *****/ - snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns), + snprintf (StrAns,sizeof (StrAns), "Ans%010u", NumQst); - HTM_INPUT_TEXT (StrQstIndOrAns,11,Result->Questions[NumQst].StrAnswers,false, + HTM_INPUT_TEXT (StrAns,11,Exam->Questions[NumQst].StrAnswers,false, "size=\"11\""); } -/*****************************************************************************/ -/**************** Write integer answer when assessing a test *****************/ -/*****************************************************************************/ - -static void Tst_WriteIntAnsResult (struct UsrData *UsrDat, - const struct TsR_Result *Result, - unsigned NumQst, - const struct Tst_Question *Question, - MYSQL_RES *mysql_res, - unsigned Visibility) - { - MYSQL_ROW row; - long IntAnswerUsr; - long IntAnswerCorr; - /* - row[0] AnsInd - row[1] Answer - row[2] Feedback - row[3] MedCod - row[4] Correct - */ - /***** Check if number of rows is correct *****/ - Tst_CheckIfNumberOfAnswersIsOne (Question); - - /***** Get the numerical value of the correct answer *****/ - row = mysql_fetch_row (mysql_res); - if (sscanf (row[1],"%ld",&IntAnswerCorr) != 1) - Lay_ShowErrorAndExit ("Wrong integer answer."); - - /***** Header with the title of each column *****/ - HTM_TABLE_BeginPadding (2); - HTM_TR_Begin (NULL); - Tst_WriteHeadUserCorrect (UsrDat); - HTM_TR_End (); - - HTM_TR_Begin (NULL); - - /***** Write the user answer *****/ - if (Result->Questions[NumQst].StrAnswers[0]) // If user has answered the question - { - if (sscanf (Result->Questions[NumQst].StrAnswers,"%ld",&IntAnswerUsr) == 1) - { - HTM_TD_Begin ("class=\"%s CM\"", - TsV_IsVisibleCorrectAns (Visibility) ? - (IntAnswerUsr == IntAnswerCorr ? "ANS_OK" : - "ANS_BAD") : - "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 (TsV_IsVisibleQstAndAnsTxt (Visibility) && - TsV_IsVisibleCorrectAns (Visibility)) - HTM_Long (IntAnswerCorr); - else - Ico_PutIconNotVisible (); - HTM_TD_End (); - - HTM_TR_End (); - - /***** Write the score of this question *****/ - if (TsV_IsVisibleEachQstScore (Visibility)) - { - Tst_WriteScoreStart (2); - if (!Result->Questions[NumQst].StrAnswers[0]) // If user has omitted the answer - { - HTM_SPAN_Begin ("class=\"ANS_0\""); - HTM_Double2Decimals (0.0); - } - else if (IntAnswerUsr == IntAnswerCorr) // If correct - { - HTM_SPAN_Begin ("class=\"ANS_OK\""); - HTM_Double2Decimals (1.0); - } - else // If wrong - { - HTM_SPAN_Begin ("class=\"ANS_BAD\""); - HTM_Double2Decimals (0.0); - } - HTM_SPAN_End (); - Tst_WriteScoreEnd (); - } - - HTM_TABLE_End (); - } - /*****************************************************************************/ /****************** Write float answer when editing a test *******************/ /*****************************************************************************/ @@ -4387,130 +3430,19 @@ static void Tst_WriteFloatAnsEdit (const struct Tst_Question *Question, /****************** Write float answer when seeing a test ********************/ /*****************************************************************************/ -static void Tst_WriteFloatAnsSeeing (const struct TsR_Result *Result, +static void Tst_WriteFloatAnsSeeing (const struct TstExa_Exam *Exam, unsigned NumQst) { - char StrQstIndOrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Qstxx...x", "Indxx...x" or "Ansxx...x" + char StrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x" /***** Write input field for the answer *****/ - snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns), + snprintf (StrAns,sizeof (StrAns), "Ans%010u", NumQst); - HTM_INPUT_TEXT (StrQstIndOrAns,Tst_MAX_BYTES_FLOAT_ANSWER,Result->Questions[NumQst].StrAnswers,false, + HTM_INPUT_TEXT (StrAns,Tst_MAX_BYTES_FLOAT_ANSWER,Exam->Questions[NumQst].StrAnswers,false, "size=\"11\""); } -/*****************************************************************************/ -/***************** Write float answer when assessing a test ******************/ -/*****************************************************************************/ - -static void Tst_WriteFloatAnsResult (struct UsrData *UsrDat, - const struct TsR_Result *Result, - unsigned NumQst, - const struct Tst_Question *Question, - MYSQL_RES *mysql_res, - unsigned Visibility) - { - MYSQL_ROW row; - unsigned i; - double FloatAnsUsr = 0.0,Tmp; - double FloatAnsCorr[2]; - /* - row[0] AnsInd - row[1] Answer - row[2] Feedback - row[3] MedCod - row[4] Correct - */ - /***** Check if number of rows is correct *****/ - if (Question->Answer.NumOptions != 2) - Lay_ShowErrorAndExit ("Wrong float range."); - - /***** Get the numerical value of the minimum and maximum correct answers *****/ - for (i = 0; - i < 2; - i++) - { - row = mysql_fetch_row (mysql_res); - FloatAnsCorr[i] = Str_GetDoubleFromStr (row[1]); - } - if (FloatAnsCorr[0] > FloatAnsCorr[1]) // The maximum and the minimum are swapped - { - /* Swap maximum and minimum */ - Tmp = FloatAnsCorr[0]; - FloatAnsCorr[0] = FloatAnsCorr[1]; - FloatAnsCorr[1] = Tmp; - } - - /***** Header with the title of each column *****/ - HTM_TABLE_BeginPadding (2); - HTM_TR_Begin (NULL); - Tst_WriteHeadUserCorrect (UsrDat); - HTM_TR_End (); - - HTM_TR_Begin (NULL); - - /***** Write the user answer *****/ - if (Result->Questions[NumQst].StrAnswers[0]) // If user has answered the question - { - FloatAnsUsr = Str_GetDoubleFromStr (Result->Questions[NumQst].StrAnswers); - // A bad formatted floating point answer will interpreted as 0.0 - HTM_TD_Begin ("class=\"%s CM\"", - TsV_IsVisibleCorrectAns (Visibility) ? - ((FloatAnsUsr >= FloatAnsCorr[0] && - FloatAnsUsr <= FloatAnsCorr[1]) ? "ANS_OK" : - "ANS_BAD") : - "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 (TsV_IsVisibleQstAndAnsTxt (Visibility) && - TsV_IsVisibleCorrectAns (Visibility)) - { - HTM_Txt ("["); - HTM_Double (FloatAnsCorr[0]); - HTM_Txt ("; "); - HTM_Double (FloatAnsCorr[1]); - HTM_Txt ("]"); - } - else - Ico_PutIconNotVisible (); - HTM_TD_End (); - - HTM_TR_End (); - - /***** Write the score of this question *****/ - if (TsV_IsVisibleEachQstScore (Visibility)) - { - Tst_WriteScoreStart (2); - if (!Result->Questions[NumQst].StrAnswers[0]) // If user has omitted the answer - { - HTM_SPAN_Begin ("class=\"ANS_0\""); - HTM_Double2Decimals (0.0); - } - else if (FloatAnsUsr >= FloatAnsCorr[0] && - FloatAnsUsr <= FloatAnsCorr[1]) // If correct (inside the interval) - { - HTM_SPAN_Begin ("class=\"ANS_OK\""); - HTM_Double2Decimals (1.0); - } - else // If wrong (outside the interval) - { - HTM_SPAN_Begin ("class=\"ANS_BAD\""); - HTM_Double2Decimals (0.0); - } - HTM_SPAN_End (); - Tst_WriteScoreEnd (); - } - - HTM_TABLE_End (); - } - /*****************************************************************************/ /*********** Write false / true answer when listing test questions ***********/ /*****************************************************************************/ @@ -4544,7 +3476,7 @@ static void Tst_WriteTFAnsListing (const struct Tst_Question *Question, /************** Write false / true answer when seeing a test ****************/ /*****************************************************************************/ -static void Tst_WriteTFAnsSeeing (const struct TsR_Result *Result, +static void Tst_WriteTFAnsSeeing (const struct TstExa_Exam *Exam, unsigned NumQst) { extern const char *Txt_TF_QST[2]; @@ -4555,9 +3487,9 @@ static void Tst_WriteTFAnsSeeing (const struct TsR_Result *Result, ==> the exam may be half filled ==> the answers displayed will be those selected by the user. */ HTM_SELECT_Begin (false, "name=\"Ans%010u\"",NumQst); - HTM_OPTION (HTM_Type_STRING,"" ,true ,Result->Questions[NumQst].StrAnswers[0] == '\0'," "); - HTM_OPTION (HTM_Type_STRING,"T",false,Result->Questions[NumQst].StrAnswers[0] == 'T' ,"%s",Txt_TF_QST[0]); - HTM_OPTION (HTM_Type_STRING,"F",false,Result->Questions[NumQst].StrAnswers[0] == 'F' ,"%s",Txt_TF_QST[1]); + HTM_OPTION (HTM_Type_STRING,"" ,Exam->Questions[NumQst].StrAnswers[0] == '\0',false," "); + HTM_OPTION (HTM_Type_STRING,"T",Exam->Questions[NumQst].StrAnswers[0] == 'T' ,false,"%s",Txt_TF_QST[0]); + HTM_OPTION (HTM_Type_STRING,"F",Exam->Questions[NumQst].StrAnswers[0] == 'F' ,false,"%s",Txt_TF_QST[1]); HTM_SELECT_End (); } @@ -4583,87 +3515,6 @@ void Tst_WriteAnsTF (char AnsTF) } } -/*****************************************************************************/ -/************** Write false / true answer when assessing a test **************/ -/*****************************************************************************/ - -static void Tst_WriteTFAnsResult (struct UsrData *UsrDat, - const struct TsR_Result *Result, - unsigned NumQst, - const struct Tst_Question *Question, - MYSQL_RES *mysql_res, - unsigned Visibility) - { - MYSQL_ROW row; - char AnsTF; - /* - row[0] AnsInd - row[1] Answer - row[2] Feedback - row[3] MedCod - row[4] Correct - */ - /***** Check if number of rows is correct *****/ - Tst_CheckIfNumberOfAnswersIsOne (Question); - - /***** Get answer true or false *****/ - row = mysql_fetch_row (mysql_res); - AnsTF = Result->Questions[NumQst].StrAnswers[0]; - - /***** Header with the title of each column *****/ - HTM_TABLE_BeginPadding (2); - HTM_TR_Begin (NULL); - Tst_WriteHeadUserCorrect (UsrDat); - HTM_TR_End (); - - HTM_TR_Begin (NULL); - - /***** Write the user answer *****/ - HTM_TD_Begin ("class=\"%s CM\"", - TsV_IsVisibleCorrectAns (Visibility) ? - (AnsTF == row[1][0] ? "ANS_OK" : - "ANS_BAD") : - "ANS_0"); - Tst_WriteAnsTF (AnsTF); - HTM_TD_End (); - - /***** Write the correct answer *****/ - HTM_TD_Begin ("class=\"ANS_0 CM\""); - if (TsV_IsVisibleQstAndAnsTxt (Visibility) && - TsV_IsVisibleCorrectAns (Visibility)) - Tst_WriteAnsTF (row[1][0]); - else - Ico_PutIconNotVisible (); - HTM_TD_End (); - - HTM_TR_End (); - - /***** Write the score of this question *****/ - if (TsV_IsVisibleEachQstScore (Visibility)) - { - Tst_WriteScoreStart (2); - if (AnsTF == '\0') // If user has omitted the answer - { - HTM_SPAN_Begin ("class=\"ANS_0\""); - HTM_Double2Decimals (0.0); - } - else if (AnsTF == row[1][0]) // If correct - { - HTM_SPAN_Begin ("class=\"ANS_OK\""); - HTM_Double2Decimals (1.0); - } - else // If wrong - { - HTM_SPAN_Begin ("class=\"ANS_BAD\""); - HTM_Double2Decimals (-1.0); - } - HTM_SPAN_End (); - Tst_WriteScoreEnd (); - } - - HTM_TABLE_End (); - } - /*****************************************************************************/ /**** Write single or multiple choice answer when listing test questions *****/ /*****************************************************************************/ @@ -4767,7 +3618,7 @@ static void Tst_WriteChoiceAnsListing (struct Tst_Question *Question, /******** Write single or multiple choice answer when seeing a test **********/ /*****************************************************************************/ -static void Tst_WriteChoiceAnsSeeing (const struct TsR_Result *Result, +static void Tst_WriteChoiceAnsSeeing (const struct TstExa_Exam *Exam, unsigned NumQst, struct Tst_Question *Question, MYSQL_RES *mysql_res) @@ -4775,7 +3626,7 @@ static void Tst_WriteChoiceAnsSeeing (const struct TsR_Result *Result, unsigned NumOpt; unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION]; // Indexes of all answers of this question bool UsrAnswers[Tst_MAX_OPTIONS_PER_QUESTION]; - char StrQstIndOrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Qstxx...x", "Indxx...x" or "Ansxx...x" + char StrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x" /* row[0] AnsInd row[1] Answer @@ -4788,10 +3639,10 @@ static void Tst_WriteChoiceAnsSeeing (const struct TsR_Result *Result, Tst_GetChoiceAns (Question,mysql_res); /***** Get indexes for this question from string *****/ - Tst_GetIndexesFromStr (Result->Questions[NumQst].StrIndexes,Indexes); + TstExa_GetIndexesFromStr (Exam->Questions[NumQst].StrIndexes,Indexes); /***** Get the user's answers for this question from string *****/ - Tst_GetAnswersFromStr (Result->Questions[NumQst].StrAnswers,UsrAnswers); + TstExa_GetAnswersFromStr (Exam->Questions[NumQst].StrAnswers,UsrAnswers); /***** Begin table *****/ HTM_TABLE_BeginPadding (2); @@ -4800,13 +3651,8 @@ static void Tst_WriteChoiceAnsSeeing (const struct TsR_Result *Result, NumOpt < Question->Answer.NumOptions; NumOpt++) { - /***** Allocate memory for text in this choice answer *****/ - if (!Tst_AllocateTextChoiceAnswer (Question,NumOpt)) - /* Abort on error */ - Ale_ShowAlertsAndExit (); - - /***** Indexes are 0,1,2,3... if no shuffle - or 3,1,0,2... (example) if shuffle *****/ + /***** Indexes are 0 1 2 3... if no shuffle + or 3 1 0 2... (example) if shuffle *****/ HTM_TR_Begin (NULL); /***** Write selectors and letter of this option *****/ @@ -4815,16 +3661,11 @@ static void Tst_WriteChoiceAnsSeeing (const struct TsR_Result *Result, ==> the exam may be half filled ==> the answers displayed will be those selected by the user. */ HTM_TD_Begin ("class=\"LT\""); - snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns), // TODO: Remove indexes from this form because they will be got from tst_exam_questions in database - "Ind%010u", - NumQst); - Par_PutHiddenParamUnsigned (NULL,StrQstIndOrAns,Indexes[NumOpt]); - - snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns), + snprintf (StrAns,sizeof (StrAns), "Ans%010u", NumQst); if (Question->Answer.Type == Tst_ANS_UNIQUE_CHOICE) - HTM_INPUT_RADIO (StrQstIndOrAns,false, + HTM_INPUT_RADIO (StrAns,false, "id=\"Ans%010u_%u\" value=\"%u\"%s" " onclick=\"selectUnselectRadio(this,this.form.Ans%010u,%u);\"", NumQst,NumOpt, @@ -4833,7 +3674,7 @@ static void Tst_WriteChoiceAnsSeeing (const struct TsR_Result *Result, "", NumQst,Question->Answer.NumOptions); else // Answer.Type == Tst_ANS_MULTIPLE_CHOICE - HTM_INPUT_CHECKBOX (StrQstIndOrAns,HTM_DONT_SUBMIT_ON_CHANGE, + HTM_INPUT_CHECKBOX (StrAns,HTM_DONT_SUBMIT_ON_CHANGE, "id=\"Ans%010u_%u\" value=\"%u\"%s", NumQst,NumOpt, Indexes[NumOpt], @@ -4865,160 +3706,11 @@ static void Tst_WriteChoiceAnsSeeing (const struct TsR_Result *Result, HTM_TABLE_End (); } -/*****************************************************************************/ -/******* Write single or multiple choice answer when assessing a test ********/ -/*****************************************************************************/ - -static void Tst_WriteChoiceAnsResult (struct UsrData *UsrDat, - const struct TsR_Result *Result, - unsigned NumQst, - struct Tst_Question *Question, - MYSQL_RES *mysql_res, - 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 text and correctness of answers for this question - from database (one row per answer) *****/ - Tst_GetChoiceAns (Question,mysql_res); - - /***** Get indexes for this question from string *****/ - Tst_GetIndexesFromStr (Result->Questions[NumQst].StrIndexes,Indexes); - - /***** Get the user's answers for this question from string *****/ - Tst_GetAnswersFromStr (Result->Questions[NumQst].StrAnswers,UsrAnswers); - - /***** Begin table *****/ - HTM_TABLE_BeginPadding (2); - HTM_TR_Begin (NULL); - Tst_WriteHeadUserCorrect (UsrDat); - 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 (TsV_IsVisibleCorrectAns (Visibility)) - { - if (Question->Answer.Options[Indexes[NumOpt]].Correct) - { - Ans.Class = "ANS_OK"; - Ans.Str = "✓"; - } - else - { - Ans.Class = "ANS_BAD"; - Ans.Str = "✗"; - } - } - else - { - Ans.Class = "ANS_0"; - Ans.Str = "•"; - } - - 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 (TsV_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 ("•"); - 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) ",'a' + (char) NumOpt); - HTM_TD_End (); - - /* Answer text and feedback */ - HTM_TD_Begin ("class=\"LT\""); - - HTM_DIV_Begin ("class=\"ANS_TXT\""); - if (TsV_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 (TsV_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 (); - } - - /***** Write the score of this question *****/ - if (TsV_IsVisibleEachQstScore (Visibility)) - { - Tst_WriteScoreStart (4); - if (Result->Questions[NumQst].Score == 0.0) - HTM_SPAN_Begin ("class=\"ANS_0\""); - else if (Result->Questions[NumQst].Score > 0.0) - HTM_SPAN_Begin ("class=\"ANS_OK\""); - else - HTM_SPAN_Begin ("class=\"ANS_BAD\""); - HTM_Double2Decimals (Result->Questions[NumQst].Score); - HTM_SPAN_End (); - Tst_WriteScoreEnd (); - } - - /***** End table *****/ - HTM_TABLE_End (); - } - /*****************************************************************************/ /************************ Get choice answer from row *************************/ /*****************************************************************************/ -static void Tst_GetChoiceAns (struct Tst_Question *Question,MYSQL_RES *mysql_res) +void Tst_GetChoiceAns (struct Tst_Question *Question,MYSQL_RES *mysql_res) { unsigned NumOpt; MYSQL_ROW row; @@ -5054,7 +3746,7 @@ static void Tst_GetChoiceAns (struct Tst_Question *Question,MYSQL_RES *mysql_res /***** Copy answer feedback (row[2]) and convert it, that is in HTML, to rigorous HTML ******/ - if (TsV_IsVisibleFeedbackTxt (TstCfg_GetConfigVisibility ())) + if (TstVis_IsVisibleFeedbackTxt (TstCfg_GetConfigVisibility ())) if (row[2]) if (row[2][0]) { @@ -5078,252 +3770,31 @@ static void Tst_GetChoiceAns (struct Tst_Question *Question,MYSQL_RES *mysql_res /******************** Write text answer when seeing a test *******************/ /*****************************************************************************/ -static void Tst_WriteTextAnsSeeing (const struct TsR_Result *Result, +static void Tst_WriteTextAnsSeeing (const struct TstExa_Exam *Exam, unsigned NumQst) { - char StrQstIndOrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Qstxx...x", "Indxx...x" or "Ansxx...x" + char StrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x" /***** Write input field for the answer *****/ - snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns), + snprintf (StrAns,sizeof (StrAns), "Ans%010u", NumQst); - HTM_INPUT_TEXT (StrQstIndOrAns,Tst_MAX_BYTES_ANSWERS_ONE_QST,Result->Questions[NumQst].StrAnswers,false, + HTM_INPUT_TEXT (StrAns,TstExa_MAX_BYTES_ANSWERS_ONE_QST,Exam->Questions[NumQst].StrAnswers,false, "size=\"40\""); } -/*****************************************************************************/ -/***************** Write text answer when assessing a test *******************/ -/*****************************************************************************/ - -static void Tst_WriteTextAnsResult (struct UsrData *UsrDat, - const struct TsR_Result *Result, - unsigned NumQst, - struct Tst_Question *Question, - MYSQL_RES *mysql_res, - unsigned Visibility) - { - unsigned NumOpt; - MYSQL_ROW row; - char TextAnsUsr[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1]; - char TextAnsOK[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1]; - bool Correct = false; - /* - row[0] AnsInd - row[1] Answer - row[2] Feedback - row[3] MedCod - row[4] Correct - */ - /***** Get text and correctness of answers for this question from database (one row per answer) *****/ - for (NumOpt = 0; - NumOpt < Question->Answer.NumOptions; - NumOpt++) - { - /***** Get next answer *****/ - row = mysql_fetch_row (mysql_res); - - /***** Allocate memory for text in this choice answer *****/ - if (!Tst_AllocateTextChoiceAnswer (Question,NumOpt)) - /* Abort on error */ - Ale_ShowAlertsAndExit (); - - /***** Copy answer text (row[1]) and convert it, that is in HTML, to rigorous HTML ******/ - Str_Copy (Question->Answer.Options[NumOpt].Text,row[1], - Tst_MAX_BYTES_ANSWER_OR_FEEDBACK); - Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, - Question->Answer.Options[NumOpt].Text, - Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false); - - /***** Copy answer feedback (row[2]) and convert it, that is in HTML, to rigorous HTML ******/ - if (TsV_IsVisibleFeedbackTxt (Visibility)) - if (row[2]) - if (row[2][0]) - { - Str_Copy (Question->Answer.Options[NumOpt].Feedback,row[2], - Tst_MAX_BYTES_ANSWER_OR_FEEDBACK); - Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, - Question->Answer.Options[NumOpt].Feedback, - Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false); - } - - /***** Assign correctness (row[4]) of this answer (this option) *****/ - Question->Answer.Options[NumOpt].Correct = (row[4][0] == 'Y'); - } - - /***** Header with the title of each column *****/ - HTM_TABLE_BeginPadding (2); - HTM_TR_Begin (NULL); - Tst_WriteHeadUserCorrect (UsrDat); - HTM_TR_End (); - - HTM_TR_Begin (NULL); - - /***** Write the user answer *****/ - if (Result->Questions[NumQst].StrAnswers[0]) // If user has answered the question - { - /* Filter the user answer */ - Str_Copy (TextAnsUsr,Result->Questions[NumQst].StrAnswers, - Tst_MAX_BYTES_ANSWERS_ONE_QST); - - /* 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, - Tst_MAX_BYTES_ANSWERS_ONE_QST); - Str_ConvertToComparable (TextAnsOK); - - /* Check is user answer is correct */ - if (!strcoll (TextAnsUsr,TextAnsOK)) - { - Correct = true; - break; - } - } - HTM_TD_Begin ("class=\"%s CT\"", - TsV_IsVisibleCorrectAns (Visibility) ? - (Correct ? "ANS_OK" : - "ANS_BAD") : - "ANS_0"); - HTM_Txt (Result->Questions[NumQst].StrAnswers); - } - else // If user has omitted the answer - HTM_TD_Begin (NULL); - HTM_TD_End (); - - /***** Write the correct answers *****/ - if (TsV_IsVisibleQstAndAnsTxt (Visibility) && - TsV_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) ",'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 (TsV_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 (); - - /***** Write the score of this question *****/ - if (TsV_IsVisibleEachQstScore (Visibility)) - { - Tst_WriteScoreStart (4); - if (!Result->Questions[NumQst].StrAnswers[0]) // If user has omitted the answer - { - HTM_SPAN_Begin ("class=\"ANS_0\""); - HTM_Double2Decimals (0.0); - } - else if (Correct) // If correct - { - HTM_SPAN_Begin ("class=\"ANS_OK\""); - HTM_Double2Decimals (1.0); - } - else // If wrong - { - HTM_SPAN_Begin ("class=\"ANS_BAD\""); - HTM_Double2Decimals (0.0); - } - HTM_SPAN_End (); - Tst_WriteScoreEnd (); - } - - HTM_TABLE_End (); - } - -/*****************************************************************************/ -/********* Write head with two columns: ********/ -/********* one for the user's answer and other for the correct answer ********/ -/*****************************************************************************/ - -static void Tst_WriteHeadUserCorrect (struct UsrData *UsrDat) - { - 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 (); - } - -/*****************************************************************************/ -/*********** Write the start ans the end of the score of an answer ***********/ -/*****************************************************************************/ - -static void Tst_WriteScoreStart (unsigned ColSpan) - { - extern const char *Txt_Score; - - HTM_TR_Begin (NULL); - HTM_TD_Begin ("colspan=\"%u\" class=\"DAT_SMALL LM\"",ColSpan); - HTM_TxtColonNBSP (Txt_Score); - } - -static void Tst_WriteScoreEnd (void) - { - HTM_TD_End (); - HTM_TR_End (); - } - /*****************************************************************************/ /*************** Write parameter with the code of a question *****************/ /*****************************************************************************/ static void Tst_WriteParamQstCod (unsigned NumQst,long QstCod) { - char StrQstIndOrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Qstxx...x", "Indxx...x" or "Ansxx...x" + char StrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x" - snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns), + snprintf (StrAns,sizeof (StrAns), "Qst%010u", NumQst); - Par_PutHiddenParamLong (NULL,StrQstIndOrAns,QstCod); + Par_PutHiddenParamLong (NULL,StrAns,QstCod); } /*****************************************************************************/ @@ -5344,7 +3815,8 @@ unsigned long Tst_GetTagsQst (long QstCod,MYSQL_RES **mysql_res) { /***** Get the tags of a question from database *****/ return DB_QuerySELECT (mysql_res,"can not get the tags of a question", - "SELECT tst_tags.TagTxt FROM tst_question_tags,tst_tags" + "SELECT tst_tags.TagTxt" // row[0] + " FROM tst_question_tags,tst_tags" " WHERE tst_question_tags.QstCod=%ld" " AND tst_question_tags.TagCod=tst_tags.TagCod" " AND tst_tags.CrsCod=%ld" @@ -5364,7 +3836,7 @@ void Tst_GetAndWriteTagsQst (long QstCod) MYSQL_RES *mysql_res; MYSQL_ROW row; - if ((NumRows = Tst_GetTagsQst (QstCod,&mysql_res))) // Result: TagTxt + if ((NumRows = Tst_GetTagsQst (QstCod,&mysql_res))) { /***** Write the tags *****/ HTM_UL_Begin ("class=\"TEST_TAG_LIST DAT_SMALL\""); @@ -5434,7 +3906,7 @@ static bool Tst_GetParamsTst (struct Tst_Test *Test, /* Check number of types of answer */ if (Tst_CountNumAnswerTypesInList (&Test->AnswerTypes) == 0) // If no types of answer selected... - { // ...write warning alert + { // ...write warning alert Ale_ShowAlert (Ale_WARNING,Txt_You_must_select_one_ore_more_types_of_answer); Error = true; } @@ -5491,10 +3963,10 @@ static bool Tst_GetParamsTst (struct Tst_Test *Test, } /*****************************************************************************/ -/******************** Get parameter with the number of test ******************/ +/******** Get parameter with the number of test exam generated by me *********/ /*****************************************************************************/ -static unsigned Tst_GetAndCheckParamNumTst (void) +static unsigned Tst_GetParamNumTst (void) { return (unsigned) Par_GetParToUnsignedLong ("NumTst", 1, @@ -6103,7 +4575,7 @@ bool Tst_AllocateTextChoiceAnswer (struct Tst_Question *Question,unsigned NumOpt { // Tst_FreeTagsList (&Question->Tags); // TODO: Necessary? - Tst_FreeTextChoiceAnswer (Question,NumOpt); + // Tst_FreeTextChoiceAnswer (Question,NumOpt);// TODO: Necessary? if ((Question->Answer.Options[NumOpt].Text = (char *) malloc (Tst_MAX_BYTES_ANSWER_OR_FEEDBACK + 1)) == NULL) @@ -6194,7 +4666,7 @@ static void Tst_FreeMediaOfQuestion (struct Tst_Question *Question) /*************** Get answer type of a question from database *****************/ /*****************************************************************************/ -static Tst_AnswerType_t Tst_GetQstAnswerType (long QstCod) +Tst_AnswerType_t Tst_GetQstAnswerType (long QstCod) { MYSQL_RES *mysql_res; MYSQL_ROW row; @@ -6509,7 +4981,7 @@ static void Tst_GetQstFromForm (struct Tst_Question *Question, char TagStr[6 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; char AnsStr[6 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; char FbStr[5 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; - char StrMultiAns[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1]; + char StrMultiAns[TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1]; char TF[1 + 1]; // (T)rue or (F)alse const char *Ptr; unsigned NumCorrectAns; @@ -6661,7 +5133,7 @@ static void Tst_GetQstFromForm (struct Tst_Question *Question, } else if (Question->Answer.Type == Tst_ANS_MULTIPLE_CHOICE) { - Par_GetParMultiToText ("AnsMulti",StrMultiAns,Tst_MAX_BYTES_ANSWERS_ONE_QST); + Par_GetParMultiToText ("AnsMulti",StrMultiAns,TstExa_MAX_BYTES_ANSWERS_ONE_QST); Ptr = StrMultiAns; while (*Ptr) { diff --git a/swad_test.h b/swad_test.h index 96ea07ed..14e9f230 100644 --- a/swad_test.h +++ b/swad_test.h @@ -30,7 +30,7 @@ #include "swad_game.h" #include "swad_media.h" #include "swad_test_config.h" -#include "swad_test_result.h" +// #include "swad_test_exam.h" /*****************************************************************************/ /***************************** Public constants ******************************/ @@ -46,6 +46,8 @@ #define Tst_MAX_BYTES_ANSWER_TYPE 32 +#define Tst_MAX_OPTIONS_PER_QUESTION 10 + /*****************************************************************************/ /******************************* Public types ********************************/ /*****************************************************************************/ @@ -155,23 +157,8 @@ void Tst_ShowNewTest (void); void Tst_RequestAssessTest (void); void Tst_AssessTest (void); -void Tst_ComputeChoiceAnsScore (struct TsR_Result *Result, - unsigned NumQst, - struct Tst_Question *Question); -void Tst_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]); - -void Tst_ComputeAndShowGrade (unsigned NumQsts,double Score,double MaxGrade); -double Tst_ComputeGrade (unsigned NumQsts,double Score,double MaxGrade); -void Tst_ShowGrade (double Grade,double MaxGrade); - void Tst_ShowTagList (unsigned NumTags,MYSQL_RES *mysql_res); -void Tst_WriteQstAndAnsTestResult (struct UsrData *UsrDat, - struct TsR_Result *Result, - unsigned NumQst, - MYSQL_ROW row, - unsigned Visibility); void Tst_WriteNumQst (unsigned NumQst); void Tst_WriteAnswerType (Tst_AnswerType_t AnswerType); void Tst_WriteQstStem (const char *Stem,const char *ClassStem,bool Visible); @@ -191,6 +178,7 @@ void Tst_GetAnswersQst (struct Tst_Question *Question,MYSQL_RES **mysql_res, void Tst_WriteAnswersListing (struct Tst_Question *Question); bool Tst_CheckIfQuestionIsValidForGame (long QstCod); void Tst_WriteAnsTF (char AnsTF); +void Tst_GetChoiceAns (struct Tst_Question *Question,MYSQL_RES *mysql_res); void Tst_CheckIfNumberOfAnswersIsOne (const struct Tst_Question *Question); unsigned long Tst_GetTagsQst (long QstCod,MYSQL_RES **mysql_res); @@ -210,6 +198,7 @@ void Tst_QstDestructor (struct Tst_Question *Question); bool Tst_AllocateTextChoiceAnswer (struct Tst_Question *Question,unsigned NumOpt); +Tst_AnswerType_t Tst_GetQstAnswerType (long QstCod); Tst_AnswerType_t Tst_ConvertFromStrAnsTypDBToAnsTyp (const char *StrAnsTypeBD); void Tst_ReceiveQst (void); bool Tst_CheckIfQstFormatIsCorrectAndCountNumOptions (struct Tst_Question *Question); diff --git a/swad_test_config.c b/swad_test_config.c index fca6d747..f480656d 100644 --- a/swad_test_config.c +++ b/swad_test_config.c @@ -103,7 +103,7 @@ void TstCfg_GetConfigFromDB (void) Gbl.Hierarchy.Crs.CrsCod); TstCfg_SetConfigMinTimeNxtTstPerQst (0UL); - TstCfg_SetConfigVisibility (TsV_VISIBILITY_DEFAULT); + TstCfg_SetConfigVisibility (TstVis_VISIBILITY_DEFAULT); if (NumRows == 0) { TstCfg_SetConfigPluggable (TstCfg_PLUGGABLE_UNKNOWN); @@ -173,7 +173,7 @@ void TstCfg_GetConfigFromRow (MYSQL_ROW row) TstCfg_SetConfigMinTimeNxtTstPerQst (0UL); /***** Get visibility (row[5]) *****/ - TstCfg_SetConfigVisibility (TsV_GetVisibilityFromStr (row[5])); + TstCfg_SetConfigVisibility (TstVis_GetVisibilityFromStr (row[5])); } /*****************************************************************************/ @@ -219,7 +219,7 @@ void TstCfg_ReceiveConfigTst (void) 0)); /***** Get visibility from form *****/ - TstCfg_SetConfigVisibility (TsV_GetVisibilityFromForm ()); + TstCfg_SetConfigVisibility (TstVis_GetVisibilityFromForm ()); /***** Update database *****/ DB_QueryREPLACE ("can not save configuration of tests", diff --git a/swad_test_exam.c b/swad_test_exam.c new file mode 100644 index 00000000..fd04be0b --- /dev/null +++ b/swad_test_exam.c @@ -0,0 +1,2676 @@ +// swad_test_exam.c: test exams made by users + +/* + 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ñ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 boolean type +#include // For NULL +#include // For asprintf +#include // For free +#include // 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" +#include "swad_test.h" +#include "swad_test_exam.h" +#include "swad_test_visibility.h" +#include "swad_user.h" + +/*****************************************************************************/ +/***************************** Public constants ******************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/**************************** Private constants ******************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/******************************* Private 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; + +/*****************************************************************************/ +/************************* Private global variables **************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/***************************** Private prototypes ****************************/ +/*****************************************************************************/ + +static void TstExa_ComputeAnswerScore (struct TstExa_Exam *Exam, + unsigned NumQst, + struct Tst_Question *Question); +static void TstExa_ComputeIntAnsScore (struct TstExa_Exam *Exam, + unsigned NumQst, + struct Tst_Question *Question); +static void TstExa_GetCorrectIntAnswerFromDB (struct Tst_Question *Question); +static void TstExa_ComputeFloatAnsScore (struct TstExa_Exam *Exam, + unsigned NumQst, + struct Tst_Question *Question); +static void TstExa_GetCorrectFloatAnswerFromDB (struct Tst_Question *Question); +static void TstExa_ComputeTFAnsScore (struct TstExa_Exam *Exam, + unsigned NumQst, + struct Tst_Question *Question); +static void TstExa_GetCorrectTFAnswerFromDB (struct Tst_Question *Question); +static void TstExa_GetCorrectChoiceAnswerFromDB (struct Tst_Question *Question); + +static void TstExa_ComputeScoreQst (struct TstExa_Exam *Exam, + unsigned NumQst, + 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]); +static void TstExa_ComputeTextAnsScore (struct TstExa_Exam *Exam, + unsigned NumQst, + struct Tst_Question *Question); +static void TstExa_GetCorrectTextAnswerFromDB (struct Tst_Question *Question); + +static void TstExa_WriteAnswersExam (struct UsrData *UsrDat, + const struct TstExa_Exam *Exam, + unsigned NumQst, + struct Tst_Question *Question, + unsigned Visibility); +static void TstExa_WriteIntAnsExam (struct UsrData *UsrDat, + const struct TstExa_Exam *Exam, + unsigned NumQst, + const struct Tst_Question *Question, + MYSQL_RES *mysql_res, + unsigned Visibility); +static void TstExa_WriteFloatAnsExam (struct UsrData *UsrDat, + const struct TstExa_Exam *Exam, + unsigned NumQst, + const struct Tst_Question *Question, + MYSQL_RES *mysql_res, + unsigned Visibility); +static void TstExa_WriteTFAnsExam (struct UsrData *UsrDat, + const struct TstExa_Exam *Exam, + unsigned NumQst, + const struct Tst_Question *Question, + MYSQL_RES *mysql_res, + unsigned Visibility); +static void TstExa_WriteChoiceAnsExam (struct UsrData *UsrDat, + const struct TstExa_Exam *Exam, + unsigned NumQst, + struct Tst_Question *Question, + MYSQL_RES *mysql_res, + unsigned Visibility); +static void TstExa_WriteTextAnsExam (struct UsrData *UsrDat, + const struct TstExa_Exam *Exam, + unsigned NumQst, + struct Tst_Question *Question, + MYSQL_RES *mysql_res, + unsigned Visibility); +static void TstExa_WriteHeadUserCorrect (struct UsrData *UsrDat); +static void TstExa_WriteScoreStart (unsigned ColSpan); +static void TstExa_WriteScoreEnd (void); + +static void TstExa_StoreOneExamQstInDB (const struct TstExa_Exam *Exam, + unsigned NumQst); +static void Tst_UpdateQstScoreInDB (const struct TstExa_Exam *Exam,unsigned NumQst); + +static void TstExa_ShowUsrsExams (void); +static void TstExa_ShowHeaderExams (void); +static void TstExa_ShowExams (struct UsrData *UsrDat); +static void TstExa_ShowExamsSummaryRow (bool ItsMe, + unsigned NumExams, + unsigned NumTotalQsts, + unsigned NumTotalQstsNotBlank, + double TotalScoreOfAllTests); +static void TstExa_ShowTagsPresentInAnExam (long ExaCod); + +/*****************************************************************************/ +/***************** Create new blank test exam in database ********************/ +/*****************************************************************************/ + +void TstExa_CreateExamInDB (struct TstExa_Exam *Exam) + { + /***** Insert new test exam into table *****/ + Exam->ExaCod = + DB_QueryINSERTandReturnCode ("can not create new test exam", + "INSERT INTO tst_exams" + " (CrsCod,UsrCod,StartTime,EndTime,NumQsts,AllowTeachers)" + " VALUES" + " (%ld,%ld,NOW(),NOW(),%u,'%c')", + Gbl.Hierarchy.Crs.CrsCod, + Gbl.Usrs.Me.UsrDat.UsrCod, + Exam->NumQsts, + Exam->AllowTeachers ? 'Y' : + 'N'); + } + +/*****************************************************************************/ +/********************** Update test exam in database *************************/ +/*****************************************************************************/ + +void TstExa_UpdateExamInDB (const struct TstExa_Exam *Exam) + { + /***** Update score in test exam *****/ + 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," + "Score='%.15lg'" + " WHERE ExaCod=%ld" + " AND CrsCod=%ld AND UsrCod=%ld", // Extra checks + Exam->NumQstsNotBlank, + Exam->Score, + Exam->ExaCod, + Gbl.Hierarchy.Crs.CrsCod, + Gbl.Usrs.Me.UsrDat.UsrCod); + Str_SetDecimalPointToLocal (); // Return to local system + } + +/*****************************************************************************/ +/********************* Show test exam after assessing it *********************/ +/*****************************************************************************/ + +void TstExa_ShowExamAfterAssess (struct TstExa_Exam *Exam) + { + extern const char *Txt_Question_removed; + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NumQst; + + /***** Begin table *****/ + HTM_TABLE_BeginWideMarginPadding (10); + + /***** Initialize score and number of questions not blank *****/ + Exam->NumQstsNotBlank = 0; + Exam->Score = 0.0; + + for (NumQst = 0; + NumQst < Exam->NumQsts; + NumQst++) + { + Gbl.RowEvenOdd = NumQst % 2; + + /***** Query database *****/ + if (Tst_GetOneQuestionByCod (Exam->Questions[NumQst].QstCod,&mysql_res)) // Question exists + { + /***** Write question and answers *****/ + row = mysql_fetch_row (mysql_res); + TstExa_WriteQstAndAnsExam (&Gbl.Usrs.Me.UsrDat, + Exam, + NumQst, + row, + TstCfg_GetConfigVisibility ()); + + /***** Store test exam question in database *****/ + TstExa_StoreOneExamQstInDB (Exam, + NumQst); // 0, 1, 2, 3... + + /***** Compute total score *****/ + Exam->Score += Exam->Questions[NumQst].Score; + if (Exam->Questions[NumQst].AnswerIsNotBlank) + Exam->NumQstsNotBlank++; + + /***** Update the number of accesses and the score of this question *****/ + if (Gbl.Usrs.Me.Role.Logged == Rol_STD) + Tst_UpdateQstScoreInDB (Exam,NumQst); + } + else + { + /***** Question does not exists *****/ + HTM_TR_Begin (NULL); + + HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd); + Tst_WriteNumQst (NumQst + 1); + HTM_TD_End (); + + HTM_TD_Begin ("class=\"DAT_LIGHT LT COLOR%u\"",Gbl.RowEvenOdd); + HTM_Txt (Txt_Question_removed); + HTM_TD_End (); + + HTM_TR_End (); + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + + /***** End table *****/ + HTM_TABLE_End (); + } + +/*****************************************************************************/ +/********** Write a row of a test, with one question and its answer **********/ +/*****************************************************************************/ + +void TstExa_WriteQstAndAnsExam (struct UsrData *UsrDat, + struct TstExa_Exam *Exam, + unsigned NumQst, + MYSQL_ROW row, + unsigned Visibility) + { + struct Tst_Question Question; + bool IsVisibleQstAndAnsTxt = TstVis_IsVisibleQstAndAnsTxt (Visibility); + /* + row[0] UNIX_TIMESTAMP(EditTime) + row[1] AnsType + row[2] Shuffle + row[3] Stem + row[4] Feedback + row[5] MedCod + row[6] NumHits + row[7] NumHitsNotBlank + row[8] Score + */ + + /***** Create test question *****/ + Tst_QstConstructor (&Question); + Question.QstCod = Exam->Questions[NumQst].QstCod; + + /***** 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); + Question.Answer.Type = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[1]); + Tst_WriteAnswerType (Question.Answer.Type); + HTM_TD_End (); + + /***** Stem, media and answers *****/ + HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd); + + /* Stem (row[3]) */ + Tst_WriteQstStem (row[3],"TEST_EXA",IsVisibleQstAndAnsTxt); + + /* Media (row[5]) */ + if (IsVisibleQstAndAnsTxt) + { + Question.Media.MedCod = Str_ConvertStrCodToLongCod (row[5]); + Med_GetMediaDataByCod (&Question.Media); + Med_ShowMedia (&Question.Media, + "TEST_MED_SHOW_CONT", + "TEST_MED_SHOW"); + } + + /* Answers */ + TstExa_ComputeAnswerScore (Exam,NumQst,&Question); + TstExa_WriteAnswersExam (UsrDat, + Exam,NumQst,&Question, + Visibility); + + /* Question feedback (row[4]) */ + if (TstVis_IsVisibleFeedbackTxt (Visibility)) + Tst_WriteQstFeedback (row[4],"TEST_EXA_LIGHT"); + + HTM_TD_End (); + + /***** End row *****/ + HTM_TR_End (); + + /***** Destroy test question *****/ + Tst_QstDestructor (&Question); + } + +/*****************************************************************************/ +/*********** Compute score of each question and store in database ************/ +/*****************************************************************************/ + +void TstExa_ComputeScoresAndStoreExamQuestions (struct TstExa_Exam *Exam, + bool UpdateQstScore) + { + unsigned NumQst; + struct Tst_Question Question; + + /***** Initialize total score *****/ + Exam->Score = 0.0; + Exam->NumQstsNotBlank = 0; + + /***** Compute and store scores of all questions *****/ + for (NumQst = 0; + NumQst < Exam->NumQsts; + NumQst++) + { + /* Compute question score */ + Tst_QstConstructor (&Question); + Question.QstCod = Exam->Questions[NumQst].QstCod; + Question.Answer.Type = Tst_GetQstAnswerType (Question.QstCod); + TstExa_ComputeAnswerScore (Exam,NumQst,&Question); + Tst_QstDestructor (&Question); + + /* Store test exam question in database */ + TstExa_StoreOneExamQstInDB (Exam, + NumQst); // 0, 1, 2, 3... + + /* Accumulate total score */ + Exam->Score += Exam->Questions[NumQst].Score; + if (Exam->Questions[NumQst].AnswerIsNotBlank) + Exam->NumQstsNotBlank++; + + + /* Update the number of hits and the score of this question in tests database */ + if (UpdateQstScore) + Tst_UpdateQstScoreInDB (Exam,NumQst); + } + + /***** Store test exam in database *****/ + TstExa_UpdateExamInDB (Exam); + } + +/*****************************************************************************/ +/************* Write answers of a question when assessing a test *************/ +/*****************************************************************************/ + +static void TstExa_ComputeAnswerScore (struct TstExa_Exam *Exam, + unsigned NumQst, + struct Tst_Question *Question) + { + /***** Write answer depending on type *****/ + switch (Question->Answer.Type) + { + case Tst_ANS_INT: + TstExa_ComputeIntAnsScore (Exam,NumQst,Question); + break; + case Tst_ANS_FLOAT: + TstExa_ComputeFloatAnsScore (Exam,NumQst,Question); + break; + case Tst_ANS_TRUE_FALSE: + TstExa_ComputeTFAnsScore (Exam,NumQst,Question); + break; + case Tst_ANS_UNIQUE_CHOICE: + case Tst_ANS_MULTIPLE_CHOICE: + TstExa_ComputeChoiceAnsScore (Exam,NumQst,Question); + break; + case Tst_ANS_TEXT: + TstExa_ComputeTextAnsScore (Exam,NumQst,Question); + break; + default: + break; + } + } + +/*****************************************************************************/ +/**************** Write integer answer when assessing a test *****************/ +/*****************************************************************************/ + +static void TstExa_ComputeIntAnsScore (struct TstExa_Exam *Exam, + unsigned NumQst, + struct Tst_Question *Question) + { + long AnswerUsr; + + /***** Get the numerical value of the correct answer *****/ + TstExa_GetCorrectIntAnswerFromDB (Question); + + /***** Compute score *****/ + Exam->Questions[NumQst].Score = 0.0; // Default score for blank or wrong answer + Exam->Questions[NumQst].AnswerIsNotBlank = (Exam->Questions[NumQst].StrAnswers[0] != '\0'); + if (Exam->Questions[NumQst].AnswerIsNotBlank) // If user has answered the answer + if (sscanf (Exam->Questions[NumQst].StrAnswers,"%ld",&AnswerUsr) == 1) + if (AnswerUsr == Question->Answer.Integer) // Correct answer + Exam->Questions[NumQst].Score = 1.0; + } + +static void TstExa_GetCorrectIntAnswerFromDB (struct Tst_Question *Question) + { + 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 ******************/ +/*****************************************************************************/ + +static void TstExa_ComputeFloatAnsScore (struct TstExa_Exam *Exam, + unsigned NumQst, + struct Tst_Question *Question) + { + double AnswerUsr; + + /***** Get the numerical value of the minimum and maximum correct answers *****/ + TstExa_GetCorrectFloatAnswerFromDB (Question); + + /***** Compute score *****/ + Exam->Questions[NumQst].Score = 0.0; // Default score for blank or wrong answer + Exam->Questions[NumQst].AnswerIsNotBlank = (Exam->Questions[NumQst].StrAnswers[0] != '\0'); + if (Exam->Questions[NumQst].AnswerIsNotBlank) // If user has answered the answer + { + AnswerUsr = Str_GetDoubleFromStr (Exam->Questions[NumQst].StrAnswers); + // A bad formatted floating point answer will interpreted as 0.0 + Exam->Questions[NumQst].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) + } + } + +static void TstExa_GetCorrectFloatAnswerFromDB (struct Tst_Question *Question) + { + 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 **************/ +/*****************************************************************************/ + +static void TstExa_ComputeTFAnsScore (struct TstExa_Exam *Exam, + unsigned NumQst, + struct Tst_Question *Question) + { + /***** Get answer true or false *****/ + TstExa_GetCorrectTFAnswerFromDB (Question); + + /***** Compute score *****/ + Exam->Questions[NumQst].AnswerIsNotBlank = (Exam->Questions[NumQst].StrAnswers[0] != '\0'); + if (Exam->Questions[NumQst].AnswerIsNotBlank) // User has selected T or F + Exam->Questions[NumQst].Score = (Exam->Questions[NumQst].StrAnswers[0] == Question->Answer.TF) ? 1.0 : // Correct + -1.0; // Wrong + else + Exam->Questions[NumQst].Score = 0.0; + } + +static void TstExa_GetCorrectTFAnswerFromDB (struct Tst_Question *Question) + { + 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 *************/ +/*****************************************************************************/ + +void TstExa_ComputeChoiceAnsScore (struct TstExa_Exam *Exam, + unsigned NumQst, + 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 *****/ + TstExa_GetCorrectChoiceAnswerFromDB (Question); + + /***** Get indexes for this question from string *****/ + TstExa_GetIndexesFromStr (Exam->Questions[NumQst].StrIndexes,Indexes); + + /***** Get the user's answers for this question from string *****/ + TstExa_GetAnswersFromStr (Exam->Questions[NumQst].StrAnswers,UsrAnswers); + + /***** Compute the total score of this question *****/ + TstExa_ComputeScoreQst (Exam,NumQst,Question,Indexes,UsrAnswers); + } + +static void TstExa_GetCorrectChoiceAnswerFromDB (struct Tst_Question *Question) + { + 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 *********************/ +/*****************************************************************************/ + +void TstExa_GetIndexesFromStr (const char StrIndexesOneQst[TstExa_MAX_BYTES_INDEXES_ONE_QST + 1], // 0 1 2 3, 3 0 2 1, etc. + unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION]) + { + 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 *****************/ +/*****************************************************************************/ + +void TstExa_GetAnswersFromStr (const char StrAnswersOneQst[TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1], + 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 *********************/ +/*****************************************************************************/ + +static void TstExa_ComputeScoreQst (struct TstExa_Exam *Exam, + unsigned NumQst, + 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? */ + Exam->Questions[NumQst].AnswerIsNotBlank = NumAnsGood != 0 || NumAnsBad != 0; + if (Exam->Questions[NumQst].AnswerIsNotBlank) + { + /* Compute the score */ + if (Question->Answer.Type == Tst_ANS_UNIQUE_CHOICE) + { + if (NumOptTotInQst >= 2) // It should be 2 options at least + Exam->Questions[NumQst].Score = (double) NumAnsGood - + (double) NumAnsBad / (double) (NumOptTotInQst - 1); + else // 0 or 1 options (impossible) + Exam->Questions[NumQst].Score = (double) NumAnsGood; + } + 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) + Exam->Questions[NumQst].Score = (double) NumAnsGood / (double) NumOptCorrInQst - + (double) NumAnsBad / (double) (NumOptTotInQst - NumOptCorrInQst); + else // Si todas the opciones son correctas (caso raro) + Exam->Questions[NumQst].Score = (double) NumAnsGood / (double) NumOptCorrInQst; + } + else + { + if (NumOptTotInQst) // There are options but none is correct (extrange case) + Exam->Questions[NumQst].Score = - (double) NumAnsBad / (double) NumOptTotInQst; + else // There are no options (impossible!) + Exam->Questions[NumQst].Score = 0.0; + } + } + } + else // Answer is blank + Exam->Questions[NumQst].Score = 0.0; + } + +/*****************************************************************************/ +/********************* Compute score for text answer *************************/ +/*****************************************************************************/ + +static void TstExa_ComputeTextAnsScore (struct TstExa_Exam *Exam, + unsigned NumQst, + struct Tst_Question *Question) + { + unsigned NumOpt; + char TextAnsUsr[TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1]; + char TextAnsOK[TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1]; + bool Correct = false; + + /***** Get correct answers for this question from database *****/ + TstExa_GetCorrectTextAnswerFromDB (Question); + + /***** Compute score *****/ + Exam->Questions[NumQst].Score = 0.0; // Default score for blank or wrong answer + Exam->Questions[NumQst].AnswerIsNotBlank = (Exam->Questions[NumQst].StrAnswers[0] != '\0'); + if (Exam->Questions[NumQst].AnswerIsNotBlank) // If user has answered the answer + { + /* Filter the user answer */ + Str_Copy (TextAnsUsr,Exam->Questions[NumQst].StrAnswers, + TstExa_MAX_BYTES_ANSWERS_ONE_QST); + + /* 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, + TstExa_MAX_BYTES_ANSWERS_ONE_QST); + Str_ConvertToComparable (TextAnsOK); + + /* Check is user answer is correct */ + if (!strcoll (TextAnsUsr,TextAnsOK)) + { + Correct = true; + break; + } + } + + if (Correct) + Exam->Questions[NumQst].Score = 1.0; // Correct answer + } + } + +static void TstExa_GetCorrectTextAnswerFromDB (struct Tst_Question *Question) + { + 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 (); + + /***** Copy answer text (row[1]) and convert it, that is in HTML, to rigorous HTML ******/ + Str_Copy (Question->Answer.Options[NumOpt].Text,row[1], + Tst_MAX_BYTES_ANSWER_OR_FEEDBACK); + Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, + Question->Answer.Options[NumOpt].Text, + Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false); + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/************ Compute and show total grade out of maximum grade **************/ +/*****************************************************************************/ + +void TstExa_ComputeAndShowGrade (unsigned NumQsts,double Score,double MaxGrade) + { + TstExa_ShowGrade (TstExa_ComputeGrade (NumQsts,Score,MaxGrade),MaxGrade); + } + +/*****************************************************************************/ +/**************** Compute total grade out of maximum grade *******************/ +/*****************************************************************************/ + +double TstExa_ComputeGrade (unsigned NumQsts,double Score,double MaxGrade) + { + 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 ********************/ +/*****************************************************************************/ + +void TstExa_ShowGrade (double Grade,double MaxGrade) + { + /***** Write grade over maximum grade *****/ + HTM_Double2Decimals (Grade); + HTM_Txt ("/"); + HTM_Double2Decimals (MaxGrade); + } + +/*****************************************************************************/ +/************* Write answers of a question when assessing a test *************/ +/*****************************************************************************/ + +static void TstExa_WriteAnswersExam (struct UsrData *UsrDat, + const struct TstExa_Exam *Exam, + unsigned NumQst, + struct Tst_Question *Question, + unsigned Visibility) + { + MYSQL_RES *mysql_res; + + /***** Get answer of a question from database *****/ + Tst_GetAnswersQst (Question,&mysql_res, + false); // Don't shuffle + /* + row[0] AnsInd + row[1] Answer + row[2] Feedback + row[3] MedCod + row[4] Correct + */ + + /***** Write answer depending on type *****/ + switch (Question->Answer.Type) + { + case Tst_ANS_INT: + TstExa_WriteIntAnsExam (UsrDat,Exam, + NumQst,Question,mysql_res, + Visibility); + break; + case Tst_ANS_FLOAT: + TstExa_WriteFloatAnsExam (UsrDat,Exam, + NumQst,Question,mysql_res, + Visibility); + break; + case Tst_ANS_TRUE_FALSE: + TstExa_WriteTFAnsExam (UsrDat,Exam, + NumQst,Question,mysql_res, + Visibility); + break; + case Tst_ANS_UNIQUE_CHOICE: + case Tst_ANS_MULTIPLE_CHOICE: + TstExa_WriteChoiceAnsExam (UsrDat,Exam, + NumQst,Question,mysql_res, + Visibility); + break; + case Tst_ANS_TEXT: + TstExa_WriteTextAnsExam (UsrDat,Exam, + NumQst,Question,mysql_res, + Visibility); + break; + default: + break; + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/******************* Write integer answer in a test exam *********************/ +/*****************************************************************************/ + +static void TstExa_WriteIntAnsExam (struct UsrData *UsrDat, + const struct TstExa_Exam *Exam, + unsigned NumQst, + const struct Tst_Question *Question, + MYSQL_RES *mysql_res, + unsigned Visibility) + { + MYSQL_ROW row; + long IntAnswerUsr; + long IntAnswerCorr; + /* + row[0] AnsInd + row[1] Answer + row[2] Feedback + row[3] MedCod + row[4] Correct + */ + /***** Check if number of rows is correct *****/ + Tst_CheckIfNumberOfAnswersIsOne (Question); + + /***** Get the numerical value of the correct answer *****/ + row = mysql_fetch_row (mysql_res); + if (sscanf (row[1],"%ld",&IntAnswerCorr) != 1) + Lay_ShowErrorAndExit ("Wrong integer answer."); + + /***** Header with the title of each column *****/ + HTM_TABLE_BeginPadding (2); + HTM_TR_Begin (NULL); + TstExa_WriteHeadUserCorrect (UsrDat); + HTM_TR_End (); + + HTM_TR_Begin (NULL); + + /***** Write the user answer *****/ + if (Exam->Questions[NumQst].StrAnswers[0]) // If user has answered the question + { + if (sscanf (Exam->Questions[NumQst].StrAnswers,"%ld",&IntAnswerUsr) == 1) + { + HTM_TD_Begin ("class=\"%s CM\"", + TstVis_IsVisibleCorrectAns (Visibility) ? + (IntAnswerUsr == IntAnswerCorr ? "ANS_OK" : + "ANS_BAD") : + "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)) + HTM_Long (IntAnswerCorr); + else + Ico_PutIconNotVisible (); + HTM_TD_End (); + + HTM_TR_End (); + + /***** Write the score of this question *****/ + if (TstVis_IsVisibleEachQstScore (Visibility)) + { + TstExa_WriteScoreStart (2); + if (!Exam->Questions[NumQst].StrAnswers[0]) // If user has omitted the answer + { + HTM_SPAN_Begin ("class=\"ANS_0\""); + HTM_Double2Decimals (0.0); + } + else if (IntAnswerUsr == IntAnswerCorr) // If correct + { + HTM_SPAN_Begin ("class=\"ANS_OK\""); + HTM_Double2Decimals (1.0); + } + else // If wrong + { + HTM_SPAN_Begin ("class=\"ANS_BAD\""); + HTM_Double2Decimals (0.0); + } + HTM_SPAN_End (); + TstExa_WriteScoreEnd (); + } + + HTM_TABLE_End (); + } + +/*****************************************************************************/ +/******************** Write float answer in an test exam *********************/ +/*****************************************************************************/ + +static void TstExa_WriteFloatAnsExam (struct UsrData *UsrDat, + const struct TstExa_Exam *Exam, + unsigned NumQst, + const struct Tst_Question *Question, + MYSQL_RES *mysql_res, + unsigned Visibility) + { + MYSQL_ROW row; + unsigned i; + double FloatAnsUsr = 0.0,Tmp; + double FloatAnsCorr[2]; + /* + row[0] AnsInd + row[1] Answer + row[2] Feedback + row[3] MedCod + row[4] Correct + */ + /***** Check if number of rows is correct *****/ + if (Question->Answer.NumOptions != 2) + Lay_ShowErrorAndExit ("Wrong float range."); + + /***** Get the numerical value of the minimum and maximum correct answers *****/ + for (i = 0; + i < 2; + i++) + { + row = mysql_fetch_row (mysql_res); + FloatAnsCorr[i] = Str_GetDoubleFromStr (row[1]); + } + if (FloatAnsCorr[0] > FloatAnsCorr[1]) // The maximum and the minimum are swapped + { + /* Swap maximum and minimum */ + Tmp = FloatAnsCorr[0]; + FloatAnsCorr[0] = FloatAnsCorr[1]; + FloatAnsCorr[1] = Tmp; + } + + /***** Header with the title of each column *****/ + HTM_TABLE_BeginPadding (2); + HTM_TR_Begin (NULL); + TstExa_WriteHeadUserCorrect (UsrDat); + HTM_TR_End (); + + HTM_TR_Begin (NULL); + + /***** Write the user answer *****/ + if (Exam->Questions[NumQst].StrAnswers[0]) // If user has answered the question + { + FloatAnsUsr = Str_GetDoubleFromStr (Exam->Questions[NumQst].StrAnswers); + // A bad formatted floating point answer will interpreted as 0.0 + HTM_TD_Begin ("class=\"%s CM\"", + TstVis_IsVisibleCorrectAns (Visibility) ? + ((FloatAnsUsr >= FloatAnsCorr[0] && + FloatAnsUsr <= FloatAnsCorr[1]) ? "ANS_OK" : + "ANS_BAD") : + "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 ("["); + HTM_Double (FloatAnsCorr[0]); + HTM_Txt ("; "); + HTM_Double (FloatAnsCorr[1]); + HTM_Txt ("]"); + } + else + Ico_PutIconNotVisible (); + HTM_TD_End (); + + HTM_TR_End (); + + /***** Write the score of this question *****/ + if (TstVis_IsVisibleEachQstScore (Visibility)) + { + TstExa_WriteScoreStart (2); + if (!Exam->Questions[NumQst].StrAnswers[0]) // If user has omitted the answer + { + HTM_SPAN_Begin ("class=\"ANS_0\""); + HTM_Double2Decimals (0.0); + } + else if (FloatAnsUsr >= FloatAnsCorr[0] && + FloatAnsUsr <= FloatAnsCorr[1]) // If correct (inside the interval) + { + HTM_SPAN_Begin ("class=\"ANS_OK\""); + HTM_Double2Decimals (1.0); + } + else // If wrong (outside the interval) + { + HTM_SPAN_Begin ("class=\"ANS_BAD\""); + HTM_Double2Decimals (0.0); + } + HTM_SPAN_End (); + TstExa_WriteScoreEnd (); + } + + HTM_TABLE_End (); + } + +/*****************************************************************************/ +/***************** Write false / true answer in a test exam ******************/ +/*****************************************************************************/ + +static void TstExa_WriteTFAnsExam (struct UsrData *UsrDat, + const struct TstExa_Exam *Exam, + unsigned NumQst, + const struct Tst_Question *Question, + MYSQL_RES *mysql_res, + unsigned Visibility) + { + MYSQL_ROW row; + char AnsTF; + /* + row[0] AnsInd + row[1] Answer + row[2] Feedback + row[3] MedCod + row[4] Correct + */ + /***** Check if number of rows is correct *****/ + Tst_CheckIfNumberOfAnswersIsOne (Question); + + /***** Get answer true or false *****/ + row = mysql_fetch_row (mysql_res); + AnsTF = Exam->Questions[NumQst].StrAnswers[0]; + + /***** Header with the title of each column *****/ + HTM_TABLE_BeginPadding (2); + HTM_TR_Begin (NULL); + TstExa_WriteHeadUserCorrect (UsrDat); + HTM_TR_End (); + + HTM_TR_Begin (NULL); + + /***** Write the user answer *****/ + HTM_TD_Begin ("class=\"%s CM\"", + TstVis_IsVisibleCorrectAns (Visibility) ? + (AnsTF == row[1][0] ? "ANS_OK" : + "ANS_BAD") : + "ANS_0"); + Tst_WriteAnsTF (AnsTF); + HTM_TD_End (); + + /***** Write the correct answer *****/ + HTM_TD_Begin ("class=\"ANS_0 CM\""); + if (TstVis_IsVisibleQstAndAnsTxt (Visibility) && + TstVis_IsVisibleCorrectAns (Visibility)) + Tst_WriteAnsTF (row[1][0]); + else + Ico_PutIconNotVisible (); + HTM_TD_End (); + + HTM_TR_End (); + + /***** Write the score of this question *****/ + if (TstVis_IsVisibleEachQstScore (Visibility)) + { + TstExa_WriteScoreStart (2); + if (AnsTF == '\0') // If user has omitted the answer + { + HTM_SPAN_Begin ("class=\"ANS_0\""); + HTM_Double2Decimals (0.0); + } + else if (AnsTF == row[1][0]) // If correct + { + HTM_SPAN_Begin ("class=\"ANS_OK\""); + HTM_Double2Decimals (1.0); + } + else // If wrong + { + HTM_SPAN_Begin ("class=\"ANS_BAD\""); + HTM_Double2Decimals (-1.0); + } + HTM_SPAN_End (); + TstExa_WriteScoreEnd (); + } + + HTM_TABLE_End (); + } + +/*****************************************************************************/ +/********** Write single or multiple choice answer in a test exam ************/ +/*****************************************************************************/ + +static void TstExa_WriteChoiceAnsExam (struct UsrData *UsrDat, + const struct TstExa_Exam *Exam, + unsigned NumQst, + struct Tst_Question *Question, + MYSQL_RES *mysql_res, + unsigned Visibility) + { + extern const char *Txt_TST_Answer_given_by_the_user; + 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 text and correctness of answers for this question + from database (one row per answer) *****/ + /* + row[0] AnsInd + row[1] Answer + row[2] Feedback + row[3] MedCod + row[4] Correct + */ + Tst_GetChoiceAns (Question,mysql_res); + + /***** Get indexes for this question from string *****/ + TstExa_GetIndexesFromStr (Exam->Questions[NumQst].StrIndexes,Indexes); + + /***** Get the user's answers for this question from string *****/ + TstExa_GetAnswersFromStr (Exam->Questions[NumQst].StrAnswers,UsrAnswers); + + /***** Begin table *****/ + HTM_TABLE_BeginPadding (2); + HTM_TR_Begin (NULL); + TstExa_WriteHeadUserCorrect (UsrDat); + 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 = "✓"; + } + else + { + Ans.Class = "ANS_BAD"; + Ans.Str = "✗"; + } + } + else + { + Ans.Class = "ANS_0"; + Ans.Str = "•"; + } + + 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 ("•"); + 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) ",'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 (); + } + + /***** Write the score of this question *****/ + if (TstVis_IsVisibleEachQstScore (Visibility)) + { + TstExa_WriteScoreStart (4); + if (Exam->Questions[NumQst].Score == 0.0) + HTM_SPAN_Begin ("class=\"ANS_0\""); + else if (Exam->Questions[NumQst].Score > 0.0) + HTM_SPAN_Begin ("class=\"ANS_OK\""); + else + HTM_SPAN_Begin ("class=\"ANS_BAD\""); + HTM_Double2Decimals (Exam->Questions[NumQst].Score); + HTM_SPAN_End (); + TstExa_WriteScoreEnd (); + } + + /***** End table *****/ + HTM_TABLE_End (); + } + +/*****************************************************************************/ +/***************** Write text answer when assessing a test *******************/ +/*****************************************************************************/ + +static void TstExa_WriteTextAnsExam (struct UsrData *UsrDat, + const struct TstExa_Exam *Exam, + unsigned NumQst, + struct Tst_Question *Question, + MYSQL_RES *mysql_res, + unsigned Visibility) + { + unsigned NumOpt; + MYSQL_ROW row; + char TextAnsUsr[TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1]; + char TextAnsOK[TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1]; + bool Correct = false; + /* + row[0] AnsInd + row[1] Answer + row[2] Feedback + row[3] MedCod + row[4] Correct + */ + /***** Get text and correctness of answers for this question from database (one row per answer) *****/ + for (NumOpt = 0; + NumOpt < Question->Answer.NumOptions; + NumOpt++) + { + /***** Get next answer *****/ + row = mysql_fetch_row (mysql_res); + + /***** Allocate memory for text in this choice answer *****/ + if (!Tst_AllocateTextChoiceAnswer (Question,NumOpt)) + /* Abort on error */ + Ale_ShowAlertsAndExit (); + + /***** Copy answer text (row[1]) and convert it, that is in HTML, to rigorous HTML ******/ + Str_Copy (Question->Answer.Options[NumOpt].Text,row[1], + Tst_MAX_BYTES_ANSWER_OR_FEEDBACK); + Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, + Question->Answer.Options[NumOpt].Text, + Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false); + + /***** Copy answer feedback (row[2]) and convert it, that is in HTML, to rigorous HTML ******/ + if (TstVis_IsVisibleFeedbackTxt (Visibility)) + if (row[2]) + if (row[2][0]) + { + Str_Copy (Question->Answer.Options[NumOpt].Feedback,row[2], + Tst_MAX_BYTES_ANSWER_OR_FEEDBACK); + Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, + Question->Answer.Options[NumOpt].Feedback, + Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false); + } + + /***** Assign correctness (row[4]) of this answer (this option) *****/ + Question->Answer.Options[NumOpt].Correct = (row[4][0] == 'Y'); + } + + /***** Header with the title of each column *****/ + HTM_TABLE_BeginPadding (2); + HTM_TR_Begin (NULL); + TstExa_WriteHeadUserCorrect (UsrDat); + HTM_TR_End (); + + HTM_TR_Begin (NULL); + + /***** Write the user answer *****/ + if (Exam->Questions[NumQst].StrAnswers[0]) // If user has answered the question + { + /* Filter the user answer */ + Str_Copy (TextAnsUsr,Exam->Questions[NumQst].StrAnswers, + TstExa_MAX_BYTES_ANSWERS_ONE_QST); + + /* 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, + TstExa_MAX_BYTES_ANSWERS_ONE_QST); + 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"); + HTM_Txt (Exam->Questions[NumQst].StrAnswers); + } + 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) ",'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 (); + + /***** Write the score of this question *****/ + if (TstVis_IsVisibleEachQstScore (Visibility)) + { + TstExa_WriteScoreStart (4); + if (!Exam->Questions[NumQst].StrAnswers[0]) // If user has omitted the answer + { + HTM_SPAN_Begin ("class=\"ANS_0\""); + HTM_Double2Decimals (0.0); + } + else if (Correct) // If correct + { + HTM_SPAN_Begin ("class=\"ANS_OK\""); + HTM_Double2Decimals (1.0); + } + else // If wrong + { + HTM_SPAN_Begin ("class=\"ANS_BAD\""); + HTM_Double2Decimals (0.0); + } + HTM_SPAN_End (); + TstExa_WriteScoreEnd (); + } + + HTM_TABLE_End (); + } + +/*****************************************************************************/ +/********* Write head with two columns: ********/ +/********* one for the user's answer and other for the correct answer ********/ +/*****************************************************************************/ + +static void TstExa_WriteHeadUserCorrect (struct UsrData *UsrDat) + { + 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 (); + } + +/*****************************************************************************/ +/*********** Write the start ans the end of the score of an answer ***********/ +/*****************************************************************************/ + +static void TstExa_WriteScoreStart (unsigned ColSpan) + { + extern const char *Txt_Score; + + HTM_TR_Begin (NULL); + HTM_TD_Begin ("colspan=\"%u\" class=\"DAT_SMALL LM\"",ColSpan); + HTM_TxtColonNBSP (Txt_Score); + } + +static void TstExa_WriteScoreEnd (void) + { + HTM_TD_End (); + HTM_TR_End (); + } + +/*****************************************************************************/ +/************* Store user's answers of an test exam into database ************/ +/*****************************************************************************/ + +static void TstExa_StoreOneExamQstInDB (const struct TstExa_Exam *Exam, + unsigned NumQst) + { + char StrIndexes[TstExa_MAX_BYTES_INDEXES_ONE_QST + 1]; + char StrAnswers[TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1]; + + /***** Replace each separator of multiple parameters by a comma *****/ + /* In database commas are used as separators instead of special chars */ + Par_ReplaceSeparatorMultipleByComma (Exam->Questions[NumQst].StrIndexes,StrIndexes); + Par_ReplaceSeparatorMultipleByComma (Exam->Questions[NumQst].StrAnswers,StrAnswers); + + /***** 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')", + Exam->ExaCod,Exam->Questions[NumQst].QstCod, + NumQst, // 0, 1, 2, 3... + Exam->Questions[NumQst].Score, + StrIndexes, + StrAnswers); + Str_SetDecimalPointToLocal (); // Return to local system + } + +/*****************************************************************************/ +/*********************** Update the score of a question **********************/ +/*****************************************************************************/ + +static void Tst_UpdateQstScoreInDB (const struct TstExa_Exam *Exam,unsigned NumQst) + { + /***** Update number of clicks and score of the question *****/ + Str_SetDecimalPointToUS (); // To print the floating point as a dot + if (Exam->Questions[NumQst].AnswerIsNotBlank) + DB_QueryUPDATE ("can not update the score of a question", + "UPDATE tst_questions" + " SET NumHits=NumHits+1,NumHitsNotBlank=NumHitsNotBlank+1," + "Score=Score+(%.15lg)" + " WHERE QstCod=%ld", + Exam->Questions[NumQst].Score, + Exam->Questions[NumQst].QstCod); + else // The answer is blank + DB_QueryUPDATE ("can not update the score of a question", + "UPDATE tst_questions" + " SET NumHits=NumHits+1" + " WHERE QstCod=%ld", + Exam->Questions[NumQst].QstCod); + Str_SetDecimalPointToLocal (); // Return to local system + } + +/*****************************************************************************/ +/************* Select users and dates to show their test exams ***************/ +/*****************************************************************************/ + +void TstExa_SelUsrsToViewUsrsExams (void) + { + extern const char *Hlp_ASSESSMENT_Tests_results; + extern const char *Txt_Results; + extern const char *Txt_View_test_results; + + Usr_PutFormToSelectUsrsToGoToAct (&Gbl.Usrs.Selected, + ActSeeUsrTstRes, + NULL,NULL, + Txt_Results, + Hlp_ASSESSMENT_Tests_results, + Txt_View_test_results, + true); // Put form with date range + } + +/*****************************************************************************/ +/******************** Select dates to show my test exams *********************/ +/*****************************************************************************/ + +void TstExa_SelDatesToSeeMyExams (void) + { + 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] = + { + Dat_HMS_DO_NOT_SET, + Dat_HMS_DO_NOT_SET + }; + + /***** 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 ****************************/ +/*****************************************************************************/ + +void TstExa_ShowMyExams (void) + { + 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 *****/ + TstExa_ShowHeaderExams (); + + /***** List my test exams *****/ + TstCfg_GetConfigFromDB (); // To get feedback type + TstExa_ShowExams (&Gbl.Usrs.Me.UsrDat); + + /***** End table and box *****/ + Box_BoxTableEnd (); + } + +/*****************************************************************************/ +/******************** Get users and show their test exams ********************/ +/*****************************************************************************/ + +void TstExa_GetUsrsAndShowExams (void) + { + Usr_GetSelectedUsrsAndGoToAct (&Gbl.Usrs.Selected, + TstExa_ShowUsrsExams, + TstExa_SelUsrsToViewUsrsExams); + } + +/*****************************************************************************/ +/********************* Show test exams for several users *********************/ +/*****************************************************************************/ + +static void TstExa_ShowUsrsExams (void) + { + 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 *****/ + TstExa_ShowHeaderExams (); + + /***** 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)) + if (Usr_CheckIfICanViewTst (&Gbl.Usrs.Other.UsrDat)) + { + /***** Show test exams *****/ + Gbl.Usrs.Other.UsrDat.Accepted = Usr_CheckIfUsrHasAcceptedInCurrentCrs (&Gbl.Usrs.Other.UsrDat); + TstExa_ShowExams (&Gbl.Usrs.Other.UsrDat); + } + } + + /***** End table and box *****/ + Box_BoxTableEnd (); + } + +/*****************************************************************************/ +/************************ Show header of my test exams ***********************/ +/*****************************************************************************/ + +static void TstExa_ShowHeaderExams (void) + { + 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 ************/ +/*****************************************************************************/ + +static void TstExa_ShowExams (struct UsrData *UsrDat) + { + 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; + struct TstExa_Exam Exam; + unsigned NumTotalQsts = 0; + unsigned NumTotalQstsNotBlank = 0; + double TotalScoreOfAllTests = 0.0; + unsigned NumExamsVisibleByTchs = 0; + bool ItsMe = Usr_ItsMe (UsrDat->UsrCod); + bool ICanViewTest; + bool ICanViewScore; + 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] + "AllowTeachers," // row[5] + "Score" // row[6] + " 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]) */ + if ((Exam.ExaCod = Str_ConvertStrCodToLongCod (row[0])) < 0) + Lay_ShowErrorAndExit ("Wrong code of test exam."); + + /* Get if teachers are allowed to see this test exams (row[5]) */ + Exam.AllowTeachers = (row[5][0] == 'Y'); + ClassDat = Exam.AllowTeachers ? "DAT" : + "DAT_LIGHT"; + + switch (Gbl.Usrs.Me.Role.Logged) + { + case Rol_STD: + ICanViewTest = ItsMe; + ICanViewScore = ItsMe && + TstVis_IsVisibleTotalScore (TstCfg_GetConfigVisibility ()); + break; + case Rol_NET: + case Rol_TCH: + case Rol_DEG_ADM: + case Rol_CTR_ADM: + case Rol_INS_ADM: + ICanViewTest = + ICanViewScore = ItsMe || + Exam.AllowTeachers; + break; + case Rol_SYS_ADM: + ICanViewTest = + ICanViewScore = true; + break; + default: + ICanViewTest = + ICanViewScore = false; + break; + } + + if (NumExam) + HTM_TR_Begin (NULL); + + /* Write date and time (row[1] and row[2] hold UTC date-times) */ + Exam.TimeUTC[Dat_START_TIME] = Dat_GetUNIXTimeFromStr (row[1]); + Exam.TimeUTC[Dat_END_TIME ] = Dat_GetUNIXTimeFromStr (row[2]); + 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); + Dat_WriteLocalDateHMSFromUTC (Id,Exam.TimeUTC[StartEndTime], + Gbl.Prefs.DateFormat,Dat_SEPARATOR_BREAK, + true,true,false,0x7); + HTM_TD_End (); + free (Id); + } + + /* Get number of questions (row[3]) */ + if (sscanf (row[3],"%u",&Exam.NumQsts) != 1) + Exam.NumQsts = 0; + if (Exam.AllowTeachers) + NumTotalQsts += Exam.NumQsts; + + /* Get number of questions not blank (row[4]) */ + if (sscanf (row[4],"%u",&Exam.NumQstsNotBlank) != 1) + Exam.NumQstsNotBlank = 0; + if (Exam.AllowTeachers) + NumTotalQstsNotBlank += Exam.NumQstsNotBlank; + + /* Get score (row[6]) */ + Str_SetDecimalPointToUS (); // To get the decimal point as a dot + if (sscanf (row[6],"%lf",&Exam.Score) != 1) + Exam.Score = 0.0; + Str_SetDecimalPointToLocal (); // Return to local system + if (Exam.AllowTeachers) + TotalScoreOfAllTests += Exam.Score; + + /* Write number of questions */ + HTM_TD_Begin ("class=\"%s RT COLOR%u\"",ClassDat,Gbl.RowEvenOdd); + if (ICanViewTest) + HTM_Unsigned (Exam.NumQsts); + HTM_TD_End (); + + /* Write number of questions not blank */ + HTM_TD_Begin ("class=\"%s RT COLOR%u\"",ClassDat,Gbl.RowEvenOdd); + if (ICanViewTest) + HTM_Unsigned (Exam.NumQstsNotBlank); + HTM_TD_End (); + + /* Write score */ + HTM_TD_Begin ("class=\"%s RT COLOR%u\"",ClassDat,Gbl.RowEvenOdd); + if (ICanViewScore) + HTM_Double2Decimals (Exam.Score); + HTM_TD_End (); + + /* Write average score per question */ + HTM_TD_Begin ("class=\"%s RT COLOR%u\"",ClassDat,Gbl.RowEvenOdd); + if (ICanViewScore) + HTM_Double2Decimals (Exam.NumQsts ? Exam.Score / + (double) Exam.NumQsts : + 0.0); + HTM_TD_End (); + + /* Write grade */ + HTM_TD_Begin ("class=\"%s RT COLOR%u\"",ClassDat,Gbl.RowEvenOdd); + if (ICanViewScore) + TstExa_ComputeAndShowGrade (Exam.NumQsts, + Exam.Score, + TstExa_SCORE_MAX); + HTM_TD_End (); + + /* Link to show this test exam */ + HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd); + if (ICanViewTest) + { + Frm_StartForm (Gbl.Action.Act == ActSeeMyTstRes ? ActSeeOneTstResMe : + ActSeeOneTstResOth); + TstExa_PutParamExaCod (Exam.ExaCod); + Ico_PutIconLink ("tasks.svg",Txt_View_test); + Frm_EndForm (); + } + HTM_TD_End (); + HTM_TR_End (); + + if (Exam.AllowTeachers) + NumExamsVisibleByTchs++; + } + + /***** Write totals for this user *****/ + TstExa_ShowExamsSummaryRow (ItsMe,NumExamsVisibleByTchs, + NumTotalQsts,NumTotalQstsNotBlank, + TotalScoreOfAllTests); + } + else + { + HTM_TD_ColouredEmpty (7); + HTM_TR_End (); + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + Gbl.RowEvenOdd = 1 - Gbl.RowEvenOdd; + } + +/*****************************************************************************/ +/****************** Write parameter with code of test exam *******************/ +/*****************************************************************************/ + +void TstExa_PutParamExaCod (long ExaCod) + { + Par_PutHiddenParamLong (NULL,"ExaCod",ExaCod); + } + +/*****************************************************************************/ +/****************** Get parameter with code of test exam *********************/ +/*****************************************************************************/ + +long TstExa_GetParamExaCod (void) + { + /***** Get code of exam *****/ + return Par_GetParToLong ("ExaCod"); + } + +/*****************************************************************************/ +/**************** Show row with summary of user's test exams *****************/ +/*****************************************************************************/ + +static void TstExa_ShowExamsSummaryRow (bool ItsMe, + 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) + TstExa_ComputeAndShowGrade (NumTotalQsts, + TotalScoreOfAllTests, + TstExa_SCORE_MAX); + 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 *********************/ +/*****************************************************************************/ + +void TstExa_ShowOneExam (void) + { + 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; + struct TstExa_Exam Exam; + 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 *****/ + if ((Exam.ExaCod = TstExa_GetParamExaCod ()) == -1L) + Lay_ShowErrorAndExit ("Code of test is missing."); + + /***** Get test exam data *****/ + TstExa_GetExamDataByExaCod (&Exam); + 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 || + Exam.AllowTeachers; + 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 *****/ + TstExa_GetExamQuestionsFromDB (&Exam); + + /***** 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); + if (!Usr_CheckIfICanViewTst (&Gbl.Usrs.Other.UsrDat)) + 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 (" %s",Gbl.Usrs.Other.UsrDat.Surname1); + if (Gbl.Usrs.Other.UsrDat.Surname2[0]) + HTM_TxtF (" %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); + Dat_WriteLocalDateHMSFromUTC (Id,Exam.TimeUTC[StartEndTime], + 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)", + Exam.NumQsts, + Exam.NumQstsNotBlank,Txt_non_blank_QUESTIONS); + 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) + HTM_Double2Decimals (Exam.Score); + 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) + TstExa_ComputeAndShowGrade (Exam.NumQsts, + Exam.Score, + TstExa_SCORE_MAX); + 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\""); + TstExa_ShowTagsPresentInAnExam (Exam.ExaCod); + HTM_TD_End (); + + HTM_TR_End (); + + /***** Write answers and solutions *****/ + TstExa_ShowExamAnswers (&Gbl.Usrs.Other.UsrDat, + &Exam, + 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); + HTM_Double2Decimals (Exam.Score); + HTM_BR (); + HTM_TxtColonNBSP (Txt_Grade); + TstExa_ComputeAndShowGrade (Exam.NumQsts, + Exam.Score, + TstExa_SCORE_MAX); + 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 **********************/ +/*****************************************************************************/ + +static void TstExa_ShowTagsPresentInAnExam (long ExaCod) + { + 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", + ExaCod); + 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 ***************/ +/*****************************************************************************/ + +void TstExa_ShowExamAnswers (struct UsrData *UsrDat, + struct TstExa_Exam *Exam, + unsigned Visibility) + { + extern const char *Txt_Question_modified; + extern const char *Txt_Question_removed; + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NumQst; + bool ThisQuestionHasBeenEdited; + time_t EditTimeUTC; + + for (NumQst = 0; + NumQst < Exam->NumQsts; + NumQst++) + { + Gbl.RowEvenOdd = NumQst % 2; + + /***** Query database *****/ + if (Tst_GetOneQuestionByCod (Exam->Questions[NumQst].QstCod,&mysql_res)) // Question exists + { + /***** Get row of the result of the query *****/ + row = mysql_fetch_row (mysql_res); + + /***** If this question has been edited later than test time + ==> don't show question ****/ + EditTimeUTC = Dat_GetUNIXTimeFromStr (row[0]); + ThisQuestionHasBeenEdited = false; + if (EditTimeUTC > Exam->TimeUTC[Dat_START_TIME]) + ThisQuestionHasBeenEdited = true; + + if (ThisQuestionHasBeenEdited) + { + /***** Question has been edited *****/ + HTM_TR_Begin (NULL); + + HTM_TD_Begin ("class=\"BIG_INDEX RT COLOR%u\"",Gbl.RowEvenOdd); + HTM_Unsigned (NumQst + 1); + HTM_TD_End (); + + HTM_TD_Begin ("class=\"DAT_LIGHT LT COLOR%u\"",Gbl.RowEvenOdd); + HTM_Txt (Txt_Question_modified); + HTM_TD_End (); + + HTM_TR_End (); + } + else + /***** Write questions and answers *****/ + TstExa_WriteQstAndAnsExam (UsrDat, + Exam, + NumQst, + row, + Visibility); + } + else + { + /***** Question does not exists *****/ + HTM_TR_Begin (NULL); + + HTM_TD_Begin ("class=\"BIG_INDEX RT COLOR%u\"",Gbl.RowEvenOdd); + HTM_Unsigned (NumQst + 1); + HTM_TD_End (); + + HTM_TD_Begin ("class=\"DAT_LIGHT LT COLOR%u\"",Gbl.RowEvenOdd); + HTM_Txt (Txt_Question_removed); + HTM_TD_End (); + + HTM_TR_End (); + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + } + +/*****************************************************************************/ +/************ Get data of a test exam using its test exam code ***************/ +/*****************************************************************************/ + +void TstExa_GetExamDataByExaCod (struct TstExa_Exam *Exam) + { + 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] + "AllowTeachers," // row[5] + "Score" // row[6] + " FROM tst_exams" + " WHERE ExaCod=%ld AND CrsCod=%ld", + Exam->ExaCod, + 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) */ + Exam->TimeUTC[Dat_START_TIME] = Dat_GetUNIXTimeFromStr (row[1]); + Exam->TimeUTC[Dat_END_TIME ] = Dat_GetUNIXTimeFromStr (row[2]); + + /* Get number of questions (row[3]) */ + if (sscanf (row[3],"%u",&Exam->NumQsts) != 1) + Exam->NumQsts = 0; + + /* Get number of questions not blank (row[4]) */ + if (sscanf (row[4],"%u",&Exam->NumQstsNotBlank) != 1) + Exam->NumQstsNotBlank = 0; + + /* Get if teachers are allowed to see this test exam (row[5]) */ + Exam->AllowTeachers = (row[5][0] == 'Y'); + + /* Get score (row[6]) */ + Str_SetDecimalPointToUS (); // To get the decimal point as a dot + if (sscanf (row[6],"%lf",&Exam->Score) != 1) + Exam->Score = 0.0; + Str_SetDecimalPointToLocal (); // Return to local system + } + else + { + Exam->TimeUTC[Dat_START_TIME] = + Exam->TimeUTC[Dat_END_TIME ] = 0; + Exam->NumQsts = 0; + Exam->NumQstsNotBlank = 0; + Exam->AllowTeachers = false; + Exam->Score = 0.0; + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/************* Get the questions of a test exam from database ****************/ +/*****************************************************************************/ + +void TstExa_GetExamQuestionsFromDB (struct TstExa_Exam *Exam) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NumQst; + + /***** Get questions of a test exam from database *****/ + Exam->NumQsts = + (unsigned) DB_QuerySELECT (&mysql_res,"can not get questions" + " of a test exam", + "SELECT QstCod," // row[0] + "Indexes," // row[1] + "Answers" // row[2] + " FROM tst_exam_questions" + " WHERE ExaCod=%ld" + " ORDER BY QstInd", + Exam->ExaCod); + + /***** Get questions codes *****/ + for (NumQst = 0; + NumQst < Exam->NumQsts; + NumQst++) + { + row = mysql_fetch_row (mysql_res); + + /* Get question code */ + if ((Exam->Questions[NumQst].QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0) + Lay_ShowErrorAndExit ("Wrong code of question."); + + /* Get indexes for this question (row[1]) */ + Str_Copy (Exam->Questions[NumQst].StrIndexes,row[1], + TstExa_MAX_BYTES_INDEXES_ONE_QST); + + /* Get answers selected by user for this question (row[2]) */ + Str_Copy (Exam->Questions[NumQst].StrAnswers,row[2], + TstExa_MAX_BYTES_ANSWERS_ONE_QST); + + /* Replace each comma by a separator of multiple parameters */ + /* In database commas are used as separators instead of special chars */ + Par_ReplaceCommaBySeparatorMultiple (Exam->Questions[NumQst].StrIndexes); + Par_ReplaceCommaBySeparatorMultiple (Exam->Questions[NumQst].StrAnswers); + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/********************** Remove test exams made by a user *********************/ +/*****************************************************************************/ + +void TstExa_RemoveExamsMadeByUsrInAllCrss (long UsrCod) + { + /***** 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 ****************/ +/*****************************************************************************/ + +void TstExa_RemoveExamsMadeByUsrInCrs (long UsrCod,long CrsCod) + { + /***** 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 ******************/ +/*****************************************************************************/ + +void TstExa_RemoveCrsExams (long CrsCod) + { + /***** 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); + } diff --git a/swad_test_exam.h b/swad_test_exam.h new file mode 100644 index 00000000..5440676a --- /dev/null +++ b/swad_test_exam.h @@ -0,0 +1,110 @@ +// swad_test_exam.c: test exams made by users + +#ifndef _SWAD_TST_EXA +#define _SWAD_TST_EXA +/* + SWAD (Shared Workspace At a Distance in Spanish), + 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ñ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 ***********************************/ +/*****************************************************************************/ + +#include "swad_user.h" + +/*****************************************************************************/ +/***************************** Public constants ******************************/ +/*****************************************************************************/ + +#define TstExa_MAX_BYTES_INDEXES_ONE_QST (Tst_MAX_OPTIONS_PER_QUESTION * (3 + 1)) +#define TstExa_MAX_BYTES_ANSWERS_ONE_QST (Tst_MAX_OPTIONS_PER_QUESTION * (3 + 1)) + +#define TstExa_SCORE_MAX 10 // Maximum score of a test (10 in Spain). Must be unsigned! // TODO: Make this configurable by teachers + +/*****************************************************************************/ +/******************************* Public types ********************************/ +/*****************************************************************************/ + +struct TstExa_Exam + { + long ExaCod; // Test exam code + time_t TimeUTC[Dat_NUM_START_END_TIME]; + unsigned NumQsts; // Number of questions + unsigned NumQstsNotBlank; // Number of questions not blank + bool AllowTeachers; // Are teachers allowed to see this test exam? + double Score; // Total score of the test exam + struct + { + long QstCod; // Question code + char StrIndexes[TstExa_MAX_BYTES_INDEXES_ONE_QST + 1]; // 0 1 2 3, 3 0 2 1, etc. + char StrAnswers[TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1]; // Answers selected by user + double Score; // Question score + bool AnswerIsNotBlank; // Answer not blank? + } Questions[TstCfg_MAX_QUESTIONS_PER_TEST]; + }; + +/*****************************************************************************/ +/***************************** Public prototypes *****************************/ +/*****************************************************************************/ + +void TstExa_CreateExamInDB (struct TstExa_Exam *Exam); +void TstExa_UpdateExamInDB (const struct TstExa_Exam *Exam); + +void TstExa_ShowExamAfterAssess (struct TstExa_Exam *Exam); +void TstExa_WriteQstAndAnsExam (struct UsrData *UsrDat, + struct TstExa_Exam *Result, + unsigned NumQst, + MYSQL_ROW row, + unsigned Visibility); + +void TstExa_ComputeScoresAndStoreExamQuestions (struct TstExa_Exam *Exam, + bool UpdateQstScore); +void TstExa_ComputeChoiceAnsScore (struct TstExa_Exam *Result, + unsigned NumQst, + struct Tst_Question *Question); +void TstExa_GetIndexesFromStr (const char StrIndexesOneQst[TstExa_MAX_BYTES_INDEXES_ONE_QST + 1], // 0 1 2 3, 3 0 2 1, etc. + unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION]); +void TstExa_GetAnswersFromStr (const char StrAnswersOneQst[TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1], + bool UsrAnswers[Tst_MAX_OPTIONS_PER_QUESTION]); + +void TstExa_ComputeAndShowGrade (unsigned NumQsts,double Score,double MaxGrade); +double TstExa_ComputeGrade (unsigned NumQsts,double Score,double MaxGrade); +void TstExa_ShowGrade (double Grade,double MaxGrade); + +void TstExa_SelUsrsToViewUsrsExams (void); +void TstExa_SelDatesToSeeMyExams (void); +void TstExa_ShowMyExams (void); +void TstExa_GetUsrsAndShowExams (void); + +void TstExa_PutParamExaCod (long ExaCod); +long TstExa_GetParamExaCod (void); + +void TstExa_ShowOneExam (void); +void TstExa_ShowExamAnswers (struct UsrData *UsrDat, + struct TstExa_Exam *Exam, + unsigned Visibility); +void TstExa_GetExamDataByExaCod (struct TstExa_Exam *Exam); + +void TstExa_GetExamQuestionsFromDB (struct TstExa_Exam *Exam); +void TstExa_RemoveExamsMadeByUsrInAllCrss (long UsrCod); +void TstExa_RemoveExamsMadeByUsrInCrs (long UsrCod,long CrsCod); +void TstExa_RemoveCrsExams (long CrsCod); + +#endif diff --git a/swad_test_result.c b/swad_test_result.c deleted file mode 100644 index 97a0f834..00000000 --- a/swad_test_result.c +++ /dev/null @@ -1,1157 +0,0 @@ -// swad_test_result.c: test results - -/* - 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ñ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 boolean type -#include // For NULL -#include // For asprintf -#include // For free - -#include "swad_action.h" -#include "swad_database.h" -#include "swad_form.h" -#include "swad_global.h" -#include "swad_HTML.h" -#include "swad_ID.h" -#include "swad_test.h" -#include "swad_test_visibility.h" -#include "swad_user.h" - -/*****************************************************************************/ -/***************************** Public constants ******************************/ -/*****************************************************************************/ - -/*****************************************************************************/ -/**************************** Private constants ******************************/ -/*****************************************************************************/ - -/*****************************************************************************/ -/******************************* Private 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; - -/*****************************************************************************/ -/************************* Private global variables **************************/ -/*****************************************************************************/ - -/*****************************************************************************/ -/***************************** Private prototypes ****************************/ -/*****************************************************************************/ - -static void TsR_ShowUsrsTstResults (void); -static void TsR_ShowHeaderTestResults (void); -static void TsR_ShowTstResults (struct UsrData *UsrDat); -static void TsR_PutParamTstCod (long TstCod); -static long TsR_GetParamTstCod (void); -static void TsR_ShowTestResultsSummaryRow (bool ItsMe, - unsigned NumExams, - unsigned NumTotalQsts, - unsigned NumTotalQstsNotBlank, - double TotalScoreOfAllTests); -static void TsR_ShowTstTagsPresentInATestResult (long TstCod); -static void TsR_GetTestResultDataByTstCod (long TstCod,struct TsR_Result *Result); -static void TsR_GetTestResultQuestionsFromDB (long TstCod,struct TsR_Result *Result); - -/*****************************************************************************/ -/************ Select users and dates to show their test results **************/ -/*****************************************************************************/ - -void TsR_SelUsrsToViewUsrsTstResults (void) - { - extern const char *Hlp_ASSESSMENT_Tests_results; - extern const char *Txt_Results; - extern const char *Txt_View_test_results; - - Usr_PutFormToSelectUsrsToGoToAct (&Gbl.Usrs.Selected, - ActSeeUsrTstRes, - NULL,NULL, - Txt_Results, - Hlp_ASSESSMENT_Tests_results, - Txt_View_test_results, - true); // Put form with date range - } - -/*****************************************************************************/ -/******************* Select dates to show my test results ********************/ -/*****************************************************************************/ - -void TsR_SelDatesToSeeMyTstResults (void) - { - 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] = - { - Dat_HMS_DO_NOT_SET, - Dat_HMS_DO_NOT_SET - }; - - /***** 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 results **************************/ -/*****************************************************************************/ - -void TsR_ShowMyTstResults (void) - { - 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 *****/ - TsR_ShowHeaderTestResults (); - - /***** List my test results *****/ - TstCfg_GetConfigFromDB (); // To get feedback type - TsR_ShowTstResults (&Gbl.Usrs.Me.UsrDat); - - /***** End table and box *****/ - Box_BoxTableEnd (); - } - -/*****************************************************************************/ -/**************** Create new blank test result in database *******************/ -/*****************************************************************************/ - -void TsR_CreateTestResultInDB (struct TsR_Result *Result) - { - /***** Insert new test result into table *****/ - Result->TstCod = - DB_QueryINSERTandReturnCode ("can not create new test result", - "INSERT INTO tst_exams" - " (CrsCod,UsrCod,StartTime,EndTime,NumQsts,AllowTeachers)" - " VALUES" - " (%ld,%ld,NOW(),NOW(),%u,'%c')", - Gbl.Hierarchy.Crs.CrsCod, - Gbl.Usrs.Me.UsrDat.UsrCod, - Result->NumQsts, - Result->AllowTeachers ? 'Y' : - 'N'); - } - -/*****************************************************************************/ -/********************* Store test result in database *************************/ -/*****************************************************************************/ - -void TsR_UpdateScoreOfTestResultInDB (const struct TsR_Result *Result) - { - /***** Update score in test result *****/ - Str_SetDecimalPointToUS (); // To print the floating point as a dot - DB_QueryUPDATE ("can not update test result", - "UPDATE tst_exams" - " SET EndTime=NOW()," - "NumQstsNotBlank=%u," - "Score='%.15lg'" - " WHERE TstCod=%ld" - " AND CrsCod=%ld AND UsrCod=%ld", // Extra checks - Result->NumQstsNotBlank, - Result->Score, - Result->TstCod, - Gbl.Hierarchy.Crs.CrsCod, - Gbl.Usrs.Me.UsrDat.UsrCod); - Str_SetDecimalPointToLocal (); // Return to local system - } - -/*****************************************************************************/ -/******************* Get users and show their test results *******************/ -/*****************************************************************************/ - -void TsR_GetUsrsAndShowTstResults (void) - { - Usr_GetSelectedUsrsAndGoToAct (&Gbl.Usrs.Selected, - TsR_ShowUsrsTstResults, - TsR_SelUsrsToViewUsrsTstResults); - } - -/*****************************************************************************/ -/******************** Show test results for several users ********************/ -/*****************************************************************************/ - -static void TsR_ShowUsrsTstResults (void) - { - 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 *****/ - TsR_ShowHeaderTestResults (); - - /***** 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)) - if (Usr_CheckIfICanViewTst (&Gbl.Usrs.Other.UsrDat)) - { - /***** Show test results *****/ - Gbl.Usrs.Other.UsrDat.Accepted = Usr_CheckIfUsrHasAcceptedInCurrentCrs (&Gbl.Usrs.Other.UsrDat); - TsR_ShowTstResults (&Gbl.Usrs.Other.UsrDat); - } - } - - /***** End table and box *****/ - Box_BoxTableEnd (); - } - -/*****************************************************************************/ -/*********************** Show header of my test results **********************/ -/*****************************************************************************/ - -static void TsR_ShowHeaderTestResults (void) - { - 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 results of a user in the current course ***********/ -/*****************************************************************************/ - -static void TsR_ShowTstResults (struct UsrData *UsrDat) - { - extern const char *Txt_View_test; - MYSQL_RES *mysql_res; - MYSQL_ROW row; - unsigned NumExams; - unsigned NumTest; - static unsigned UniqueId = 0; - Dat_StartEndTime_t StartEndTime; - char *Id; - long TstCod; - struct TsR_Result Result; - unsigned NumTotalQsts = 0; - unsigned NumTotalQstsNotBlank = 0; - double TotalScoreOfAllTests = 0.0; - unsigned NumExamsVisibleByTchs = 0; - bool ItsMe = Usr_ItsMe (UsrDat->UsrCod); - bool ICanViewTest; - bool ICanViewScore; - 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 TstCod," // row[0] - "UNIX_TIMESTAMP(StartTime)," // row[1] - "UNIX_TIMESTAMP(EndTime)," // row[2] - "NumQsts," // row[3] - "NumQstsNotBlank," // row[4] - "AllowTeachers," // row[5] - "Score" // row[6] - " FROM tst_exams" - " WHERE CrsCod=%ld AND UsrCod=%ld" - " AND EndTime>=FROM_UNIXTIME(%ld)" - " AND StartTime<=FROM_UNIXTIME(%ld)" - " ORDER BY TstCod", - 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 results *****/ - if (NumExams) - { - for (NumTest = 0; - NumTest < NumExams; - NumTest++) - { - row = mysql_fetch_row (mysql_res); - - /* Get test code (row[0]) */ - if ((TstCod = Str_ConvertStrCodToLongCod (row[0])) < 0) - Lay_ShowErrorAndExit ("Wrong code of test result."); - - /* Get if teachers are allowed to see this test result (row[5]) */ - Result.AllowTeachers = (row[5][0] == 'Y'); - ClassDat = Result.AllowTeachers ? "DAT" : - "DAT_LIGHT"; - - switch (Gbl.Usrs.Me.Role.Logged) - { - case Rol_STD: - ICanViewTest = ItsMe; - ICanViewScore = ItsMe && - TsV_IsVisibleTotalScore (TstCfg_GetConfigVisibility ()); - break; - case Rol_NET: - case Rol_TCH: - case Rol_DEG_ADM: - case Rol_CTR_ADM: - case Rol_INS_ADM: - ICanViewTest = - ICanViewScore = ItsMe || - Result.AllowTeachers; - break; - case Rol_SYS_ADM: - ICanViewTest = - ICanViewScore = true; - break; - default: - ICanViewTest = - ICanViewScore = false; - break; - } - - if (NumTest) - HTM_TR_Begin (NULL); - - /* Write date and time (row[1] and row[2] hold UTC date-times) */ - Result.TimeUTC[Dat_START_TIME] = Dat_GetUNIXTimeFromStr (row[1]); - Result.TimeUTC[Dat_END_TIME ] = Dat_GetUNIXTimeFromStr (row[2]); - 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); - Dat_WriteLocalDateHMSFromUTC (Id,Result.TimeUTC[StartEndTime], - Gbl.Prefs.DateFormat,Dat_SEPARATOR_BREAK, - true,true,false,0x7); - HTM_TD_End (); - free (Id); - } - - /* Get number of questions (row[3]) */ - if (sscanf (row[3],"%u",&Result.NumQsts) != 1) - Result.NumQsts = 0; - if (Result.AllowTeachers) - NumTotalQsts += Result.NumQsts; - - /* Get number of questions not blank (row[4]) */ - if (sscanf (row[4],"%u",&Result.NumQstsNotBlank) != 1) - Result.NumQstsNotBlank = 0; - if (Result.AllowTeachers) - NumTotalQstsNotBlank += Result.NumQstsNotBlank; - - /* Get score (row[6]) */ - Str_SetDecimalPointToUS (); // To get the decimal point as a dot - if (sscanf (row[6],"%lf",&Result.Score) != 1) - Result.Score = 0.0; - Str_SetDecimalPointToLocal (); // Return to local system - if (Result.AllowTeachers) - TotalScoreOfAllTests += Result.Score; - - /* Write number of questions */ - HTM_TD_Begin ("class=\"%s RT COLOR%u\"",ClassDat,Gbl.RowEvenOdd); - if (ICanViewTest) - HTM_Unsigned (Result.NumQsts); - HTM_TD_End (); - - /* Write number of questions not blank */ - HTM_TD_Begin ("class=\"%s RT COLOR%u\"",ClassDat,Gbl.RowEvenOdd); - if (ICanViewTest) - HTM_Unsigned (Result.NumQstsNotBlank); - HTM_TD_End (); - - /* Write score */ - HTM_TD_Begin ("class=\"%s RT COLOR%u\"",ClassDat,Gbl.RowEvenOdd); - if (ICanViewScore) - HTM_Double2Decimals (Result.Score); - HTM_TD_End (); - - /* Write average score per question */ - HTM_TD_Begin ("class=\"%s RT COLOR%u\"",ClassDat,Gbl.RowEvenOdd); - if (ICanViewScore) - HTM_Double2Decimals (Result.NumQsts ? Result.Score / - (double) Result.NumQsts : - 0.0); - HTM_TD_End (); - - /* Write grade */ - HTM_TD_Begin ("class=\"%s RT COLOR%u\"",ClassDat,Gbl.RowEvenOdd); - if (ICanViewScore) - Tst_ComputeAndShowGrade (Result.NumQsts, - Result.Score, - TsR_SCORE_MAX); - HTM_TD_End (); - - /* Link to show this result */ - HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd); - if (ICanViewTest) - { - Frm_StartForm (Gbl.Action.Act == ActSeeMyTstRes ? ActSeeOneTstResMe : - ActSeeOneTstResOth); - TsR_PutParamTstCod (TstCod); - Ico_PutIconLink ("tasks.svg",Txt_View_test); - Frm_EndForm (); - } - HTM_TD_End (); - HTM_TR_End (); - - if (Result.AllowTeachers) - NumExamsVisibleByTchs++; - } - - /***** Write totals for this user *****/ - TsR_ShowTestResultsSummaryRow (ItsMe,NumExamsVisibleByTchs, - NumTotalQsts,NumTotalQstsNotBlank, - TotalScoreOfAllTests); - } - else - { - HTM_TD_ColouredEmpty (7); - HTM_TR_End (); - } - - /***** Free structure that stores the query result *****/ - DB_FreeMySQLResult (&mysql_res); - - Gbl.RowEvenOdd = 1 - Gbl.RowEvenOdd; - } - -/*****************************************************************************/ -/******************** Write parameter with code of test **********************/ -/*****************************************************************************/ - -static void TsR_PutParamTstCod (long TstCod) - { - Par_PutHiddenParamLong (NULL,"TstCod",TstCod); - } - -/*****************************************************************************/ -/********************* Get parameter with code of test ***********************/ -/*****************************************************************************/ - -static long TsR_GetParamTstCod (void) - { - /***** Get code of test *****/ - return Par_GetParToLong ("TstCod"); - } - -/*****************************************************************************/ -/**************** Show row with summary of user's test results ***************/ -/*****************************************************************************/ - -static void TsR_ShowTestResultsSummaryRow (bool ItsMe, - 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 && - TsV_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) - Tst_ComputeAndShowGrade (NumTotalQsts, - TotalScoreOfAllTests, - TsR_SCORE_MAX); - 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 result of another user ********************/ -/*****************************************************************************/ - -void TsR_ShowOneTstResult (void) - { - 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; - long TstCod; - struct TsR_Result Result; - 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 *****/ - if ((TstCod = TsR_GetParamTstCod ()) == -1L) - Lay_ShowErrorAndExit ("Code of test is missing."); - - /***** Get test result data *****/ - TsR_GetTestResultDataByTstCod (TstCod,&Result); - TstCfg_SetConfigVisibility (TsV_MAX_VISIBILITY); - - /***** Check if I can view this test result *****/ - 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 = TsV_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 || - Result.AllowTeachers; - 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 result - { - /***** Get questions and user's answers of the test result from database *****/ - TsR_GetTestResultQuestionsFromDB (TstCod,&Result); - - /***** 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); - if (!Usr_CheckIfICanViewTst (&Gbl.Usrs.Other.UsrDat)) - 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 (" %s",Gbl.Usrs.Other.UsrDat.Surname1); - if (Gbl.Usrs.Other.UsrDat.Surname2[0]) - HTM_TxtF (" %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); - Dat_WriteLocalDateHMSFromUTC (Id,Result.TimeUTC[StartEndTime], - 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)", - Result.NumQsts, - Result.NumQstsNotBlank,Txt_non_blank_QUESTIONS); - 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) - HTM_Double2Decimals (Result.Score); - 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) - Tst_ComputeAndShowGrade (Result.NumQsts, - Result.Score, - TsR_SCORE_MAX); - 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\""); - TsR_ShowTstTagsPresentInATestResult (TstCod); - HTM_TD_End (); - - HTM_TR_End (); - - /***** Write answers and solutions *****/ - TsR_ShowTestResult (&Gbl.Usrs.Other.UsrDat, - &Result, - 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); - HTM_Double2Decimals (Result.Score); - HTM_BR (); - HTM_TxtColonNBSP (Txt_Grade); - Tst_ComputeAndShowGrade (Result.NumQsts, - Result.Score, - TsR_SCORE_MAX); - HTM_DIV_End (); - } - - /***** End box *****/ - Box_BoxEnd (); - } - else // I am not allowed to view this test result - Lay_NoPermissionExit (); - } - -/*****************************************************************************/ -/******************** Show test tags in this test result *********************/ -/*****************************************************************************/ - -static void TsR_ShowTstTagsPresentInATestResult (long TstCod) - { - 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 result", - "SELECT tst_tags.TagTxt" // row[0] - " FROM" - " (SELECT DISTINCT(tst_question_tags.TagCod)" - " FROM tst_question_tags,tst_exam_questions" - " WHERE tst_exam_questions.TstCod=%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", - TstCod); - Tst_ShowTagList (NumTags,mysql_res); - - /***** Free structure that stores the query result *****/ - DB_FreeMySQLResult (&mysql_res); - } - -/*****************************************************************************/ -/************************* Show the result of a test *************************/ -/*****************************************************************************/ - -void TsR_ShowTestResult (struct UsrData *UsrDat, - struct TsR_Result *Result, - unsigned Visibility) - { - extern const char *Txt_Question_modified; - extern const char *Txt_Question_removed; - MYSQL_RES *mysql_res; - MYSQL_ROW row; - unsigned NumQst; - bool ThisQuestionHasBeenEdited; - time_t EditTimeUTC; - - for (NumQst = 0; - NumQst < Result->NumQsts; - NumQst++) - { - Gbl.RowEvenOdd = NumQst % 2; - - /***** Query database *****/ - if (Tst_GetOneQuestionByCod (Result->Questions[NumQst].QstCod,&mysql_res)) // Question exists - { - /***** Get row of the result of the query *****/ - row = mysql_fetch_row (mysql_res); - - /***** If this question has been edited later than test time - ==> don't show question ****/ - EditTimeUTC = Dat_GetUNIXTimeFromStr (row[0]); - ThisQuestionHasBeenEdited = false; - if (EditTimeUTC > Result->TimeUTC[Dat_START_TIME]) - ThisQuestionHasBeenEdited = true; - - if (ThisQuestionHasBeenEdited) - { - /***** Question has been edited *****/ - HTM_TR_Begin (NULL); - - HTM_TD_Begin ("class=\"BIG_INDEX RT COLOR%u\"",Gbl.RowEvenOdd); - HTM_Unsigned (NumQst + 1); - HTM_TD_End (); - - HTM_TD_Begin ("class=\"DAT_LIGHT LT COLOR%u\"",Gbl.RowEvenOdd); - HTM_Txt (Txt_Question_modified); - HTM_TD_End (); - - HTM_TR_End (); - } - else - /***** Write questions and answers *****/ - Tst_WriteQstAndAnsTestResult (UsrDat, - Result, - NumQst, - row, - Visibility); - } - else - { - /***** Question does not exists *****/ - HTM_TR_Begin (NULL); - - HTM_TD_Begin ("class=\"BIG_INDEX RT COLOR%u\"",Gbl.RowEvenOdd); - HTM_Unsigned (NumQst + 1); - HTM_TD_End (); - - HTM_TD_Begin ("class=\"DAT_LIGHT LT COLOR%u\"",Gbl.RowEvenOdd); - HTM_Txt (Txt_Question_removed); - HTM_TD_End (); - - HTM_TR_End (); - } - - /***** Free structure that stores the query result *****/ - DB_FreeMySQLResult (&mysql_res); - } - } - -/*****************************************************************************/ -/********* Get data of a test result using its test result code **************/ -/*****************************************************************************/ - -static void TsR_GetTestResultDataByTstCod (long TstCod,struct TsR_Result *Result) - { - MYSQL_RES *mysql_res; - MYSQL_ROW row; - - /***** Make database query *****/ - if (DB_QuerySELECT (&mysql_res,"can not get data" - " of a test result of a user", - "SELECT UsrCod," // row[0] - "UNIX_TIMESTAMP(StartTime)," // row[1] - "UNIX_TIMESTAMP(EndTime)," // row[2] - "NumQsts," // row[3] - "NumQstsNotBlank," // row[4] - "AllowTeachers," // row[5] - "Score" // row[6] - " FROM tst_exams" - " WHERE TstCod=%ld AND CrsCod=%ld", - TstCod, - 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) */ - Result->TimeUTC[Dat_START_TIME] = Dat_GetUNIXTimeFromStr (row[1]); - Result->TimeUTC[Dat_END_TIME ] = Dat_GetUNIXTimeFromStr (row[2]); - - /* Get number of questions (row[3]) */ - if (sscanf (row[3],"%u",&Result->NumQsts) != 1) - Result->NumQsts = 0; - - /* Get number of questions not blank (row[4]) */ - if (sscanf (row[4],"%u",&Result->NumQstsNotBlank) != 1) - Result->NumQstsNotBlank = 0; - - /* Get if teachers are allowed to see this test result (row[5]) */ - Result->AllowTeachers = (row[5][0] == 'Y'); - - /* Get score (row[6]) */ - Str_SetDecimalPointToUS (); // To get the decimal point as a dot - if (sscanf (row[6],"%lf",&Result->Score) != 1) - Result->Score = 0.0; - Str_SetDecimalPointToLocal (); // Return to local system - } - else - { - Result->TimeUTC[Dat_START_TIME] = - Result->TimeUTC[Dat_END_TIME ] = 0; - Result->NumQsts = 0; - Result->NumQstsNotBlank = 0; - Result->AllowTeachers = false; - Result->Score = 0.0; - } - - /***** Free structure that stores the query result *****/ - DB_FreeMySQLResult (&mysql_res); - } - -/*****************************************************************************/ -/************ Store user's answers of an test result into database ***********/ -/*****************************************************************************/ - -void TsR_StoreOneTestResultQstInDB (const struct TsR_Result *Result, - unsigned NumQst) - { - char StrIndexes[Tst_MAX_BYTES_INDEXES_ONE_QST + 1]; - char StrAnswers[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1]; - - /***** Replace each separator of multiple parameters by a comma *****/ - /* In database commas are used as separators instead of special chars */ - Par_ReplaceSeparatorMultipleByComma (Result->Questions[NumQst].StrIndexes,StrIndexes); - Par_ReplaceSeparatorMultipleByComma (Result->Questions[NumQst].StrAnswers,StrAnswers); - - /***** Insert question and user's answers into database *****/ - Str_SetDecimalPointToUS (); // To print the floating point as a dot - DB_QueryINSERT ("can not insert a question of a test result", - "INSERT INTO tst_exam_questions" - " (TstCod,QstCod,QstInd,Score,Indexes,Answers)" - " VALUES" - " (%ld,%ld,%u,'%.15lg','%s','%s')", - Result->TstCod,Result->Questions[NumQst].QstCod, - NumQst, // 0, 1, 2, 3... - Result->Questions[NumQst].Score, - StrIndexes, - StrAnswers); - Str_SetDecimalPointToLocal (); // Return to local system - } - -/*****************************************************************************/ -/************ Get the questions of a test result from database ***************/ -/*****************************************************************************/ - -static void TsR_GetTestResultQuestionsFromDB (long TstCod,struct TsR_Result *Result) - { - MYSQL_RES *mysql_res; - MYSQL_ROW row; - unsigned NumQst; - - /***** Get questions of a test result from database *****/ - Result->NumQsts = - (unsigned) DB_QuerySELECT (&mysql_res,"can not get questions" - " of a test result", - "SELECT QstCod," // row[0] - "Indexes," // row[1] - "Answers" // row[2] - " FROM tst_exam_questions" - " WHERE TstCod=%ld" - " ORDER BY QstInd", - TstCod); - - /***** Get questions codes *****/ - for (NumQst = 0; - NumQst < Result->NumQsts; - NumQst++) - { - row = mysql_fetch_row (mysql_res); - - /* Get question code */ - if ((Result->Questions[NumQst].QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0) - Lay_ShowErrorAndExit ("Wrong code of question."); - - /* Get indexes for this question (row[1]) */ - Str_Copy (Result->Questions[NumQst].StrIndexes,row[1], - Tst_MAX_BYTES_INDEXES_ONE_QST); - - /* Get answers selected by user for this question (row[2]) */ - Str_Copy (Result->Questions[NumQst].StrAnswers,row[2], - Tst_MAX_BYTES_ANSWERS_ONE_QST); - - /* Replace each comma by a separator of multiple parameters */ - /* In database commas are used as separators instead of special chars */ - Par_ReplaceCommaBySeparatorMultiple (Result->Questions[NumQst].StrIndexes); - Par_ReplaceCommaBySeparatorMultiple (Result->Questions[NumQst].StrAnswers); - } - - /***** Free structure that stores the query result *****/ - DB_FreeMySQLResult (&mysql_res); - } - -/*****************************************************************************/ -/********************** Remove test results made by a user ********************/ -/*****************************************************************************/ - -void TsR_RemoveTestResultsMadeByUsrInAllCrss (long UsrCod) - { - /***** Remove test results made by the specified user *****/ - DB_QueryDELETE ("can not remove test results made by a user", - "DELETE FROM tst_exam_questions" - " USING tst_exams,tst_exam_questions" - " WHERE tst_exams.UsrCod=%ld" - " AND tst_exams.TstCod=tst_exam_questions.TstCod", - UsrCod); - - DB_QueryDELETE ("can not remove test results made by a user", - "DELETE FROM tst_exams" - " WHERE UsrCod=%ld", - UsrCod); - } - -/*****************************************************************************/ -/************** Remove test results made by a user in a course ***************/ -/*****************************************************************************/ - -void TsR_RemoveTestResultsMadeByUsrInCrs (long UsrCod,long CrsCod) - { - /***** Remove test results made by the specified user *****/ - DB_QueryDELETE ("can not remove test results 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.TstCod=tst_exam_questions.TstCod", - CrsCod,UsrCod); - - DB_QueryDELETE ("can not remove test results made by a user in a course", - "DELETE FROM tst_exams" - " WHERE CrsCod=%ld AND UsrCod=%ld", - CrsCod,UsrCod); - } - -/*****************************************************************************/ -/****************** Remove all test results made in a course *****************/ -/*****************************************************************************/ - -void TsR_RemoveCrsTestResults (long CrsCod) - { - /***** Remove questions of test results made in the course *****/ - DB_QueryDELETE ("can not remove test results made in a course", - "DELETE FROM tst_exam_questions" - " USING tst_exams,tst_exam_questions" - " WHERE tst_exams.CrsCod=%ld" - " AND tst_exams.TstCod=tst_exam_questions.TstCod", - CrsCod); - - /***** Remove test results made in the course *****/ - DB_QueryDELETE ("can not remove test results made in a course", - "DELETE FROM tst_exams WHERE CrsCod=%ld", - CrsCod); - } diff --git a/swad_test_result.h b/swad_test_result.h deleted file mode 100644 index 3a8ab1e5..00000000 --- a/swad_test_result.h +++ /dev/null @@ -1,84 +0,0 @@ -// swad_test_results.h: test results - -#ifndef _SWAD_TSR -#define _SWAD_TSR -/* - SWAD (Shared Workspace At a Distance in Spanish), - 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ñ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 ***********************************/ -/*****************************************************************************/ - -#include "swad_user.h" - -/*****************************************************************************/ -/***************************** Public constants ******************************/ -/*****************************************************************************/ - -#define Tst_MAX_OPTIONS_PER_QUESTION 10 -#define Tst_MAX_BYTES_INDEXES_ONE_QST (Tst_MAX_OPTIONS_PER_QUESTION * (3 + 1)) -#define Tst_MAX_BYTES_ANSWERS_ONE_QST (Tst_MAX_OPTIONS_PER_QUESTION * (3 + 1)) - -#define TsR_SCORE_MAX 10 // Maximum score of a test (10 in Spain). Must be unsigned! // TODO: Make this configurable by teachers - -/*****************************************************************************/ -/******************************* Public types ********************************/ -/*****************************************************************************/ - -struct TsR_Result - { - long TstCod; // Exam code - time_t TimeUTC[Dat_NUM_START_END_TIME]; - unsigned NumQsts; // Number of questions - unsigned NumQstsNotBlank; // Number of questions not blank - bool AllowTeachers; // Are teachers allowed to see this test result? - double Score; // Total score of the test result - struct - { - long QstCod; // Question code - char StrIndexes[Tst_MAX_BYTES_INDEXES_ONE_QST + 1]; // 0 1 2 3, 3 0 2 1, etc. - char StrAnswers[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1]; // Answers selected by user - double Score; // Question score - bool AnswerIsNotBlank; // Answer not blank? - } Questions[TstCfg_MAX_QUESTIONS_PER_TEST]; - }; - -/*****************************************************************************/ -/***************************** Public prototypes *****************************/ -/*****************************************************************************/ - -void TsR_SelUsrsToViewUsrsTstResults (void); -void TsR_SelDatesToSeeMyTstResults (void); -void TsR_ShowMyTstResults (void); -void TsR_CreateTestResultInDB (struct TsR_Result *Result); -void TsR_UpdateScoreOfTestResultInDB (const struct TsR_Result *Result); -void TsR_GetUsrsAndShowTstResults (void); -void TsR_ShowOneTstResult (void); -void TsR_ShowTestResult (struct UsrData *UsrDat, - struct TsR_Result *Result, - unsigned Visibility); -void TsR_StoreOneTestResultQstInDB (const struct TsR_Result *Result, - unsigned NumQst); -void TsR_RemoveTestResultsMadeByUsrInAllCrss (long UsrCod); -void TsR_RemoveTestResultsMadeByUsrInCrs (long UsrCod,long CrsCod); -void TsR_RemoveCrsTestResults (long CrsCod); - -#endif diff --git a/swad_test_visibility.c b/swad_test_visibility.c index 3f2171a4..ae722514 100644 --- a/swad_test_visibility.c +++ b/swad_test_visibility.c @@ -1,4 +1,4 @@ -// swad_test_visibility.c: visibility of test results +// swad_test_visibility.c: visibility of test exams /* SWAD (Shared Workspace At a Distance), @@ -63,33 +63,33 @@ extern struct Globals Gbl; /******************************* Show visibility *****************************/ /*****************************************************************************/ -void TsV_ShowVisibilityIcons (unsigned SelectedVisibility,bool Hidden) +void TstVis_ShowVisibilityIcons (unsigned SelectedVisibility,bool Hidden) { - extern const char *Txt_TST_STR_VISIBILITY[TsV_NUM_ITEMS_VISIBILITY]; + extern const char *Txt_TST_STR_VISIBILITY[TstVis_NUM_ITEMS_VISIBILITY]; extern const char *Txt_TST_HIDDEN_VISIBLE[2]; - static const char *Icons[TsV_NUM_ITEMS_VISIBILITY][2] = + static const char *Icons[TstVis_NUM_ITEMS_VISIBILITY][2] = { - [TsV_VISIBLE_QST_ANS_TXT ][false] = "file-alt-red.svg", - [TsV_VISIBLE_QST_ANS_TXT ][true ] = "file-alt-green.svg", + [TstVis_VISIBLE_QST_ANS_TXT ][false] = "file-alt-red.svg", + [TstVis_VISIBLE_QST_ANS_TXT ][true ] = "file-alt-green.svg", - [TsV_VISIBLE_FEEDBACK_TXT ][false] = "file-signature-red.svg", - [TsV_VISIBLE_FEEDBACK_TXT ][true ] = "file-signature-green.svg", + [TstVis_VISIBLE_FEEDBACK_TXT ][false] = "file-signature-red.svg", + [TstVis_VISIBLE_FEEDBACK_TXT ][true ] = "file-signature-green.svg", - [TsV_VISIBLE_CORRECT_ANSWER][false] = "spell-check-red.svg", - [TsV_VISIBLE_CORRECT_ANSWER][true ] = "spell-check-green.svg", + [TstVis_VISIBLE_CORRECT_ANSWER][false] = "spell-check-red.svg", + [TstVis_VISIBLE_CORRECT_ANSWER][true ] = "spell-check-green.svg", - [TsV_VISIBLE_EACH_QST_SCORE][false] = "tasks-red.svg", - [TsV_VISIBLE_EACH_QST_SCORE][true ] = "tasks-green.svg", + [TstVis_VISIBLE_EACH_QST_SCORE][false] = "tasks-red.svg", + [TstVis_VISIBLE_EACH_QST_SCORE][true ] = "tasks-green.svg", - [TsV_VISIBLE_TOTAL_SCORE ][false] = "check-circle-regular-red.svg", - [TsV_VISIBLE_TOTAL_SCORE ][true ] = "check-circle-regular-green.svg", + [TstVis_VISIBLE_TOTAL_SCORE ][false] = "check-circle-regular-red.svg", + [TstVis_VISIBLE_TOTAL_SCORE ][true ] = "check-circle-regular-green.svg", }; - TsV_Visibility_t Visibility; + TstVis_Visibility_t Visibility; bool ItemVisible; char *Title; - for (Visibility = (TsV_Visibility_t) 0; - Visibility <= (TsV_Visibility_t) (TsV_NUM_ITEMS_VISIBILITY - 1); + for (Visibility = (TstVis_Visibility_t) 0; + Visibility <= (TstVis_Visibility_t) (TstVis_NUM_ITEMS_VISIBILITY - 1); Visibility++) { ItemVisible = (SelectedVisibility & (1 << Visibility)) != 0; @@ -106,25 +106,25 @@ void TsV_ShowVisibilityIcons (unsigned SelectedVisibility,bool Hidden) } /*****************************************************************************/ -/************ Put checkboxes in form to select result visibility *************/ +/************* Put checkboxes in form to select exam visibility **************/ /*****************************************************************************/ -void TsV_PutVisibilityCheckboxes (unsigned SelectedVisibility) +void TstVis_PutVisibilityCheckboxes (unsigned SelectedVisibility) { - extern const char *Txt_TST_STR_VISIBILITY[TsV_NUM_ITEMS_VISIBILITY]; - static const char *Icons[TsV_NUM_ITEMS_VISIBILITY] = + extern const char *Txt_TST_STR_VISIBILITY[TstVis_NUM_ITEMS_VISIBILITY]; + static const char *Icons[TstVis_NUM_ITEMS_VISIBILITY] = { - [TsV_VISIBLE_QST_ANS_TXT ] = "file-alt.svg", - [TsV_VISIBLE_FEEDBACK_TXT ] = "file-signature.svg", - [TsV_VISIBLE_CORRECT_ANSWER] = "spell-check.svg", - [TsV_VISIBLE_EACH_QST_SCORE] = "tasks.svg", - [TsV_VISIBLE_TOTAL_SCORE ] = "check-circle-regular.svg", + [TstVis_VISIBLE_QST_ANS_TXT ] = "file-alt.svg", + [TstVis_VISIBLE_FEEDBACK_TXT ] = "file-signature.svg", + [TstVis_VISIBLE_CORRECT_ANSWER] = "spell-check.svg", + [TstVis_VISIBLE_EACH_QST_SCORE] = "tasks.svg", + [TstVis_VISIBLE_TOTAL_SCORE ] = "check-circle-regular.svg", }; - TsV_Visibility_t Visibility; + TstVis_Visibility_t Visibility; bool ItemVisible; - for (Visibility = (TsV_Visibility_t) 0; - Visibility <= (TsV_Visibility_t) (TsV_NUM_ITEMS_VISIBILITY - 1); + for (Visibility = (TstVis_Visibility_t) 0; + Visibility <= (TstVis_Visibility_t) (TstVis_NUM_ITEMS_VISIBILITY - 1); Visibility++) { ItemVisible = (SelectedVisibility & (1 << Visibility)) != 0; @@ -145,18 +145,18 @@ void TsV_PutVisibilityCheckboxes (unsigned SelectedVisibility) /************************** Get visibility from form *************************/ /*****************************************************************************/ -unsigned TsV_GetVisibilityFromForm (void) +unsigned TstVis_GetVisibilityFromForm (void) { size_t MaxSizeListVisibilitySelected; char *StrVisibilitySelected; const char *Ptr; char UnsignedStr[Cns_MAX_DECIMAL_DIGITS_UINT + 1]; unsigned UnsignedNum; - TsV_Visibility_t VisibilityItem; + TstVis_Visibility_t VisibilityItem; unsigned Visibility = 0; // Nothing selected /***** Allocate memory for list of attendance events selected *****/ - MaxSizeListVisibilitySelected = TsV_NUM_ITEMS_VISIBILITY * (Cns_MAX_DECIMAL_DIGITS_UINT + 1); + MaxSizeListVisibilitySelected = TstVis_NUM_ITEMS_VISIBILITY * (Cns_MAX_DECIMAL_DIGITS_UINT + 1); if ((StrVisibilitySelected = (char *) malloc (MaxSizeListVisibilitySelected + 1)) == NULL) Lay_NotEnoughMemoryExit (); @@ -172,9 +172,9 @@ unsigned TsV_GetVisibilityFromForm (void) /* Get next visibility item selected */ Par_GetNextStrUntilSeparParamMult (&Ptr,UnsignedStr,Cns_MAX_DECIMAL_DIGITS_UINT); if (sscanf (UnsignedStr,"%u",&UnsignedNum) == 1) - if (UnsignedNum < TsV_NUM_ITEMS_VISIBILITY) + if (UnsignedNum < TstVis_NUM_ITEMS_VISIBILITY) { - VisibilityItem = (TsV_Visibility_t) UnsignedNum; + VisibilityItem = (TstVis_Visibility_t) UnsignedNum; Visibility |= (1 << VisibilityItem); } } @@ -186,16 +186,16 @@ unsigned TsV_GetVisibilityFromForm (void) /************************** Get visibility from string *************************/ /*****************************************************************************/ -unsigned TsV_GetVisibilityFromStr (const char *Str) +unsigned TstVis_GetVisibilityFromStr (const char *Str) { unsigned UnsignedNum; - unsigned Visibility = TsV_MIN_VISIBILITY; // In nothing is read, return minimum visibility + unsigned Visibility = TstVis_MIN_VISIBILITY; // In nothing is read, return minimum visibility /***** Get visibility from string *****/ if (Str) if (Str[0]) if (sscanf (Str,"%u",&UnsignedNum) == 1) - Visibility = UnsignedNum & TsV_MAX_VISIBILITY; + Visibility = UnsignedNum & TstVis_MAX_VISIBILITY; return Visibility; } @@ -204,27 +204,27 @@ unsigned TsV_GetVisibilityFromStr (const char *Str) /***************************** Get visibility items **************************/ /*****************************************************************************/ -bool TsV_IsVisibleQstAndAnsTxt (unsigned Visibility) +bool TstVis_IsVisibleQstAndAnsTxt (unsigned Visibility) { - return (Visibility & (1 << TsV_VISIBLE_QST_ANS_TXT)) != 0; + return (Visibility & (1 << TstVis_VISIBLE_QST_ANS_TXT)) != 0; } -bool TsV_IsVisibleFeedbackTxt (unsigned Visibility) +bool TstVis_IsVisibleFeedbackTxt (unsigned Visibility) { - return (Visibility & (1 << TsV_VISIBLE_FEEDBACK_TXT)) != 0; + return (Visibility & (1 << TstVis_VISIBLE_FEEDBACK_TXT)) != 0; } -bool TsV_IsVisibleCorrectAns (unsigned Visibility) +bool TstVis_IsVisibleCorrectAns (unsigned Visibility) { - return (Visibility & (1 << TsV_VISIBLE_CORRECT_ANSWER)) != 0; + return (Visibility & (1 << TstVis_VISIBLE_CORRECT_ANSWER)) != 0; } -bool TsV_IsVisibleEachQstScore (unsigned Visibility) +bool TstVis_IsVisibleEachQstScore (unsigned Visibility) { - return (Visibility & (1 << TsV_VISIBLE_EACH_QST_SCORE)) != 0; + return (Visibility & (1 << TstVis_VISIBLE_EACH_QST_SCORE)) != 0; } -bool TsV_IsVisibleTotalScore (unsigned Visibility) +bool TstVis_IsVisibleTotalScore (unsigned Visibility) { - return (Visibility & (1 << TsV_VISIBLE_TOTAL_SCORE)) != 0; + return (Visibility & (1 << TstVis_VISIBLE_TOTAL_SCORE)) != 0; } diff --git a/swad_test_visibility.h b/swad_test_visibility.h index cecaa440..90d01d59 100644 --- a/swad_test_visibility.h +++ b/swad_test_visibility.h @@ -1,7 +1,7 @@ // swad_test_visibility.h: visibility of test results -#ifndef _SWAD_TSV -#define _SWAD_TSV +#ifndef _SWAD_TST_VIS +#define _SWAD_TST_VIS /* SWAD (Shared Workspace At a Distance in Spanish), is a web platform developed at the University of Granada (Spain), @@ -37,32 +37,32 @@ /******************************* Public types ********************************/ /*****************************************************************************/ -#define TsV_NUM_ITEMS_VISIBILITY 5 +#define TstVis_NUM_ITEMS_VISIBILITY 5 typedef enum { - TsV_VISIBLE_QST_ANS_TXT = 0, // Questions and answers text - TsV_VISIBLE_FEEDBACK_TXT = 1, // Feedback text - TsV_VISIBLE_CORRECT_ANSWER = 2, // Correct answers - TsV_VISIBLE_EACH_QST_SCORE = 3, // Score of each question - TsV_VISIBLE_TOTAL_SCORE = 4, // Total score - } TsV_Visibility_t; -#define TsV_MIN_VISIBILITY 0 // Nothing visible -#define TsV_MAX_VISIBILITY ((1 << TsV_NUM_ITEMS_VISIBILITY) - 1) // All visible -#define TsV_VISIBILITY_DEFAULT TsV_MAX_VISIBILITY + TstVis_VISIBLE_QST_ANS_TXT = 0, // Questions and answers text + TstVis_VISIBLE_FEEDBACK_TXT = 1, // Feedback text + TstVis_VISIBLE_CORRECT_ANSWER = 2, // Correct answers + TstVis_VISIBLE_EACH_QST_SCORE = 3, // Score of each question + TstVis_VISIBLE_TOTAL_SCORE = 4, // Total score + } TstVis_Visibility_t; +#define TstVis_MIN_VISIBILITY 0 // Nothing visible +#define TstVis_MAX_VISIBILITY ((1 << TstVis_NUM_ITEMS_VISIBILITY) - 1) // All visible +#define TstVis_VISIBILITY_DEFAULT TstVis_MAX_VISIBILITY /*****************************************************************************/ /***************************** Public prototypes *****************************/ /*****************************************************************************/ -void TsV_ShowVisibilityIcons (unsigned SelectedVisibility,bool Hidden); -void TsV_PutVisibilityCheckboxes (unsigned SelectedVisibility); -unsigned TsV_GetVisibilityFromForm (void); -unsigned TsV_GetVisibilityFromStr (const char *Str); +void TstVis_ShowVisibilityIcons (unsigned SelectedVisibility,bool Hidden); +void TstVis_PutVisibilityCheckboxes (unsigned SelectedVisibility); +unsigned TstVis_GetVisibilityFromForm (void); +unsigned TstVis_GetVisibilityFromStr (const char *Str); -bool TsV_IsVisibleQstAndAnsTxt (unsigned Visibility); -bool TsV_IsVisibleFeedbackTxt (unsigned Visibility); -bool TsV_IsVisibleCorrectAns (unsigned Visibility); -bool TsV_IsVisibleEachQstScore (unsigned Visibility); -bool TsV_IsVisibleTotalScore (unsigned Visibility); +bool TstVis_IsVisibleQstAndAnsTxt (unsigned Visibility); +bool TstVis_IsVisibleFeedbackTxt (unsigned Visibility); +bool TstVis_IsVisibleCorrectAns (unsigned Visibility); +bool TstVis_IsVisibleEachQstScore (unsigned Visibility); +bool TstVis_IsVisibleTotalScore (unsigned Visibility); #endif diff --git a/swad_text.c b/swad_text.c index a8d14977..6299e326 100644 --- a/swad_text.c +++ b/swad_text.c @@ -51846,9 +51846,9 @@ const char *Txt_TST_STR_ORDER_SHORT[Tst_NUM_TYPES_ORDER_QST] = #endif }; -const char *Txt_TST_STR_VISIBILITY[TsV_NUM_ITEMS_VISIBILITY] = +const char *Txt_TST_STR_VISIBILITY[TstVis_NUM_ITEMS_VISIBILITY] = { - [TsV_VISIBLE_QST_ANS_TXT] = + [TstVis_VISIBLE_QST_ANS_TXT] = #if L==1 // ca "Text de preguntes i respostes" #elif L==2 // de @@ -51869,7 +51869,7 @@ const char *Txt_TST_STR_VISIBILITY[TsV_NUM_ITEMS_VISIBILITY] = "Texto de perguntas e respostas" #endif , - [TsV_VISIBLE_FEEDBACK_TXT] = + [TstVis_VISIBLE_FEEDBACK_TXT] = #if L==1 // ca "Text de realimentació" #elif L==2 // de @@ -51890,7 +51890,7 @@ const char *Txt_TST_STR_VISIBILITY[TsV_NUM_ITEMS_VISIBILITY] = "Texto de feedback" #endif , - [TsV_VISIBLE_CORRECT_ANSWER] = + [TstVis_VISIBLE_CORRECT_ANSWER] = #if L==1 // ca "Respostes correctes" #elif L==2 // de @@ -51911,7 +51911,7 @@ const char *Txt_TST_STR_VISIBILITY[TsV_NUM_ITEMS_VISIBILITY] = "Respostas corretas" #endif , - [TsV_VISIBLE_EACH_QST_SCORE] = + [TstVis_VISIBLE_EACH_QST_SCORE] = #if L==1 // ca "Puntuació de cada pregunta" #elif L==2 // de @@ -51932,7 +51932,7 @@ const char *Txt_TST_STR_VISIBILITY[TsV_NUM_ITEMS_VISIBILITY] = "Pontuação de cada pergunta" #endif , - [TsV_VISIBLE_TOTAL_SCORE] = + [TstVis_VISIBLE_TOTAL_SCORE] = #if L==1 // ca "Puntuació total" #elif L==2 // de