// 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 . */ /*****************************************************************************/ /********************************* Headers ***********************************/ /*****************************************************************************/ #define _GNU_SOURCE // For asprintf #include // For boolean type #include // For NULL #include // For asprintf #include // For free #include // 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[Par_NUM_PAR_COD]; 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 ",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_RenameCourseShrt (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 (); }