// swad_statistic.c: statistics /* 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-2019 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 log10, floor, ceil, modf, sqrt... #include // For asprintf #include // For getenv, malloc #include // For string functions #include "swad_box.h" #include "swad_database.h" #include "swad_form.h" #include "swad_global.h" #include "swad_HTML.h" #include "swad_ID.h" #include "swad_profile.h" #include "swad_role.h" #include "swad_statistic.h" /*****************************************************************************/ /************** External global variables from others modules ****************/ /*****************************************************************************/ extern struct Globals Gbl; /*****************************************************************************/ /***************************** Private constants *****************************/ /*****************************************************************************/ #define Sta_SECONDS_IN_RECENT_LOG ((time_t) (Cfg_DAYS_IN_RECENT_LOG * 24UL * 60UL * 60UL)) // Remove entries in recent log oldest than this time const unsigned Sta_CellPadding[Sta_NUM_CLICKS_GROUPED_BY] = { 2, // Sta_CLICKS_CRS_DETAILED_LIST 1, // Sta_CLICKS_CRS_PER_USR 1, // Sta_CLICKS_CRS_PER_DAY 0, // Sta_CLICKS_CRS_PER_DAY_AND_HOUR 1, // Sta_CLICKS_CRS_PER_WEEK 1, // Sta_CLICKS_CRS_PER_MONTH 1, // Sta_CLICKS_CRS_PER_YEAR 1, // Sta_CLICKS_CRS_PER_HOUR 0, // Sta_CLICKS_CRS_PER_MINUTE 1, // Sta_CLICKS_CRS_PER_ACTION 1, // Sta_CLICKS_GBL_PER_DAY 0, // Sta_CLICKS_GBL_PER_DAY_AND_HOUR 1, // Sta_CLICKS_GBL_PER_WEEK 1, // Sta_CLICKS_GBL_PER_MONTH 1, // Sta_CLICKS_GBL_PER_YEAR 1, // Sta_CLICKS_GBL_PER_HOUR 0, // Sta_CLICKS_GBL_PER_MINUTE 1, // Sta_CLICKS_GBL_PER_ACTION 1, // Sta_CLICKS_GBL_PER_PLUGIN 1, // Sta_CLICKS_GBL_PER_API_FUNCTION 1, // Sta_CLICKS_GBL_PER_BANNER 1, // Sta_CLICKS_GBL_PER_COUNTRY 1, // Sta_CLICKS_GBL_PER_INSTITUTION 1, // Sta_CLICKS_GBL_PER_CENTRE 1, // Sta_CLICKS_GBL_PER_DEGREE 1, // Sta_CLICKS_GBL_PER_COURSE }; #define Sta_STAT_RESULTS_SECTION_ID "stat_results" /*****************************************************************************/ /******************************* Private types *******************************/ /*****************************************************************************/ typedef enum { Sta_SHOW_GLOBAL_ACCESSES, Sta_SHOW_COURSE_ACCESSES, } Sta_GlobalOrCourseAccesses_t; /*****************************************************************************/ /***************************** Internal prototypes ***************************/ /*****************************************************************************/ static void Sta_PutLinkToCourseHits (void); static void Sta_PutLinkToGlobalHits (void); static void Sta_WriteSelectorCountType (void); static void Sta_WriteSelectorAction (void); static void Sta_ShowHits (Sta_GlobalOrCourseAccesses_t GlobalOrCourse); static void Sta_ShowDetailedAccessesList (unsigned long NumRows,MYSQL_RES *mysql_res); static void Sta_WriteLogComments (long LogCod); static void Sta_ShowNumHitsPerUsr (unsigned long NumRows,MYSQL_RES *mysql_res); static void Sta_ShowNumHitsPerDay (unsigned long NumRows,MYSQL_RES *mysql_res); static void Sta_ShowDistrAccessesPerDayAndHour (unsigned long NumRows,MYSQL_RES *mysql_res); static void Sta_PutHiddenParamScopeSta (void); static Sta_ColorType_t Sta_GetStatColorType (void); static void Sta_DrawBarColors (Sta_ColorType_t ColorType,float HitsMax); static void Sta_DrawAccessesPerHourForADay (Sta_ColorType_t ColorType,float HitsNum[24],float HitsMax); static void Sta_SetColor (Sta_ColorType_t ColorType,float HitsNum,float HitsMax, unsigned *R,unsigned *G,unsigned *B); static void Sta_ShowNumHitsPerWeek (unsigned long NumRows, MYSQL_RES *mysql_res); static void Sta_ShowNumHitsPerMonth (unsigned long NumRows, MYSQL_RES *mysql_res); static void Sta_ShowNumHitsPerYear (unsigned long NumRows, MYSQL_RES *mysql_res); static void Sta_ShowNumHitsPerHour (unsigned long NumRows, MYSQL_RES *mysql_res); static void Sta_WriteAccessHour (unsigned Hour,struct Sta_Hits *Hits,unsigned ColumnWidth); static void Sta_ShowAverageAccessesPerMinute (unsigned long NumRows,MYSQL_RES *mysql_res); static void Sta_WriteLabelsXAxisAccMin (float IncX,const char *Format); static void Sta_WriteAccessMinute (unsigned Minute,float HitsNum,float MaxX); static void Sta_ShowNumHitsPerAction (unsigned long NumRows, MYSQL_RES *mysql_res); static void Sta_ShowNumHitsPerPlugin (unsigned long NumRows, MYSQL_RES *mysql_res); static void Sta_ShowNumHitsPerWSFunction (unsigned long NumRows, MYSQL_RES *mysql_res); static void Sta_ShowNumHitsPerBanner (unsigned long NumRows, MYSQL_RES *mysql_res); static void Sta_ShowNumHitsPerCountry (unsigned long NumRows, MYSQL_RES *mysql_res); static void Sta_WriteCountry (long CtyCod); static void Sta_ShowNumHitsPerInstitution (unsigned long NumRows, MYSQL_RES *mysql_res); static void Sta_WriteInstitution (long InsCod); static void Sta_ShowNumHitsPerCentre (unsigned long NumRows, MYSQL_RES *mysql_res); static void Sta_WriteCentre (long CtrCod); static void Sta_ShowNumHitsPerDegree (unsigned long NumRows, MYSQL_RES *mysql_res); static void Sta_WriteDegree (long DegCod); static void Sta_ShowNumHitsPerCourse (unsigned long NumRows, MYSQL_RES *mysql_res); static void Sta_DrawBarNumHits (char Color, float HitsNum,float HitsMax,float HitsTotal, unsigned MaxBarWidth); /*****************************************************************************/ /*************** Read CGI environment variable REMOTE_ADDR *******************/ /*****************************************************************************/ /* CGI Environment Variables: REMOTE_ADDR The IP address of the remote host making the request. */ void Sta_GetRemoteAddr (void) { if (getenv ("REMOTE_ADDR")) Str_Copy (Gbl.IP,getenv ("REMOTE_ADDR"), Cns_MAX_BYTES_IP); else Gbl.IP[0] = '\0'; } /*****************************************************************************/ /**************************** Log access in database *************************/ /*****************************************************************************/ void Sta_LogAccess (const char *Comments) { long LogCod; long ActCod = Act_GetActCod (Gbl.Action.Act); 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 (log_full) */ LogCod = DB_QueryINSERTandReturnCode ("can not log access (full)", "INSERT INTO log_full " "(ActCod,CtyCod,InsCod,CtrCod,DegCod,CrsCod,UsrCod," "Role,ClickTime,TimeToGenerate,TimeToSend,IP)" " VALUES " "(%ld,%ld,%ld,%ld,%ld,%ld,%ld," "%u,NOW(),%ld,%ld,'%s')", ActCod, Gbl.Hierarchy.Cty.CtyCod, Gbl.Hierarchy.Ins.InsCod, Gbl.Hierarchy.Ctr.CtrCod, Gbl.Hierarchy.Deg.DegCod, Gbl.Hierarchy.Crs.CrsCod, Gbl.Usrs.Me.UsrDat.UsrCod, (unsigned) RoleToStore, Gbl.TimeGenerationInMicroseconds, Gbl.TimeSendInMicroseconds, Gbl.IP); /* Log access in recent log (log_recent) */ DB_QueryINSERT ("can not log access (recent)", "INSERT INTO log_recent " "(LogCod,ActCod,CtyCod,InsCod,CtrCod,DegCod,CrsCod,UsrCod," "Role,ClickTime,TimeToGenerate,TimeToSend,IP)" " VALUES " "(%ld,%ld,%ld,%ld,%ld,%ld,%ld,%ld," "%u,NOW(),%ld,%ld,'%s')", LogCod,ActCod, Gbl.Hierarchy.Cty.CtyCod, Gbl.Hierarchy.Ins.InsCod, Gbl.Hierarchy.Ctr.CtrCod, Gbl.Hierarchy.Deg.DegCod, Gbl.Hierarchy.Crs.CrsCod, Gbl.Usrs.Me.UsrDat.UsrCod, (unsigned) RoleToStore, Gbl.TimeGenerationInMicroseconds, Gbl.TimeSendInMicroseconds, Gbl.IP); /* Log comments */ if (Comments) DB_QueryINSERT ("can not log access (comments)", "INSERT INTO log_comments" " (LogCod,Comments)" " VALUES" " (%ld,'%s')", LogCod,Comments); /* Log search string */ if (Gbl.Search.LogSearch && Gbl.Search.Str[0]) DB_QueryINSERT ("can not log access (search)", "INSERT INTO log_search" " (LogCod,SearchStr)" " VALUES" " (%ld,'%s')", LogCod,Gbl.Search.Str); if (Gbl.WebService.IsWebService) /* Log web service plugin and function */ DB_QueryINSERT ("can not log access (comments)", "INSERT INTO log_ws" " (LogCod,PlgCod,FunCod)" " VALUES" " (%ld,%ld,%u)", LogCod,Gbl.WebService.PlgCod, (unsigned) Gbl.WebService.Function); else if (Gbl.Banners.BanCodClicked > 0) /* Log banner clicked */ DB_QueryINSERT ("can not log banner clicked", "INSERT INTO log_banners" " (LogCod,BanCod)" " VALUES" " (%ld,%ld)", LogCod,Gbl.Banners.BanCodClicked); /***** Increment my number of clicks *****/ if (Gbl.Usrs.Me.Logged) Prf_IncrementNumClicksUsr (Gbl.Usrs.Me.UsrDat.UsrCod); } /*****************************************************************************/ /************ Sometimes, we delete old entries in recent log table ***********/ /*****************************************************************************/ void Sta_RemoveOldEntriesRecentLog (void) { /***** Remove all expired clipboards *****/ DB_QueryDELETE ("can not remove old entries from recent log", "DELETE LOW_PRIORITY FROM log_recent" " WHERE ClickTime Sta_CLICKS_CRS_PER_ACTION) && Gbl.Stat.ClicksGroupedBy != Sta_CLICKS_CRS_DETAILED_LIST) Gbl.Stat.ClicksGroupedBy = Sta_CLICKS_GROUPED_BY_DEFAULT; HTM_INPUT_RADIO ("GroupedOrDetailed",false, "value=\"%u\"%s onclick=\"disableDetailedClicks();\"", (unsigned) Sta_CLICKS_GROUPED, Gbl.Stat.ClicksGroupedBy == Sta_CLICKS_CRS_DETAILED_LIST ? "" : " checked=\"checked\""); /* Selection of count type (number of pages generated, accesses per user, etc.) */ Sta_WriteSelectorCountType (); HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]); fprintf (Gbl.F.Out," %s ",Txt_distributed_by); HTM_SELECT_Begin (false, "id=\"GroupedBy\" name=\"GroupedBy\""); for (ClicksGroupedBy = Sta_CLICKS_CRS_PER_USR; ClicksGroupedBy <= Sta_CLICKS_CRS_PER_ACTION; ClicksGroupedBy++) { fprintf (Gbl.F.Out,"