// swad_test_database.c: self-assessment tests, 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 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 ******************************/ /*****************************************************************************/ /*****************************************************************************/ /**************************** Private constants ******************************/ /*****************************************************************************/ /*****************************************************************************/ /******************************* Private types *******************************/ /*****************************************************************************/ /*****************************************************************************/ /************** External global variables from others modules ****************/ /*****************************************************************************/ extern struct Globals Gbl; /*****************************************************************************/ /************************* Private global variables **************************/ /*****************************************************************************/ /*****************************************************************************/ /***************************** Private prototypes ****************************/ /*****************************************************************************/ /*****************************************************************************/ /************** Update my number of test prints in this course ***************/ /*****************************************************************************/ void Tst_DB_IncreaseNumMyPrints (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_user_settings" " SET NumAccTst=NumAccTst+1" " WHERE UsrCod=%ld" " AND CrsCod=%ld", Gbl.Usrs.Me.UsrDat.UsrCod, Gbl.Hierarchy.Crs.CrsCod); } /*****************************************************************************/ /******** Update date-time and number of questions of this test print ********/ /*****************************************************************************/ void Tst_DB_UpdateLastAccTst (unsigned NumQsts) { DB_QueryUPDATE ("can not update time and number of questions of this test", "UPDATE crs_user_settings" " SET LastAccTst=NOW()," "NumQstsLastTst=%u" " WHERE UsrCod=%ld" " AND CrsCod=%ld", NumQsts, Gbl.Usrs.Me.UsrDat.UsrCod, Gbl.Hierarchy.Crs.CrsCod); } /*****************************************************************************/ /********** Get date of next allowed access to test from database ************/ /*****************************************************************************/ unsigned Tst_DB_GetDateNextTstAllowed (MYSQL_RES **mysql_res) { return (unsigned) DB_QuerySELECT (mysql_res,"can not get date of last test print", "SELECT UNIX_TIMESTAMP(LastAccTst+INTERVAL (NumQstsLastTst*%lu) SECOND)-" "UNIX_TIMESTAMP()," // row[0] "UNIX_TIMESTAMP(LastAccTst+INTERVAL (NumQstsLastTst*%lu) SECOND)" // row[1] " FROM crs_user_settings" " WHERE UsrCod=%ld" " AND CrsCod=%ld", TstCfg_GetConfigMinTimeNxtTstPerQst (), TstCfg_GetConfigMinTimeNxtTstPerQst (), Gbl.Usrs.Me.UsrDat.UsrCod, Gbl.Hierarchy.Crs.CrsCod); } /*****************************************************************************/ /**************** Get number of test prints generated by me ******************/ /*****************************************************************************/ unsigned Tst_DB_GetNumPrintsGeneratedByMe (MYSQL_RES **mysql_res) { return (unsigned) DB_QuerySELECT (mysql_res,"can not get number of test prints generated", "SELECT NumAccTst" // row[0] " FROM crs_user_settings" " WHERE UsrCod=%ld" " AND CrsCod=%ld", Gbl.Usrs.Me.UsrDat.UsrCod, Gbl.Hierarchy.Crs.CrsCod); } /*****************************************************************************/ /************** 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); } /*****************************************************************************/ /****************** Remove test configuration in a course ********************/ /*****************************************************************************/ void Tst_DB_RemoveTstConfig (long CrsCod) { DB_QueryDELETE ("can not remove configuration of tests of a course", "DELETE FROM tst_config" " WHERE CrsCod=%ld", CrsCod); }