diff --git a/swad_program_database.c b/swad_program_database.c
new file mode 100644
index 00000000..005c2918
--- /dev/null
+++ b/swad_program_database.c
@@ -0,0 +1,452 @@
+// 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-2021 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 "swad_error.h"
+#include "swad_global.h"
+#include "swad_program.h"
+
+/*****************************************************************************/
+/************** External global variables from others modules ****************/
+/*****************************************************************************/
+
+extern struct Globals Gbl;
+
+/*****************************************************************************/
+/***************************** Private constants *****************************/
+/*****************************************************************************/
+
+/*****************************************************************************/
+/******************************* Private types *******************************/
+/*****************************************************************************/
+
+/*****************************************************************************/
+/***************************** Private variables *****************************/
+/*****************************************************************************/
+
+/*****************************************************************************/
+/***************************** Private prototypes ****************************/
+/*****************************************************************************/
+
+/*****************************************************************************/
+/***************** 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.Crs.CrsCod,
+ Item->Hierarchy.Index,
+ 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.Crs.CrsCod);
+ }
+
+/*****************************************************************************/
+/************************* Hide/unhide program item **************************/
+/*****************************************************************************/
+
+void Prg_DB_HideUnhideItem (long ItmCod,char YN)
+ {
+ DB_QueryUPDATE ("can not change program item",
+ "UPDATE prg_items"
+ " SET Hidden='%c'"
+ " WHERE ItmCod=%ld"
+ " AND CrsCod=%ld", // Extra check
+ YN,
+ ItmCod,
+ Gbl.Hierarchy.Crs.CrsCod);
+ }
+
+/*****************************************************************************/
+/********************* 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.Crs.CrsCod,
+ Begin,
+ End);
+ }
+
+/*****************************************************************************/
+/************ Lock tables to make the exchange of items atomic ***************/
+/*****************************************************************************/
+
+void Prg_DB_LockTable (void)
+ {
+ DB_Query ("can not lock table",
+ "LOCK TABLES prg_items WRITE");
+ Gbl.DB.LockedTables = true;
+ }
+
+/*****************************************************************************/
+/********** Unlock tables to make the exchange of items atomic ***************/
+/*****************************************************************************/
+
+void Prg_DB_UnlockTable (void)
+ {
+ Gbl.DB.LockedTables = false; // Set to false before the following unlock...
+ // ...to not retry the unlock if error in unlocking
+ DB_Query ("can not unlock tables",
+ "UNLOCK TABLES");
+ }
+
+/*****************************************************************************/
+/************ 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.Crs.CrsCod,
+ 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.Crs.CrsCod,
+ 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.Crs.CrsCod,
+ HiddenSubQuery[Gbl.Usrs.Me.Role.Logged]);
+ }
+
+/*****************************************************************************/
+/****************** Get program item data using its code *********************/
+/*****************************************************************************/
+
+unsigned Prg_DB_GetDataOfItemByCod (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.Crs.CrsCod);
+ }
+
+/*****************************************************************************/
+/******************* 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.Crs.CrsCod);
+ }
+
+/*****************************************************************************/
+/****************** 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 (HieLvl_Level_t Scope)
+ {
+ /***** Get number of courses with program items from database *****/
+ switch (Scope)
+ {
+ case HieLvl_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 HieLvl_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.Cty.CtyCod);
+ case HieLvl_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.Ins.InsCod);
+ case HieLvl_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.Ctr.CtrCod);
+ case HieLvl_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.Deg.DegCod);
+ case HieLvl_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.Crs.CrsCod);
+ default:
+ return 0;
+ }
+ }
+
+/*****************************************************************************/
+/************************ Get number of program items ************************/
+/*****************************************************************************/
+// Returns the number of program items in a hierarchy scope
+
+unsigned Prg_DB_GetNumItems (HieLvl_Level_t Scope)
+ {
+ switch (Scope)
+ {
+ case HieLvl_SYS:
+ return (unsigned)
+ DB_QueryCOUNT ("can not get number of program items",
+ "SELECT COUNT(*)"
+ " FROM prg_items"
+ " WHERE CrsCod>0");
+ case HieLvl_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.Cty.CtyCod);
+ case HieLvl_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.Ins.InsCod);
+ case HieLvl_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.Ctr.CtrCod);
+ case HieLvl_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.Deg.DegCod);
+ case HieLvl_CRS:
+ return (unsigned)
+ DB_QueryCOUNT ("can not get number of program items",
+ "SELECT COUNT(*)"
+ " FROM prg_items"
+ " WHERE CrsCod=%ld",
+ Gbl.Hierarchy.Crs.CrsCod);
+ default:
+ Err_WrongScopeExit ();
+ 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.Crs.CrsCod,
+ 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);
+ }
diff --git a/swad_program_database.h b/swad_program_database.h
new file mode 100644
index 00000000..f58b542b
--- /dev/null
+++ b/swad_program_database.h
@@ -0,0 +1,60 @@
+// swad_program_database.h: course program, operations with database
+
+#ifndef _SWAD_PRG_DB
+#define _SWAD_PRG_DB
+/*
+ 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-2021 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 "swad_hierarchy_level.h"
+#include "swad_program.h"
+
+/*****************************************************************************/
+/************************** Public types and constants ***********************/
+/*****************************************************************************/
+
+/*****************************************************************************/
+/***************************** Public prototypes *****************************/
+/*****************************************************************************/
+
+long Prg_DB_InsertItem (const struct Prg_Item *Item,const char *Txt);
+void Prg_DB_UpdateItem (const struct Prg_Item *Item,const char *Txt);
+void Prg_DB_HideUnhideItem (long ItmCod,char YN);
+void Prg_DB_UpdateIndexRange (long Diff,long Begin,long End);
+void Prg_DB_LockTable (void);
+void Prg_DB_UnlockTable (void);
+void Prg_DB_MoveDownItems (unsigned Index);
+void Prg_DB_MoveLeftRightItemRange (const struct Prg_ItemRange *ToMove,
+ Prg_MoveLeftRight_t LeftRight);
+
+unsigned Prg_DB_GetListItems (MYSQL_RES **mysql_res);
+unsigned Prg_DB_GetDataOfItemByCod (MYSQL_RES **mysql_res,long ItmCod);
+void Prg_DB_GetItemTxt (long ItmCod,char Txt[Cns_MAX_BYTES_TEXT + 1]);
+unsigned Prg_DB_GetNumCoursesWithItems (HieLvl_Level_t Scope);
+unsigned Prg_DB_GetNumItems (HieLvl_Level_t Scope);
+
+void Prg_DB_RemoveItemRange (const struct Prg_ItemRange *ToRemove);
+void Prg_DB_RemoveCrsItems (long CrsCod);
+
+#endif