// swad_log.c: access log stored in 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-2024 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 3 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 free #include // For strlen #include "swad_action.h" #include "swad_action_list.h" #include "swad_banner.h" #include "swad_box.h" #include "swad_center_database.h" #include "swad_config.h" #include "swad_database.h" #include "swad_degree_database.h" #include "swad_exam_log.h" #include "swad_global.h" #include "swad_hierarchy.h" #include "swad_HTML.h" #include "swad_institution_database.h" #include "swad_log.h" #include "swad_log_database.h" #include "swad_profile.h" #include "swad_profile_database.h" #include "swad_role.h" #include "swad_statistic.h" /*****************************************************************************/ /************** External global variables from others modules ****************/ /*****************************************************************************/ extern struct Globals Gbl; /*****************************************************************************/ /************************* Private global variables **************************/ /*****************************************************************************/ static bool Log_Search = false; // Log search in database? /*****************************************************************************/ /***************************** Private prototypes ****************************/ /*****************************************************************************/ static bool Log_GetLogSearch (void); /*****************************************************************************/ /******************** Set/get logging search in database *********************/ /*****************************************************************************/ void Log_SetLogSearch (bool Search) { Log_Search = Search; } static bool Log_GetLogSearch (void) { return Log_Search; } /*****************************************************************************/ /**************************** Log access in database *************************/ /*****************************************************************************/ void Log_LogAccess (const char *Comments) { long LogCod; long ActCod = Act_GetActCod (Gbl.Action.Act); size_t MaxLength; char *CommentsDB; long BanCodClicked; Rol_Role_t RoleToStore = (Gbl.Action.Act == ActLogOut) ? Gbl.Usrs.Me.Role.LoggedBeforeCloseSession : Gbl.Usrs.Me.Role.Logged; /***** Insert access into database *****/ /* Log access in historical log */ LogCod = Log_DB_LogAccessInHistoricalLog (ActCod,RoleToStore); /* Log access in recent log (log_recent) */ Log_DB_LogAccessInRecentLog (LogCod,ActCod,RoleToStore); /* Log access while answering exam prints */ ExaLog_LogAccess (LogCod); /* Log comments */ if (Comments) { MaxLength = strlen (Comments) * Str_MAX_BYTES_PER_CHAR; if ((CommentsDB = malloc (MaxLength + 1)) != NULL) { Str_Copy (CommentsDB,Comments,MaxLength); Str_ChangeFormat (Str_FROM_TEXT,Str_TO_TEXT, CommentsDB,MaxLength,Str_REMOVE_SPACES); // Avoid SQL injection Log_DB_LogComments (LogCod,CommentsDB); free (CommentsDB); } } /* Log search string */ if (Log_GetLogSearch ()) Log_DB_LogSearchString (LogCod,Sch_GetSearch ()); if (Gbl.WebService.IsWebService) /* Log web service plugin and API function */ Log_DB_LogAPI (LogCod); else { BanCodClicked = Ban_GetBanCodClicked (); if (BanCodClicked > 0) /* Log banner clicked */ Log_DB_LogBanner (LogCod,BanCodClicked); } /***** Increment my number of clicks *****/ if (Gbl.Usrs.Me.Logged) Prf_DB_IncrementNumClicksUsr (Gbl.Usrs.Me.UsrDat.UsrCod); } /*****************************************************************************/ /*************** Put a link to show last clicks in real time *****************/ /*****************************************************************************/ void Log_PutLinkToLastClicks (void) { extern const char *Txt_Last_clicks; Lay_PutContextualLinkIconText (ActLstClk,NULL, NULL,NULL, "mouse-pointer.svg",Ico_BLACK, Txt_Last_clicks,NULL); } /*****************************************************************************/ /****************************** Show last clicks *****************************/ /*****************************************************************************/ void Log_ShowLastClicks (void) { extern const char *Hlp_USERS_Connected_last_clicks; extern const char *Txt_Last_clicks_in_real_time; /***** Contextual menu *****/ Mnu_ContextMenuBegin (); Sta_PutLinkToGlobalHits (); // Global hits Sta_PutLinkToCourseHits (); // Course hits Mnu_ContextMenuEnd (); /***** Begin box *****/ Box_BoxBegin (Txt_Last_clicks_in_real_time,NULL,NULL, Hlp_USERS_Connected_last_clicks,Box_NOT_CLOSABLE); /***** Get and show last clicks *****/ HTM_DIV_Begin ("id=\"lastclicks\" class=\"CM\""); // Used for AJAX based refresh Log_GetAndShowLastClicks (); HTM_DIV_End (); // Used for AJAX based refresh /***** End box *****/ Box_BoxEnd (); } /*****************************************************************************/ /**************** Get last clicks from database and show them ****************/ /*****************************************************************************/ void Log_GetAndShowLastClicks (void) { extern const char *Txt_Click; extern const char *Txt_ELAPSED_TIME; extern const char *Txt_Role; extern const char *Txt_HIERARCHY_SINGUL_Abc[Hie_NUM_LEVELS]; extern const char *Txt_Action; extern const char *Txt_ROLES_SINGUL_Abc[Rol_NUM_ROLES][Usr_NUM_SEXS]; MYSQL_RES *mysql_res; MYSQL_ROW row; unsigned NumClicks; unsigned NumClick; long ActCod; Act_Action_t Action; const char *ClassRow; time_t TimeDiff; struct Hie_Node Hie[Hie_NUM_LEVELS]; Hie_Level_t Level; /***** Get last clicks from database *****/ NumClicks = Log_DB_GetLastClicks (&mysql_res); /***** Write list of connected users *****/ HTM_TABLE_Begin ("TBL_SCROLL"); /* Heading row */ HTM_TR_Begin (NULL); HTM_TH_Span (Txt_Click ,HTM_HEAD_RIGHT,1,1,"LC_CLK"); // Click HTM_TH_Span (Txt_ELAPSED_TIME ,HTM_HEAD_RIGHT,1,1,"LC_TIM"); // Elapsed time HTM_TH_Span (Txt_Role ,HTM_HEAD_LEFT ,1,1,"LC_ROL"); // Role for (Level = Hie_CTY; Level <= Hie_DEG; Level++) HTM_TH_Span (Txt_HIERARCHY_SINGUL_Abc[Level],HTM_HEAD_LEFT ,1,1,"LC_HIE"); // Country, Institution, Center, Degree HTM_TH_Span (Txt_Action ,HTM_HEAD_LEFT ,1,1,"LC_ACT"); // Action HTM_TR_End (); for (NumClick = 0; NumClick < NumClicks; NumClick++) { row = mysql_fetch_row (mysql_res); /* Get action code (row[1]) */ ActCod = Str_ConvertStrCodToLongCod (row[1]); Action = Act_GetActionFromActCod (ActCod); /* Use a special color for this row depending on the action */ ClassRow = (Act_GetBrowserTab (Action) == Act_DWN) ? "DAT_SMALL_YELLOW" : (ActCod == Act_GetActCod (ActLogIn ) || ActCod == Act_GetActCod (ActLogInNew)) ? "DAT_SMALL_GREEN" : (ActCod == Act_GetActCod (ActLogOut )) ? "DAT_SMALL_RED" : (ActCod == Act_GetActCod (ActWebSvc )) ? "DAT_SMALL_BLUE" : "DAT_SMALL"; /* Compute elapsed time from last access */ if (sscanf (row[2],"%ld",&TimeDiff) != 1) TimeDiff = (time_t) 0; /* Get country code (row[4]) */ Hie[Hie_CTY].HieCod = Str_ConvertStrCodToLongCod (row[4]); Cty_GetCountryNameInLanguage (Hie[Hie_CTY].HieCod,Gbl.Prefs.Language, Hie[Hie_CTY].FullName); /* Get institution code (row[5]), center code (row[6]) and degree code (row[7]) */ Hie[Hie_INS].HieCod = Str_ConvertStrCodToLongCod (row[5]); Hie[Hie_CTR].HieCod = Str_ConvertStrCodToLongCod (row[6]); Hie[Hie_DEG].HieCod = Str_ConvertStrCodToLongCod (row[7]); Ins_DB_GetInsShrtName (Hie[Hie_INS].HieCod,Hie[Hie_INS].ShrtName); Ctr_DB_GetCtrShrtName (Hie[Hie_CTR].HieCod,Hie[Hie_CTR].ShrtName); Deg_DB_GetDegShrtName (Hie[Hie_DEG].HieCod,Hie[Hie_DEG].ShrtName); /* Print table row */ HTM_TR_Begin (NULL); HTM_TD_Begin ("class=\"LC_CLK %s_%s\"", ClassRow,The_GetSuffix ()); HTM_Txt (row[0]); // Click HTM_TD_End (); HTM_TD_Begin ("class=\"LC_TIM %s_%s\"", ClassRow,The_GetSuffix ()); Dat_WriteHoursMinutesSecondsFromSeconds (TimeDiff); // Elapsed time HTM_TD_End (); HTM_TD_Begin ("class=\"LC_ROL %s_%s\"", ClassRow,The_GetSuffix ()); HTM_Txt (Txt_ROLES_SINGUL_Abc[Rol_ConvertUnsignedStrToRole (row[3])][Usr_SEX_UNKNOWN]); // Role HTM_TD_End (); HTM_TD_Begin ("class=\"LC_HIE %s_%s\"", ClassRow,The_GetSuffix ()); HTM_Txt (Hie[Hie_CTY].FullName); // Country HTM_TD_End (); for (Level = Hie_INS; Level <= Hie_DEG; Level++) { HTM_TD_Begin ("class=\"LC_HIE %s_%s\"", ClassRow,The_GetSuffix ()); HTM_Txt (Hie[Level].ShrtName); // Institution, Center, Degree HTM_TD_End (); } HTM_TD_Begin ("class=\"LC_ACT %s_%s\"", ClassRow,The_GetSuffix ()); HTM_Txt (Act_GetActionText (Action)); // Action HTM_TD_End (); HTM_TR_End (); } HTM_TABLE_End (); /***** Free structure that stores the query result *****/ DB_FreeMySQLResult (&mysql_res); }