swad-core/swad_program_database.c

698 lines
25 KiB
C

// swad_program_database.c: course program, 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 <http://www.gnu.org/licenses/>.
*/
/*****************************************************************************/
/********************************* Headers ***********************************/
/*****************************************************************************/
#include "swad_error.h"
#include "swad_global.h"
#include "swad_program.h"
#include "swad_program_database.h"
#include "swad_program_resource.h"
/*****************************************************************************/
/************** External global variables from others modules ****************/
/*****************************************************************************/
extern struct Globals Gbl;
/*****************************************************************************/
/***************** Create a new program item into database *******************/
/*****************************************************************************/
long Prg_DB_InsertItem (const struct Prg_Item *Item,const char *Txt)
{
return
DB_QueryINSERTandReturnCode ("can not create new program item",
"INSERT INTO prg_items"
" (CrsCod,ItmInd,Level,UsrCod,"
"StartTime,EndTime,"
"Title,Txt)"
" VALUES"
" (%ld,%u,%u,%ld,"
"FROM_UNIXTIME(%ld),FROM_UNIXTIME(%ld),"
"'%s','%s')",
Gbl.Hierarchy.Node[Hie_CRS].HieCod,
Item->Hierarchy.ItmInd,
Item->Hierarchy.Level,
Gbl.Usrs.Me.UsrDat.UsrCod,
Item->TimeUTC[Dat_STR_TIME],
Item->TimeUTC[Dat_END_TIME],
Item->Title,
Txt);
}
/*****************************************************************************/
/******************** Update an existing program item ************************/
/*****************************************************************************/
void Prg_DB_UpdateItem (const struct Prg_Item *Item,const char *Txt)
{
DB_QueryUPDATE ("can not update program item",
"UPDATE prg_items"
" SET StartTime=FROM_UNIXTIME(%ld),"
"EndTime=FROM_UNIXTIME(%ld),"
"Title='%s',"
"Txt='%s'"
" WHERE ItmCod=%ld"
" AND CrsCod=%ld", // Extra check
Item->TimeUTC[Dat_STR_TIME],
Item->TimeUTC[Dat_END_TIME],
Item->Title,
Txt,
Item->Hierarchy.ItmCod,
Gbl.Hierarchy.Node[Hie_CRS].HieCod);
}
/*****************************************************************************/
/************************* Hide/unhide a program item ************************/
/*****************************************************************************/
void Prg_DB_HideOrUnhideItem (long ItmCod,
HidVis_HiddenOrVisible_t HiddenOrVisible)
{
extern const char HidVis_YN[HidVis_NUM_HIDDEN_VISIBLE];
DB_QueryUPDATE ("can not hide/unhide program item",
"UPDATE prg_items"
" SET Hidden='%c'"
" WHERE ItmCod=%ld"
" AND CrsCod=%ld", // Extra check
HidVis_YN[HiddenOrVisible],
ItmCod,
Gbl.Hierarchy.Node[Hie_CRS].HieCod);
}
/*****************************************************************************/
/********************* Change index of a set in an exam **********************/
/*****************************************************************************/
void Prg_DB_UpdateIndexRange (long Diff,long Begin,long End)
{
DB_QueryUPDATE ("can not exchange indexes of items",
"UPDATE prg_items"
" SET ItmInd=-ItmInd+%ld"
" WHERE CrsCod=%ld"
" AND ItmInd>=%ld"
" AND ItmInd<=%ld",
Diff,
Gbl.Hierarchy.Node[Hie_CRS].HieCod,
Begin,
End);
}
/*****************************************************************************/
/************ Lock table to make the exchange of items atomic ****************/
/*****************************************************************************/
void Prg_DB_LockTableItems (void)
{
DB_Query ("can not lock table",
"LOCK TABLES prg_items WRITE");
DB_SetThereAreLockedTables ();
}
/*****************************************************************************/
/************ Move down all indexes of after last child of parent ************/
/*****************************************************************************/
void Prg_DB_MoveDownItems (unsigned Index)
{
DB_QueryUPDATE ("can not move down items",
"UPDATE prg_items"
" SET ItmInd=ItmInd+1"
" WHERE CrsCod=%ld"
" AND ItmInd>=%u"
" ORDER BY ItmInd DESC", // Necessary to not create duplicate key (CrsCod,ItmInd)
Gbl.Hierarchy.Node[Hie_CRS].HieCod,
Index);
}
/*****************************************************************************/
/**************** Move item and its children to left or right ****************/
/*****************************************************************************/
void Prg_DB_MoveLeftRightItemRange (const struct Prg_ItemRange *ToMove,
Prg_MoveLeftRight_t LeftRight)
{
static const char IncDec[Prg_NUM_MOVEMENTS_LEFT_RIGHT] =
{
[Prg_MOVE_LEFT ] = '-',
[Prg_MOVE_RIGHT] = '+',
};
DB_QueryUPDATE ("can not move items",
"UPDATE prg_items"
" SET Level=Level%c1"
" WHERE CrsCod=%ld"
" AND ItmInd>=%u"
" AND ItmInd<=%u",
IncDec[LeftRight],
Gbl.Hierarchy.Node[Hie_CRS].HieCod,
ToMove->Begin,
ToMove->End);
}
/*****************************************************************************/
/******************* Get list of program items from database *****************/
/*****************************************************************************/
unsigned Prg_DB_GetListItems (MYSQL_RES **mysql_res)
{
static const char *HiddenSubQuery[Rol_NUM_ROLES] =
{
[Rol_UNK ] = " AND Hidden='N'",
[Rol_GST ] = " AND Hidden='N'",
[Rol_USR ] = " AND Hidden='N'",
[Rol_STD ] = " AND Hidden='N'",
[Rol_NET ] = " AND Hidden='N'",
[Rol_TCH ] = "",
[Rol_DEG_ADM] = " AND Hidden='N'",
[Rol_CTR_ADM] = " AND Hidden='N'",
[Rol_INS_ADM] = " AND Hidden='N'",
[Rol_SYS_ADM] = "",
};
return (unsigned)
DB_QuerySELECT (mysql_res,"can not get program items",
"SELECT ItmCod," // row[0]
"ItmInd," // row[1]
"Level," // row[2]
"Hidden" // row[3]
" FROM prg_items"
" WHERE CrsCod=%ld"
"%s"
" ORDER BY ItmInd",
Gbl.Hierarchy.Node[Hie_CRS].HieCod,
HiddenSubQuery[Gbl.Usrs.Me.Role.Logged]);
}
/*****************************************************************************/
/****************** Get program item data using its code *********************/
/*****************************************************************************/
unsigned Prg_DB_GetItemDataByCod (MYSQL_RES **mysql_res,long ItmCod)
{
return (unsigned)
DB_QuerySELECT (mysql_res,"can not get program item data",
"SELECT ItmCod," // row[0]
"ItmInd," // row[1]
"Level," // row[2]
"Hidden," // row[3]
"UsrCod," // row[4]
"UNIX_TIMESTAMP(StartTime)," // row[5]
"UNIX_TIMESTAMP(EndTime)," // row[6]
"NOW() BETWEEN StartTime AND EndTime," // row[7]
"Title" // row[8]
" FROM prg_items"
" WHERE ItmCod=%ld"
" AND CrsCod=%ld", // Extra check
ItmCod,
Gbl.Hierarchy.Node[Hie_CRS].HieCod);
}
/*****************************************************************************/
/******************* Get program item text from database *********************/
/*****************************************************************************/
void Prg_DB_GetItemTxt (long ItmCod,char Txt[Cns_MAX_BYTES_TEXT + 1])
{
DB_QuerySELECTString (Txt,Cns_MAX_BYTES_TEXT,"can not get program item text",
"SELECT Txt"
" FROM prg_items"
" WHERE ItmCod=%ld"
" AND CrsCod=%ld", // Extra check
ItmCod,
Gbl.Hierarchy.Node[Hie_CRS].HieCod);
}
/*****************************************************************************/
/****************** Get number of courses with program items *****************/
/*****************************************************************************/
// Returns the number of courses with program items
// in this location (all the platform, current degree or current course)
unsigned Prg_DB_GetNumCoursesWithItems (Hie_Level_t Level)
{
/***** Get number of courses with program items from database *****/
switch (Level)
{
case Hie_SYS:
return (unsigned)
DB_QueryCOUNT ("can not get number of courses with program items",
"SELECT COUNT(DISTINCT CrsCod)"
" FROM prg_items"
" WHERE CrsCod>0");
case Hie_CTY:
return (unsigned)
DB_QueryCOUNT ("can not get number of courses with program items",
"SELECT COUNT(DISTINCT prg_items.CrsCod)"
" FROM ins_instits,"
"ctr_centers,"
"deg_degrees,"
"crs_courses,"
"prg_items"
" WHERE ins_instits.CtyCod=%ld"
" AND ins_instits.InsCod=ctr_centers.InsCod"
" AND ctr_centers.CtrCod=deg_degrees.CtrCod"
" AND deg_degrees.DegCod=crs_courses.DegCod"
" AND crs_courses.CrsCod=prg_items.CrsCod",
Gbl.Hierarchy.Node[Hie_CTY].HieCod);
case Hie_INS:
return (unsigned)
DB_QueryCOUNT ("can not get number of courses with program items",
"SELECT COUNT(DISTINCT prg_items.CrsCod)"
" FROM ctr_centers,"
"deg_degrees,"
"crs_courses,"
"prg_items"
" WHERE ctr_centers.InsCod=%ld"
" AND ctr_centers.CtrCod=deg_degrees.CtrCod"
" AND deg_degrees.DegCod=crs_courses.DegCod"
" AND crs_courses.CrsCod=prg_items.CrsCod",
Gbl.Hierarchy.Node[Hie_INS].HieCod);
case Hie_CTR:
return (unsigned)
DB_QueryCOUNT ("can not get number of courses with program items",
"SELECT COUNT(DISTINCT prg_items.CrsCod)"
" FROM deg_degrees,"
"crs_courses,"
"prg_items"
" WHERE deg_degrees.CtrCod=%ld"
" AND deg_degrees.DegCod=crs_courses.DegCod"
" AND crs_courses.CrsCod=prg_items.CrsCod",
Gbl.Hierarchy.Node[Hie_CTR].HieCod);
case Hie_DEG:
return (unsigned)
DB_QueryCOUNT ("can not get number of courses with program items",
"SELECT COUNT(DISTINCT prg_items.CrsCod)"
" FROM crs_courses,"
"prg_items"
" WHERE crs_courses.DegCod=%ld"
" AND crs_courses.CrsCod=prg_items.CrsCod",
Gbl.Hierarchy.Node[Hie_DEG].HieCod);
case Hie_CRS:
return (unsigned)
DB_QueryCOUNT ("can not get number of courses with program items",
"SELECT COUNT(DISTINCT CrsCod)"
" FROM prg_items"
" WHERE CrsCod=%ld",
Gbl.Hierarchy.Node[Hie_CRS].HieCod);
default:
return 0;
}
}
/*****************************************************************************/
/************************ Get number of program items ************************/
/*****************************************************************************/
// Returns the number of program items in a hierarchy scope
unsigned Prg_DB_GetNumItems (Hie_Level_t Level)
{
switch (Level)
{
case Hie_SYS:
return (unsigned)
DB_QueryCOUNT ("can not get number of program items",
"SELECT COUNT(*)"
" FROM prg_items"
" WHERE CrsCod>0");
case Hie_CTY:
return (unsigned)
DB_QueryCOUNT ("can not get number of program items",
"SELECT COUNT(*)"
" FROM ins_instits,"
"ctr_centers,"
"deg_degrees,"
"crs_courses,"
"prg_items"
" WHERE ins_instits.CtyCod=%ld"
" AND ins_instits.InsCod=ctr_centers.InsCod"
" AND ctr_centers.CtrCod=deg_degrees.CtrCod"
" AND deg_degrees.DegCod=crs_courses.DegCod"
" AND crs_courses.CrsCod=prg_items.CrsCod",
Gbl.Hierarchy.Node[Hie_CTY].HieCod);
case Hie_INS:
return (unsigned)
DB_QueryCOUNT ("can not get number of program items",
"SELECT COUNT(*)"
" FROM ctr_centers,"
"deg_degrees,"
"crs_courses,"
"prg_items"
" WHERE ctr_centers.InsCod=%ld"
" AND ctr_centers.CtrCod=deg_degrees.CtrCod"
" AND deg_degrees.DegCod=crs_courses.DegCod"
" AND crs_courses.CrsCod=prg_items.CrsCod",
Gbl.Hierarchy.Node[Hie_INS].HieCod);
case Hie_CTR:
return (unsigned)
DB_QueryCOUNT ("can not get number of program items",
"SELECT COUNT(*)"
" FROM deg_degrees,"
"crs_courses,"
"prg_items"
" WHERE deg_degrees.CtrCod=%ld"
" AND deg_degrees.DegCod=crs_courses.DegCod"
" AND crs_courses.CrsCod=prg_items.CrsCod",
Gbl.Hierarchy.Node[Hie_CTR].HieCod);
case Hie_DEG:
return (unsigned)
DB_QueryCOUNT ("can not get number of program items",
"SELECT COUNT(*)"
" FROM crs_courses,"
"prg_items"
" WHERE crs_courses.DegCod=%ld"
" AND crs_courses.CrsCod=prg_items.CrsCod",
Gbl.Hierarchy.Node[Hie_DEG].HieCod);
case Hie_CRS:
return (unsigned)
DB_QueryCOUNT ("can not get number of program items",
"SELECT COUNT(*)"
" FROM prg_items"
" WHERE CrsCod=%ld",
Gbl.Hierarchy.Node[Hie_CRS].HieCod);
default:
Err_WrongHierarchyLevelExit ();
return 0; // Not reached
}
}
/*****************************************************************************/
/******************* Remove a program item and its children ******************/
/*****************************************************************************/
void Prg_DB_RemoveItemRange (const struct Prg_ItemRange *ToRemove)
{
DB_QueryDELETE ("can not remove program item",
"DELETE FROM prg_items"
" WHERE CrsCod=%ld"
" AND ItmInd>=%u"
" AND ItmInd<=%u",
Gbl.Hierarchy.Node[Hie_CRS].HieCod,
ToRemove->Begin,
ToRemove->End);
}
/*****************************************************************************/
/******************* Remove all program items in a course ********************/
/*****************************************************************************/
void Prg_DB_RemoveCrsItems (long CrsCod)
{
DB_QueryDELETE ("can not remove all program items in a course",
"DELETE FROM prg_items"
" WHERE CrsCod=%ld",
CrsCod);
}
/*****************************************************************************/
/************************** Create a new resource ****************************/
/*****************************************************************************/
long Prg_DB_CreateResource (const struct Prg_Item *Item)
{
return
DB_QueryINSERTandReturnCode ("can not create new resource",
"INSERT INTO prg_resources"
" (ItmCod,RscInd,Hidden,Title)"
" SELECT %ld,COALESCE(MAX(t2.RscInd),0)+1,'N','%s'"
" FROM prg_resources AS t2"
" WHERE t2.ItmCod=%ld",
Item->Hierarchy.ItmCod,
Item->Resource.Title,
Item->Hierarchy.ItmCod);
}
/*****************************************************************************/
/**************************** Update resource title **************************/
/*****************************************************************************/
void Prg_DB_UpdateResourceTitle (long ItmCod,long RscCod,
const char NewTitle[Rsc_MAX_BYTES_RESOURCE_TITLE + 1])
{
DB_QueryUPDATE ("can not update the title of a resource",
"UPDATE prg_resources,"
"prg_items"
" SET prg_resources.Title='%s'"
" WHERE prg_resources.RscCod=%ld"
" AND prg_resources.ItmCod=%ld"
" AND prg_resources.ItmCod=prg_items.ItmCod"
" AND prg_items.CrsCod=%ld",
NewTitle,
RscCod,
ItmCod,
Gbl.Hierarchy.Node[Hie_CRS].HieCod);
}
/*****************************************************************************/
/****************** Get list of item resources from database *****************/
/*****************************************************************************/
unsigned Prg_DB_GetListResources (MYSQL_RES **mysql_res,long ItmCod,
bool ShowHiddenResources)
{
static const char *HiddenSubQuery[2] =
{
[false] = " AND prg_resources.Hidden='N'",
[true ] = "",
};
return (unsigned)
DB_QuerySELECT (mysql_res,"can not get item resources",
"SELECT prg_resources.ItmCod," // row[0]
"prg_resources.RscCod," // row[1]
"prg_resources.RscInd," // row[2]
"prg_resources.Hidden," // row[3]
"prg_resources.Type," // row[4]
"prg_resources.Cod," // row[5]
"prg_resources.Title" // row[6]
" FROM prg_resources,"
"prg_items"
" WHERE prg_resources.ItmCod=%ld"
"%s"
" AND prg_resources.ItmCod=prg_items.ItmCod"
" AND prg_items.CrsCod=%ld" // Extra check
" ORDER BY prg_resources.RscInd",
ItmCod,
HiddenSubQuery[ShowHiddenResources],
Gbl.Hierarchy.Node[Hie_CRS].HieCod);
}
/*****************************************************************************/
/****************** Get item resource data using its code ********************/
/*****************************************************************************/
unsigned Prg_DB_GetResourceDataByCod (MYSQL_RES **mysql_res,long RscCod)
{
return (unsigned)
DB_QuerySELECT (mysql_res,"can not get item resource data",
"SELECT prg_resources.ItmCod," // row[0]
"prg_resources.RscCod," // row[1]
"prg_resources.RscInd," // row[2]
"prg_resources.Hidden," // row[3]
"prg_resources.Type," // row[4]
"prg_resources.Cod," // row[5]
"prg_resources.Title" // row[6]
" FROM prg_resources,"
"prg_items"
" WHERE prg_resources.RscCod=%ld"
" AND prg_resources.ItmCod=prg_items.ItmCod"
" AND prg_items.CrsCod=%ld", // Extra check
RscCod,
Gbl.Hierarchy.Node[Hie_CRS].HieCod);
}
/*****************************************************************************/
/************* Get the resource index before/after a given one ***************/
/*****************************************************************************/
unsigned Prg_DB_GetRscIndBefore (long ItmCod,unsigned RscInd)
{
return
DB_QuerySELECTUnsigned ("can not get the resource before",
"SELECT COALESCE(MAX(RscInd),0)"
" FROM prg_resources"
" WHERE ItmCod=%ld"
" AND RscInd<%u",
ItmCod,RscInd);
}
unsigned Prg_DB_GetRscIndAfter (long ItmCod,unsigned RscInd)
{
return
DB_QuerySELECTUnsigned ("can not get the resource after",
"SELECT COALESCE(MIN(RscInd),0)"
" FROM prg_resources"
" WHERE ItmCod=%ld"
" AND RscInd>%u",
ItmCod,RscInd);
}
/*****************************************************************************/
/*********** Get resource code given item code and resource index ************/
/*****************************************************************************/
long Prg_DB_GetRscCodFromRscInd (long ItmCod,unsigned RscInd)
{
/***** Trivial check: resource index should be > 0 *****/
if (RscInd == 0)
return -1L;
/***** Get resource code given item code and resource index *****/
return DB_QuerySELECTCode ("can not get resource code",
"SELECT RscCod"
" FROM prg_resources"
" WHERE ItmCod=%ld"
" AND RscInd=%u",
ItmCod,RscInd);
}
/*****************************************************************************/
/************************** Remove an item resource **************************/
/*****************************************************************************/
void Prg_DB_RemoveResource (const struct Prg_Item *Item)
{
DB_QueryDELETE ("can not remove item resource",
"DELETE FROM prg_resources"
" USING prg_resources,"
"prg_items"
" WHERE prg_resources.RscCod=%ld"
" AND prg_resources.ItmCod=%ld"
" AND prg_resources.ItmCod=prg_items.ItmCod"
" AND prg_items.CrsCod=%ld", // Extra check
Item->Resource.Hierarchy.RscCod,
Item->Hierarchy.ItmCod,
Gbl.Hierarchy.Node[Hie_CRS].HieCod);
}
/*****************************************************************************/
/************************ Hide/unhide an item resource ***********************/
/*****************************************************************************/
void Prg_DB_HideOrUnhideResource (long RscCod,
HidVis_HiddenOrVisible_t HiddenOrVisible)
{
extern const char HidVis_YN[HidVis_NUM_HIDDEN_VISIBLE];
DB_QueryUPDATE ("can not hide/unhide item resource",
"UPDATE prg_resources"
" SET Hidden='%c'"
" WHERE RscCod=%ld",
HidVis_YN[HiddenOrVisible],
RscCod);
}
/*****************************************************************************/
/********** Lock table to make the exchange of resources atomic **************/
/*****************************************************************************/
void Prg_DB_LockTableResources (void)
{
DB_Query ("can not lock table",
"LOCK TABLES prg_resources WRITE");
DB_SetThereAreLockedTables ();
}
/*****************************************************************************/
/************* Update the index of a resource given its code *****************/
/*****************************************************************************/
void Prg_DB_UpdateRscInd (long RscCod,int RscInd)
{
DB_QueryUPDATE ("can not update index of resource",
"UPDATE prg_resources"
" SET RscInd=%d"
" WHERE RscCod=%ld",
RscInd,
RscCod);
}
/*****************************************************************************/
/************* Update the link of a resource given its code *****************/
/*****************************************************************************/
void Prg_DB_UpdateRscLink (const struct Prg_Item *Item)
{
extern const char *Rsc_ResourceTypesDB[Rsc_NUM_TYPES];
DB_QueryUPDATE ("can not update link of resource",
"UPDATE prg_resources"
" SET Type='%s',"
"Cod=%ld"
" WHERE RscCod=%ld",
Rsc_ResourceTypesDB[Item->Resource.Link.Type],
Item->Resource.Link.Cod,
Item->Resource.Hierarchy.RscCod);
}
/*****************************************************************************/
/************************ Insert item in expanded items **********************/
/*****************************************************************************/
void Prg_DB_InsertItemInExpandedItems (long ItmCod)
{
DB_QueryREPLACE ("can not expand item",
"REPLACE INTO prg_expanded"
" (UsrCod,ItmCod,ClickTime)"
" VALUES"
" (%ld,%ld,NOW())",
Gbl.Usrs.Me.UsrDat.UsrCod,
ItmCod);
}
/*****************************************************************************/
/******************** Check if a program item is expanded ********************/
/*****************************************************************************/
ConExp_ContractedOrExpanded_t Prg_DB_GetIfContractedOrExpandedItem (long ItmCod)
{
return
DB_QueryEXISTS ("can not check if an item is expanded",
"SELECT EXISTS"
"(SELECT *"
" FROM prg_expanded"
" WHERE UsrCod=%ld"
" AND ItmCod=%ld)",
Gbl.Usrs.Me.UsrDat.UsrCod,
ItmCod) ? ConExp_EXPANDED :
ConExp_CONTRACTED;
}
/*****************************************************************************/
/********************** Remove item from expanded items **********************/
/*****************************************************************************/
void Prg_DB_RemoveItemFromExpandedItems (long ItmCod)
{
DB_QueryDELETE ("can not contract item",
"DELETE FROM prg_expanded"
" WHERE UsrCod=%ld"
" AND ItmCod=%ld",
Gbl.Usrs.Me.UsrDat.UsrCod,
ItmCod);
}