// swad_statistic_database.c: statistics, operations with 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 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 ***********************************/ /*****************************************************************************/ #include // For NULL #include // For free #include // For string functions #include "swad_action_list.h" #include "swad_database.h" #include "swad_error.h" #include "swad_global.h" #include "swad_ID.h" #include "swad_statistic.h" /*****************************************************************************/ /************** External global variables from others modules ****************/ /*****************************************************************************/ extern struct Globals Gbl; /*****************************************************************************/ /******************** Compute and show access statistics ********************/ /*****************************************************************************/ #define Sta_DB_MAX_BYTES_QUERY (1024 + (10 + ID_MAX_BYTES_USR_ID) * 5000 - 1) unsigned Sta_DB_GetHits (MYSQL_RES **mysql_res, const struct Sta_Stats *Stats, const char *LogTable, const char BrowserTimeZone[Dat_MAX_BYTES_TIME_ZONE + 1], unsigned NumUsrsInList, const long *LstSelectedUsrCods) { char SubQueryCountType[256]; char SubQueryRole[256]; char SubQuery[512]; char *Query = NULL; long LengthQuery; unsigned NumUsr; unsigned NumHits; /***** Build subquery depending on the type of count *****/ switch (Stats->CountType) { case Sta_TOTAL_CLICKS: Str_Copy (SubQueryCountType,"COUNT(*)",sizeof (SubQueryCountType) - 1); break; case Sta_DISTINCT_USRS: sprintf (SubQueryCountType,"COUNT(DISTINCT %s.UsrCod)",LogTable); break; case Sta_CLICKS_PER_USR: sprintf (SubQueryCountType,"COUNT(*)/GREATEST(COUNT(DISTINCT %s.UsrCod),1)+0.000000",LogTable); break; case Sta_GENERATION_TIME: sprintf (SubQueryCountType,"(AVG(%s.TimeToGenerate)/1E6)+0.000000",LogTable); break; case Sta_SEND_TIME: sprintf (SubQueryCountType,"(AVG(%s.TimeToSend)/1E6)+0.000000",LogTable); break; } /***** Allocate memory for the query *****/ if ((Query = malloc (Sta_DB_MAX_BYTES_QUERY + 1)) == NULL) Err_NotEnoughMemoryExit (); /***** Begin building the query *****/ switch (Stats->ClicksGroupedBy) { case Sta_CLICKS_CRS_DETAILED_LIST: snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, "SELECT SQL_NO_CACHE LogCod," "UsrCod," "Role," "UNIX_TIMESTAMP(ClickTime) AS F," "ActCod" " FROM %s", LogTable); break; case Sta_CLICKS_CRS_PER_USR: snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, "SELECT SQL_NO_CACHE UsrCod," "%s AS Num" " FROM %s", SubQueryCountType, LogTable); break; case Sta_CLICKS_CRS_PER_DAY: case Sta_CLICKS_GBL_PER_DAY: snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, "SELECT SQL_NO_CACHE DATE_FORMAT(CONVERT_TZ(ClickTime,@@session.time_zone,'%s'),'%%Y%%m%%d') AS Day," "%s" " FROM %s", BrowserTimeZone, SubQueryCountType, LogTable); break; case Sta_CLICKS_CRS_PER_DAY_AND_HOUR: case Sta_CLICKS_GBL_PER_DAY_AND_HOUR: snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, "SELECT SQL_NO_CACHE DATE_FORMAT(CONVERT_TZ(ClickTime,@@session.time_zone,'%s'),'%%Y%%m%%d') AS Day," "DATE_FORMAT(CONVERT_TZ(ClickTime,@@session.time_zone,'%s'),'%%H') AS Hour," "%s" " FROM %s", BrowserTimeZone, BrowserTimeZone, SubQueryCountType, LogTable); break; case Sta_CLICKS_CRS_PER_WEEK: case Sta_CLICKS_GBL_PER_WEEK: /* With %x%v the weeks are counted from monday to sunday. With %X%V the weeks are counted from sunday to saturday. */ snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, (Gbl.Prefs.FirstDayOfWeek == 0) ? "SELECT SQL_NO_CACHE DATE_FORMAT(CONVERT_TZ(ClickTime,@@session.time_zone,'%s'),'%%x%%v') AS Week,"// Weeks start on monday "%s" " FROM %s" : "SELECT SQL_NO_CACHE DATE_FORMAT(CONVERT_TZ(ClickTime,@@session.time_zone,'%s'),'%%X%%V') AS Week,"// Weeks start on sunday "%s" " FROM %s", BrowserTimeZone, SubQueryCountType, LogTable); break; case Sta_CLICKS_CRS_PER_MONTH: case Sta_CLICKS_GBL_PER_MONTH: snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, "SELECT SQL_NO_CACHE DATE_FORMAT(CONVERT_TZ(ClickTime,@@session.time_zone,'%s'),'%%Y%%m') AS Month," "%s" " FROM %s", BrowserTimeZone, SubQueryCountType, LogTable); break; case Sta_CLICKS_CRS_PER_YEAR: case Sta_CLICKS_GBL_PER_YEAR: snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, "SELECT SQL_NO_CACHE DATE_FORMAT(CONVERT_TZ(ClickTime,@@session.time_zone,'%s'),'%%Y') AS Year," "%s" " FROM %s", BrowserTimeZone, SubQueryCountType, LogTable); break; case Sta_CLICKS_CRS_PER_HOUR: case Sta_CLICKS_GBL_PER_HOUR: snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, "SELECT SQL_NO_CACHE DATE_FORMAT(CONVERT_TZ(ClickTime,@@session.time_zone,'%s'),'%%H') AS Hour," "%s" " FROM %s", BrowserTimeZone, SubQueryCountType, LogTable); break; case Sta_CLICKS_CRS_PER_MINUTE: case Sta_CLICKS_GBL_PER_MINUTE: snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, "SELECT SQL_NO_CACHE DATE_FORMAT(CONVERT_TZ(ClickTime,@@session.time_zone,'%s'),'%%H%%i') AS Minute," "%s" " FROM %s", BrowserTimeZone, SubQueryCountType, LogTable); break; case Sta_CLICKS_CRS_PER_ACTION: case Sta_CLICKS_GBL_PER_ACTION: snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, "SELECT SQL_NO_CACHE ActCod," "%s AS Num" " FROM %s", SubQueryCountType, LogTable); break; case Sta_CLICKS_GBL_PER_PLUGIN: snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, "SELECT SQL_NO_CACHE log_api.PlgCod," "%s AS Num" " FROM %s," "log_api", SubQueryCountType, LogTable); break; case Sta_CLICKS_GBL_PER_API_FUNCTION: snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, "SELECT SQL_NO_CACHE log_api.FunCod," "%s AS Num" " FROM %s," "log_api", SubQueryCountType, LogTable); break; case Sta_CLICKS_GBL_PER_BANNER: snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, "SELECT SQL_NO_CACHE log_banners.BanCod," "%s AS Num" " FROM %s," "log_banners", SubQueryCountType, LogTable); break; case Sta_CLICKS_GBL_PER_COUNTRY: snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, "SELECT SQL_NO_CACHE CtyCod," "%s AS Num" " FROM %s", SubQueryCountType, LogTable); break; case Sta_CLICKS_GBL_PER_INSTITUTION: snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, "SELECT SQL_NO_CACHE InsCod," "%s AS Num" " FROM %s", SubQueryCountType, LogTable); break; case Sta_CLICKS_GBL_PER_CENTER: snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, "SELECT SQL_NO_CACHE CtrCod," "%s AS Num" " FROM %s", SubQueryCountType, LogTable); break; case Sta_CLICKS_GBL_PER_DEGREE: snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, "SELECT SQL_NO_CACHE DegCod," "%s AS Num" " FROM %s", SubQueryCountType, LogTable); break; case Sta_CLICKS_GBL_PER_COURSE: snprintf (Query,Sta_DB_MAX_BYTES_QUERY + 1, "SELECT SQL_NO_CACHE CrsCod," "%s AS Num" " FROM %s", SubQueryCountType, LogTable); break; } sprintf (SubQuery," WHERE %s.ClickTime" " BETWEEN FROM_UNIXTIME(%ld)" " AND FROM_UNIXTIME(%ld)", LogTable, (long) Dat_GetRangeTimeUTC (Dat_STR_TIME), (long) Dat_GetRangeTimeUTC (Dat_END_TIME)); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); switch (Stats->GlobalOrCourse) { case Sta_SHOW_GLOBAL_ACCESSES: /* Scope */ switch (Gbl.Scope.Current) { case Hie_UNK: case Hie_SYS: break; case Hie_CTY: if (Gbl.Hierarchy.Node[Hie_CTY].HieCod > 0) { sprintf (SubQuery," AND %s.CtyCod=%ld", LogTable,Gbl.Hierarchy.Node[Hie_CTY].HieCod); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); } break; case Hie_INS: if (Gbl.Hierarchy.Node[Hie_INS].HieCod > 0) { sprintf (SubQuery," AND %s.InsCod=%ld", LogTable,Gbl.Hierarchy.Node[Hie_INS].HieCod); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); } break; case Hie_CTR: if (Gbl.Hierarchy.Node[Hie_CTR].HieCod > 0) { sprintf (SubQuery," AND %s.CtrCod=%ld", LogTable,Gbl.Hierarchy.Node[Hie_CTR].HieCod); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); } break; case Hie_DEG: if (Gbl.Hierarchy.Node[Hie_DEG].HieCod > 0) { sprintf (SubQuery," AND %s.DegCod=%ld", LogTable,Gbl.Hierarchy.Node[Hie_DEG].HieCod); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); } break; case Hie_CRS: if (Gbl.Hierarchy.Level == Hie_CRS) { sprintf (SubQuery," AND %s.CrsCod=%ld", LogTable,Gbl.Hierarchy.Node[Hie_CRS].HieCod); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); } break; } /* Type of users */ switch (Stats->Role) { case Sta_ROLE_IDENTIFIED_USRS: sprintf (SubQueryRole," AND %s.Role<>%u", LogTable,(unsigned) Rol_UNK); break; case Sta_ROLE_ALL_USRS: switch (Stats->CountType) { case Sta_TOTAL_CLICKS: case Sta_GENERATION_TIME: case Sta_SEND_TIME: SubQueryRole[0] = '\0'; break; case Sta_DISTINCT_USRS: case Sta_CLICKS_PER_USR: sprintf (SubQueryRole," AND %s.Role<>%u", LogTable,(unsigned) Rol_UNK); break; } break; case Sta_ROLE_INS_ADMINS: sprintf (SubQueryRole," AND %s.Role=%u", LogTable,(unsigned) Rol_INS_ADM); break; case Sta_ROLE_CTR_ADMINS: sprintf (SubQueryRole," AND %s.Role=%u", LogTable,(unsigned) Rol_CTR_ADM); break; case Sta_ROLE_DEG_ADMINS: sprintf (SubQueryRole," AND %s.Role=%u", LogTable,(unsigned) Rol_DEG_ADM); break; case Sta_ROLE_TEACHERS: sprintf (SubQueryRole," AND %s.Role=%u", LogTable,(unsigned) Rol_TCH); break; case Sta_ROLE_NON_EDITING_TEACHERS: sprintf (SubQueryRole," AND %s.Role=%u", LogTable,(unsigned) Rol_NET); break; case Sta_ROLE_STUDENTS: sprintf (SubQueryRole," AND %s.Role=%u", LogTable,(unsigned) Rol_STD); break; case Sta_ROLE_USERS: sprintf (SubQueryRole," AND %s.Role=%u", LogTable,(unsigned) Rol_USR); break; case Sta_ROLE_GUESTS: sprintf (SubQueryRole," AND %s.Role=%u", LogTable,(unsigned) Rol_GST); break; case Sta_ROLE_UNKNOWN_USRS: sprintf (SubQueryRole," AND %s.Role=%u", LogTable,(unsigned) Rol_UNK); break; case Sta_ROLE_ME: sprintf (SubQueryRole," AND %s.UsrCod=%ld", LogTable,Gbl.Usrs.Me.UsrDat.UsrCod); break; } Str_Concat (Query,SubQueryRole,Sta_DB_MAX_BYTES_QUERY); switch (Stats->ClicksGroupedBy) { case Sta_CLICKS_GBL_PER_PLUGIN: case Sta_CLICKS_GBL_PER_API_FUNCTION: sprintf (SubQuery," AND %s.LogCod=log_api.LogCod", LogTable); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_GBL_PER_BANNER: sprintf (SubQuery," AND %s.LogCod=log_banners.LogCod", LogTable); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); break; default: break; } break; case Sta_SHOW_COURSE_ACCESSES: sprintf (SubQuery," AND %s.CrsCod=%ld", LogTable,Gbl.Hierarchy.Node[Hie_CRS].HieCod); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); if (NumUsrsInList) { LengthQuery = strlen (Query); for (NumUsr = 0; NumUsr < NumUsrsInList; NumUsr++) { LengthQuery = LengthQuery + 25 + 10 + 1; if (LengthQuery > Sta_DB_MAX_BYTES_QUERY - 128) Err_QuerySizeExceededExit (); sprintf (SubQuery, NumUsr ? " OR %s.UsrCod=%ld" : " AND (%s.UsrCod=%ld", LogTable,LstSelectedUsrCods[NumUsr]); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); } Str_Concat (Query,")",Sta_DB_MAX_BYTES_QUERY); } break; } /* Select action */ if (Stats->NumAction != ActAll) { sprintf (SubQuery," AND %s.ActCod=%ld", LogTable,Act_GetActCod (Stats->NumAction)); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); } /* End the query */ switch (Stats->ClicksGroupedBy) { case Sta_CLICKS_CRS_DETAILED_LIST: Str_Concat (Query," ORDER BY F", Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_CRS_PER_USR: sprintf (SubQuery," GROUP BY %s.UsrCod" " ORDER BY Num DESC", LogTable); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_CRS_PER_DAY: case Sta_CLICKS_GBL_PER_DAY: Str_Concat (Query," GROUP BY Day" " ORDER BY Day DESC", Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_CRS_PER_DAY_AND_HOUR: case Sta_CLICKS_GBL_PER_DAY_AND_HOUR: Str_Concat (Query," GROUP BY Day,Hour" " ORDER BY Day DESC,Hour", Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_CRS_PER_WEEK: case Sta_CLICKS_GBL_PER_WEEK: Str_Concat (Query," GROUP BY Week" " ORDER BY Week DESC", Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_CRS_PER_MONTH: case Sta_CLICKS_GBL_PER_MONTH: Str_Concat (Query," GROUP BY Month" " ORDER BY Month DESC", Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_CRS_PER_YEAR: case Sta_CLICKS_GBL_PER_YEAR: Str_Concat (Query," GROUP BY Year" " ORDER BY Year DESC", Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_CRS_PER_HOUR: case Sta_CLICKS_GBL_PER_HOUR: Str_Concat (Query," GROUP BY Hour" " ORDER BY Hour", Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_CRS_PER_MINUTE: case Sta_CLICKS_GBL_PER_MINUTE: Str_Concat (Query," GROUP BY Minute" " ORDER BY Minute", Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_CRS_PER_ACTION: case Sta_CLICKS_GBL_PER_ACTION: sprintf (SubQuery," GROUP BY %s.ActCod" " ORDER BY Num DESC", LogTable); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_GBL_PER_PLUGIN: Str_Concat (Query," GROUP BY log_api.PlgCod" " ORDER BY Num DESC", Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_GBL_PER_API_FUNCTION: Str_Concat (Query," GROUP BY log_api.FunCod" " ORDER BY Num DESC", Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_GBL_PER_BANNER: Str_Concat (Query," GROUP BY log_banners.BanCod" " ORDER BY Num DESC", Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_GBL_PER_COUNTRY: sprintf (SubQuery," GROUP BY %s.CtyCod" " ORDER BY Num DESC", LogTable); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_GBL_PER_INSTITUTION: sprintf (SubQuery," GROUP BY %s.InsCod" " ORDER BY Num DESC", LogTable); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_GBL_PER_CENTER: sprintf (SubQuery," GROUP BY %s.CtrCod" " ORDER BY Num DESC", LogTable); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_GBL_PER_DEGREE: sprintf (SubQuery," GROUP BY %s.DegCod" " ORDER BY Num DESC", LogTable); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); break; case Sta_CLICKS_GBL_PER_COURSE: sprintf (SubQuery," GROUP BY %s.CrsCod" " ORDER BY Num DESC", LogTable); Str_Concat (Query,SubQuery,Sta_DB_MAX_BYTES_QUERY); break; } /***** Write query for debug *****/ /* if (Gbl.Usrs.Me.Role.Logged == Rol_SYS_ADM) Ale_ShowAlert (Ale_INFO,Query); */ /***** Make the query *****/ NumHits = (unsigned) DB_QuerySELECT (mysql_res,"can not get clicks", "%s", Query); /* Free memory for the query */ free (Query); return NumHits; }