// swad_degree.c: degrees /* 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-2019 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 isprint, isspace, etc. #include // For NULL #include // For boolean type #include // For fprintf, etc. #include // For exit, system, calloc, free, etc. #include // For string functions #include // To access MySQL databases #include "swad_box.h" #include "swad_changelog.h" #include "swad_config.h" #include "swad_database.h" #include "swad_degree.h" #include "swad_degree_type.h" #include "swad_exam.h" #include "swad_form.h" #include "swad_global.h" #include "swad_help.h" #include "swad_hierarchy.h" #include "swad_HTML.h" #include "swad_indicator.h" #include "swad_info.h" #include "swad_language.h" #include "swad_logo.h" #include "swad_notification.h" #include "swad_parameter.h" #include "swad_QR.h" #include "swad_RSS.h" #include "swad_string.h" #include "swad_tab.h" #include "swad_theme.h" /*****************************************************************************/ /************** External global variables from others modules ****************/ /*****************************************************************************/ extern struct Globals Gbl; /*****************************************************************************/ /*************************** Public constants ********************************/ /*****************************************************************************/ /*****************************************************************************/ /***************************** Private types *********************************/ /*****************************************************************************/ typedef enum { Deg_FIRST_YEAR, Deg_LAST_YEAR, } Deg_FirstOrLastYear_t; /*****************************************************************************/ /**************************** Private variables ******************************/ /*****************************************************************************/ static struct Degree *Deg_EditingDeg = NULL; // Static variable to keep the degree being edited /*****************************************************************************/ /**************************** Private prototypes *****************************/ /*****************************************************************************/ static void Deg_Configuration (bool PrintView); static void Deg_PutIconsToPrintAndUpload (void); static void Deg_ShowNumUsrsInCrssOfDeg (Rol_Role_t Role); static void Deg_ListDegreesForEdition (void); static bool Deg_CheckIfICanEditADegree (struct Degree *Deg); static Deg_StatusTxt_t Deg_GetStatusTxtFromStatusBits (Deg_Status_t Status); static Deg_Status_t Deg_GetStatusBitsFromStatusTxt (Deg_StatusTxt_t StatusTxt); static void Deg_PutFormToCreateDegree (void); static void Deg_PutHeadDegreesForSeeing (void); static void Deg_PutHeadDegreesForEdition (void); static void Deg_CreateDegree (unsigned Status); static void Deg_ListDegrees (void); static bool Deg_CheckIfICanCreateDegrees (void); static void Deg_PutIconsListingDegrees (void); static void Deg_PutIconToEditDegrees (void); static void Deg_ListOneDegreeForSeeing (struct Degree *Deg,unsigned NumDeg); static void Deg_EditDegreesInternal (void); static void Deg_PutIconsEditingDegrees (void); static void Deg_RecFormRequestOrCreateDeg (unsigned Status); static void Deg_PutParamOtherDegCod (long DegCod); static void Deg_GetDataOfDegreeFromRow (struct Degree *Deg,MYSQL_ROW row); static void Deg_RenameDegree (struct Degree *Deg,Cns_ShrtOrFullName_t ShrtOrFullName); static bool Deg_CheckIfDegNameExistsInCtr (const char *FieldName,const char *Name, long DegCod,long CtrCod); static void Deg_UpdateDegNameDB (long DegCod,const char *FieldName,const char *NewDegName); static void Deg_UpdateDegCtrDB (long DegCod,long CtrCod); static void Deg_UpdateDegWWWDB (long DegCod,const char NewWWW[Cns_MAX_BYTES_WWW + 1]); static void Deg_ShowAlertAndButtonToGoToDeg (void); static void Deg_PutParamGoToDeg (void); static void Deg_EditingDegreeConstructor (void); static void Deg_EditingDegreeDestructor (void); /*****************************************************************************/ /******************* List degrees with pending courses ***********************/ /*****************************************************************************/ void Deg_SeeDegWithPendingCrss (void) { extern const char *Hlp_SYSTEM_Hierarchy_pending; extern const char *Txt_Degrees_with_pending_courses; extern const char *Txt_Degree; extern const char *Txt_Courses_ABBREVIATION; extern const char *Txt_There_are_no_degrees_with_requests_for_courses_to_be_confirmed; MYSQL_RES *mysql_res; MYSQL_ROW row; unsigned NumDegs; unsigned NumDeg; struct Degree Deg; const char *BgColor; /***** Get degrees with pending courses *****/ switch (Gbl.Usrs.Me.Role.Logged) { case Rol_DEG_ADM: NumDegs = (unsigned) DB_QuerySELECT (&mysql_res,"can not get degrees" " with pending courses", "SELECT courses.DegCod,COUNT(*)" " FROM admin,courses,degrees" " WHERE admin.UsrCod=%ld AND admin.Scope='%s'" " AND admin.Cod=courses.DegCod" " AND (courses.Status & %u)<>0" " AND courses.DegCod=degrees.DegCod" " GROUP BY courses.DegCod ORDER BY degrees.ShortName", Gbl.Usrs.Me.UsrDat.UsrCod, Sco_GetDBStrFromScope (Hie_DEG), (unsigned) Crs_STATUS_BIT_PENDING); break; case Rol_SYS_ADM: NumDegs = (unsigned) DB_QuerySELECT (&mysql_res,"can not get degrees" " with pending courses", "SELECT courses.DegCod,COUNT(*)" " FROM courses,degrees" " WHERE (courses.Status & %u)<>0" " AND courses.DegCod=degrees.DegCod" " GROUP BY courses.DegCod ORDER BY degrees.ShortName", (unsigned) Crs_STATUS_BIT_PENDING); break; default: // Forbidden for other users return; } /***** Get degrees *****/ if (NumDegs) { /***** Begin box and table *****/ Box_StartBoxTable (NULL,Txt_Degrees_with_pending_courses,NULL, Hlp_SYSTEM_Hierarchy_pending,Box_NOT_CLOSABLE,2); /***** Write heading *****/ HTM_TR_Begin (NULL); HTM_TH (1,1,"LM",Txt_Degree); HTM_TH (1,1,"RM",Txt_Courses_ABBREVIATION); HTM_TR_End (); /***** List the degrees *****/ for (NumDeg = 0; NumDeg < NumDegs; NumDeg++) { /* Get next degree */ row = mysql_fetch_row (mysql_res); /* Get degree code (row[0]) */ Deg.DegCod = Str_ConvertStrCodToLongCod (row[0]); BgColor = (Deg.DegCod == Gbl.Hierarchy.Deg.DegCod) ? "LIGHT_BLUE" : Gbl.ColorRows[Gbl.RowEvenOdd]; /* Get data of degree */ Deg_GetDataOfDegreeByCod (&Deg); HTM_TR_Begin (NULL); /* Degree logo and full name */ HTM_TD_Begin ("class=\"LM %s\"",BgColor); Deg_DrawDegreeLogoAndNameWithLink (&Deg,ActSeeCrs, "DAT_NOBR","CM"); HTM_TD_End (); /* Number of pending courses (row[1]) */ HTM_TD_Begin ("class=\"DAT RM %s\"",BgColor); fprintf (Gbl.F.Out,"%s",row[1]); HTM_TD_End (); HTM_TR_End (); Gbl.RowEvenOdd = 1 - Gbl.RowEvenOdd; } /***** End table and box *****/ Box_EndBoxTable (); } else Ale_ShowAlert (Ale_INFO,Txt_There_are_no_degrees_with_requests_for_courses_to_be_confirmed); /***** Free structure that stores the query result *****/ DB_FreeMySQLResult (&mysql_res); } /*****************************************************************************/ /******************** Draw degree logo and name with link ********************/ /*****************************************************************************/ void Deg_DrawDegreeLogoAndNameWithLink (struct Degree *Deg,Act_Action_t Action, const char *ClassLink,const char *ClassLogo) { extern const char *Txt_Go_to_X; /***** Begin form *****/ Frm_StartFormGoTo (Action); Deg_PutParamDegCod (Deg->DegCod); /***** Link to action *****/ snprintf (Gbl.Title,sizeof (Gbl.Title), Txt_Go_to_X, Deg->FullName); Frm_LinkFormSubmit (Gbl.Title,ClassLink,NULL); /***** Degree logo and name *****/ Log_DrawLogo (Hie_DEG,Deg->DegCod,Deg->ShrtName,16,ClassLogo,true); fprintf (Gbl.F.Out," %s",Deg->FullName); /***** End link *****/ Frm_LinkFormEnd (); /***** End form *****/ Frm_EndForm (); } /*****************************************************************************/ /****************** Show information of the current degree *******************/ /*****************************************************************************/ void Deg_ShowConfiguration (void) { Deg_Configuration (false); /***** Show help to enrol me *****/ Hlp_ShowHelpWhatWouldYouLikeToDo (); } /*****************************************************************************/ /****************** Print information of the current degree ******************/ /*****************************************************************************/ void Deg_PrintConfiguration (void) { Deg_Configuration (true); } /*****************************************************************************/ /******************* Information of the current degree ***********************/ /*****************************************************************************/ static void Deg_Configuration (bool PrintView) { extern const char *Hlp_DEGREE_Information; extern const char *The_ClassFormInBox[The_NUM_THEMES]; extern const char *Txt_Centre; extern const char *Txt_Degree; extern const char *Txt_Short_name; extern const char *Txt_Web; extern const char *Txt_Shortcut; extern const char *Lan_STR_LANG_ID[1 + Lan_NUM_LANGUAGES]; extern const char *Txt_Courses; extern const char *Txt_Courses_of_DEGREE_X; extern const char *Txt_QR_code; unsigned NumCtr; bool PutLink; /***** Trivial check *****/ if (Gbl.Hierarchy.Deg.DegCod <= 0) // No degree selected return; /***** Begin box *****/ if (PrintView) Box_BoxBegin (NULL,NULL,NULL, NULL,Box_NOT_CLOSABLE); else Box_BoxBegin (NULL,NULL,Deg_PutIconsToPrintAndUpload, Hlp_DEGREE_Information,Box_NOT_CLOSABLE); /***** Title *****/ PutLink = !PrintView && Gbl.Hierarchy.Deg.WWW[0]; HTM_DIV_Begin ("class=\"FRAME_TITLE FRAME_TITLE_BIG\""); if (PutLink) HTM_A_Begin ("href=\"%s\" target=\"_blank\"" " class=\"FRAME_TITLE_BIG\" title=\"%s\">", Gbl.Hierarchy.Deg.WWW, Gbl.Hierarchy.Deg.FullName); Log_DrawLogo (Hie_DEG,Gbl.Hierarchy.Deg.DegCod, Gbl.Hierarchy.Deg.ShrtName,64,NULL,true); fprintf (Gbl.F.Out,"
%s", Gbl.Hierarchy.Deg.FullName); if (PutLink) HTM_A_End (); HTM_DIV_End (); /***** Begin table *****/ HTM_TABLE_BeginWidePadding (2); /***** Centre *****/ HTM_TR_Begin (NULL); HTM_TD_Begin ("class=\"RM\""); HTM_LABEL_Begin ("for=\"OthCtrCod\" class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]); fprintf (Gbl.F.Out,"%s:",Txt_Centre); HTM_LABEL_End (); HTM_TD_End (); HTM_TD_Begin ("class=\"DAT_N LM\""); if (!PrintView && Gbl.Usrs.Me.Role.Logged >= Rol_INS_ADM) // Only institution admins and system admin can move a degree to another centre { /* Get list of centres of the current institution */ Ctr_GetListCentres (Gbl.Hierarchy.Ins.InsCod); /* Put form to select centre */ Frm_StartForm (ActChgDegCtrCfg); fprintf (Gbl.F.Out," 0) fprintf (Gbl.F.Out," onchange=\"document.getElementById('%s').submit();return false;\"", Gbl.Form.Id); else fprintf (Gbl.F.Out," disabled=\"disabled\""); fprintf (Gbl.F.Out,">" "", Txt_Degree); if (Gbl.Hierarchy.Ctr.CtrCod > 0) { /***** Get degrees belonging to the current centre from database *****/ NumDegs = (unsigned) DB_QuerySELECT (&mysql_res,"can not get degrees" " of a centre", "SELECT DegCod,ShortName" " FROM degrees" " WHERE CtrCod=%ld" " ORDER BY ShortName", Gbl.Hierarchy.Ctr.CtrCod); /***** Get degrees of this centre *****/ for (NumDeg = 0; NumDeg < NumDegs; NumDeg++) { /* Get next degree */ row = mysql_fetch_row (mysql_res); /* Get degree code (row[0]) */ if ((DegCod = Str_ConvertStrCodToLongCod (row[0])) < 0) Lay_ShowErrorAndExit ("Wrong degree."); /* Write option */ fprintf (Gbl.F.Out,"",row[1]); } /***** Free structure that stores the query result *****/ DB_FreeMySQLResult (&mysql_res); } /***** End form *****/ HTM_SELECT_End (); Frm_EndForm (); } /*****************************************************************************/ /************* Show the degrees belonging to the current centre **************/ /*****************************************************************************/ void Deg_ShowDegsOfCurrentCtr (void) { /***** Trivial check *****/ if (Gbl.Hierarchy.Ctr.CtrCod <= 0) // No centre selected return; /***** Get list of centres and degrees *****/ Ctr_GetListCentres (Gbl.Hierarchy.Ins.InsCod); Deg_GetListDegsOfCurrentCtr (); /***** Write menu to select country, institution and centre *****/ Hie_WriteMenuHierarchy (); /***** Show list of degrees *****/ Deg_ListDegrees (); /***** Free list of degrees and centres *****/ Deg_FreeListDegs (&Gbl.Hierarchy.Ctr.Degs); Ctr_FreeListCentres (); } /*****************************************************************************/ /********************* List current degrees for edition **********************/ /*****************************************************************************/ static void Deg_ListDegreesForEdition (void) { extern const char *Txt_DEGREE_STATUS[Deg_NUM_STATUS_TXT]; unsigned NumDeg; struct DegreeType *DegTyp; struct Degree *Deg; unsigned NumDegTyp; char WWW[Cns_MAX_BYTES_WWW + 1]; struct UsrData UsrDat; bool ICanEdit; Deg_StatusTxt_t StatusTxt; unsigned NumCrss; /***** Initialize structure with user's data *****/ Usr_UsrDataConstructor (&UsrDat); /***** Write heading *****/ HTM_TABLE_BeginWidePadding (2); Deg_PutHeadDegreesForEdition (); /***** List the degrees *****/ for (NumDeg = 0; NumDeg < Gbl.Hierarchy.Ctr.Degs.Num; NumDeg++) { Deg = &(Gbl.Hierarchy.Ctr.Degs.Lst[NumDeg]); NumCrss = Crs_GetNumCrssInDeg (Deg->DegCod); ICanEdit = Deg_CheckIfICanEditADegree (Deg); HTM_TR_Begin (NULL); /* Put icon to remove degree */ HTM_TD_Begin ("class=\"BM\""); if (NumCrss || // Degree has courses ==> deletion forbidden !ICanEdit) Ico_PutIconRemovalNotAllowed (); else { Frm_StartForm (ActRemDeg); Deg_PutParamOtherDegCod (Deg->DegCod); Ico_PutIconRemove (); Frm_EndForm (); } HTM_TD_End (); /* Degree code */ HTM_TD_Begin ("class=\"DAT CODE\""); fprintf (Gbl.F.Out,"%ld",Deg->DegCod); HTM_TD_End (); /* Degree logo */ HTM_TD_Begin ("title=\"%s\" class=\"HIE_LOGO\"",Deg->FullName); Log_DrawLogo (Hie_DEG,Deg->DegCod,Deg->ShrtName,20,NULL,true); HTM_TD_End (); /* Degree short name */ HTM_TD_Begin ("class=\"DAT LM\""); if (ICanEdit) { Frm_StartForm (ActRenDegSho); Deg_PutParamOtherDegCod (Deg->DegCod); HTM_INPUT_TEXT ("ShortName",Hie_MAX_CHARS_SHRT_NAME,Deg->ShrtName,true, "class=\"INPUT_SHORT_NAME\""); Frm_EndForm (); } else fprintf (Gbl.F.Out,"%s",Deg->ShrtName); HTM_TD_End (); /* Degree full name */ HTM_TD_Begin ("class=\"DAT LM\""); if (ICanEdit) { Frm_StartForm (ActRenDegFul); Deg_PutParamOtherDegCod (Deg->DegCod); HTM_INPUT_TEXT ("FullName",Hie_MAX_CHARS_FULL_NAME,Deg->FullName,true, "class=\"INPUT_FULL_NAME\""); Frm_EndForm (); } else fprintf (Gbl.F.Out,"%s",Deg->FullName); HTM_TD_End (); /* Degree type */ HTM_TD_Begin ("class=\"DAT LM\""); if (ICanEdit) { Frm_StartForm (ActChgDegTyp); Deg_PutParamOtherDegCod (Deg->DegCod); fprintf (Gbl.F.Out,"" "" "", Gbl.Form.Id, (unsigned) Deg_GetStatusBitsFromStatusTxt (Deg_STATUS_PENDING), Txt_DEGREE_STATUS[Deg_STATUS_PENDING], (unsigned) Deg_GetStatusBitsFromStatusTxt (Deg_STATUS_ACTIVE), Txt_DEGREE_STATUS[Deg_STATUS_ACTIVE]); HTM_SELECT_End (); Frm_EndForm (); } else if (StatusTxt != Deg_STATUS_ACTIVE) // If active ==> do not show anything fprintf (Gbl.F.Out,"%s",Txt_DEGREE_STATUS[StatusTxt]); HTM_TD_End (); HTM_TR_End (); } /***** End table *****/ HTM_TABLE_End (); /***** Free memory used for user's data *****/ Usr_UsrDataDestructor (&UsrDat); } /*****************************************************************************/ /************** Check if I can edit, remove, etc. a degree *******************/ /*****************************************************************************/ static bool Deg_CheckIfICanEditADegree (struct Degree *Deg) { return (bool) (Gbl.Usrs.Me.Role.Logged >= Rol_CTR_ADM || // I am a centre administrator or higher ((Deg->Status & Deg_STATUS_BIT_PENDING) != 0 && // Degree is not yet activated Gbl.Usrs.Me.UsrDat.UsrCod == Deg->RequesterUsrCod)); // I am the requester } /*****************************************************************************/ /******************* Set StatusTxt depending on status bits ******************/ /*****************************************************************************/ // Deg_STATUS_UNKNOWN = 0 // Other // Deg_STATUS_ACTIVE = 1 // 00 (Status == 0) // Deg_STATUS_PENDING = 2 // 01 (Status == Deg_STATUS_BIT_PENDING) // Deg_STATUS_REMOVED = 3 // 1- (Status & Deg_STATUS_BIT_REMOVED) static Deg_StatusTxt_t Deg_GetStatusTxtFromStatusBits (Deg_Status_t Status) { if (Status == 0) return Deg_STATUS_ACTIVE; if (Status == Deg_STATUS_BIT_PENDING) return Deg_STATUS_PENDING; if (Status & Deg_STATUS_BIT_REMOVED) return Deg_STATUS_REMOVED; return Deg_STATUS_UNKNOWN; } /*****************************************************************************/ /******************* Set status bits depending on StatusTxt ******************/ /*****************************************************************************/ // Deg_STATUS_UNKNOWN = 0 // Other // Deg_STATUS_ACTIVE = 1 // 00 (Status == 0) // Deg_STATUS_PENDING = 2 // 01 (Status == Deg_STATUS_BIT_PENDING) // Deg_STATUS_REMOVED = 3 // 1- (Status & Deg_STATUS_BIT_REMOVED) static Deg_Status_t Deg_GetStatusBitsFromStatusTxt (Deg_StatusTxt_t StatusTxt) { switch (StatusTxt) { case Deg_STATUS_UNKNOWN: case Deg_STATUS_ACTIVE: return (Deg_Status_t) 0; case Deg_STATUS_PENDING: return Deg_STATUS_BIT_PENDING; case Deg_STATUS_REMOVED: return Deg_STATUS_BIT_REMOVED; } return (Deg_Status_t) 0; } /*****************************************************************************/ /*********************** Put a form to create a new degree *******************/ /*****************************************************************************/ static void Deg_PutFormToCreateDegree (void) { extern const char *Txt_New_degree; extern const char *Txt_Create_degree; struct DegreeType *DegTyp; unsigned NumDegTyp; /***** Begin form *****/ if (Gbl.Usrs.Me.Role.Logged >= Rol_CTR_ADM) Frm_StartForm (ActNewDeg); else if (Gbl.Usrs.Me.Role.Max >= Rol_GST) Frm_StartForm (ActReqDeg); else Lay_NoPermissionExit (); /***** Begin box and table *****/ Box_StartBoxTable (NULL,Txt_New_degree,NULL, NULL,Box_NOT_CLOSABLE,2); /***** Write heading *****/ Deg_PutHeadDegreesForEdition (); HTM_TR_Begin (NULL); /***** Column to remove degree, disabled here *****/ HTM_TD_Begin ("class=\"BM\""); HTM_TD_End (); /***** Degree code *****/ HTM_TD_Begin ("class=\"CODE\""); HTM_TD_End (); /***** Degree logo *****/ HTM_TD_Begin ("title=\"%s\" class=\"HIE_LOGO\"",Deg_EditingDeg->FullName); Log_DrawLogo (Hie_DEG,-1L,"",20,NULL,true); HTM_TD_End (); /***** Degree short name *****/ HTM_TD_Begin ("class=\"LM\""); HTM_INPUT_TEXT ("ShortName",Hie_MAX_CHARS_SHRT_NAME,Deg_EditingDeg->ShrtName,false, "class=\"INPUT_SHORT_NAME\" required=\"required\""); HTM_TD_End (); /***** Degree full name *****/ HTM_TD_Begin ("class=\"LM\""); HTM_INPUT_TEXT ("FullName",Hie_MAX_CHARS_FULL_NAME,Deg_EditingDeg->FullName,false, "class=\"INPUT_FULL_NAME\" required=\"required\""); HTM_TD_End (); /***** Degree type *****/ HTM_TD_Begin ("class=\"LM\""); fprintf (Gbl.F.Out,"