// 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,"