From 72eb33e914bd1b2971561a2f048be28b464bab36 Mon Sep 17 00:00:00 2001 From: acanas Date: Wed, 27 Oct 2021 21:40:19 +0200 Subject: [PATCH] Version 21.47: Oct 27, 2021 New module swad_questions_database for database queries related to test/exam/game questions. --- Makefile | 3 +- swad_changelog.h | 3 +- swad_question.c | 581 ++---------------------------- swad_question.h | 5 +- swad_question_database.c | 757 +++++++++++++++++++++++++++++++++++++++ swad_question_database.h | 63 ++++ swad_test.c | 3 +- swad_test_database.c | 164 --------- swad_test_database.h | 6 - swad_test_print.c | 9 +- 10 files changed, 862 insertions(+), 732 deletions(-) create mode 100644 swad_question_database.c create mode 100644 swad_question_database.h diff --git a/Makefile b/Makefile index bbfa517d..92df7321 100644 --- a/Makefile +++ b/Makefile @@ -74,7 +74,8 @@ OBJS = swad_account.o swad_account_database.o swad_action.o swad_admin.o \ swad_place.o swad_place_database.o swad_plugin.o swad_plugin_database.o \ swad_privacy.o swad_profile.o swad_profile_database.o swad_program.o \ swad_program_database.o swad_project.o swad_project_database.o \ - swad_question.o swad_question_import.o swad_QR.o \ + swad_question.o swad_question_database.o swad_question_import.o \ + swad_QR.o \ swad_record.o swad_record_database.o swad_report.o \ swad_report_database.o swad_role.o swad_role_database.o swad_room.o \ swad_room_database.o swad_RSS.o \ diff --git a/swad_changelog.h b/swad_changelog.h index b33cb296..7ca46f67 100644 --- a/swad_changelog.h +++ b/swad_changelog.h @@ -602,13 +602,14 @@ TODO: FIX BUG, URGENT! En las fechas como par TODO: En las encuestas, que los estudiantes no puedan ver los resultados hasta que no finalice el plazo. */ -#define Log_PLATFORM_VERSION "SWAD 21.46.5 (2021-10-27)" +#define Log_PLATFORM_VERSION "SWAD 21.47 (2021-10-27)" #define CSS_FILE "swad20.45.css" #define JS_FILE "swad20.69.1.js" /* TODO: Rename CENTRE to CENTER in help wiki. TODO: Rename ASSESSMENT.Announcements to ASSESSMENT.Calls_for_exams + Version 21.47: Oct 27, 2021 New module swad_questions_database for database queries related to test/exam/game questions. (321236 lines) Version 21.46.5: Oct 27, 2021 Queries moved to module swad_test_database. (321140 lines) Version 21.46.4: Oct 27, 2021 Code refactoring in test prints. (321059 lines) Version 21.46.3: Oct 27, 2021 Queries moved to module swad_test_database. (321054 lines) diff --git a/swad_question.c b/swad_question.c index ecaba219..33ca079a 100644 --- a/swad_question.c +++ b/swad_question.c @@ -36,6 +36,7 @@ #include "swad_form.h" #include "swad_global.h" #include "swad_question.h" +#include "swad_question_database.h" #include "swad_question_import.h" #include "swad_tag_database.h" #include "swad_test.h" @@ -55,16 +56,6 @@ const char *Qst_StrAnswerTypesXML[Qst_NUM_ANS_TYPES] = [Qst_ANS_TEXT ] = "text", }; -const char *Qst_DB_StrAnswerTypes[Qst_NUM_ANS_TYPES] = - { - [Qst_ANS_INT ] = "int", - [Qst_ANS_FLOAT ] = "float", - [Qst_ANS_TRUE_FALSE ] = "true_false", - [Qst_ANS_UNIQUE_CHOICE ] = "unique_choice", - [Qst_ANS_MULTIPLE_CHOICE] = "multiple_choice", - [Qst_ANS_TEXT ] = "text", - }; - /*****************************************************************************/ /**************************** Private constants ******************************/ /*****************************************************************************/ @@ -1803,6 +1794,7 @@ void Qst_CheckIfNumberOfAnswersIsOne (const struct Qst_Question *Question) void Qst_GetQuestions (struct Qst_Questions *Questions,MYSQL_RES **mysql_res) { + extern const char *Qst_DB_StrAnswerTypes[Qst_NUM_ANS_TYPES]; extern const char *Txt_No_questions_found_matching_your_search_criteria; char *Query = NULL; long LengthQuery; @@ -1975,6 +1967,7 @@ void Qst_ChangeFormatAnswersFeedback (struct Qst_Question *Question) Qst_AnswerType_t Qst_ConvertFromStrAnsTypDBToAnsTyp (const char *StrAnsTypeDB) { + extern const char *Qst_DB_StrAnswerTypes[Qst_NUM_ANS_TYPES]; Qst_AnswerType_t AnsType; if (StrAnsTypeDB != NULL) @@ -4152,195 +4145,36 @@ unsigned Qst_DB_GetShuffledAnswersIndexes (MYSQL_RES **mysql_res, // Returns the number of test questions // in this location (all the platform, current degree or current course) -unsigned Qst_GetNumQuestions (HieLvl_Level_t Scope,Qst_AnswerType_t AnsType,struct Qst_Stats *Stats) +unsigned Qst_GetNumQuestions (HieLvl_Level_t Scope,Qst_AnswerType_t AnsType, + struct Qst_Stats *Stats) { extern const char *Qst_DB_StrAnswerTypes[Qst_NUM_ANS_TYPES]; MYSQL_RES *mysql_res; MYSQL_ROW row; - /***** Get number of test questions from database *****/ - switch (Scope) - { - case HieLvl_SYS: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - DB_QuerySELECT (&mysql_res,"can not get number of test questions", - "SELECT COUNT(*)," // row[0] - "SUM(NumHits)," // row[1] - "SUM(Score)" // row[2] - " FROM tst_questions"); - else - DB_QuerySELECT (&mysql_res,"can not get number of test questions", - "SELECT COUNT(*)," // row[0] - "SUM(NumHits)," // row[1] - "SUM(Score)" // row[2] - " FROM tst_questions" - " WHERE AnsType='%s'", - Qst_DB_StrAnswerTypes[AnsType]); - break; - case HieLvl_CTY: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - DB_QuerySELECT (&mysql_res,"can not get number of test questions", - "SELECT COUNT(*)," // row[0] - "SUM(NumHits)," // row[1] - "SUM(Score)" // row[2] - " FROM ins_instits," - "ctr_centers," - "deg_degrees," - "crs_courses," - "tst_questions" - " WHERE ins_instits.CtyCod=%ld" - " AND ins_instits.InsCod=ctr_centers.InsCod" - " AND ctr_centers.CtrCod=deg_degrees.CtrCod" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod", - Gbl.Hierarchy.Cty.CtyCod); - else - DB_QuerySELECT (&mysql_res,"can not get number of test questions", - "SELECT COUNT(*)," // row[0] - "SUM(NumHits)," // row[1] - "SUM(Score)" // row[2] - " FROM ins_instits," - "ctr_centers," - "deg_degrees," - "crs_courses," - "tst_questions" - " WHERE ins_instits.CtyCod=%ld" - " AND ins_instits.InsCod=ctr_centers.InsCod" - " AND ctr_centers.CtrCod=deg_degrees.CtrCod" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod" - " AND tst_questions.AnsType='%s'", - Gbl.Hierarchy.Cty.CtyCod, - Qst_DB_StrAnswerTypes[AnsType]); - break; - case HieLvl_INS: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - DB_QuerySELECT (&mysql_res,"can not get number of test questions", - "SELECT COUNT(*)," // row[0] - "SUM(NumHits)," // row[1] - "SUM(Score)" // row[2] - " FROM ctr_centers," - "deg_degrees," - "crs_courses," - "tst_questions" - " WHERE ctr_centers.InsCod=%ld" - " AND ctr_centers.CtrCod=deg_degrees.CtrCod" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod", - Gbl.Hierarchy.Ins.InsCod); - else - DB_QuerySELECT (&mysql_res,"can not get number of test questions", - "SELECT COUNT(*)," // row[0] - "SUM(NumHits)," // row[1] - "SUM(Score)" // row[2] - " FROM ctr_centers," - "deg_degrees," - "crs_courses," - "tst_questions" - " WHERE ctr_centers.InsCod=%ld" - " AND ctr_centers.CtrCod=deg_degrees.CtrCod" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod" - " AND tst_questions.AnsType='%s'", - Gbl.Hierarchy.Ins.InsCod, - Qst_DB_StrAnswerTypes[AnsType]); - break; - case HieLvl_CTR: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - DB_QuerySELECT (&mysql_res,"can not get number of test questions", - "SELECT COUNT(*)," // row[0] - "SUM(NumHits)," // row[1] - "SUM(Score)" // row[2] - " FROM deg_degrees," - "crs_courses," - "tst_questions" - " WHERE deg_degrees.CtrCod=%ld" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod", - Gbl.Hierarchy.Ctr.CtrCod); - else - DB_QuerySELECT (&mysql_res,"can not get number of test questions", - "SELECT COUNT(*)," // row[0] - "SUM(NumHits)," // row[1] - "SUM(Score)" // row[2] - " FROM deg_degrees," - "crs_courses," - "tst_questions" - " WHERE deg_degrees.CtrCod=%ld" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod" - " AND tst_questions.AnsType='%s'", - Gbl.Hierarchy.Ctr.CtrCod, - Qst_DB_StrAnswerTypes[AnsType]); - break; - case HieLvl_DEG: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - DB_QuerySELECT (&mysql_res,"can not get number of test questions", - "SELECT COUNT(*)," // row[0] - "SUM(NumHits)," // row[1] - "SUM(Score)" // row[2] - " FROM crs_courses," - "tst_questions" - " WHERE crs_courses.DegCod=%ld" - " AND crs_courses.CrsCod=tst_questions.CrsCod", - Gbl.Hierarchy.Deg.DegCod); - else - DB_QuerySELECT (&mysql_res,"can not get number of test questions", - "SELECT COUNT(*)," // row[0] - "SUM(NumHits)," // row[1] - "SUM(Score)" // row[2] - " FROM crs_courses," - "tst_questions" - " WHERE crs_courses.DegCod=%ld" - " AND crs_courses.CrsCod=tst_questions.CrsCod" - " AND tst_questions.AnsType='%s'", - Gbl.Hierarchy.Deg.DegCod, - Qst_DB_StrAnswerTypes[AnsType]); - break; - case HieLvl_CRS: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - DB_QuerySELECT (&mysql_res,"can not get number of test questions", - "SELECT COUNT(*)," // row[0] - "SUM(NumHits)," // row[1] - "SUM(Score)" // row[2] - " FROM tst_questions" - " WHERE CrsCod=%ld", - Gbl.Hierarchy.Crs.CrsCod); - else - DB_QuerySELECT (&mysql_res,"can not get number of test questions", - "SELECT COUNT(*)," // row[0] - "SUM(NumHits)," // row[1] - "SUM(Score)" // row[2] - " FROM tst_questions" - " WHERE CrsCod=%ld" - " AND AnsType='%s'", - Gbl.Hierarchy.Crs.CrsCod, - Qst_DB_StrAnswerTypes[AnsType]); - break; - default: - Err_WrongScopeExit (); - break; - } + /***** Reset default stats *****/ + Stats->NumQsts = 0; + Stats->NumHits = 0L; + Stats->TotalScore = 0.0; - /***** Get number of questions *****/ - row = mysql_fetch_row (mysql_res); - if (sscanf (row[0],"%u",&(Stats->NumQsts)) != 1) - Err_ShowErrorAndExit ("Error when getting number of test questions."); - - if (Stats->NumQsts) + /***** Get number of questions from database *****/ + if (Qst_DB_GetNumQsts (&mysql_res,Scope,AnsType)) { - if (sscanf (row[1],"%lu",&(Stats->NumHits)) != 1) - Err_ShowErrorAndExit ("Error when getting total number of hits in test questions."); + /***** Get number of questions *****/ + row = mysql_fetch_row (mysql_res); + if (sscanf (row[0],"%u",&(Stats->NumQsts)) != 1) + Err_ShowErrorAndExit ("Error when getting number of test questions."); - Str_SetDecimalPointToUS (); // To get the decimal point as a dot - if (sscanf (row[2],"%lf",&(Stats->TotalScore)) != 1) - Err_ShowErrorAndExit ("Error when getting total score in test questions."); - Str_SetDecimalPointToLocal (); // Return to local system - } - else - { - Stats->NumHits = 0L; - Stats->TotalScore = 0.0; + if (Stats->NumQsts) + { + if (sscanf (row[1],"%lu",&(Stats->NumHits)) != 1) + Err_ShowErrorAndExit ("Error when getting total number of hits in test questions."); + + Str_SetDecimalPointToUS (); // To get the decimal point as a dot + if (sscanf (row[2],"%lf",&(Stats->TotalScore)) != 1) + Err_ShowErrorAndExit ("Error when getting total score in test questions."); + Str_SetDecimalPointToLocal (); // Return to local system + } } /***** Free structure that stores the query result *****/ @@ -4349,367 +4183,6 @@ unsigned Qst_GetNumQuestions (HieLvl_Level_t Scope,Qst_AnswerType_t AnsType,stru return Stats->NumQsts; } -/*****************************************************************************/ -/**************** Get number of courses with test questions ******************/ -/*****************************************************************************/ -// Returns the number of courses with test questions -// in this location (all the platform, current degree or current course) - -unsigned Qst_GetNumCoursesWithQuestions (HieLvl_Level_t Scope,Qst_AnswerType_t AnsType) - { - extern const char *Qst_DB_StrAnswerTypes[Qst_NUM_ANS_TYPES]; - - /***** Get number of courses with test questions from database *****/ - switch (Scope) - { - case HieLvl_SYS: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with test questions", - "SELECT COUNT(DISTINCT CrsCod)" - " FROM tst_questions"); - - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with test questions", - "SELECT COUNT(DISTINCT CrsCod)" - " FROM tst_questions" - " WHERE AnsType='%s'", - Qst_DB_StrAnswerTypes[AnsType]); - case HieLvl_CTY: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM ins_instits," - "ctr_centers," - "deg_degrees," - "crs_courses," - "tst_questions" - " WHERE ins_instits.CtyCod=%ld" - " AND ins_instits.InsCod=ctr_centers.InsCod" - " AND ctr_centers.CtrCod=deg_degrees.CtrCod" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod", - Gbl.Hierarchy.Cty.CtyCod); - - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM ins_instits," - "ctr_centers," - "deg_degrees," - "crs_courses," - "tst_questions" - " WHERE ins_instits.CtyCod=%ld" - " AND ins_instits.InsCod=ctr_centers.InsCod" - " AND ctr_centers.CtrCod=deg_degrees.CtrCod" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod" - " AND tst_questions.AnsType='%s'", - Gbl.Hierarchy.Cty.CtyCod, - Qst_DB_StrAnswerTypes[AnsType]); - case HieLvl_INS: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM ctr_centers," - "deg_degrees," - "crs_courses," - "tst_questions" - " WHERE ctr_centers.InsCod=%ld" - " AND ctr_centers.CtrCod=deg_degrees.CtrCod" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod", - Gbl.Hierarchy.Ins.InsCod); - - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM ctr_centers," - "deg_degrees," - "crs_courses," - "tst_questions" - " WHERE ctr_centers.InsCod=%ld" - " AND ctr_centers.CtrCod=deg_degrees.CtrCod" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod" - " AND tst_questions.AnsType='%s'", - Gbl.Hierarchy.Ins.InsCod, - Qst_DB_StrAnswerTypes[AnsType]); - case HieLvl_CTR: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM deg_degrees," - "crs_courses," - "tst_questions" - " WHERE deg_degrees.CtrCod=%ld" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod", - Gbl.Hierarchy.Ctr.CtrCod); - - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM deg_degrees," - "crs_courses," - "tst_questions" - " WHERE deg_degrees.CtrCod=%ld" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod" - " AND tst_questions.AnsType='%s'", - Gbl.Hierarchy.Ctr.CtrCod, - Qst_DB_StrAnswerTypes[AnsType]); - case HieLvl_DEG: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with test questions", - "SELECT COUNTDISTINCT (tst_questions.CrsCod)" - " FROM crs_courses," - "tst_questions" - " WHERE crs_courses.DegCod=%ld" - " AND crs_courses.CrsCod=tst_questions.CrsCod", - Gbl.Hierarchy.Deg.DegCod); - - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM crs_courses," - "tst_questions" - " WHERE crs_courses.DegCod=%ld" - " AND crs_courses.CrsCod=tst_questions.CrsCod" - " AND tst_questions.AnsType='%s'", - Gbl.Hierarchy.Deg.DegCod, - Qst_DB_StrAnswerTypes[AnsType]); - case HieLvl_CRS: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with test questions", - "SELECT COUNT(DISTINCT CrsCod)" - " FROM tst_questions" - " WHERE CrsCod=%ld", - Gbl.Hierarchy.Crs.CrsCod); - - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with test questions", - "SELECT COUNT(DISTINCT CrsCod)" - " FROM tst_questions" - " WHERE CrsCod=%ld" - " AND AnsType='%s'", - Gbl.Hierarchy.Crs.CrsCod, - Qst_DB_StrAnswerTypes[AnsType]); - default: - return 0; - } - } - -/*****************************************************************************/ -/*********** Get number of courses with pluggable test questions *************/ -/*****************************************************************************/ -// Returns the number of courses with pluggable test questions -// in this location (all the platform, current degree or current course) - -unsigned Qst_GetNumCoursesWithPluggableQuestions (HieLvl_Level_t Scope,Qst_AnswerType_t AnsType) - { - extern const char *Qst_DB_StrAnswerTypes[Qst_NUM_ANS_TYPES]; - extern const char *Tst_DB_Pluggable[TstCfg_NUM_OPTIONS_PLUGGABLE]; - - /***** Get number of courses with test questions from database *****/ - switch (Scope) - { - case HieLvl_SYS: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with pluggable test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM tst_questions," - "tst_config" - " WHERE tst_questions.CrsCod=tst_config.CrsCod" - " AND tst_config.pluggable='%s'", - Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); - - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with pluggable test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM tst_questions," - "tst_config" - " WHERE tst_questions.AnsType='%s'" - " AND tst_questions.CrsCod=tst_config.CrsCod" - " AND tst_config.pluggable='%s'", - Qst_DB_StrAnswerTypes[AnsType], - Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); - case HieLvl_CTY: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with pluggable test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM ins_instits," - "ctr_centers," - "deg_degrees," - "crs_courses," - "tst_questions," - "tst_config" - " WHERE ins_instits.CtyCod=%ld" - " AND ins_instits.InsCod=ctr_centers.InsCod" - " AND ctr_centers.CtrCod=deg_degrees.CtrCod" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod" - " AND tst_questions.CrsCod=tst_config.CrsCod" - " AND tst_config.pluggable='%s'", - Gbl.Hierarchy.Cty.CtyCod, - Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); - - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with pluggable test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM ins_instits," - "ctr_centers," - "deg_degrees," - "crs_courses," - "tst_questions," - "tst_config" - " WHERE ins_instits.CtyCod=%ld" - " AND ins_instits.InsCod=ctr_centers.InsCod" - " AND ctr_centers.CtrCod=deg_degrees.CtrCod" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod" - " AND tst_questions.AnsType='%s'" - " AND tst_questions.CrsCod=tst_config.CrsCod" - " AND tst_config.pluggable='%s'", - Gbl.Hierarchy.Cty.CtyCod, - Qst_DB_StrAnswerTypes[AnsType], - Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); - case HieLvl_INS: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with pluggable test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM ctr_centers," - "deg_degrees," - "crs_courses," - "tst_questions," - "tst_config" - " WHERE ctr_centers.InsCod=%ld" - " AND ctr_centers.CtrCod=deg_degrees.CtrCod" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod" - " AND tst_questions.CrsCod=tst_config.CrsCod" - " AND tst_config.pluggable='%s'", - Gbl.Hierarchy.Ins.InsCod, - Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); - - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with pluggable test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM ctr_centers," - "deg_degrees," - "crs_courses," - "tst_questions," - "tst_config" - " WHERE ctr_centers.InsCod=%ld" - " AND ctr_centers.CtrCod=deg_degrees.CtrCod" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod" - " AND tst_questions.AnsType='%s'" - " AND tst_questions.CrsCod=tst_config.CrsCod" - " AND tst_config.pluggable='%s'", - Gbl.Hierarchy.Ins.InsCod, - Qst_DB_StrAnswerTypes[AnsType], - Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); - case HieLvl_CTR: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with pluggable test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM deg_degrees," - "crs_courses," - "tst_questions," - "tst_config" - " WHERE deg_degrees.CtrCod=%ld" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod" - " AND tst_questions.CrsCod=tst_config.CrsCod" - " AND tst_config.pluggable='%s'", - Gbl.Hierarchy.Ctr.CtrCod, - Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); - - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with pluggable test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM deg_degrees," - "crs_courses," - "tst_questions," - "tst_config" - " WHERE deg_degrees.CtrCod=%ld" - " AND deg_degrees.DegCod=crs_courses.DegCod" - " AND crs_courses.CrsCod=tst_questions.CrsCod" - " AND tst_questions.AnsType='%s'" - " AND tst_questions.CrsCod=tst_config.CrsCod" - " AND tst_config.pluggable='%s'", - Gbl.Hierarchy.Ctr.CtrCod, - Qst_DB_StrAnswerTypes[AnsType], - Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); - case HieLvl_DEG: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with pluggable test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM crs_courses," - "tst_questions," - "tst_config" - " WHERE crs_courses.DegCod=%ld" - " AND crs_courses.CrsCod=tst_questions.CrsCod" - " AND tst_questions.CrsCod=tst_config.CrsCod" - " AND tst_config.pluggable='%s'", - Gbl.Hierarchy.Deg.DegCod, - Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); - - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with pluggable test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM crs_courses," - "tst_questions," - "tst_config" - " WHERE crs_courses.DegCod=%ld" - " AND crs_courses.CrsCod=tst_questions.CrsCod" - " AND tst_questions.AnsType='%s'" - " AND tst_questions.CrsCod=tst_config.CrsCod" - " AND tst_config.pluggable='%s'", - Gbl.Hierarchy.Deg.DegCod, - Qst_DB_StrAnswerTypes[AnsType], - Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); - case HieLvl_CRS: - if (AnsType == Qst_ANS_UNKNOWN) // Any type - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with pluggable test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM tst_questions," - "tst_config" - " WHERE tst_questions.CrsCod=%ld" - " AND tst_questions.CrsCod=tst_config.CrsCod" - " AND tst_config.pluggable='%s'", - Gbl.Hierarchy.Crs.CrsCod, - Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); - - return (unsigned) - DB_QueryCOUNT ("can not get number of courses with pluggable test questions", - "SELECT COUNT(DISTINCT tst_questions.CrsCod)" - " FROM tst_questions," - "tst_config" - " WHERE tst_questions.CrsCod=%ld" - " AND tst_questions.AnsType='%s'" - " AND tst_questions.CrsCod=tst_config.CrsCod" - " AND tst_config.pluggable='%s'", - Gbl.Hierarchy.Crs.CrsCod, - Qst_DB_StrAnswerTypes[AnsType], - Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); - default: - return 0; - } - } - /*****************************************************************************/ /*********************** Get stats about test questions **********************/ /*****************************************************************************/ @@ -4727,9 +4200,9 @@ void Qst_GetTestStats (Qst_AnswerType_t AnsType,struct Qst_Stats *Stats) if (Qst_GetNumQuestions (Gbl.Scope.Current,AnsType,Stats)) { - if ((Stats->NumCoursesWithQuestions = Qst_GetNumCoursesWithQuestions (Gbl.Scope.Current,AnsType)) != 0) + if ((Stats->NumCoursesWithQuestions = Qst_DB_GetNumCrssWithQsts (Gbl.Scope.Current,AnsType)) != 0) { - Stats->NumCoursesWithPluggableQuestions = Qst_GetNumCoursesWithPluggableQuestions (Gbl.Scope.Current,AnsType); + Stats->NumCoursesWithPluggableQuestions = Qst_DB_GetNumCrssWithPluggableQsts (Gbl.Scope.Current,AnsType); Stats->AvgQstsPerCourse = (double) Stats->NumQsts / (double) Stats->NumCoursesWithQuestions; Stats->AvgHitsPerCourse = (double) Stats->NumHits / (double) Stats->NumCoursesWithQuestions; } diff --git a/swad_question.h b/swad_question.h index b28907be..9b0eb77c 100644 --- a/swad_question.h +++ b/swad_question.h @@ -289,9 +289,8 @@ void Qst_RemoveAllMedFilesFromAnsOfAllQstsInCrs (long CrsCod); unsigned Qst_DB_GetShuffledAnswersIndexes (MYSQL_RES **mysql_res, const struct Qst_Question *Question); -unsigned Qst_GetNumQuestions (HieLvl_Level_t Scope,Qst_AnswerType_t AnsType,struct Qst_Stats *Stats); -unsigned Qst_GetNumCoursesWithQuestions (HieLvl_Level_t Scope,Qst_AnswerType_t AnsType); -unsigned Qst_GetNumCoursesWithPluggableQuestions (HieLvl_Level_t Scope,Qst_AnswerType_t AnsType); +unsigned Qst_GetNumQuestions (HieLvl_Level_t Scope,Qst_AnswerType_t AnsType, + struct Qst_Stats *Stats); void Qst_GetTestStats (Qst_AnswerType_t AnsType,struct Qst_Stats *Stats); diff --git a/swad_question_database.c b/swad_question_database.c new file mode 100644 index 00000000..912f1fb9 --- /dev/null +++ b/swad_question_database.c @@ -0,0 +1,757 @@ +// swad_question_database.c: test/exam/game questions, operations with database + +/* + 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-2021 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 asprintf +#include // For string functions + +#include "swad_database.h" +#include "swad_error.h" +// #include "swad_exam_set.h" +// #include "swad_figure.h" +// #include "swad_form.h" +#include "swad_global.h" +#include "swad_question.h" +// #include "swad_question_import.h" +// #include "swad_tag_database.h" +// #include "swad_test.h" +#include "swad_test_print.h" + +/*****************************************************************************/ +/***************************** Public constants ******************************/ +/*****************************************************************************/ + +const char *Qst_DB_StrAnswerTypes[Qst_NUM_ANS_TYPES] = + { + [Qst_ANS_INT ] = "int", + [Qst_ANS_FLOAT ] = "float", + [Qst_ANS_TRUE_FALSE ] = "true_false", + [Qst_ANS_UNIQUE_CHOICE ] = "unique_choice", + [Qst_ANS_MULTIPLE_CHOICE] = "multiple_choice", + [Qst_ANS_TEXT ] = "text", + }; + +/*****************************************************************************/ +/**************************** Private constants ******************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/******************************* Private types *******************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/************** External global variables from others modules ****************/ +/*****************************************************************************/ + +extern struct Globals Gbl; + +/*****************************************************************************/ +/************************* Private global variables **************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/***************************** Private prototypes ****************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/*********************** Update the score of a question **********************/ +/*****************************************************************************/ + +void Qst_DB_UpdateQstScore (long QstCod,bool AnswerIsNotBlank,double Score) + { + /***** Update number of clicks and score of the question *****/ + Str_SetDecimalPointToUS (); // To print the floating point as a dot + if (AnswerIsNotBlank) // User's answer is not blank + 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", + Score, + QstCod); + else // User's answer is blank + DB_QueryUPDATE ("can not update the score of a question", + "UPDATE tst_questions" + " SET NumHits=NumHits+1" + " WHERE QstCod=%ld", + QstCod); + Str_SetDecimalPointToLocal (); // Return to local system + } + +/*****************************************************************************/ +/******************* Get questions for a new test print **********************/ +/*****************************************************************************/ + +#define Tst_MAX_BYTES_QUERY_QUESTIONS (16 * 1024 - 1) + +unsigned Qst_DB_GetQuestionsForNewTestPrint (MYSQL_RES **mysql_res, + const struct Qst_Questions *Questions) + { + extern const char *Qst_DB_StrAnswerTypes[Qst_NUM_ANS_TYPES]; + char *Query = NULL; + long LengthQuery; + unsigned NumItemInList; + const char *Ptr; + char TagText[Tag_MAX_BYTES_TAG + 1]; + char UnsignedStr[Cns_MAX_DECIMAL_DIGITS_UINT + 1]; + Qst_AnswerType_t AnswerType; + char StrNumQsts[Cns_MAX_DECIMAL_DIGITS_UINT + 1]; + + /***** Allocate space for query *****/ + if ((Query = malloc (Tst_MAX_BYTES_QUERY_QUESTIONS + 1)) == NULL) + Err_NotEnoughMemoryExit (); + + /***** Select questions without hidden tags *****/ + /* Begin query */ + // Reject questions with any tag hidden + // Select only questions with tags + // DISTINCTROW is necessary to not repeat questions + snprintf (Query,Tst_MAX_BYTES_QUERY_QUESTIONS + 1, + "SELECT DISTINCTROW tst_questions.QstCod," // row[0] + "tst_questions.AnsType," // row[1] + "tst_questions.Shuffle" // row[2] + " FROM tst_questions,tst_question_tags,tst_tags" + " WHERE tst_questions.CrsCod=%ld" + " AND tst_questions.QstCod NOT IN" + " (SELECT tst_question_tags.QstCod" + " FROM tst_tags,tst_question_tags" + " WHERE tst_tags.CrsCod=%ld" + " AND tst_tags.TagHidden='Y'" + " AND tst_tags.TagCod=tst_question_tags.TagCod)" + " AND tst_questions.QstCod=tst_question_tags.QstCod" + " AND tst_question_tags.TagCod=tst_tags.TagCod" + " AND tst_tags.CrsCod=%ld", + Gbl.Hierarchy.Crs.CrsCod, + Gbl.Hierarchy.Crs.CrsCod, + Gbl.Hierarchy.Crs.CrsCod); + + if (!Questions->Tags.All) // User has not selected all the tags + { + /* Add selected tags */ + LengthQuery = strlen (Query); + NumItemInList = 0; + Ptr = Questions->Tags.List; + while (*Ptr) + { + Par_GetNextStrUntilSeparParamMult (&Ptr,TagText,Tag_MAX_BYTES_TAG); + LengthQuery = LengthQuery + 35 + strlen (TagText) + 1; + if (LengthQuery > Tst_MAX_BYTES_QUERY_QUESTIONS - 128) + Err_QuerySizeExceededExit (); + Str_Concat (Query, + NumItemInList ? " OR tst_tags.TagTxt='" : + " AND (tst_tags.TagTxt='", + Tst_MAX_BYTES_QUERY_QUESTIONS); + Str_Concat (Query,TagText,Tst_MAX_BYTES_QUERY_QUESTIONS); + Str_Concat (Query,"'",Tst_MAX_BYTES_QUERY_QUESTIONS); + NumItemInList++; + } + Str_Concat (Query,")",Tst_MAX_BYTES_QUERY_QUESTIONS); + } + + /* Add answer types selected */ + if (!Questions->AnswerTypes.All) + { + LengthQuery = strlen (Query); + NumItemInList = 0; + Ptr = Questions->AnswerTypes.List; + while (*Ptr) + { + Par_GetNextStrUntilSeparParamMult (&Ptr,UnsignedStr,Tag_MAX_BYTES_TAG); + AnswerType = Qst_ConvertFromUnsignedStrToAnsTyp (UnsignedStr); + LengthQuery = LengthQuery + 35 + strlen (Qst_DB_StrAnswerTypes[AnswerType]) + 1; + if (LengthQuery > Tst_MAX_BYTES_QUERY_QUESTIONS - 128) + Err_QuerySizeExceededExit (); + Str_Concat (Query, + NumItemInList ? " OR tst_questions.AnsType='" : + " AND (tst_questions.AnsType='", + Tst_MAX_BYTES_QUERY_QUESTIONS); + Str_Concat (Query,Qst_DB_StrAnswerTypes[AnswerType],Tst_MAX_BYTES_QUERY_QUESTIONS); + Str_Concat (Query,"'",Tst_MAX_BYTES_QUERY_QUESTIONS); + NumItemInList++; + } + Str_Concat (Query,")",Tst_MAX_BYTES_QUERY_QUESTIONS); + } + + /* End query */ + Str_Concat (Query," ORDER BY RAND() LIMIT ",Tst_MAX_BYTES_QUERY_QUESTIONS); + snprintf (StrNumQsts,sizeof (StrNumQsts),"%u",Questions->NumQsts); + Str_Concat (Query,StrNumQsts,Tst_MAX_BYTES_QUERY_QUESTIONS); +/* + if (Gbl.Usrs.Me.Roles.LoggedRole == Rol_SYS_ADM) + Lay_ShowAlert (Lay_INFO,Query); +*/ + /* Make the query */ + return (unsigned) + DB_QuerySELECT (mysql_res,"can not get questions", + "%s", + Query); + } + +/*****************************************************************************/ +/*********************** Get number of test questions ************************/ +/*****************************************************************************/ +// Returns the number of test questions +// in this location (all the platform, current degree or current course) + +unsigned Qst_DB_GetNumQsts (MYSQL_RES **mysql_res, + HieLvl_Level_t Scope,Qst_AnswerType_t AnsType) + { + switch (Scope) + { + case HieLvl_SYS: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QuerySELECT (mysql_res,"can not get number of test questions", + "SELECT COUNT(*)," // row[0] + "SUM(NumHits)," // row[1] + "SUM(Score)" // row[2] + " FROM tst_questions"); + + return (unsigned) + DB_QuerySELECT (mysql_res,"can not get number of test questions", + "SELECT COUNT(*)," // row[0] + "SUM(NumHits)," // row[1] + "SUM(Score)" // row[2] + " FROM tst_questions" + " WHERE AnsType='%s'", + Qst_DB_StrAnswerTypes[AnsType]); + case HieLvl_CTY: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QuerySELECT (mysql_res,"can not get number of test questions", + "SELECT COUNT(*)," // row[0] + "SUM(NumHits)," // row[1] + "SUM(Score)" // row[2] + " FROM ins_instits," + "ctr_centers," + "deg_degrees," + "crs_courses," + "tst_questions" + " WHERE ins_instits.CtyCod=%ld" + " AND ins_instits.InsCod=ctr_centers.InsCod" + " AND ctr_centers.CtrCod=deg_degrees.CtrCod" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod", + Gbl.Hierarchy.Cty.CtyCod); + + return (unsigned) + DB_QuerySELECT (mysql_res,"can not get number of test questions", + "SELECT COUNT(*)," // row[0] + "SUM(NumHits)," // row[1] + "SUM(Score)" // row[2] + " FROM ins_instits," + "ctr_centers," + "deg_degrees," + "crs_courses," + "tst_questions" + " WHERE ins_instits.CtyCod=%ld" + " AND ins_instits.InsCod=ctr_centers.InsCod" + " AND ctr_centers.CtrCod=deg_degrees.CtrCod" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod" + " AND tst_questions.AnsType='%s'", + Gbl.Hierarchy.Cty.CtyCod, + Qst_DB_StrAnswerTypes[AnsType]); + case HieLvl_INS: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QuerySELECT (mysql_res,"can not get number of test questions", + "SELECT COUNT(*)," // row[0] + "SUM(NumHits)," // row[1] + "SUM(Score)" // row[2] + " FROM ctr_centers," + "deg_degrees," + "crs_courses," + "tst_questions" + " WHERE ctr_centers.InsCod=%ld" + " AND ctr_centers.CtrCod=deg_degrees.CtrCod" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod", + Gbl.Hierarchy.Ins.InsCod); + + return (unsigned) + DB_QuerySELECT (mysql_res,"can not get number of test questions", + "SELECT COUNT(*)," // row[0] + "SUM(NumHits)," // row[1] + "SUM(Score)" // row[2] + " FROM ctr_centers," + "deg_degrees," + "crs_courses," + "tst_questions" + " WHERE ctr_centers.InsCod=%ld" + " AND ctr_centers.CtrCod=deg_degrees.CtrCod" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod" + " AND tst_questions.AnsType='%s'", + Gbl.Hierarchy.Ins.InsCod, + Qst_DB_StrAnswerTypes[AnsType]); + case HieLvl_CTR: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QuerySELECT (mysql_res,"can not get number of test questions", + "SELECT COUNT(*)," // row[0] + "SUM(NumHits)," // row[1] + "SUM(Score)" // row[2] + " FROM deg_degrees," + "crs_courses," + "tst_questions" + " WHERE deg_degrees.CtrCod=%ld" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod", + Gbl.Hierarchy.Ctr.CtrCod); + + return (unsigned) + DB_QuerySELECT (mysql_res,"can not get number of test questions", + "SELECT COUNT(*)," // row[0] + "SUM(NumHits)," // row[1] + "SUM(Score)" // row[2] + " FROM deg_degrees," + "crs_courses," + "tst_questions" + " WHERE deg_degrees.CtrCod=%ld" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod" + " AND tst_questions.AnsType='%s'", + Gbl.Hierarchy.Ctr.CtrCod, + Qst_DB_StrAnswerTypes[AnsType]); + case HieLvl_DEG: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QuerySELECT (mysql_res,"can not get number of test questions", + "SELECT COUNT(*)," // row[0] + "SUM(NumHits)," // row[1] + "SUM(Score)" // row[2] + " FROM crs_courses," + "tst_questions" + " WHERE crs_courses.DegCod=%ld" + " AND crs_courses.CrsCod=tst_questions.CrsCod", + Gbl.Hierarchy.Deg.DegCod); + + return (unsigned) + DB_QuerySELECT (mysql_res,"can not get number of test questions", + "SELECT COUNT(*)," // row[0] + "SUM(NumHits)," // row[1] + "SUM(Score)" // row[2] + " FROM crs_courses," + "tst_questions" + " WHERE crs_courses.DegCod=%ld" + " AND crs_courses.CrsCod=tst_questions.CrsCod" + " AND tst_questions.AnsType='%s'", + Gbl.Hierarchy.Deg.DegCod, + Qst_DB_StrAnswerTypes[AnsType]); + case HieLvl_CRS: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QuerySELECT (mysql_res,"can not get number of test questions", + "SELECT COUNT(*)," // row[0] + "SUM(NumHits)," // row[1] + "SUM(Score)" // row[2] + " FROM tst_questions" + " WHERE CrsCod=%ld", + Gbl.Hierarchy.Crs.CrsCod); + + return (unsigned) + DB_QuerySELECT (mysql_res,"can not get number of test questions", + "SELECT COUNT(*)," // row[0] + "SUM(NumHits)," // row[1] + "SUM(Score)" // row[2] + " FROM tst_questions" + " WHERE CrsCod=%ld" + " AND AnsType='%s'", + Gbl.Hierarchy.Crs.CrsCod, + Qst_DB_StrAnswerTypes[AnsType]); + default: + Err_WrongScopeExit (); + return 0; // Not reached + } + } + +/*****************************************************************************/ +/**************** Get number of courses with test questions ******************/ +/*****************************************************************************/ +// Returns the number of courses with test questions +// in this location (all the platform, current degree or current course) + +unsigned Qst_DB_GetNumCrssWithQsts (HieLvl_Level_t Scope, + Qst_AnswerType_t AnsType) + { + extern const char *Qst_DB_StrAnswerTypes[Qst_NUM_ANS_TYPES]; + + /***** Get number of courses with test questions from database *****/ + switch (Scope) + { + case HieLvl_SYS: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with test questions", + "SELECT COUNT(DISTINCT CrsCod)" + " FROM tst_questions"); + + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with test questions", + "SELECT COUNT(DISTINCT CrsCod)" + " FROM tst_questions" + " WHERE AnsType='%s'", + Qst_DB_StrAnswerTypes[AnsType]); + case HieLvl_CTY: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM ins_instits," + "ctr_centers," + "deg_degrees," + "crs_courses," + "tst_questions" + " WHERE ins_instits.CtyCod=%ld" + " AND ins_instits.InsCod=ctr_centers.InsCod" + " AND ctr_centers.CtrCod=deg_degrees.CtrCod" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod", + Gbl.Hierarchy.Cty.CtyCod); + + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM ins_instits," + "ctr_centers," + "deg_degrees," + "crs_courses," + "tst_questions" + " WHERE ins_instits.CtyCod=%ld" + " AND ins_instits.InsCod=ctr_centers.InsCod" + " AND ctr_centers.CtrCod=deg_degrees.CtrCod" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod" + " AND tst_questions.AnsType='%s'", + Gbl.Hierarchy.Cty.CtyCod, + Qst_DB_StrAnswerTypes[AnsType]); + case HieLvl_INS: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM ctr_centers," + "deg_degrees," + "crs_courses," + "tst_questions" + " WHERE ctr_centers.InsCod=%ld" + " AND ctr_centers.CtrCod=deg_degrees.CtrCod" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod", + Gbl.Hierarchy.Ins.InsCod); + + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM ctr_centers," + "deg_degrees," + "crs_courses," + "tst_questions" + " WHERE ctr_centers.InsCod=%ld" + " AND ctr_centers.CtrCod=deg_degrees.CtrCod" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod" + " AND tst_questions.AnsType='%s'", + Gbl.Hierarchy.Ins.InsCod, + Qst_DB_StrAnswerTypes[AnsType]); + case HieLvl_CTR: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM deg_degrees," + "crs_courses," + "tst_questions" + " WHERE deg_degrees.CtrCod=%ld" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod", + Gbl.Hierarchy.Ctr.CtrCod); + + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM deg_degrees," + "crs_courses," + "tst_questions" + " WHERE deg_degrees.CtrCod=%ld" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod" + " AND tst_questions.AnsType='%s'", + Gbl.Hierarchy.Ctr.CtrCod, + Qst_DB_StrAnswerTypes[AnsType]); + case HieLvl_DEG: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with test questions", + "SELECT COUNTDISTINCT (tst_questions.CrsCod)" + " FROM crs_courses," + "tst_questions" + " WHERE crs_courses.DegCod=%ld" + " AND crs_courses.CrsCod=tst_questions.CrsCod", + Gbl.Hierarchy.Deg.DegCod); + + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM crs_courses," + "tst_questions" + " WHERE crs_courses.DegCod=%ld" + " AND crs_courses.CrsCod=tst_questions.CrsCod" + " AND tst_questions.AnsType='%s'", + Gbl.Hierarchy.Deg.DegCod, + Qst_DB_StrAnswerTypes[AnsType]); + case HieLvl_CRS: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with test questions", + "SELECT COUNT(DISTINCT CrsCod)" + " FROM tst_questions" + " WHERE CrsCod=%ld", + Gbl.Hierarchy.Crs.CrsCod); + + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with test questions", + "SELECT COUNT(DISTINCT CrsCod)" + " FROM tst_questions" + " WHERE CrsCod=%ld" + " AND AnsType='%s'", + Gbl.Hierarchy.Crs.CrsCod, + Qst_DB_StrAnswerTypes[AnsType]); + default: + Err_WrongScopeExit (); + return 0; // Not reached + } + } + +/*****************************************************************************/ +/*********** Get number of courses with pluggable test questions *************/ +/*****************************************************************************/ +// Returns the number of courses with pluggable test questions +// in this location (all the platform, current degree or current course) + +unsigned Qst_DB_GetNumCrssWithPluggableQsts (HieLvl_Level_t Scope, + Qst_AnswerType_t AnsType) + { + extern const char *Qst_DB_StrAnswerTypes[Qst_NUM_ANS_TYPES]; + extern const char *Tst_DB_Pluggable[TstCfg_NUM_OPTIONS_PLUGGABLE]; + + /***** Get number of courses with test questions from database *****/ + switch (Scope) + { + case HieLvl_SYS: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with pluggable test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM tst_questions," + "tst_config" + " WHERE tst_questions.CrsCod=tst_config.CrsCod" + " AND tst_config.pluggable='%s'", + Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); + + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with pluggable test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM tst_questions," + "tst_config" + " WHERE tst_questions.AnsType='%s'" + " AND tst_questions.CrsCod=tst_config.CrsCod" + " AND tst_config.pluggable='%s'", + Qst_DB_StrAnswerTypes[AnsType], + Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); + case HieLvl_CTY: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with pluggable test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM ins_instits," + "ctr_centers," + "deg_degrees," + "crs_courses," + "tst_questions," + "tst_config" + " WHERE ins_instits.CtyCod=%ld" + " AND ins_instits.InsCod=ctr_centers.InsCod" + " AND ctr_centers.CtrCod=deg_degrees.CtrCod" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod" + " AND tst_questions.CrsCod=tst_config.CrsCod" + " AND tst_config.pluggable='%s'", + Gbl.Hierarchy.Cty.CtyCod, + Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); + + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with pluggable test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM ins_instits," + "ctr_centers," + "deg_degrees," + "crs_courses," + "tst_questions," + "tst_config" + " WHERE ins_instits.CtyCod=%ld" + " AND ins_instits.InsCod=ctr_centers.InsCod" + " AND ctr_centers.CtrCod=deg_degrees.CtrCod" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod" + " AND tst_questions.AnsType='%s'" + " AND tst_questions.CrsCod=tst_config.CrsCod" + " AND tst_config.pluggable='%s'", + Gbl.Hierarchy.Cty.CtyCod, + Qst_DB_StrAnswerTypes[AnsType], + Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); + case HieLvl_INS: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with pluggable test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM ctr_centers," + "deg_degrees," + "crs_courses," + "tst_questions," + "tst_config" + " WHERE ctr_centers.InsCod=%ld" + " AND ctr_centers.CtrCod=deg_degrees.CtrCod" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod" + " AND tst_questions.CrsCod=tst_config.CrsCod" + " AND tst_config.pluggable='%s'", + Gbl.Hierarchy.Ins.InsCod, + Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); + + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with pluggable test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM ctr_centers," + "deg_degrees," + "crs_courses," + "tst_questions," + "tst_config" + " WHERE ctr_centers.InsCod=%ld" + " AND ctr_centers.CtrCod=deg_degrees.CtrCod" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod" + " AND tst_questions.AnsType='%s'" + " AND tst_questions.CrsCod=tst_config.CrsCod" + " AND tst_config.pluggable='%s'", + Gbl.Hierarchy.Ins.InsCod, + Qst_DB_StrAnswerTypes[AnsType], + Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); + case HieLvl_CTR: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with pluggable test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM deg_degrees," + "crs_courses," + "tst_questions," + "tst_config" + " WHERE deg_degrees.CtrCod=%ld" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod" + " AND tst_questions.CrsCod=tst_config.CrsCod" + " AND tst_config.pluggable='%s'", + Gbl.Hierarchy.Ctr.CtrCod, + Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); + + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with pluggable test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM deg_degrees," + "crs_courses," + "tst_questions," + "tst_config" + " WHERE deg_degrees.CtrCod=%ld" + " AND deg_degrees.DegCod=crs_courses.DegCod" + " AND crs_courses.CrsCod=tst_questions.CrsCod" + " AND tst_questions.AnsType='%s'" + " AND tst_questions.CrsCod=tst_config.CrsCod" + " AND tst_config.pluggable='%s'", + Gbl.Hierarchy.Ctr.CtrCod, + Qst_DB_StrAnswerTypes[AnsType], + Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); + case HieLvl_DEG: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with pluggable test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM crs_courses," + "tst_questions," + "tst_config" + " WHERE crs_courses.DegCod=%ld" + " AND crs_courses.CrsCod=tst_questions.CrsCod" + " AND tst_questions.CrsCod=tst_config.CrsCod" + " AND tst_config.pluggable='%s'", + Gbl.Hierarchy.Deg.DegCod, + Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); + + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with pluggable test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM crs_courses," + "tst_questions," + "tst_config" + " WHERE crs_courses.DegCod=%ld" + " AND crs_courses.CrsCod=tst_questions.CrsCod" + " AND tst_questions.AnsType='%s'" + " AND tst_questions.CrsCod=tst_config.CrsCod" + " AND tst_config.pluggable='%s'", + Gbl.Hierarchy.Deg.DegCod, + Qst_DB_StrAnswerTypes[AnsType], + Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); + case HieLvl_CRS: + if (AnsType == Qst_ANS_UNKNOWN) // Any type + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with pluggable test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM tst_questions," + "tst_config" + " WHERE tst_questions.CrsCod=%ld" + " AND tst_questions.CrsCod=tst_config.CrsCod" + " AND tst_config.pluggable='%s'", + Gbl.Hierarchy.Crs.CrsCod, + Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); + + return (unsigned) + DB_QueryCOUNT ("can not get number of courses with pluggable test questions", + "SELECT COUNT(DISTINCT tst_questions.CrsCod)" + " FROM tst_questions," + "tst_config" + " WHERE tst_questions.CrsCod=%ld" + " AND tst_questions.AnsType='%s'" + " AND tst_questions.CrsCod=tst_config.CrsCod" + " AND tst_config.pluggable='%s'", + Gbl.Hierarchy.Crs.CrsCod, + Qst_DB_StrAnswerTypes[AnsType], + Tst_DB_Pluggable[TstCfg_PLUGGABLE_YES]); + default: + Err_WrongScopeExit (); + return 0; // Not reached + } + } diff --git a/swad_question_database.h b/swad_question_database.h new file mode 100644 index 00000000..3152a738 --- /dev/null +++ b/swad_question_database.h @@ -0,0 +1,63 @@ +// swad_question_database.h: test/exam/game questions, operations with database + +#ifndef _SWAD_QST_DB +#define _SWAD_QST_DB +/* + 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-2021 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 // For boolean type +// #include // For time_t + +// #include "swad_exam.h" +// #include "swad_game.h" +// #include "swad_question_type.h" +// #include "swad_media.h" +// #include "swad_string.h" +// #include "swad_tag.h" + +/*****************************************************************************/ +/***************************** Public constants ******************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/******************************* Public types ********************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/***************************** Public prototypes *****************************/ +/*****************************************************************************/ + +void Qst_DB_UpdateQstScore (long QstCod,bool AnswerIsNotBlank,double Score); + +unsigned Qst_DB_GetQuestionsForNewTestPrint (MYSQL_RES **mysql_res, + const struct Qst_Questions *Questions); +unsigned Qst_DB_GetNumQsts (MYSQL_RES **mysql_res, + HieLvl_Level_t Scope,Qst_AnswerType_t AnsType); +unsigned Qst_DB_GetNumCrssWithQsts (HieLvl_Level_t Scope, + Qst_AnswerType_t AnsType); +unsigned Qst_DB_GetNumCrssWithPluggableQsts (HieLvl_Level_t Scope, + Qst_AnswerType_t AnsType); + +#endif diff --git a/swad_test.c b/swad_test.c index 8b4e69a8..b5641236 100644 --- a/swad_test.c +++ b/swad_test.c @@ -53,6 +53,7 @@ #include "swad_media.h" #include "swad_parameter.h" #include "swad_question.h" +#include "swad_question_database.h" #include "swad_question_import.h" #include "swad_tag_database.h" #include "swad_test.h" @@ -508,7 +509,7 @@ static void Tst_GetQuestionsForNewTest (struct Qst_Questions *Questions, /***** Get questions and answers from database *****/ Print->NumQsts.All = - Questions->NumQsts = Tst_DB_GetQuestionsForNewTest (&mysql_res,Questions); + Questions->NumQsts = Qst_DB_GetQuestionsForNewTestPrint (&mysql_res,Questions); for (QstInd = 0; QstInd < Print->NumQsts.All; diff --git a/swad_test_database.c b/swad_test_database.c index 8806c6dc..e66bf1a3 100644 --- a/swad_test_database.c +++ b/swad_test_database.c @@ -25,43 +25,14 @@ /*********************************** Headers *********************************/ /*****************************************************************************/ -// #define _GNU_SOURCE // For asprintf -// #include // For UINT_MAX -// #include // For PATH_MAX -// #include // To access MySQL databases -// #include // For boolean type -// #include // For NULL -// #include // For asprintf -// #include // For exit, system, malloc, free, etc #include // For string functions -// #include // For mkdir -// #include // For mkdir -// #include "swad_action.h" -// #include "swad_box.h" #include "swad_database.h" #include "swad_error.h" -// #include "swad_exam_set.h" -// #include "swad_figure.h" -// #include "swad_form.h" #include "swad_global.h" -// #include "swad_hierarchy_level.h" -// #include "swad_HTML.h" -// #include "swad_ID.h" -// #include "swad_language.h" -// #include "swad_match.h" -// #include "swad_media.h" -// #include "swad_parameter.h" #include "swad_question.h" -// #include "swad_question_import.h" -// #include "swad_tag_database.h" -// #include "swad_test.h" #include "swad_test_config.h" #include "swad_test_print.h" -// #include "swad_test_visibility.h" -// #include "swad_theme.h" -// #include "swad_user.h" -// #include "swad_xml.h" /*****************************************************************************/ /***************************** Public constants ******************************/ @@ -169,141 +140,6 @@ unsigned Tst_DB_GetNumPrintsGeneratedByMe (MYSQL_RES **mysql_res) Gbl.Hierarchy.Crs.CrsCod); } -/*****************************************************************************/ -/*********************** Update the score of a question **********************/ -/*****************************************************************************/ - -void Tst_DB_UpdateQstScore (const struct TstPrn_PrintedQuestion *PrintedQuestion) - { - /***** Update number of clicks and score of the question *****/ - Str_SetDecimalPointToUS (); // To print the floating point as a dot - if (PrintedQuestion->StrAnswers[0]) // User's answer is not blank - 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", - PrintedQuestion->Score, - PrintedQuestion->QstCod); - else // User's answer is blank - DB_QueryUPDATE ("can not update the score of a question", - "UPDATE tst_questions" - " SET NumHits=NumHits+1" - " WHERE QstCod=%ld", - PrintedQuestion->QstCod); - Str_SetDecimalPointToLocal (); // Return to local system - } - -/*****************************************************************************/ -/************** Get questions for a new test from the database ***************/ -/*****************************************************************************/ - -#define Tst_MAX_BYTES_QUERY_QUESTIONS (16 * 1024 - 1) - -unsigned Tst_DB_GetQuestionsForNewTest (MYSQL_RES **mysql_res, - const struct Qst_Questions *Questions) - { - extern const char *Qst_DB_StrAnswerTypes[Qst_NUM_ANS_TYPES]; - char *Query = NULL; - long LengthQuery; - unsigned NumItemInList; - const char *Ptr; - char TagText[Tag_MAX_BYTES_TAG + 1]; - char UnsignedStr[Cns_MAX_DECIMAL_DIGITS_UINT + 1]; - Qst_AnswerType_t AnswerType; - char StrNumQsts[Cns_MAX_DECIMAL_DIGITS_UINT + 1]; - - /***** Allocate space for query *****/ - if ((Query = malloc (Tst_MAX_BYTES_QUERY_QUESTIONS + 1)) == NULL) - Err_NotEnoughMemoryExit (); - - /***** Select questions without hidden tags *****/ - /* Begin query */ - // Reject questions with any tag hidden - // Select only questions with tags - // DISTINCTROW is necessary to not repeat questions - snprintf (Query,Tst_MAX_BYTES_QUERY_QUESTIONS + 1, - "SELECT DISTINCTROW tst_questions.QstCod," // row[0] - "tst_questions.AnsType," // row[1] - "tst_questions.Shuffle" // row[2] - " FROM tst_questions,tst_question_tags,tst_tags" - " WHERE tst_questions.CrsCod=%ld" - " AND tst_questions.QstCod NOT IN" - " (SELECT tst_question_tags.QstCod" - " FROM tst_tags,tst_question_tags" - " WHERE tst_tags.CrsCod=%ld" - " AND tst_tags.TagHidden='Y'" - " AND tst_tags.TagCod=tst_question_tags.TagCod)" - " AND tst_questions.QstCod=tst_question_tags.QstCod" - " AND tst_question_tags.TagCod=tst_tags.TagCod" - " AND tst_tags.CrsCod=%ld", - Gbl.Hierarchy.Crs.CrsCod, - Gbl.Hierarchy.Crs.CrsCod, - Gbl.Hierarchy.Crs.CrsCod); - - if (!Questions->Tags.All) // User has not selected all the tags - { - /* Add selected tags */ - LengthQuery = strlen (Query); - NumItemInList = 0; - Ptr = Questions->Tags.List; - while (*Ptr) - { - Par_GetNextStrUntilSeparParamMult (&Ptr,TagText,Tag_MAX_BYTES_TAG); - LengthQuery = LengthQuery + 35 + strlen (TagText) + 1; - if (LengthQuery > Tst_MAX_BYTES_QUERY_QUESTIONS - 128) - Err_QuerySizeExceededExit (); - Str_Concat (Query, - NumItemInList ? " OR tst_tags.TagTxt='" : - " AND (tst_tags.TagTxt='", - Tst_MAX_BYTES_QUERY_QUESTIONS); - Str_Concat (Query,TagText,Tst_MAX_BYTES_QUERY_QUESTIONS); - Str_Concat (Query,"'",Tst_MAX_BYTES_QUERY_QUESTIONS); - NumItemInList++; - } - Str_Concat (Query,")",Tst_MAX_BYTES_QUERY_QUESTIONS); - } - - /* Add answer types selected */ - if (!Questions->AnswerTypes.All) - { - LengthQuery = strlen (Query); - NumItemInList = 0; - Ptr = Questions->AnswerTypes.List; - while (*Ptr) - { - Par_GetNextStrUntilSeparParamMult (&Ptr,UnsignedStr,Tag_MAX_BYTES_TAG); - AnswerType = Qst_ConvertFromUnsignedStrToAnsTyp (UnsignedStr); - LengthQuery = LengthQuery + 35 + strlen (Qst_DB_StrAnswerTypes[AnswerType]) + 1; - if (LengthQuery > Tst_MAX_BYTES_QUERY_QUESTIONS - 128) - Err_QuerySizeExceededExit (); - Str_Concat (Query, - NumItemInList ? " OR tst_questions.AnsType='" : - " AND (tst_questions.AnsType='", - Tst_MAX_BYTES_QUERY_QUESTIONS); - Str_Concat (Query,Qst_DB_StrAnswerTypes[AnswerType],Tst_MAX_BYTES_QUERY_QUESTIONS); - Str_Concat (Query,"'",Tst_MAX_BYTES_QUERY_QUESTIONS); - NumItemInList++; - } - Str_Concat (Query,")",Tst_MAX_BYTES_QUERY_QUESTIONS); - } - - /* End query */ - Str_Concat (Query," ORDER BY RAND() LIMIT ",Tst_MAX_BYTES_QUERY_QUESTIONS); - snprintf (StrNumQsts,sizeof (StrNumQsts),"%u",Questions->NumQsts); - Str_Concat (Query,StrNumQsts,Tst_MAX_BYTES_QUERY_QUESTIONS); -/* - if (Gbl.Usrs.Me.Roles.LoggedRole == Rol_SYS_ADM) - Lay_ShowAlert (Lay_INFO,Query); -*/ - /* Make the query */ - return (unsigned) - DB_QuerySELECT (mysql_res,"can not get questions", - "%s", - Query); - } - /*****************************************************************************/ /************** Save configuration of test for current course ****************/ /*****************************************************************************/ diff --git a/swad_test_database.h b/swad_test_database.h index 765d2bb7..81e34bce 100644 --- a/swad_test_database.h +++ b/swad_test_database.h @@ -56,12 +56,6 @@ void Tst_DB_UpdateLastAccTst (unsigned NumQsts); unsigned Tst_DB_GetDateNextTstAllowed (MYSQL_RES **mysql_res); unsigned Tst_DB_GetNumPrintsGeneratedByMe (MYSQL_RES **mysql_res); -//---------------------------- Test questions --------------------------------- -void Tst_DB_UpdateQstScore (const struct TstPrn_PrintedQuestion *PrintedQuestion); - -unsigned Tst_DB_GetQuestionsForNewTest (MYSQL_RES **mysql_res, - const struct Qst_Questions *Questions); - //--------------------------- Test configuration ------------------------------ void Tst_DB_SaveConfig (void); unsigned Tst_DB_GetConfig (MYSQL_RES **mysql_res,long CrsCod); diff --git a/swad_test_print.c b/swad_test_print.c index b6595fc4..9cd7ecfc 100644 --- a/swad_test_print.c +++ b/swad_test_print.c @@ -41,6 +41,7 @@ #include "swad_ID.h" #include "swad_photo.h" #include "swad_question.h" +#include "swad_question_database.h" #include "swad_test.h" #include "swad_test_database.h" #include "swad_test_print.h" @@ -574,7 +575,9 @@ void TstPrn_ShowPrintAfterAssess (struct TstPrn_Print *Print) /***** Update the number of accesses and the score of this question *****/ if (Gbl.Usrs.Me.Role.Logged == Rol_STD) - Tst_DB_UpdateQstScore (&Print->PrintedQuestions[QstInd]); + Qst_DB_UpdateQstScore (Print->PrintedQuestions[QstInd].QstCod, + Print->PrintedQuestions[QstInd].StrAnswers[0] != '\0', + Print->PrintedQuestions[QstInd].Score); /***** Destroy test question *****/ Qst_QstDestructor (&Question); @@ -758,7 +761,9 @@ void TstPrn_ComputeScoresAndStoreQuestionsOfPrint (struct TstPrn_Print *Print, /* Update the number of hits and the score of this question in tests database */ if (UpdateQstScore) - Tst_DB_UpdateQstScore (&Print->PrintedQuestions[QstInd]); + Qst_DB_UpdateQstScore (Print->PrintedQuestions[QstInd].QstCod, + Print->PrintedQuestions[QstInd].StrAnswers[0] != '\0', + Print->PrintedQuestions[QstInd].Score); } }