diff --git a/swad_changelog.h b/swad_changelog.h index c4375630..d059186d 100644 --- a/swad_changelog.h +++ b/swad_changelog.h @@ -557,11 +557,11 @@ enscript -2 --landscape --color --file-align=2 --highlight --line-numbers -o - * En OpenSWAD: ps2pdf source.ps destination.pdf */ -#define Log_PLATFORM_VERSION "SWAD 19.242.1 (2020-05-23)" +#define Log_PLATFORM_VERSION "SWAD 19.243 (2020-05-23)" #define CSS_FILE "swad19.238.2.css" #define JS_FILE "swad19.239.6.js" /* - Version 19.243: May 23, 2020 List exam log. (? lines) + Version 19.243: May 23, 2020 List exam log. (302180 lines) Version 19.242.1: May 23, 2020 Bug fixing and code refactoring in exam log. (301932 lines) 1 change necessary in database: ALTER TABLE exa_log CHANGE COLUMN Open CanAnswer ENUM('N','Y') NOT NULL DEFAULT 'N'; diff --git a/swad_exam_log.c b/swad_exam_log.c index dfacf645..3856977f 100644 --- a/swad_exam_log.c +++ b/swad_exam_log.c @@ -25,6 +25,10 @@ /********************************* Headers ***********************************/ /*****************************************************************************/ +#define _GNU_SOURCE // For asprintf +#include // For asprintf +#include // For string functions + #include "swad_action.h" #include "swad_database.h" #include "swad_exam_log.h" @@ -142,7 +146,7 @@ void ExaLog_LogAccess (long LogCod) Redundant data (also present in log table) are stored for speed */ DB_QueryINSERT ("can not log exam access", "INSERT INTO exa_log " - "(LogCod,PrnCod,ActCod,QstInd,UserCanAnswer,ClickTime,IP,SessionId)" + "(LogCod,PrnCod,ActCod,QstInd,CanAnswer,ClickTime,IP,SessionId)" " VALUES " "(%ld,%ld,%ld,%d,'%c',NOW(),'%s','%s')", LogCod, @@ -156,3 +160,191 @@ void ExaLog_LogAccess (long LogCod) Gbl.Session.Id); } } + +/*****************************************************************************/ +/****************************** Show exam log ********************************/ +/*****************************************************************************/ + +void ExaLog_ShowExamLog (const struct ExaPrn_Print *Print) + { + extern const char *Txt_Hits; + extern const char *Txt_Date_and_time; + extern const char *Txt_Action; + extern const char *Txt_Question; + extern const char *Txt_EXAM_Open; + extern const char *Txt_IP; + extern const char *Txt_Session; + extern const char *Txt_EXAM_LOG_ACTIONS[ExaLog_NUM_ACTIONS]; + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NumClicks; + unsigned NumClick; + unsigned ActCod; + int QstInd; + bool UsrCouldAnswer; + time_t ClickTimeUTC; + char IP[Cns_MAX_BYTES_IP + 1]; + char SessionId[Cns_BYTES_SESSION_ID + 1]; + char *Id; + size_t Length; + char Anonymized[14 + 1]; // XXX…XXX + // 12345678901234 + + /***** Check if I can view this print result *****/ + switch (Gbl.Usrs.Me.Role.Logged) + { + case Rol_NET: + case Rol_TCH: + case Rol_DEG_ADM: + case Rol_CTR_ADM: + case Rol_INS_ADM: + case Rol_SYS_ADM: + break; + default: // Other users can not see log + return; + } + + /***** Get print log from database *****/ + NumClicks = (unsigned) + DB_QuerySELECT (&mysql_res,"can not get exam print log", + "SELECT ActCod," // row[0] + "QstInd," // row[1] + "CanAnswer," // row[2] + "UNIX_TIMESTAMP(ClickTime)," // row[3] + "IP," // row[4] + "SessionId" // row[5] + " FROM exa_log" + " WHERE PrnCod=%ld" + " ORDER BY LogCod", + Print->PrnCod); + + if (NumClicks) + { + /***** Begin box *****/ + Box_BoxTableBegin (NULL,Txt_Hits, + NULL,NULL, + NULL,Box_CLOSABLE,2); + + /***** Begin table *****/ + HTM_TABLE_BeginWideMarginPadding (2); + + /***** Write heading *****/ + HTM_TR_Begin (NULL); + + HTM_TH (1,1,"LB",Txt_Date_and_time); + HTM_TH (1,1,"LB",Txt_Action); + HTM_TH (1,1,"RB",Txt_Question); + HTM_TH (1,1,"CB",Txt_EXAM_Open); + HTM_TH (1,1,"LB",Txt_IP); + HTM_TH (1,1,"LB",Txt_Session); + + HTM_TR_End (); + + /***** Write clicks *****/ + for (NumClick = 0; + NumClick < NumClicks; + NumClick++) + { + /***** Get row *****/ + row = mysql_fetch_row (mysql_res); + + /* Get code of action (row[0]) */ + ActCod = Str_ConvertStrToUnsigned (row[0]); + if (ActCod >= ExaLog_NUM_ACTIONS) + ActCod = ExaLog_UNKNOWN_ACTION; + + /* Get question index (row[1]) */ + QstInd = (int) Str_ConvertStrCodToLongCod (row[1]); + + /* Get if the user could answer (row[2]) */ + UsrCouldAnswer = (row[2][0] == 'Y'); + + /* Get click time (row[3] holds the UTC time) */ + ClickTimeUTC = Dat_GetUNIXTimeFromStr (row[3]); + + /* Get IP (row[4]) */ + Str_Copy (IP,row[4], + Cns_MAX_BYTES_IP); + + /* Get session id (row[5]) */ + Str_Copy (SessionId,row[5], + Cns_BYTES_SESSION_ID); + + /***** Write row *****/ + HTM_TR_Begin (NULL); + + /* Write click time */ + if (asprintf (&Id,"click_date_%u",NumClick) < 0) + Lay_NotEnoughMemoryExit (); + HTM_TD_Begin ("id=\"%s\" class=\"LM DAT\"",Id); + Dat_WriteLocalDateHMSFromUTC (Id,ClickTimeUTC, + Gbl.Prefs.DateFormat,Dat_SEPARATOR_COMMA, + true,true,true,0x7); + free (Id); + HTM_TD_End (); + + /* Write action */ + HTM_TD_Begin ("class=\"LM DAT\""); + HTM_Txt (Txt_EXAM_LOG_ACTIONS[ActCod]); + HTM_TD_End (); + + /* Write number of question */ + HTM_TD_Begin ("class=\"RM DAT\""); + if (QstInd >= 0) + HTM_Unsigned ((unsigned) QstInd + 1); + HTM_TD_End (); + + /* Write if exam print was open and accesible to answer */ + HTM_TD_Begin ("class=\"CM %s\"",UsrCouldAnswer ? "DAT_GREEN" : + "DAT_RED"); + HTM_Txt (UsrCouldAnswer ? "✓" : + "✗"); + HTM_TD_End (); + + /* Write IP */ + HTM_TD_Begin ("class=\"CM DAT\""); + Length = strlen (IP); + if (Length > 6) + { + sprintf (Anonymized,"%c%c%c…%c%c%c", + IP[0], + IP[1], + IP[2], + IP[Length - 3], + IP[Length - 2], + IP[Length - 1]); + HTM_Txt (Anonymized); + } + else + HTM_Txt ("…"); + HTM_TD_End (); + + /* Write session id */ + HTM_TD_Begin ("class=\"CM DAT\""); + Length = strlen (SessionId); + Length = strlen (IP); + if (Length > 6) + { + sprintf (Anonymized,"%c%c%c…%c%c%c", + SessionId[0], + SessionId[1], + SessionId[2], + SessionId[Length - 3], + SessionId[Length - 2], + SessionId[Length - 1]); + HTM_Txt (Anonymized); + } + else + HTM_Txt ("…"); + HTM_TD_End (); + + HTM_TR_End (); + } + + /***** End table and box *****/ + Box_BoxTableEnd (); + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } diff --git a/swad_exam_log.h b/swad_exam_log.h index d075ffe9..201587ef 100644 --- a/swad_exam_log.h +++ b/swad_exam_log.h @@ -27,6 +27,8 @@ /********************************** Headers **********************************/ /*****************************************************************************/ +#include "swad_exam_print.h" + /*****************************************************************************/ /************************* Public types and constants ************************/ /*****************************************************************************/ @@ -57,4 +59,6 @@ bool ExaLog_GetIfCanAnswer (void); void ExaLog_LogAccess (long LogCod); +void ExaLog_ShowExamLog (const struct ExaPrn_Print *Print); + #endif diff --git a/swad_exam_result.c b/swad_exam_result.c index 8b2283e5..2906acb4 100644 --- a/swad_exam_result.c +++ b/swad_exam_result.c @@ -1138,10 +1138,11 @@ void ExaRes_ShowOneExaResult (void) ExaRes_ShowExamResult (&Exam,&Session,&Print,UsrDat); /***** Show exam log *****/ + ExaLog_ShowExamLog (&Print); } /*****************************************************************************/ -/*************************** Show one exam result ****************************/ +/***************************** Show exam result ******************************/ /*****************************************************************************/ static void ExaRes_ShowExamResult (const struct Exa_Exam *Exam, diff --git a/swad_exam_session.c b/swad_exam_session.c index ca4c4b87..06371b8c 100644 --- a/swad_exam_session.c +++ b/swad_exam_session.c @@ -888,8 +888,8 @@ static void ExaSes_GetSessionDataFromRow (MYSQL_RES *mysql_res, void ExaSes_RequestRemoveSession (void) { - extern const char *Txt_Do_you_really_want_to_remove_the_event_X; - extern const char *Txt_Remove_event; + extern const char *Txt_Do_you_really_want_to_remove_the_session_X; + extern const char *Txt_Remove_session; struct Exa_Exams Exams; struct Exa_Exam Exam; struct ExaSes_Session Session; @@ -907,8 +907,8 @@ void ExaSes_RequestRemoveSession (void) Exams.SesCod = Session.SesCod; Ale_ShowAlertAndButton (ActRemExaSes,NULL,NULL, ExaSes_PutParamsEdit,&Exams, - Btn_REMOVE_BUTTON,Txt_Remove_event, - Ale_QUESTION,Txt_Do_you_really_want_to_remove_the_event_X, + Btn_REMOVE_BUTTON,Txt_Remove_session, + Ale_QUESTION,Txt_Do_you_really_want_to_remove_the_session_X, Session.Title); /***** Show current exam *****/ diff --git a/swad_text.c b/swad_text.c index a329495b..8bb261b3 100644 --- a/swad_text.c +++ b/swad_text.c @@ -7203,6 +7203,27 @@ const char *Txt_Date = "Data"; #endif +const char *Txt_Date_and_time = +#if L==1 // ca + "Data i hora"; +#elif L==2 // de + "Datum und Uhrzeit"; +#elif L==3 // en + "Date and time"; +#elif L==4 // es + "Fecha y hora"; +#elif L==5 // fr + "Date et heure"; +#elif L==6 // gn + "Fecha y hora"; // Okoteve traducción +#elif L==7 // it + "Data e ora"; +#elif L==8 // pl + "Data i godzina"; +#elif L==9 // pt + "Data e hora"; +#endif + const char *Txt_Date_of_birth = #if L==1 // ca "Data naixement"; @@ -9523,6 +9544,27 @@ const char *Txt_Do_you_really_want_to_remove_the_selected_questions = "Você realmente deseja remover as perguntas selecionadas?"; #endif +const char *Txt_Do_you_really_want_to_remove_the_session_X = // Warning: it is very important to include %s in the following sentences +#if L==1 // ca + "De veres voleu eliminar la sessió %s?"; +#elif L==2 // de + "Wollen Sie der Sitzung %s wirklich entfernen?"; +#elif L==3 // en + "Do you really want to remove the session %s?"; +#elif L==4 // es + "¿Realmente desea eliminar la sesión %s?"; +#elif L==5 // fr + "Voulez-vous vraiment supprimer la session %s?"; +#elif L==6 // gn + "¿Realmente desea eliminar la sesión %s?"; // Okoteve traducción +#elif L==7 // it + "Vuoi realmente rimuovere la sessione %s?"; +#elif L==8 // pl + "Czy na pewno chcesz usunac sesji %s?"; +#elif L==9 // pt + "Você realmente deseja remover a sessão %s?"; +#endif + const char *Txt_Do_you_really_want_to_remove_the_set_of_questions_X = // Warning: it is very important to include %s in the following sentences #if L==1 // ca "¿De veres voleu eliminar el conjunt de preguntes %s?"; @@ -12006,6 +12048,27 @@ const char *Txt_Exam_of_X = // Warning: it is very important to include %s in th "Exame de %s"; #endif +const char *Txt_EXAM_Open = +#if L==1 // ca + "Obert"; +#elif L==2 // de + "Geöffneten"; +#elif L==3 // en + "Open"; +#elif L==4 // es + "Abierto"; +#elif L==5 // fr + "Ouvert"; +#elif L==6 // gn + "Abierto"; // Okoteve traducción +#elif L==7 // it + "Aperto"; +#elif L==8 // pl + "otwarte"; +#elif L==9 // pt + "Aberta"; +#endif + const char *Txt_Exam_X_removed = // Warning: it is very important to include %s in the following sentences #if L==1 // ca "Examen %s eliminat."; @@ -24470,7 +24533,7 @@ const char *Txt_MSG_Not_replied = const char *Txt_MSG_Open = #if L==1 // ca - "Abierto"; // Necessita traduccio + "Obert"; #elif L==2 // de "Geöffneten"; #elif L==3 // en @@ -34415,6 +34478,27 @@ const char *Txt_Remove_record_field = "Remover campo de cartão"; #endif +const char *Txt_Remove_session = +#if L==1 // ca + "Eliminar sessió"; +#elif L==2 // de + "Entfernen Sitzung"; +#elif L==3 // en + "Remove session"; +#elif L==4 // es + "Eliminar sesión"; +#elif L==5 // fr + "Supprimer session"; +#elif L==6 // gn + "Eliminar sesión"; // Okoteve traducción +#elif L==7 // it + "Rimuovere sessione"; +#elif L==8 // pl + "Usuń sesji"; +#elif L==9 // pt + "Remover sessão"; +#endif + const char *Txt_Remove_set_of_questions = #if L==1 // ca "Eliminar conjunt de preguntes";