// 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); }