swad-core/swad_course_config.c

583 lines
20 KiB
C

// swad_course_config.c: configuration of current course
/*
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 ***********************************/
/*****************************************************************************/
#define _GNU_SOURCE // For asprintf
#include <stdbool.h> // For boolean type
#include <stddef.h> // For NULL
#include <stdio.h> // For asprintf
#include <stdlib.h> // For free
#include <string.h> // For string functions
#include "swad_action_list.h"
#include "swad_alert.h"
#include "swad_box.h"
#include "swad_course_database.h"
#include "swad_database.h"
#include "swad_error.h"
#include "swad_form.h"
#include "swad_global.h"
#include "swad_hierarchy.h"
#include "swad_hierarchy_config.h"
#include "swad_hierarchy_type.h"
#include "swad_HTML.h"
#include "swad_indicator.h"
#include "swad_logo.h"
#include "swad_parameter.h"
#include "swad_role.h"
/*****************************************************************************/
/************** External global variables from others modules ****************/
/*****************************************************************************/
extern struct Globals Gbl;
/*****************************************************************************/
/**************************** Private prototypes *****************************/
/*****************************************************************************/
static void CrsCfg_PutIconToPrint (__attribute__((unused)) void *Args);
static void CrsCfg_Degree (Vie_ViewType_t ViewType,Frm_PutForm_t PutForm);
static void CrsCfg_Year (Frm_PutForm_t PutForm);
static void CrsCfg_InstitutionalCode (Frm_PutForm_t PutForm);
static void CrsCfg_InternalCode (void);
static void CrsCfg_Indicators (void);
/*****************************************************************************/
/***************** Configuration of the current course ***********************/
/*****************************************************************************/
void CrsCfg_Configuration (Vie_ViewType_t ViewType)
{
extern const char *Hlp_COURSE_Information;
Hie_PutLink_t PutLink;
Frm_PutForm_t PutFormDeg;
Frm_PutForm_t PutFormName;
Frm_PutForm_t PutFormYear;
Frm_PutForm_t PutFormInsCod;
/***** Trivial check *****/
if (Gbl.Hierarchy.Node[Hie_CRS].HieCod <= 0) // No course selected
return;
/***** Initializations *****/
PutLink = (ViewType == Vie_VIEW &&
Gbl.Hierarchy.Node[Hie_DEG].WWW[0]) ? Hie_PUT_LINK :
Hie_DONT_PUT_LINK;
PutFormDeg = (ViewType == Vie_VIEW &&
Gbl.Usrs.Me.Role.Logged >= Rol_CTR_ADM) ? Frm_PUT_FORM :
Frm_DONT_PUT_FORM;
PutFormName = (ViewType == Vie_VIEW &&
Gbl.Usrs.Me.Role.Logged >= Rol_DEG_ADM) ? Frm_PUT_FORM :
Frm_DONT_PUT_FORM;
PutFormYear =
PutFormInsCod = (ViewType == Vie_VIEW &&
Gbl.Usrs.Me.Role.Logged >= Rol_TCH) ? Frm_PUT_FORM :
Frm_DONT_PUT_FORM;
/***** Contextual menu *****/
if (ViewType == Vie_VIEW &&
(Gbl.Usrs.Me.Role.Logged == Rol_GST ||
Gbl.Usrs.Me.Role.Logged == Rol_USR))
{
Mnu_ContextMenuBegin ();
Enr_PutLinkToRequestSignUp (); // Request enrolment in the current course
Mnu_ContextMenuEnd ();
}
/***** Begin box *****/
Box_BoxBegin (NULL,
ViewType == Vie_VIEW ? CrsCfg_PutIconToPrint :
NULL,NULL,
ViewType == Vie_VIEW ? Hlp_COURSE_Information :
NULL,Box_NOT_CLOSABLE);
/***** Title *****/
HieCfg_Title (PutLink,Hie_CRS);
/**************************** Left part ***********************************/
HTM_DIV_Begin ("class=\"HIE_CFG_LEFT\"");
/***** Begin table *****/
HTM_TABLE_BeginPadding (2);
/***** Degree *****/
CrsCfg_Degree (ViewType,PutFormDeg);
/***** Course name *****/
HieCfg_Name (PutFormName,Hie_CRS,Nam_FULL_NAME);
HieCfg_Name (PutFormName,Hie_CRS,Nam_SHRT_NAME);
/***** Course year *****/
CrsCfg_Year (PutFormYear);
if (ViewType == Vie_VIEW)
{
/***** Institutional code of the course *****/
CrsCfg_InstitutionalCode (PutFormInsCod);
/***** Internal code of the course *****/
CrsCfg_InternalCode ();
}
/***** Shortcut to the couse *****/
HieCfg_Shortcut (ViewType,ParCod_Crs,Gbl.Hierarchy.Node[Hie_CRS].HieCod);
switch (ViewType)
{
case Vie_VIEW:
/***** Number of users *****/
HieCfg_NumUsrsInCrss (Hie_CRS,Gbl.Hierarchy.Node[Hie_CRS].HieCod,Rol_TCH);
HieCfg_NumUsrsInCrss (Hie_CRS,Gbl.Hierarchy.Node[Hie_CRS].HieCod,Rol_NET);
HieCfg_NumUsrsInCrss (Hie_CRS,Gbl.Hierarchy.Node[Hie_CRS].HieCod,Rol_STD);
HieCfg_NumUsrsInCrss (Hie_CRS,Gbl.Hierarchy.Node[Hie_CRS].HieCod,Rol_UNK);
/***** Indicators *****/
CrsCfg_Indicators ();
break;
case Vie_PRINT:
/***** QR code with link to the course *****/
HieCfg_QR (ParCod_Crs,Gbl.Hierarchy.Node[Hie_CRS].HieCod);
break;
default:
Err_WrongTypeExit ();
break;
}
/***** End table *****/
HTM_TABLE_End ();
/***** End of left part *****/
HTM_DIV_End ();
/***** End box *****/
Box_BoxEnd ();
}
/*****************************************************************************/
/************* Put icon to print the configuration of a course ***************/
/*****************************************************************************/
static void CrsCfg_PutIconToPrint (__attribute__((unused)) void *Args)
{
Ico_PutContextualIconToPrint (ActPrnCrsInf,NULL,NULL);
}
/*****************************************************************************/
/***************** Print configuration of the current course *****************/
/*****************************************************************************/
void CrsCfg_PrintConfiguration (void)
{
CrsCfg_Configuration (Vie_PRINT);
}
/*****************************************************************************/
/******************** Show degree in course configuration ********************/
/*****************************************************************************/
static void CrsCfg_Degree (Vie_ViewType_t ViewType,Frm_PutForm_t PutForm)
{
extern const char *Par_CodeStr[];
extern const char *Txt_HIERARCHY_SINGUL_Abc[Hie_NUM_LEVELS];
unsigned NumDeg;
const struct Hie_Node *Deg;
const char *Id[Frm_NUM_PUT_FORM] =
{
[Frm_DONT_PUT_FORM] = NULL,
[Frm_PUT_FORM ] = Par_CodeStr[ParCod_OthDeg],
};
/***** Degree *****/
HTM_TR_Begin (NULL);
/* Label */
Frm_LabelColumn ("Frm_C1 RT",Id[PutForm],Txt_HIERARCHY_SINGUL_Abc[Hie_DEG]);
/* Data */
HTM_TD_Begin ("class=\"Frm_C2 LT DAT_%s\"",The_GetSuffix ());
switch (PutForm)
{
case Frm_DONT_PUT_FORM: // I can not move course to another degree
if (ViewType == Vie_VIEW)
{
Frm_BeginFormGoTo (ActSeeDegInf);
ParCod_PutPar (ParCod_Deg,Gbl.Hierarchy.Node[Hie_DEG].HieCod);
HTM_BUTTON_Submit_Begin (Str_BuildGoToTitle (Gbl.Hierarchy.Node[Hie_DEG].ShrtName),
"class=\"LT BT_LINK\"");
Str_FreeGoToTitle ();
}
Lgo_DrawLogo (Hie_DEG,&Gbl.Hierarchy.Node[Hie_DEG],"LM ICO20x20");
HTM_NBSP ();
HTM_Txt (Gbl.Hierarchy.Node[Hie_DEG].FullName);
if (ViewType == Vie_VIEW)
{
HTM_BUTTON_End ();
Frm_EndForm ();
}
break;
case Frm_PUT_FORM:
/* Get list of degrees of the current center */
Deg_GetListDegsInCurrentCtr ();
/* Put form to select degree */
Frm_BeginForm (ActChgCrsDegCfg);
HTM_SELECT_Begin (HTM_SUBMIT_ON_CHANGE,NULL,
"id=\"OthDegCod\" name=\"OthDegCod\""
" class=\"Frm_C2_INPUT INPUT_%s\"",
The_GetSuffix ());
for (NumDeg = 0;
NumDeg < Gbl.Hierarchy.List[Hie_CTR].Num;
NumDeg++)
{
Deg = &Gbl.Hierarchy.List[Hie_CTR].Lst[NumDeg];
HTM_OPTION (HTM_Type_LONG,&Deg->HieCod,
Deg->HieCod == Gbl.Hierarchy.Node[Hie_DEG].HieCod ? HTM_OPTION_SELECTED :
HTM_OPTION_UNSELECTED,
HTM_OPTION_ENABLED,
"%s",Deg->ShrtName);
}
HTM_SELECT_End ();
Frm_EndForm ();
/* Free list of degrees of the current center */
Hie_FreeList (Hie_CTR);
break;
}
HTM_TD_End ();
HTM_TR_End ();
}
/*****************************************************************************/
/***************** Show course year in course configuration ******************/
/*****************************************************************************/
static void CrsCfg_Year (Frm_PutForm_t PutForm)
{
extern const char *Txt_Year_OF_A_DEGREE;
extern const char *Txt_YEAR_OF_DEGREE[1 + Deg_MAX_YEARS_PER_DEGREE];
extern const char *Txt_Not_applicable;
unsigned Year;
static const char *Id[Frm_NUM_PUT_FORM] =
{
[Frm_DONT_PUT_FORM] = NULL,
[Frm_PUT_FORM ] = "OthCrsYear",
};
/***** Academic year *****/
HTM_TR_Begin (NULL);
/* Label */
Frm_LabelColumn ("Frm_C1 RT",Id[PutForm],Txt_Year_OF_A_DEGREE);
/* Data */
HTM_TD_Begin ("class=\"Frm_C2 LB DAT_%s\"",The_GetSuffix ());
switch (PutForm)
{
case Frm_DONT_PUT_FORM:
HTM_Txt (Gbl.Hierarchy.Node[Hie_CRS].Specific.Year ? Txt_YEAR_OF_DEGREE[Gbl.Hierarchy.Node[Hie_CRS].Specific.Year] :
Txt_Not_applicable);
break;
case Frm_PUT_FORM:
Frm_BeginForm (ActChgCrsYeaCfg);
HTM_SELECT_Begin (HTM_SUBMIT_ON_CHANGE,NULL,
"id=\"OthCrsYear\" name=\"OthCrsYear\""
" class=\"Frm_C2_INPUT INPUT_%s\"",
The_GetSuffix ());
for (Year = 0;
Year <= Deg_MAX_YEARS_PER_DEGREE;
Year++)
HTM_OPTION (HTM_Type_UNSIGNED,&Year,
Year == Gbl.Hierarchy.Node[Hie_CRS].Specific.Year ? HTM_OPTION_SELECTED :
HTM_OPTION_UNSELECTED,
HTM_OPTION_ENABLED,
"%s",Txt_YEAR_OF_DEGREE[Year]);
HTM_SELECT_End ();
Frm_EndForm ();
break;
}
HTM_TD_End ();
HTM_TR_End ();
}
/*****************************************************************************/
/************* Show institutional code in course configuration ***************/
/*****************************************************************************/
static void CrsCfg_InstitutionalCode (Frm_PutForm_t PutForm)
{
extern const char *Txt_Institutional_code;
static const char *Id[Frm_NUM_PUT_FORM] =
{
[Frm_DONT_PUT_FORM] = NULL,
[Frm_PUT_FORM ] = "InsCrsCod",
};
/***** Institutional course code *****/
HTM_TR_Begin (NULL);
/* Label */
Frm_LabelColumn ("Frm_C1 RT",Id[PutForm],Txt_Institutional_code);
/* Data */
HTM_TD_Begin ("class=\"Frm_C2 LB DAT_%s\"",The_GetSuffix ());
switch (PutForm)
{
case Frm_DONT_PUT_FORM:
HTM_Txt (Gbl.Hierarchy.Node[Hie_CRS].InstitutionalCod);
break;
case Frm_PUT_FORM:
Frm_BeginForm (ActChgInsCrsCodCfg);
HTM_INPUT_TEXT ("InsCrsCod",Hie_MAX_CHARS_INSTITUTIONAL_COD,
Gbl.Hierarchy.Node[Hie_CRS].InstitutionalCod,
HTM_SUBMIT_ON_CHANGE,
"id=\"InsCrsCod\" maxlength=\"%u\""
" class=\"Frm_C2_INPUT INPUT_%s\"",
Hie_MAX_CHARS_INSTITUTIONAL_COD,
The_GetSuffix ());
Frm_EndForm ();
break;
}
HTM_TD_End ();
HTM_TR_End ();
}
/*****************************************************************************/
/**************** Show internal code in course configuration *****************/
/*****************************************************************************/
static void CrsCfg_InternalCode (void)
{
extern const char *Txt_Internal_code;
/***** Internal course code *****/
HTM_TR_Begin (NULL);
/* Label */
Frm_LabelColumn ("Frm_C1 RT",NULL,Txt_Internal_code);
/* Data */
HTM_TD_Begin ("class=\"Frm_C2 LB DAT_%s\"",The_GetSuffix ());
HTM_Long (Gbl.Hierarchy.Node[Hie_CRS].HieCod);
HTM_TD_End ();
HTM_TR_End ();
}
/*****************************************************************************/
/****************** Show indicators in course configuration ******************/
/*****************************************************************************/
static void CrsCfg_Indicators (void)
{
extern const char *Txt_Indicators;
extern const char *Txt_of_PART_OF_A_TOTAL;
struct Ind_IndicatorsCrs IndicatorsCrs;
int NumIndicatorsFromDB = Ind_GetNumIndicatorsCrsFromDB (Gbl.Hierarchy.Node[Hie_CRS].HieCod);
char *Title;
/***** Compute indicators ******/
Ind_ComputeAndStoreIndicatorsCrs (Gbl.Hierarchy.Node[Hie_CRS].HieCod,
NumIndicatorsFromDB,&IndicatorsCrs);
/***** Number of indicators *****/
HTM_TR_Begin (NULL);
/* Label */
Frm_LabelColumn ("Frm_C1 RT",NULL,Txt_Indicators);
/* Data */
HTM_TD_Begin ("class=\"Frm_C2 LB DAT_%s\"",The_GetSuffix ());
Frm_BeginForm (ActReqStaCrs);
if (asprintf (&Title,"%u %s %u",
IndicatorsCrs.NumIndicators,
Txt_of_PART_OF_A_TOTAL,Ind_NUM_INDICATORS) < 0)
Err_NotEnoughMemoryExit ();
HTM_BUTTON_Submit_Begin (Title,"class=\"LB BT_LINK\"");
HTM_TxtF ("%s&nbsp;",Title);
if (IndicatorsCrs.NumIndicators == Ind_NUM_INDICATORS)
Ico_PutIcon ("check-circle.svg",Ico_GREEN,
Title,"ICO16x16");
else
Ico_PutIcon ("exclamation-triangle.svg",Ico_YELLOW,
Title,"ICO16x16");
HTM_BUTTON_End ();
free (Title);
Frm_EndForm ();
HTM_TD_End ();
HTM_TR_End ();
}
/*****************************************************************************/
/***************** Change the degree of the current course *******************/
/*****************************************************************************/
void CrsCfg_ChangeCrsDeg (void)
{
extern bool (*Hie_GetDataByCod[Hie_NUM_LEVELS]) (struct Hie_Node *Node);
extern const char *Txt_YEAR_OF_DEGREE[1 + Deg_MAX_YEARS_PER_DEGREE];
extern const char *Txt_The_course_X_has_been_moved_to_the_degree_Y;
struct Hie_Node NewDeg;
const char *Names[Nam_NUM_SHRT_FULL_NAMES];
/***** Get parameter with degree code *****/
NewDeg.HieCod = ParCod_GetAndCheckPar (ParCod_OthDeg);
/***** Check if degree has changed *****/
if (NewDeg.HieCod != Gbl.Hierarchy.Node[Hie_CRS].PrtCod)
{
/***** Get data of new degree *****/
Hie_GetDataByCod[Hie_DEG] (&NewDeg);
/***** If name of course was not in database in the new degree... *****/
Names[Nam_SHRT_NAME] = Gbl.Hierarchy.Node[Hie_CRS].ShrtName;
Names[Nam_FULL_NAME] = Gbl.Hierarchy.Node[Hie_CRS].FullName;
if (!Nam_CheckIfNameExists (Crs_DB_CheckIfCrsNameExistsInYearOfDeg,
Names,
-1L,
NewDeg.HieCod,
Gbl.Hierarchy.Node[Hie_CRS].Specific.Year))
{
/***** Update degree in table of courses *****/
Crs_DB_UpdateCrsDeg (Gbl.Hierarchy.Node[Hie_CRS].HieCod,NewDeg.HieCod);
Gbl.Hierarchy.Node[Hie_CRS].PrtCod =
Gbl.Hierarchy.Node[Hie_DEG].HieCod = NewDeg.HieCod;
/***** Initialize again current course, degree, center... *****/
Hie_InitHierarchy ();
/***** Create alert to show the change made *****/
Ale_CreateAlert (Ale_SUCCESS,NULL,
Txt_The_course_X_has_been_moved_to_the_degree_Y,
Names[Nam_FULL_NAME],
Gbl.Hierarchy.Node[Hie_DEG].FullName);
}
}
}
/*****************************************************************************/
/*************** Change the name of a course in configuration ****************/
/*****************************************************************************/
void CrsCfg_RenameCourseShort (void)
{
Crs_RenameCourse (&Gbl.Hierarchy.Node[Hie_CRS],Nam_SHRT_NAME);
}
void CrsCfg_RenameCourseFull (void)
{
Crs_RenameCourse (&Gbl.Hierarchy.Node[Hie_CRS],Nam_FULL_NAME);
}
/*****************************************************************************/
/*********** Change the year of a course in course configuration *************/
/*****************************************************************************/
void CrsCfg_ChangeCrsYear (void)
{
extern const char *Txt_YEAR_OF_DEGREE[1 + Deg_MAX_YEARS_PER_DEGREE];
extern const char *Txt_The_year_of_the_course_X_has_changed;
extern const char *Txt_The_year_X_is_not_allowed;
char YearStr[2 + 1];
unsigned NewYear;
const char *Names[Nam_NUM_SHRT_FULL_NAMES];
/***** Get parameter with year/semester *****/
Par_GetParText ("OthCrsYear",YearStr,2);
NewYear = Deg_ConvStrToYear (YearStr);
if (NewYear <= Deg_MAX_YEARS_PER_DEGREE) // If year is valid
{
/***** If name of course was not in database in the new year... *****/
Names[Nam_SHRT_NAME] = Gbl.Hierarchy.Node[Hie_CRS].ShrtName;
Names[Nam_FULL_NAME] = Gbl.Hierarchy.Node[Hie_CRS].FullName;
if (!Nam_CheckIfNameExists (Crs_DB_CheckIfCrsNameExistsInYearOfDeg,
Names,
-1L,
Gbl.Hierarchy.Node[Hie_CRS].PrtCod,
NewYear))
{
/***** Update year in table of courses *****/
Crs_UpdateCrsYear (&Gbl.Hierarchy.Node[Hie_CRS],NewYear);
/***** Create alert to show the change made *****/
Ale_CreateAlert (Ale_SUCCESS,NULL,
Txt_The_year_of_the_course_X_has_changed,
Names[Nam_FULL_NAME]);
}
}
else // Year not valid
Ale_CreateAlert (Ale_WARNING,NULL,
Txt_The_year_X_is_not_allowed,
NewYear);
}
/*****************************************************************************/
/***** Change the institutional code of a course in course configuration *****/
/*****************************************************************************/
void CrsCfg_ChangeInsCrsCod (void)
{
extern const char *Txt_The_institutional_code_of_the_course_X_has_changed_to_Y;
extern const char *Txt_The_institutional_code_of_the_course_X_has_not_changed;
char NewInstitutionalCrsCod[Hie_MAX_BYTES_INSTITUTIONAL_COD + 1];
/***** Get institutional code from form *****/
Par_GetParText ("InsCrsCod",NewInstitutionalCrsCod,Hie_MAX_BYTES_INSTITUTIONAL_COD);
/***** Change the institutional course code *****/
if (strcmp (NewInstitutionalCrsCod,Gbl.Hierarchy.Node[Hie_CRS].InstitutionalCod))
{
Crs_UpdateInstitutionalCrsCod (&Gbl.Hierarchy.Node[Hie_CRS],NewInstitutionalCrsCod);
Ale_CreateAlert (Ale_SUCCESS,NULL,
Txt_The_institutional_code_of_the_course_X_has_changed_to_Y,
Gbl.Hierarchy.Node[Hie_CRS].ShrtName,
NewInstitutionalCrsCod);
}
else // The same institutional code
Ale_CreateAlert (Ale_INFO,NULL,
Txt_The_institutional_code_of_the_course_X_has_not_changed,
Gbl.Hierarchy.Node[Hie_CRS].ShrtName);
}
/*****************************************************************************/
/** Show message of success after changing a course in course configuration **/
/*****************************************************************************/
void CrsCfg_ContEditAfterChgCrs (void)
{
/***** Write error/success message *****/
Ale_ShowAlerts (NULL);
/***** Show the form again *****/
Crs_ShowIntroduction ();
}