From 78b655d1b93efd1130e7588c007f8c9ac929d1fa Mon Sep 17 00:00:00 2001 From: acanas Date: Mon, 20 Apr 2020 01:26:46 +0200 Subject: [PATCH] Version19.191 --- Makefile | 2 +- swad_RSS.c | 4 +- swad_action.c | 22 +- swad_calendar.c | 2 +- swad_changelog.h | 5 +- swad_config.h | 4 +- swad_course.c | 4 +- swad_database.c | 2 +- swad_exam.c | 4286 ++++++++++++++++++++++++-------------- swad_exam.h | 181 +- swad_exam_announcement.c | 1793 ++++++++++++++++ swad_exam_announcement.h | 128 ++ swad_exam_event.c | 4069 ++++++++++++++++++++++++++++++++++++ swad_exam_event.h | 149 ++ swad_exam_result.c | 1457 +++++++++++++ swad_exam_result.h | 56 + swad_game.h | 4 +- swad_global.c | 2 +- swad_layout.c | 10 +- swad_match.c | 3 - swad_notification.c | 4 +- swad_timeline.c | 6 +- 22 files changed, 10500 insertions(+), 1693 deletions(-) create mode 100644 swad_exam_announcement.c create mode 100644 swad_exam_announcement.h create mode 100644 swad_exam_event.c create mode 100644 swad_exam_event.h create mode 100644 swad_exam_result.c create mode 100644 swad_exam_result.h diff --git a/Makefile b/Makefile index 3fae8779..8c66f939 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ OBJS = swad_account.o swad_action.o swad_agenda.o swad_alert.o \ swad_course_config.o swad_cryptography.o \ swad_database.o swad_date.o swad_degree.o swad_degree_config.o \ swad_degree_type.o swad_department.o swad_duplicate.o \ - swad_enrolment.o swad_exam.o \ + swad_enrolment.o swad_exam_announcement.o \ swad_figure.o swad_file.o swad_file_browser.o swad_file_extension.o \ swad_file_MIME.o swad_firewall.o swad_follow.o swad_form.o \ swad_forum.o \ diff --git a/swad_RSS.c b/swad_RSS.c index b6f68463..ec645994 100644 --- a/swad_RSS.c +++ b/swad_RSS.c @@ -31,7 +31,7 @@ #include "swad_changelog.h" #include "swad_database.h" -#include "swad_exam.h" +#include "swad_exam_announcement.h" #include "swad_global.h" #include "swad_notice.h" #include "swad_RSS.h" @@ -264,7 +264,7 @@ static void RSS_WriteExamAnnouncements (FILE *FileRSS,struct Course *Crs) " WHERE CrsCod=%ld AND Status=%u AND ExamDate>=NOW()" " ORDER BY T", Gbl.Hierarchy.Crs.CrsCod, - (unsigned) Exa_VISIBLE_EXAM_ANNOUNCEMENT); + (unsigned) ExaAnn_VISIBLE_EXAM_ANNOUNCEMENT); /***** Write items with notices *****/ if (NumExamAnnouncements) diff --git a/swad_action.c b/swad_action.c index 17b06e25..e67001aa 100644 --- a/swad_action.c +++ b/swad_action.c @@ -46,7 +46,7 @@ #include "swad_degree_type.h" #include "swad_department.h" #include "swad_duplicate.h" -#include "swad_exam.h" +#include "swad_exam_announcement.h" #include "swad_enrolment.h" #include "swad_figure.h" #include "swad_follow.h" @@ -545,7 +545,7 @@ const struct Act_Actions Act_Actions[Act_NUM_ACTIONS] = [ActReqTst ] = { 103, 3,TabAss,ActReqTst ,0x238,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Tst_RequestTest ,"check" }, [ActSeeAllGam ] = {1649, 4,TabAss,ActSeeAllGam ,0x238,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Gam_SeeAllGames ,"gamepad" }, [ActSeeAllSvy ] = { 966, 5,TabAss,ActSeeAllSvy ,0x3F8,0x3C0,0x3C0,0x3C0,0x3C0,0x3C0,0x3C0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Svy_SeeAllSurveys ,"poll" }, - [ActSeeAllExaAnn ] = { 85, 6,TabAss,ActSeeAllExaAnn ,0x3F8,0x3C7, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Exa_ListExamAnnouncementsSee ,"bullhorn" }, + [ActSeeAllExaAnn ] = { 85, 6,TabAss,ActSeeAllExaAnn ,0x3F8,0x3C7, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,ExaAnn_ListExamAnnouncementsSee ,"bullhorn" }, // Actions not in menu: [ActEdiAss ] = { 69,-1,TabUnk,ActSeeAss ,0x220,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Inf_FormsToSelSendInfo ,NULL}, @@ -738,15 +738,15 @@ const struct Act_Actions Act_Actions[Act_NUM_ACTIONS] = [ActReqRemSvyQst ] = {1524,-1,TabUnk,ActSeeAllSvy ,0x3E0,0x3C0,0x3C0,0x3C0,0x3C0,0x3C0,0x3C0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Svy_RequestRemoveQst ,NULL}, [ActRemSvyQst ] = { 981,-1,TabUnk,ActSeeAllSvy ,0x3E0,0x3C0,0x3C0,0x3C0,0x3C0,0x3C0,0x3C0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Svy_RemoveQst ,NULL}, - [ActSeeOneExaAnn ] = {1572,-1,TabUnk,ActSeeAllExaAnn ,0x3F8,0x3C7, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Exa_ListExamAnnouncementsCod ,NULL}, - [ActSeeDatExaAnn ] = {1571,-1,TabUnk,ActSeeAllExaAnn ,0x3F8,0x3C7, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Exa_ListExamAnnouncementsDay ,NULL}, - [ActEdiExaAnn ] = { 91,-1,TabUnk,ActSeeAllExaAnn ,0x220,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Exa_PutFrmEditAExamAnnouncement,NULL}, - [ActRcvExaAnn ] = { 110,-1,TabUnk,ActSeeAllExaAnn ,0x220,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,Exa_ReceiveExamAnnouncement1 ,Exa_ReceiveExamAnnouncement2 ,NULL}, - [ActPrnExaAnn ] = { 179,-1,TabUnk,ActSeeAllExaAnn ,0x3F8,0x3C7, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_NEW_TAB,NULL ,Exa_PrintExamAnnouncement ,NULL}, - [ActReqRemExaAnn ] = {1619,-1,TabUnk,ActSeeAllExaAnn ,0x220,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Exa_ReqRemoveExamAnnouncement ,NULL}, - [ActRemExaAnn ] = { 187,-1,TabUnk,ActSeeAllExaAnn ,0x220,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,Exa_RemoveExamAnnouncement1 ,Exa_RemoveExamAnnouncement2 ,NULL}, - [ActHidExaAnn ] = {1620,-1,TabUnk,ActSeeAllExaAnn ,0x220,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,Exa_HideExamAnnouncement ,Exa_ListExamAnnouncementsEdit ,NULL}, - [ActShoExaAnn ] = {1621,-1,TabUnk,ActSeeAllExaAnn ,0x220,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,Exa_UnhideExamAnnouncement ,Exa_ListExamAnnouncementsEdit ,NULL}, + [ActSeeOneExaAnn ] = {1572,-1,TabUnk,ActSeeAllExaAnn ,0x3F8,0x3C7, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,ExaAnn_ListExamAnnouncementsCod ,NULL}, + [ActSeeDatExaAnn ] = {1571,-1,TabUnk,ActSeeAllExaAnn ,0x3F8,0x3C7, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,ExaAnn_ListExamAnnouncementsDay ,NULL}, + [ActEdiExaAnn ] = { 91,-1,TabUnk,ActSeeAllExaAnn ,0x220,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,ExaAnn_PutFrmEditAExamAnnouncement,NULL}, + [ActRcvExaAnn ] = { 110,-1,TabUnk,ActSeeAllExaAnn ,0x220,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,ExaAnn_ReceiveExamAnnouncement1 ,ExaAnn_ReceiveExamAnnouncement2 ,NULL}, + [ActPrnExaAnn ] = { 179,-1,TabUnk,ActSeeAllExaAnn ,0x3F8,0x3C7, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_NEW_TAB,NULL ,ExaAnn_PrintExamAnnouncement ,NULL}, + [ActReqRemExaAnn ] = {1619,-1,TabUnk,ActSeeAllExaAnn ,0x220,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,ExaAnn_ReqRemoveExamAnnouncement ,NULL}, + [ActRemExaAnn ] = { 187,-1,TabUnk,ActSeeAllExaAnn ,0x220,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,ExaAnn_RemoveExamAnnouncement1 ,ExaAnn_RemoveExamAnnouncement2 ,NULL}, + [ActHidExaAnn ] = {1620,-1,TabUnk,ActSeeAllExaAnn ,0x220,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,ExaAnn_HideExamAnnouncement ,ExaAnn_ListExamAnnouncementsEdit ,NULL}, + [ActShoExaAnn ] = {1621,-1,TabUnk,ActSeeAllExaAnn ,0x220,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,ExaAnn_UnhideExamAnnouncement ,ExaAnn_ListExamAnnouncementsEdit ,NULL}, // TabFil ****************************************************************** // Actions in menu: diff --git a/swad_calendar.c b/swad_calendar.c index ea341d1a..c1df3a11 100644 --- a/swad_calendar.c +++ b/swad_calendar.c @@ -30,7 +30,7 @@ #include "swad_box.h" #include "swad_calendar.h" #include "swad_database.h" -#include "swad_exam.h" +#include "swad_exam_announcement.h" #include "swad_figure.h" #include "swad_form.h" #include "swad_global.h" diff --git a/swad_changelog.h b/swad_changelog.h index a7fdc43f..58a611f6 100644 --- a/swad_changelog.h +++ b/swad_changelog.h @@ -497,7 +497,7 @@ enscript -2 --landscape --color --file-align=2 --highlight --line-numbers -o - * En OpenSWAD: ps2pdf source.ps destination.pdf */ -#define Log_PLATFORM_VERSION "SWAD 19.190.5 (2020-04-19)" +#define Log_PLATFORM_VERSION "SWAD 19.191 (2020-04-20)" #define CSS_FILE "swad19.190.css" #define JS_FILE "swad19.172.1.js" /* @@ -550,7 +550,10 @@ Funci // TODO: Oresti Baños: cambiar ojos por candados en descriptores para prohibir/permitir y dejar los ojos para poder elegir descriptores // TODO: Integrar pull requests con traducciones del alemán del usuario eruedin en GitHub // TODO: Cambiar icono notificaciones nuevas con "bell-on.svg" +// TODO: Ahmed El Moukhtari Koubaa: Cuando le damos a la opción de mostrar solo los mensajes no leídos, se muestran estos mensajes, pero cuando los intentamos leer, es decir, hacemos clic sobre ellos se recarga toda la página por así decirlo y vuelve a dar una lista con los mensajes, pero descartando aquel que clicamos porque, entiendo yo al menos, que ya lo ha marcado como leído. + Version 19.191: Apr 20, 2020 Module swad_exam for exam announcements is renamed as swad_exam_announcement. + New modules swad_exam, swad_exam_event and swad_exam_result to make exams. (295885 lines) Version 19.190.5: Apr 19, 2020 Anchors in forms to edit buildings and rooms. (288527 lines) Version 19.190.4: Apr 19, 2020 Icons for room types. (288516 lines) Copy the following 23 icons to icon public directory: diff --git a/swad_config.h b/swad_config.h index b9ef34c6..19f84932 100644 --- a/swad_config.h +++ b/swad_config.h @@ -28,9 +28,9 @@ /** Uncomment one of the following installations of SWAD or create your own **/ /*****************************************************************************/ -#define LOCALHOST_UBUNTU // Comment this line if not applicable +//#define LOCALHOST_UBUNTU // Comment this line if not applicable //#define OPENSWAD_ORG // Comment this line if not applicable -//#define SWAD_UGR_ES // Comment this line if not applicable +#define SWAD_UGR_ES // Comment this line if not applicable //#define SWADBERRY_UGR_ES // Comment this line if not applicable /*****************************************************************************/ diff --git a/swad_course.c b/swad_course.c index 4c348d1a..56b91f01 100644 --- a/swad_course.c +++ b/swad_course.c @@ -35,7 +35,7 @@ #include "swad_course.h" #include "swad_course_config.h" #include "swad_database.h" -#include "swad_exam.h" +#include "swad_exam_announcement.h" #include "swad_figure.h" #include "swad_form.h" #include "swad_forum.h" @@ -1835,7 +1835,7 @@ static void Crs_EmptyCourseCompletely (long CrsCod) DB_QueryUPDATE ("can not remove exam announcements of a course", "UPDATE exam_announcements SET Status=%u" " WHERE CrsCod=%ld", - (unsigned) Exa_DELETED_EXAM_ANNOUNCEMENT,CrsCod); + (unsigned) ExaAnn_DELETED_EXAM_ANNOUNCEMENT,CrsCod); /***** Remove course cards of the course *****/ /* Remove content of course cards */ diff --git a/swad_database.c b/swad_database.c index 76ca30b9..d2fd627a 100644 --- a/swad_database.c +++ b/swad_database.c @@ -1012,7 +1012,7 @@ mysql> DESCRIBE exam_announcements; "NumNotif INT NOT NULL DEFAULT 0," "CrsFullName VARCHAR(2047) NOT NULL," // Hie_MAX_BYTES_FULL_NAME "Year TINYINT NOT NULL," - "ExamSession VARCHAR(2047) NOT NULL," // Exa_MAX_BYTES_SESSION + "ExamSession VARCHAR(2047) NOT NULL," // ExaAnn_MAX_BYTES_SESSION "CallDate DATETIME NOT NULL," "ExamDate DATETIME NOT NULL," "Duration TIME NOT NULL," diff --git a/swad_exam.c b/swad_exam.c index 6aa058ee..cbdf8105 100644 --- a/swad_exam.c +++ b/swad_exam.c @@ -1,20 +1,15 @@ -// swad_exam.c: exam announcements +// swad_exam.c: exams /* SWAD (Shared Workspace At a Distance), is a web platform developed at the University of Granada (Spain), and used to support university teaching. - Copyright (C) 1999-2014 - SWAD core (1999-2014): - Antonio Cañas Vargas - Photo processing (2006-2014): - Daniel J. Calandria Hernández - Chat (2009-2014): - Daniel J. Calandria Hernández + This file is part of SWAD core. + Copyright (C) 1999-2020 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 + it under the terms of the GNU Affero General 3 License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. @@ -26,30 +21,31 @@ 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 DBL_MAX +#include // For PATH_MAX #include // For NULL -#include // For sscanf, asprintf, etc. -#include // For exit, system, malloc, calloc, free, etc. +#include // For asprintf +#include // For calloc #include // For string functions -#include "swad_box.h" -#include "swad_config.h" #include "swad_database.h" #include "swad_exam.h" +#include "swad_figure.h" #include "swad_form.h" #include "swad_global.h" #include "swad_HTML.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_timeline.h" +#include "swad_match.h" +#include "swad_match_result.h" +#include "swad_pagination.h" +#include "swad_role.h" +#include "swad_test.h" +#include "swad_test_visibility.h" /*****************************************************************************/ /************** External global variables from others modules ****************/ @@ -61,6 +57,46 @@ extern struct Globals Gbl; /***************************** Private constants *****************************/ /*****************************************************************************/ +#define Exa_MAX_CHARS_ANSWER (1024 - 1) // 1023 +#define Exa_MAX_BYTES_ANSWER ((Exa_MAX_CHARS_ANSWER + 1) * Str_MAX_BYTES_PER_CHAR - 1) // 16383 + +#define Exa_MAX_ANSWERS_PER_QUESTION 10 + +#define Exa_MAX_SELECTED_QUESTIONS 10000 +#define Exa_MAX_BYTES_LIST_SELECTED_QUESTIONS (Exa_MAX_SELECTED_QUESTIONS * (Cns_MAX_DECIMAL_DIGITS_LONG + 1)) + +/* Score range [0...max.score] + will be converted to + grade range [0...max.grade] + Example: Exam with 5 questions, unique-choice, 4 options per question + max.score = 5 * 1 = 5 + min.score = 5 * (-0.33) = -1,67 + max.grade given by teacher = 0.2 ==> min.grade = -0,067 + + grade + ^ + | / + max.grade--> +---------+ + | /| + | / | + | / | + | / | + | / | + | / | + | / | + | / | + |/ | + ------+---0-+---------+---------> score + ^ /0 ^ + min.score/ | max.score + | / | (num.questions) + | / | + |/ | + +-----+ <--min.grade + / | +*/ +#define Exa_MAX_GRADE_DEFAULT 1.0 + /*****************************************************************************/ /******************************* Private types *******************************/ /*****************************************************************************/ @@ -73,1726 +109,2804 @@ extern struct Globals Gbl; /***************************** Private prototypes ****************************/ /*****************************************************************************/ -static struct Exa_ExamAnnouncements *Exa_GetGlobalExamAnnouncements (void); +static void Exa_ListAllExams (struct Exa_Exams *Exams); +static bool Exa_CheckIfICanEditExams (void); +static void Exa_PutIconsListExams (void *Exams); +static void Exa_PutIconToCreateNewExam (struct Exa_Exams *Exams); +static void Exa_PutButtonToCreateNewExam (struct Exa_Exams *Exams); +static void Exa_PutParamsToCreateNewExam (void *Exams); -static long Exa_GetParamsExamAnnouncement (struct Exa_ExamAnnouncements *ExamAnns); +static void Exa_ShowOneExam (struct Exa_Exams *Exams, + struct Exa_Exam *Exam,bool ShowOnlyThisExam); -static void Exa_AllocMemExamAnnouncement (struct Exa_ExamAnnouncements *ExamAnns); -static void Exa_FreeMemExamAnnouncement (struct Exa_ExamAnnouncements *ExamAnns); +static void Exa_PutIconToShowResultsOfExam (void *Exams); +static void Exa_WriteAuthor (struct Exa_Exam *Exam); -static void Exa_UpdateNumUsrsNotifiedByEMailAboutExamAnnouncement (long ExaCod,unsigned NumUsrsToBeNotifiedByEMail); +static void Exa_PutHiddenParamExamOrder (Exa_Order_t SelectedOrder); -static void Exa_GetExaCodToHighlight (struct Exa_ExamAnnouncements *ExamAnns); -static void Exa_GetDateToHighlight (struct Exa_ExamAnnouncements *ExamAnns); +static void Exa_PutFormsToRemEditOneExam (struct Exa_Exams *Exams, + const struct Exa_Exam *Exam, + const char *Anchor); -static void Exa_ListExamAnnouncements (struct Exa_ExamAnnouncements *ExamAnns, - Exa_TypeViewExamAnnouncement_t TypeViewExamAnnouncement); -static void Exa_PutIconToCreateNewExamAnnouncement (__attribute__((unused)) void *Args); -static void Exa_PutButtonToCreateNewExamAnnouncement (void); +static void Exa_PutParamsOneQst (void *Exams); +static void Exa_PutHiddenParamOrder (Exa_Order_t SelectedOrder); +static Exa_Order_t Exa_GetParamOrder (void); -static long Exa_AddExamAnnouncementToDB (const struct Exa_ExamAnnouncements *ExamAnns); -static void Exa_ModifyExamAnnouncementInDB (const struct Exa_ExamAnnouncements *ExamAnns, - long ExaCod); -static void Exa_GetDataExamAnnouncementFromDB (struct Exa_ExamAnnouncements *ExamAnns, - long ExaCod); -static void Exa_ShowExamAnnouncement (struct Exa_ExamAnnouncements *ExamAnns, - long ExaCod, - Exa_TypeViewExamAnnouncement_t TypeViewExamAnnouncement, - bool HighLight); -static void Exa_PutIconsExamAnnouncement (void *ExamAnns); -static void Exa_PutParamExaCodToEdit (void *ExaCod); -static long Exa_GetParamExaCod (void); +static void Exa_ResetExam (struct Exa_Exam *Exam); -static void Exa_GetNotifContentExamAnnouncement (const struct Exa_ExamAnnouncements *ExamAnns, - char **ContentStr); +static void Exa_GetExamTxtFromDB (long ExaCod,char Txt[Cns_MAX_BYTES_TEXT + 1]); + +static void Exa_RemoveExamFromAllTables (long ExaCod); + +static bool Exa_CheckIfSimilarExamExists (const struct Exa_Exam *Exam); + +static void Exa_PutFormsEditionExam (struct Exa_Exams *Exams, + struct Exa_Exam *Exam, + char Txt[Cns_MAX_BYTES_TEXT + 1], + bool ItsANewExam); +static void Exa_ReceiveExamFieldsFromForm (struct Exa_Exam *Exam, + char Txt[Cns_MAX_BYTES_TEXT + 1]); +static bool Exa_CheckExamFieldsReceivedFromForm (const struct Exa_Exam *Exam); + +static void Exa_CreateExam (struct Exa_Exam *Exam,const char *Txt); +static void Exa_UpdateExam (struct Exa_Exam *Exam,const char *Txt); + +static void Exa_RemAnswersOfAQuestion (long ExaCod,unsigned QstInd); + +static unsigned Exa_GetMaxQuestionIndexInExam (long ExaCod); +static void Exa_ListExamQuestions (struct Exa_Exams *Exams,struct Exa_Exam *Exam); +static void Exa_ListOneOrMoreQuestionsForEdition (struct Exa_Exams *Exams, + long ExaCod,unsigned NumQsts, + MYSQL_RES *mysql_res, + bool ICanEditQuestions); +static void Exa_ListQuestionForEdition (const struct Tst_Question *Question, + unsigned QstInd,bool QuestionExists); +static void Exa_PutIconToAddNewQuestions (void *Exams); +static void Exa_PutButtonToAddNewQuestions (struct Exa_Exams *Exams); + +static void Exa_AllocateListSelectedQuestions (struct Exa_Exams *Exams); +static void Exa_FreeListsSelectedQuestions (struct Exa_Exams *Exams); +static unsigned Exa_CountNumQuestionsInList (const struct Exa_Exams *Exams); + +static void Exa_ExchangeQuestions (long ExaCod, + unsigned QstIndTop,unsigned QstIndBottom); + +static bool Exa_CheckIfEditable (const struct Exa_Exam *Exam); /*****************************************************************************/ -/******************* Get global exam announcements context *******************/ +/******************************* Reset exams *********************************/ /*****************************************************************************/ -static struct Exa_ExamAnnouncements *Exa_GetGlobalExamAnnouncements (void) +void Exa_ResetExams (struct Exa_Exams *Exams) { - static struct Exa_ExamAnnouncements Exa_GlobalExamAnns; // Used to preserve information between priori and posteriori functions - - return &Exa_GlobalExamAnns; + Exams->LstIsRead = false; // List not read from database... + Exams->Num = 0; // Total number of exams + Exams->NumSelected = 0; // Number of exams selected + Exams->Lst = NULL; // List of exams + Exams->SelectedOrder = Exa_ORDER_DEFAULT; + Exams->CurrentPage = 0; + Exams->ListQuestions = NULL; + Exams->ExaCodsSelected = NULL; // String with selected exam codes separated by separator multiple + Exams->ExaCod = -1L; // Selected/current exam code + Exams->EvtCod = -1L; // Selected/current match code + Exams->QstInd = 0; // Current question index } /*****************************************************************************/ -/********************** Reset exam announcements context *********************/ +/***************************** List all exams ********************************/ /*****************************************************************************/ -void Exa_ResetExamAnnouncements (struct Exa_ExamAnnouncements *ExamAnns) +void Exa_SeeAllExams (void) { - ExamAnns->NumExaAnns = 0; - ExamAnns->Lst = NULL; - ExamAnns->NewExaCod = -1L; - ExamAnns->HighlightExaCod = -1L; - ExamAnns->HighlightDate[0] = '\0'; // No exam announcements highlighted - ExamAnns->ExaCod = -1L; - ExamAnns->Anchor = NULL; + struct Exa_Exams Exams; + + /***** Reset exams *****/ + Exa_ResetExams (&Exams); + + /***** Get parameters *****/ + Exa_GetParams (&Exams); // Return value ignored + + /***** Show all exams *****/ + Exa_ListAllExams (&Exams); } /*****************************************************************************/ -/********************** Form to edit an exam announcement ********************/ +/**************************** Show all the exams *****************************/ /*****************************************************************************/ -void Exa_PutFrmEditAExamAnnouncement (void) +static void Exa_ListAllExams (struct Exa_Exams *Exams) { - struct Exa_ExamAnnouncements ExamAnns; - long ExaCod; - - /***** Reset exam announcements context *****/ - Exa_ResetExamAnnouncements (&ExamAnns); - - /***** Allocate memory for the exam announcement *****/ - Exa_AllocMemExamAnnouncement (&ExamAnns); - - /***** Get the code of the exam announcement *****/ - ExaCod = Exa_GetParamsExamAnnouncement (&ExamAnns); - - if (ExaCod > 0) // -1 indicates that this is a new exam announcement - /***** Read exam announcement from the database *****/ - Exa_GetDataExamAnnouncementFromDB (&ExamAnns,ExaCod); - - /***** Show exam announcement *****/ - Exa_ShowExamAnnouncement (&ExamAnns,ExaCod,Exa_FORM_VIEW, - false); // Don't highlight - - /***** Free memory of the exam announcement *****/ - Exa_FreeMemExamAnnouncement (&ExamAnns); - } - -/*****************************************************************************/ -/**************** Get parameters of an exam announcement *********************/ -/*****************************************************************************/ - -static long Exa_GetParamsExamAnnouncement (struct Exa_ExamAnnouncements *ExamAnns) - { - long ExaCod; - - /***** Get the code of the exam announcement *****/ - ExaCod = Exa_GetParamExaCod (); - - /***** Get the name of the course (it is allowed to be different from the official name of the course) *****/ - Par_GetParToText ("CrsName",ExamAnns->ExamAnn.CrsFullName,Hie_MAX_BYTES_FULL_NAME); - // If the parameter is not present or is empty, initialize the string to the full name of the current course - if (!ExamAnns->ExamAnn.CrsFullName[0]) - Str_Copy (ExamAnns->ExamAnn.CrsFullName,Gbl.Hierarchy.Crs.FullName, - Hie_MAX_BYTES_FULL_NAME); - - /***** Get the year *****/ - ExamAnns->ExamAnn.Year = (unsigned) - Par_GetParToUnsignedLong ("Year", - 0, // N.A. - Deg_MAX_YEARS_PER_DEGREE, - (unsigned long) Gbl.Hierarchy.Crs.Year); - - /***** Get the type of exam announcement *****/ - Par_GetParToText ("ExamSession",ExamAnns->ExamAnn.Session,Exa_MAX_BYTES_SESSION); - - /***** Get the data of the exam *****/ - Dat_GetDateFromForm ("ExamDay","ExamMonth","ExamYear", - &ExamAnns->ExamAnn.ExamDate.Day, - &ExamAnns->ExamAnn.ExamDate.Month, - &ExamAnns->ExamAnn.ExamDate.Year); - if (ExamAnns->ExamAnn.ExamDate.Day == 0 || - ExamAnns->ExamAnn.ExamDate.Month == 0 || - ExamAnns->ExamAnn.ExamDate.Year == 0) - { - ExamAnns->ExamAnn.ExamDate.Day = Gbl.Now.Date.Day; - ExamAnns->ExamAnn.ExamDate.Month = Gbl.Now.Date.Month; - ExamAnns->ExamAnn.ExamDate.Year = Gbl.Now.Date.Year; - } - - /***** Get the hour of the exam *****/ - ExamAnns->ExamAnn.StartTime.Hour = (unsigned) Par_GetParToUnsignedLong ("ExamHour", - 0,23,0); - ExamAnns->ExamAnn.StartTime.Minute = (unsigned) Par_GetParToUnsignedLong ("ExamMinute", - 0,59,0); - - /***** Get the duration of the exam *****/ - ExamAnns->ExamAnn.Duration.Hour = (unsigned) Par_GetParToUnsignedLong ("DurationHour", - 0,23,0); - ExamAnns->ExamAnn.Duration.Minute = (unsigned) Par_GetParToUnsignedLong ("DurationMinute", - 0,59,0); - - /***** Get the place where the exam will happen *****/ - Par_GetParToHTML ("Place",ExamAnns->ExamAnn.Place,Cns_MAX_BYTES_TEXT); - - /***** Get the modality of exam *****/ - Par_GetParToHTML ("ExamMode",ExamAnns->ExamAnn.Mode,Cns_MAX_BYTES_TEXT); - - /***** Get the structure of exam *****/ - Par_GetParToHTML ("Structure",ExamAnns->ExamAnn.Structure,Cns_MAX_BYTES_TEXT); - - /***** Get the mandatory documentation *****/ - Par_GetParToHTML ("DocRequired",ExamAnns->ExamAnn.DocRequired,Cns_MAX_BYTES_TEXT); - - /***** Get the mandatory material *****/ - Par_GetParToHTML ("MatRequired",ExamAnns->ExamAnn.MatRequired,Cns_MAX_BYTES_TEXT); - - /***** Get the allowed material *****/ - Par_GetParToHTML ("MatAllowed",ExamAnns->ExamAnn.MatAllowed,Cns_MAX_BYTES_TEXT); - - /***** Get other information *****/ - Par_GetParToHTML ("OtherInfo",ExamAnns->ExamAnn.OtherInfo,Cns_MAX_BYTES_TEXT); - - return ExaCod; - } - -/*****************************************************************************/ -/* Allocate memory for those parameters of an exam anno. with a lot of text **/ -/*****************************************************************************/ - -static void Exa_AllocMemExamAnnouncement (struct Exa_ExamAnnouncements *ExamAnns) - { - if ((ExamAnns->ExamAnn.Place = (char *) malloc (Cns_MAX_BYTES_TEXT + 1)) == NULL) - Lay_NotEnoughMemoryExit (); - - if ((ExamAnns->ExamAnn.Mode = (char *) malloc (Cns_MAX_BYTES_TEXT + 1)) == NULL) - Lay_NotEnoughMemoryExit (); - - if ((ExamAnns->ExamAnn.Structure = (char *) malloc (Cns_MAX_BYTES_TEXT + 1)) == NULL) - Lay_NotEnoughMemoryExit (); - - if ((ExamAnns->ExamAnn.DocRequired = (char *) malloc (Cns_MAX_BYTES_TEXT + 1)) == NULL) - Lay_NotEnoughMemoryExit (); - - if ((ExamAnns->ExamAnn.MatRequired = (char *) malloc (Cns_MAX_BYTES_TEXT + 1)) == NULL) - Lay_NotEnoughMemoryExit (); - - if ((ExamAnns->ExamAnn.MatAllowed = (char *) malloc (Cns_MAX_BYTES_TEXT + 1)) == NULL) - Lay_NotEnoughMemoryExit (); - - if ((ExamAnns->ExamAnn.OtherInfo = (char *) malloc (Cns_MAX_BYTES_TEXT + 1)) == NULL) - Lay_NotEnoughMemoryExit (); - } - -/*****************************************************************************/ -/* Free memory of those parameters of an exam announcem. with a lot of text **/ -/*****************************************************************************/ - -static void Exa_FreeMemExamAnnouncement (struct Exa_ExamAnnouncements *ExamAnns) - { - if (ExamAnns->ExamAnn.Place) - { - free (ExamAnns->ExamAnn.Place); - ExamAnns->ExamAnn.Place = NULL; - } - if (ExamAnns->ExamAnn.Mode) - { - free (ExamAnns->ExamAnn.Mode); - ExamAnns->ExamAnn.Mode = NULL; - } - if (ExamAnns->ExamAnn.Structure) - { - free (ExamAnns->ExamAnn.Structure); - ExamAnns->ExamAnn.Structure = NULL; - } - if (ExamAnns->ExamAnn.DocRequired) - { - free (ExamAnns->ExamAnn.DocRequired); - ExamAnns->ExamAnn.DocRequired = NULL; - } - if (ExamAnns->ExamAnn.MatRequired) - { - free (ExamAnns->ExamAnn.MatRequired); - ExamAnns->ExamAnn.MatRequired = NULL; - } - if (ExamAnns->ExamAnn.MatAllowed) - { - free (ExamAnns->ExamAnn.MatAllowed); - ExamAnns->ExamAnn.MatAllowed = NULL; - } - if (ExamAnns->ExamAnn.OtherInfo) - { - free (ExamAnns->ExamAnn.OtherInfo); - ExamAnns->ExamAnn.OtherInfo = NULL; - } - } - -/*****************************************************************************/ -/************************ Receive an exam announcement ***********************/ -/*****************************************************************************/ -// This function is splitted into a-priori and a-posteriori functions -// in order to view updated links in month of left column - -void Exa_ReceiveExamAnnouncement1 (void) - { - extern const char *Txt_Created_new_announcement_of_exam; - extern const char *Txt_The_announcement_of_exam_has_been_successfully_updated; - struct Exa_ExamAnnouncements *ExamAnns = Exa_GetGlobalExamAnnouncements (); - long ExaCod; - bool NewExamAnnouncement; - char *Anchor = NULL; - - /***** Reset exam announcements context *****/ - Exa_ResetExamAnnouncements (ExamAnns); - - /***** Allocate memory for the exam announcement *****/ - Exa_AllocMemExamAnnouncement (ExamAnns); - - /***** Get parameters of the exam announcement *****/ - ExaCod = Exa_GetParamsExamAnnouncement (ExamAnns); - NewExamAnnouncement = (ExaCod < 0); - - /***** Add the exam announcement to the database and read it again from the database *****/ - if (NewExamAnnouncement) - ExamAnns->NewExaCod = ExaCod = Exa_AddExamAnnouncementToDB (ExamAnns); - else - Exa_ModifyExamAnnouncementInDB (ExamAnns,ExaCod); - - /***** Free memory of the exam announcement *****/ - Exa_FreeMemExamAnnouncement (ExamAnns); - - /***** Create alert to show the change made *****/ - Frm_SetAnchorStr (ExaCod,&Anchor); - Ale_CreateAlert (Ale_SUCCESS,Anchor, - NewExamAnnouncement ? Txt_Created_new_announcement_of_exam : - Txt_The_announcement_of_exam_has_been_successfully_updated); - Frm_FreeAnchorStr (Anchor); - - /***** Set exam to be highlighted *****/ - ExamAnns->HighlightExaCod = ExaCod; - } - -void Exa_ReceiveExamAnnouncement2 (void) - { - struct Exa_ExamAnnouncements *ExamAnns = Exa_GetGlobalExamAnnouncements (); - unsigned NumUsrsToBeNotifiedByEMail; - struct TL_Publication SocPub; - - /***** Notify by email about the new exam announcement *****/ - if ((NumUsrsToBeNotifiedByEMail = Ntf_StoreNotifyEventsToAllUsrs (Ntf_EVENT_EXAM_ANNOUNCEMENT,ExamAnns->HighlightExaCod))) - Exa_UpdateNumUsrsNotifiedByEMailAboutExamAnnouncement (ExamAnns->HighlightExaCod,NumUsrsToBeNotifiedByEMail); - - /***** Create a new social note about the new exam announcement *****/ - TL_StoreAndPublishNote (TL_NOTE_EXAM_ANNOUNCEMENT,ExamAnns->HighlightExaCod,&SocPub); - - /***** Update RSS of current course *****/ - RSS_UpdateRSSFileForACrs (&Gbl.Hierarchy.Crs); - - /***** Show exam announcements *****/ - Exa_ListExamAnnouncementsEdit (); - } - -/*****************************************************************************/ -/***** Update number of users notified in table of exam announcements ********/ -/*****************************************************************************/ - -static void Exa_UpdateNumUsrsNotifiedByEMailAboutExamAnnouncement (long ExaCod,unsigned NumUsrsToBeNotifiedByEMail) - { - /***** Update number of users notified *****/ - DB_QueryUPDATE ("can not update the number of notifications" - " of an exam announcement", - "UPDATE exam_announcements SET NumNotif=NumNotif+%u" - " WHERE ExaCod=%ld", - NumUsrsToBeNotifiedByEMail,ExaCod); - } - -/*****************************************************************************/ -/************************* Print an exam announcement ************************/ -/*****************************************************************************/ - -void Exa_PrintExamAnnouncement (void) - { - struct Exa_ExamAnnouncements ExamAnns; - long ExaCod; - - /***** Reset exam announcements context *****/ - Exa_ResetExamAnnouncements (&ExamAnns); - - /***** Allocate memory for the exam announcement *****/ - Exa_AllocMemExamAnnouncement (&ExamAnns); - - /***** Get the code of the exam announcement *****/ - if ((ExaCod = Exa_GetParamExaCod ()) <= 0) - Lay_ShowErrorAndExit ("Code of exam announcement is missing."); - - /***** Read exam announcement from the database *****/ - Exa_GetDataExamAnnouncementFromDB (&ExamAnns,ExaCod); - - /***** Show exam announcement *****/ - Exa_ShowExamAnnouncement (&ExamAnns,ExaCod,Exa_PRINT_VIEW, - false); // Don't highlight - - /***** Free memory of the exam announcement *****/ - Exa_FreeMemExamAnnouncement (&ExamAnns); - } - -/*****************************************************************************/ -/************************ Remove an exam announcement ************************/ -/*****************************************************************************/ - -void Exa_ReqRemoveExamAnnouncement (void) - { - extern const char *Txt_Do_you_really_want_to_remove_the_following_announcement_of_exam; - extern const char *Txt_Remove; - struct Exa_ExamAnnouncements ExamAnns; - long ExaCod; - - /***** Reset exam announcements context *****/ - Exa_ResetExamAnnouncements (&ExamAnns); - - /***** Get the code of the exam announcement *****/ - if ((ExaCod = Exa_GetParamExaCod ()) <= 0) - Lay_ShowErrorAndExit ("Code of exam announcement is missing."); - - /***** Show question and button to remove exam announcement *****/ - /* Start alert */ - Ale_ShowAlertAndButton1 (Ale_QUESTION,Txt_Do_you_really_want_to_remove_the_following_announcement_of_exam); - - /* Show announcement */ - Exa_AllocMemExamAnnouncement (&ExamAnns); - Exa_GetDataExamAnnouncementFromDB (&ExamAnns,ExaCod); - Exa_ShowExamAnnouncement (&ExamAnns,ExaCod,Exa_NORMAL_VIEW, - false); // Don't highlight - Exa_FreeMemExamAnnouncement (&ExamAnns); - - /* End alert */ - - Ale_ShowAlertAndButton2 (ActRemExaAnn,NULL,NULL, - Exa_PutParamExaCodToEdit,&ExamAnns.ExaCod, - Btn_REMOVE_BUTTON,Txt_Remove); - } - -/*****************************************************************************/ -/************************ Remove an exam announcement ************************/ -/*****************************************************************************/ -// This function is splitted into a-priori and a-posteriori functions -// in order to view updated links in month of left column - -void Exa_RemoveExamAnnouncement1 (void) - { - struct Exa_ExamAnnouncements *ExamAnns = Exa_GetGlobalExamAnnouncements (); - long ExaCod; - - /***** Reset exam announcements context *****/ - Exa_ResetExamAnnouncements (ExamAnns); - - /***** Get the code of the exam announcement *****/ - if ((ExaCod = Exa_GetParamExaCod ()) <= 0) - Lay_ShowErrorAndExit ("Code of exam announcement is missing."); - - /***** Mark the exam announcement as deleted in the database *****/ - DB_QueryUPDATE ("can not remove exam announcement", - "UPDATE exam_announcements SET Status=%u" - " WHERE ExaCod=%ld AND CrsCod=%ld", - (unsigned) Exa_DELETED_EXAM_ANNOUNCEMENT, - ExaCod,Gbl.Hierarchy.Crs.CrsCod); - - /***** Mark possible notifications as removed *****/ - Ntf_MarkNotifAsRemoved (Ntf_EVENT_EXAM_ANNOUNCEMENT,ExaCod); - - /***** Mark possible social note as unavailable *****/ - TL_MarkNoteAsUnavailableUsingNoteTypeAndCod (TL_NOTE_EXAM_ANNOUNCEMENT,ExaCod); - - /***** Update RSS of current course *****/ - RSS_UpdateRSSFileForACrs (&Gbl.Hierarchy.Crs); - } - -void Exa_RemoveExamAnnouncement2 (void) - { - extern const char *Txt_Announcement_of_exam_removed; - - /***** Write message *****/ - Ale_ShowAlert (Ale_SUCCESS,Txt_Announcement_of_exam_removed); - - /***** List again all the remaining exam announcements *****/ - Exa_ListExamAnnouncementsEdit (); - } - -/*****************************************************************************/ -/************************ Hide an exam announcement **************************/ -/*****************************************************************************/ -// This function is splitted into a-priori and a-posteriori functions -// in order to view updated links in month of left column - -void Exa_HideExamAnnouncement (void) - { - struct Exa_ExamAnnouncements *ExamAnns = Exa_GetGlobalExamAnnouncements (); - long ExaCod; - - /***** Reset exam announcements context *****/ - Exa_ResetExamAnnouncements (ExamAnns); - - /***** Get the code of the exam announcement *****/ - if ((ExaCod = Exa_GetParamExaCod ()) <= 0) - Lay_ShowErrorAndExit ("Code of exam announcement is missing."); - - /***** Mark the exam announcement as hidden in the database *****/ - DB_QueryUPDATE ("can not hide exam announcement", - "UPDATE exam_announcements SET Status=%u" - " WHERE ExaCod=%ld AND CrsCod=%ld", - (unsigned) Exa_HIDDEN_EXAM_ANNOUNCEMENT, - ExaCod,Gbl.Hierarchy.Crs.CrsCod); - - /***** Set exam to be highlighted *****/ - ExamAnns->HighlightExaCod = ExaCod; - } - -/*****************************************************************************/ -/************************ Unhide an exam announcement ************************/ -/*****************************************************************************/ -// This function is splitted into a-priori and a-posteriori functions -// in order to view updated links in month of left column - -void Exa_UnhideExamAnnouncement (void) - { - struct Exa_ExamAnnouncements *ExamAnns = Exa_GetGlobalExamAnnouncements (); - long ExaCod; - - /***** Reset exam announcements context *****/ - Exa_ResetExamAnnouncements (ExamAnns); - - /***** Get the code of the exam announcement *****/ - if ((ExaCod = Exa_GetParamExaCod ()) <= 0) - Lay_ShowErrorAndExit ("Code of exam announcement is missing."); - - /***** Mark the exam announcement as visible in the database *****/ - DB_QueryUPDATE ("can not unhide exam announcement", - "UPDATE exam_announcements SET Status=%u" - " WHERE ExaCod=%ld AND CrsCod=%ld", - (unsigned) Exa_VISIBLE_EXAM_ANNOUNCEMENT, - ExaCod,Gbl.Hierarchy.Crs.CrsCod); - - /***** Set exam to be highlighted *****/ - ExamAnns->HighlightExaCod = ExaCod; - } - -/*****************************************************************************/ -/*************** List all the exam announcements to see them *****************/ -/*****************************************************************************/ - -void Exa_ListExamAnnouncementsSee (void) - { - struct Exa_ExamAnnouncements ExamAnns; - - /***** Reset exam announcements context *****/ - Exa_ResetExamAnnouncements (&ExamAnns); - - /***** List all exam announcements *****/ - Exa_ListExamAnnouncements (&ExamAnns,Exa_NORMAL_VIEW); - - /***** Mark possible notifications as seen *****/ - Ntf_MarkNotifAsSeen (Ntf_EVENT_EXAM_ANNOUNCEMENT, - -1L,Gbl.Hierarchy.Crs.CrsCod, - Gbl.Usrs.Me.UsrDat.UsrCod); - } - -/*****************************************************************************/ -/********** List all the exam announcements to edit or remove them ***********/ -/*****************************************************************************/ - -void Exa_ListExamAnnouncementsEdit (void) - { - struct Exa_ExamAnnouncements *ExamAnns = Exa_GetGlobalExamAnnouncements (); - - Exa_ListExamAnnouncements (ExamAnns,Exa_NORMAL_VIEW); - } - -/*****************************************************************************/ -/********** List exam announcement given an exam announcement code ***********/ -/*****************************************************************************/ - -void Exa_ListExamAnnouncementsCod (void) - { - struct Exa_ExamAnnouncements ExamAnns; - - /***** Reset exam announcements context *****/ - Exa_ResetExamAnnouncements (&ExamAnns); - - /***** Get exam announcement code *****/ - Exa_GetExaCodToHighlight (&ExamAnns); - - /***** List all exam announcements *****/ - Exa_ListExamAnnouncements (&ExamAnns,Exa_NORMAL_VIEW); - } - -/*****************************************************************************/ -/***************** List exam announcements on a given date *******************/ -/*****************************************************************************/ - -void Exa_ListExamAnnouncementsDay (void) - { - struct Exa_ExamAnnouncements ExamAnns; - - /***** Reset exam announcements context *****/ - Exa_ResetExamAnnouncements (&ExamAnns); - - /***** Get date *****/ - Exa_GetDateToHighlight (&ExamAnns); - - /***** List all exam announcements *****/ - Exa_ListExamAnnouncements (&ExamAnns,Exa_NORMAL_VIEW); - } - -/*****************************************************************************/ -/*********** Get date of exam announcements to show highlighted **************/ -/*****************************************************************************/ - -static void Exa_GetExaCodToHighlight (struct Exa_ExamAnnouncements *ExamAnns) - { - /***** Get the exam announcement code - of the exam announcement to highlight *****/ - ExamAnns->HighlightExaCod = Exa_GetParamExaCod (); - } - -/*****************************************************************************/ -/*********** Get date of exam announcements to show highlighted **************/ -/*****************************************************************************/ - -static void Exa_GetDateToHighlight (struct Exa_ExamAnnouncements *ExamAnns) - { - /***** Get the date (in YYYYMMDD format) - of the exam announcements to highlight *****/ - Par_GetParToText ("Date",ExamAnns->HighlightDate,4 + 2 + 2); - } - -/*****************************************************************************/ -/******************** List all the exam announcements ************************/ -/*****************************************************************************/ - -static void Exa_ListExamAnnouncements (struct Exa_ExamAnnouncements *ExamAnns, - Exa_TypeViewExamAnnouncement_t TypeViewExamAnnouncement) - { - extern const char *Hlp_ASSESSMENT_Announcements; - extern const char *Txt_Announcements_of_exams; - extern const char *Txt_No_announcements_of_exams_of_X; - char SubQueryStatus[64]; - MYSQL_RES *mysql_res; - MYSQL_ROW row; - unsigned long NumExaAnn; - unsigned long NumExaAnns; - long ExaCod; - bool HighLight; - bool ICanEdit = (Gbl.Usrs.Me.Role.Logged == Rol_TCH || - Gbl.Usrs.Me.Role.Logged == Rol_SYS_ADM); - - /***** Build subquery about status depending on my role *****/ - if (ICanEdit) - sprintf (SubQueryStatus,"Status<>%u", - (unsigned) Exa_DELETED_EXAM_ANNOUNCEMENT); - else - sprintf (SubQueryStatus,"Status=%u", - (unsigned) Exa_VISIBLE_EXAM_ANNOUNCEMENT); - - /***** Get exam announcements (the most recent first) - in current course from database *****/ - NumExaAnns = DB_QuerySELECT (&mysql_res,"can not get exam announcements" - " in this course for listing", - "SELECT ExaCod" - " FROM exam_announcements" - " WHERE CrsCod=%ld AND %s" - " ORDER BY ExamDate DESC", - Gbl.Hierarchy.Crs.CrsCod,SubQueryStatus); + extern const char *Hlp_ASSESSMENT_Exams; + extern const char *Txt_Exams; + extern const char *Txt_EXAMS_ORDER_HELP[Exa_NUM_ORDERS]; + extern const char *Txt_EXAMS_ORDER[Exa_NUM_ORDERS]; + extern const char *Txt_Matches; + extern const char *Txt_No_exams; + Exa_Order_t Order; + struct Pagination Pagination; + unsigned NumExam; + struct Exa_Exam Exam; + + /***** Get number of groups in current course *****/ + if (!Gbl.Crs.Grps.NumGrps) + Gbl.Crs.Grps.WhichGrps = Grp_ALL_GROUPS; + + /***** Get list of exams *****/ + Exa_GetListExams (Exams,Exams->SelectedOrder); + + /***** Compute variables related to pagination *****/ + Pagination.NumItems = Exams->Num; + Pagination.CurrentPage = (int) Exams->CurrentPage; + Pag_CalculatePagination (&Pagination); + Exams->CurrentPage = (unsigned) Pagination.CurrentPage; /***** Begin box *****/ - if (ICanEdit) - Box_BoxBegin (NULL,Txt_Announcements_of_exams, - Exa_PutIconToCreateNewExamAnnouncement,NULL, - Hlp_ASSESSMENT_Announcements,Box_NOT_CLOSABLE); - else - Box_BoxBegin (NULL,Txt_Announcements_of_exams, - NULL,NULL, - Hlp_ASSESSMENT_Announcements,Box_NOT_CLOSABLE); + Box_BoxBegin ("100%",Txt_Exams, + Exa_PutIconsListExams,Exams, + Hlp_ASSESSMENT_Exams,Box_NOT_CLOSABLE); - /***** The result of the query may be empty *****/ - if (!NumExaAnns) - Ale_ShowAlert (Ale_INFO,Txt_No_announcements_of_exams_of_X, - Gbl.Hierarchy.Crs.FullName); + /***** Write links to pages *****/ + Pag_WriteLinksToPagesCentered (Pag_EXAMS,&Pagination, + Exams,-1L); - /***** List the existing exam announcements *****/ - for (NumExaAnn = 0; - NumExaAnn < NumExaAnns; - NumExaAnn++) + if (Exams->Num) { - /***** Get the code of the exam announcement (row[0]) *****/ - row = mysql_fetch_row (mysql_res); + /***** Table head *****/ + HTM_TABLE_BeginWideMarginPadding (2); + HTM_TR_Begin (NULL); + if (Exa_CheckIfICanEditExams ()) + HTM_TH (1,1,"CONTEXT_COL",NULL); // Column for contextual icons - if (sscanf (row[0],"%ld",&ExaCod) != 1) - Lay_ShowErrorAndExit ("Wrong code of exam announcement."); + for (Order = (Exa_Order_t) 0; + Order <= (Exa_Order_t) (Exa_NUM_ORDERS - 1); + Order++) + { + HTM_TH_Begin (1,1,"LM"); - /***** Allocate memory for the exam announcement *****/ - Exa_AllocMemExamAnnouncement (ExamAnns); + /* Form to change order */ + Frm_StartForm (ActSeeAllExa); + Pag_PutHiddenParamPagNum (Pag_EXAMS,Exams->CurrentPage); + Par_PutHiddenParamUnsigned (NULL,"Order",(unsigned) Order); + HTM_BUTTON_SUBMIT_Begin (Txt_EXAMS_ORDER_HELP[Order],"BT_LINK TIT_TBL",NULL); + if (Order == Exams->SelectedOrder) + HTM_U_Begin (); + HTM_Txt (Txt_EXAMS_ORDER[Order]); + if (Order == Exams->SelectedOrder) + HTM_U_End (); + HTM_BUTTON_End (); + Frm_EndForm (); - /***** Read the data of the exam announcement *****/ - Exa_GetDataExamAnnouncementFromDB (ExamAnns,ExaCod); + HTM_TH_End (); + } - /***** Show exam announcement *****/ - HighLight = false; - if (ExaCod == ExamAnns->HighlightExaCod) - HighLight = true; - else if (ExamAnns->HighlightDate[0]) - { - if (!strcmp (ExamAnns->ExamAnn.ExamDate.YYYYMMDD, - ExamAnns->HighlightDate)) - HighLight = true; - } - Exa_ShowExamAnnouncement (ExamAnns,ExaCod,TypeViewExamAnnouncement, - HighLight); + HTM_TH (1,1,"RM",Txt_Matches); - /***** Free memory of the exam announcement *****/ - Exa_FreeMemExamAnnouncement (ExamAnns); + HTM_TR_End (); + + /***** Write all exams *****/ + for (NumExam = Pagination.FirstItemVisible; + NumExam <= Pagination.LastItemVisible; + NumExam++) + { + /* Get data of this exam */ + Exam.ExaCod = Exams->Lst[NumExam - 1].ExaCod; + Exa_GetDataOfExamByCod (&Exam); + + /* Show exam */ + Exa_ShowOneExam (Exams, + &Exam, + false); // Do not show only this exam + } + + /***** End table *****/ + HTM_TABLE_End (); } + else // No exams created + Ale_ShowAlert (Ale_INFO,Txt_No_exams); - /***** Free structure that stores the query result *****/ - DB_FreeMySQLResult (&mysql_res); + /***** Write again links to pages *****/ + Pag_WriteLinksToPagesCentered (Pag_EXAMS,&Pagination, + Exams,-1L); - /***** Button to create a new exam announcement *****/ - if (ICanEdit) - Exa_PutButtonToCreateNewExamAnnouncement (); + /***** Button to create a new exam *****/ + if (Exa_CheckIfICanEditExams ()) + Exa_PutButtonToCreateNewExam (Exams); /***** End box *****/ Box_BoxEnd (); + + /***** Free list of exams *****/ + Exa_FreeListExams (Exams); } /*****************************************************************************/ -/***************** Put icon to create a new exam announcement ****************/ +/************************ Check if I can edit exams **************************/ /*****************************************************************************/ -static void Exa_PutIconToCreateNewExamAnnouncement (__attribute__((unused)) void *Args) +static bool Exa_CheckIfICanEditExams (void) { - extern const char *Txt_New_announcement_OF_EXAM; - - Ico_PutContextualIconToAdd (ActEdiExaAnn,NULL, - NULL,NULL, - Txt_New_announcement_OF_EXAM); + switch (Gbl.Usrs.Me.Role.Logged) + { + case Rol_TCH: + case Rol_SYS_ADM: + return true; + default: + return false; + } + return false; } /*****************************************************************************/ -/**************** Put button to create a new exam announcement ***************/ +/***************** Put contextual icons in list of exams *******************/ /*****************************************************************************/ -static void Exa_PutButtonToCreateNewExamAnnouncement (void) +static void Exa_PutIconsListExams (void *Exams) { - extern const char *Txt_New_announcement_OF_EXAM; + if (Exams) + { + /***** Put icon to create a new exam *****/ + if (Exa_CheckIfICanEditExams ()) + Exa_PutIconToCreateNewExam ((struct Exa_Exams *) Exams); - Frm_StartForm (ActEdiExaAnn); - Btn_PutConfirmButton (Txt_New_announcement_OF_EXAM); + /***** Put icon to view matches results *****/ + switch (Gbl.Usrs.Me.Role.Logged) + { + case Rol_STD: + Ico_PutContextualIconToShowResults (ActSeeMyExaEvtResCrs,NULL, + NULL,NULL); + break; + case Rol_NET: + case Rol_TCH: + case Rol_SYS_ADM: + Ico_PutContextualIconToShowResults (ActReqSeeAllExaEvtRes,NULL, + NULL,NULL); + break; + default: + break; + } + + /***** Put icon to show a figure *****/ + Fig_PutIconToShowFigure (Fig_EXAMS); + } + } + +/*****************************************************************************/ +/********************** Put icon to create a new exam **********************/ +/*****************************************************************************/ + +static void Exa_PutIconToCreateNewExam (struct Exa_Exams *Exams) + { + extern const char *Txt_New_exam; + + Ico_PutContextualIconToAdd (ActFrmNewExa,NULL, + Exa_PutParamsToCreateNewExam,Exams, + Txt_New_exam); + } + +/*****************************************************************************/ +/********************* Put button to create a new exam *********************/ +/*****************************************************************************/ + +static void Exa_PutButtonToCreateNewExam (struct Exa_Exams *Exams) + { + extern const char *Txt_New_exam; + + Frm_StartForm (ActFrmNewExa); + Exa_PutParamsToCreateNewExam (Exams); + Btn_PutConfirmButton (Txt_New_exam); Frm_EndForm (); } /*****************************************************************************/ -/****************** Add an exam announcement to the database *****************/ +/******************* Put parameters to create a new exam *******************/ /*****************************************************************************/ -// Return the code of the exam announcement just added -static long Exa_AddExamAnnouncementToDB (const struct Exa_ExamAnnouncements *ExamAnns) +static void Exa_PutParamsToCreateNewExam (void *Exams) { - long ExaCod; - - /***** Add exam announcement *****/ - ExaCod = - DB_QueryINSERTandReturnCode ("can not create a new exam announcement", - "INSERT INTO exam_announcements " - "(CrsCod,Status,NumNotif,CrsFullName,Year,ExamSession," - "CallDate,ExamDate,Duration," - "Place,ExamMode,Structure,DocRequired,MatRequired,MatAllowed,OtherInfo)" - " VALUES " - "(%ld,%u,0,'%s',%u,'%s'," - "NOW(),'%04u-%02u-%02u %02u:%02u:00','%02u:%02u:00','%s'," - "'%s','%s','%s','%s','%s','%s')", - Gbl.Hierarchy.Crs.CrsCod, - (unsigned) Exa_VISIBLE_EXAM_ANNOUNCEMENT, - ExamAnns->ExamAnn.CrsFullName, - ExamAnns->ExamAnn.Year, - ExamAnns->ExamAnn.Session, - ExamAnns->ExamAnn.ExamDate.Year, - ExamAnns->ExamAnn.ExamDate.Month, - ExamAnns->ExamAnn.ExamDate.Day, - ExamAnns->ExamAnn.StartTime.Hour, - ExamAnns->ExamAnn.StartTime.Minute, - ExamAnns->ExamAnn.Duration.Hour, - ExamAnns->ExamAnn.Duration.Minute, - ExamAnns->ExamAnn.Place, - ExamAnns->ExamAnn.Mode, - ExamAnns->ExamAnn.Structure, - ExamAnns->ExamAnn.DocRequired, - ExamAnns->ExamAnn.MatRequired, - ExamAnns->ExamAnn.MatAllowed, - ExamAnns->ExamAnn.OtherInfo); - - return ExaCod; - } - -/*****************************************************************************/ -/*************** Modify an exam announcement in the database *****************/ -/*****************************************************************************/ - -static void Exa_ModifyExamAnnouncementInDB (const struct Exa_ExamAnnouncements *ExamAnns, - long ExaCod) - { - /***** Modify exam announcement *****/ - DB_QueryUPDATE ("can not update an exam announcement", - "UPDATE exam_announcements" - " SET CrsFullName='%s',Year=%u,ExamSession='%s'," - "ExamDate='%04u-%02u-%02u %02u:%02u:00'," - "Duration='%02u:%02u:00'," - "Place='%s',ExamMode='%s',Structure='%s'," - "DocRequired='%s',MatRequired='%s',MatAllowed='%s',OtherInfo='%s'" - " WHERE ExaCod=%ld", - ExamAnns->ExamAnn.CrsFullName, - ExamAnns->ExamAnn.Year, - ExamAnns->ExamAnn.Session, - ExamAnns->ExamAnn.ExamDate.Year, - ExamAnns->ExamAnn.ExamDate.Month, - ExamAnns->ExamAnn.ExamDate.Day, - ExamAnns->ExamAnn.StartTime.Hour, - ExamAnns->ExamAnn.StartTime.Minute, - ExamAnns->ExamAnn.Duration.Hour, - ExamAnns->ExamAnn.Duration.Minute, - ExamAnns->ExamAnn.Place, - ExamAnns->ExamAnn.Mode, - ExamAnns->ExamAnn.Structure, - ExamAnns->ExamAnn.DocRequired, - ExamAnns->ExamAnn.MatRequired, - ExamAnns->ExamAnn.MatAllowed, - ExamAnns->ExamAnn.OtherInfo, - ExaCod); - } - -/*****************************************************************************/ -/******* Create a list with the dates of all the exam announcements **********/ -/*****************************************************************************/ - -void Exa_CreateListExamAnnouncements (struct Exa_ExamAnnouncements *ExamAnns) - { - MYSQL_RES *mysql_res; - MYSQL_ROW row; - unsigned long NumExaAnn; - unsigned long NumExaAnns; - - if (Gbl.DB.DatabaseIsOpen) + if (Exams) { - /***** Get exam dates (ordered from more recent to older) - of visible exam announcements - in current course from database *****/ - NumExaAnns = DB_QuerySELECT (&mysql_res,"can not get exam announcements" - " in this course", - "SELECT ExaCod,DATE(ExamDate)" - " FROM exam_announcements" - " WHERE CrsCod=%ld AND Status=%u" - " ORDER BY ExamDate DESC", - Gbl.Hierarchy.Crs.CrsCod, - (unsigned) Exa_VISIBLE_EXAM_ANNOUNCEMENT); - - /***** The result of the query may be empty *****/ - ExamAnns->Lst = NULL; - ExamAnns->NumExaAnns = 0; - if (NumExaAnns) - { - /***** Allocate memory for the list *****/ - if ((ExamAnns->Lst = (struct Exa_ExamCodeAndDate *) calloc (NumExaAnns,sizeof (struct Exa_ExamCodeAndDate))) == NULL) - Lay_NotEnoughMemoryExit (); - - /***** Get the dates of the existing exam announcements *****/ - for (NumExaAnn = 0; - NumExaAnn < NumExaAnns; - NumExaAnn++) - { - /***** Get next exam announcement *****/ - row = mysql_fetch_row (mysql_res); - - /* Get exam code (row[0]) */ - ExamAnns->Lst[ExamAnns->NumExaAnns].ExaCod = Str_ConvertStrCodToLongCod (row[0]); - - /* Read the date of the exam (row[1]) */ - if (sscanf (row[1],"%04u-%02u-%02u", - &ExamAnns->Lst[ExamAnns->NumExaAnns].ExamDate.Year, - &ExamAnns->Lst[ExamAnns->NumExaAnns].ExamDate.Month, - &ExamAnns->Lst[ExamAnns->NumExaAnns].ExamDate.Day) != 3) - Lay_ShowErrorAndExit ("Wrong date of exam."); - - /***** Increment number of elements in list *****/ - ExamAnns->NumExaAnns++; - } - } - - /***** Free structure that stores the query result *****/ - DB_FreeMySQLResult (&mysql_res); + Exa_PutHiddenParamExamOrder (((struct Exa_Exams *) Exams)->SelectedOrder); + Pag_PutHiddenParamPagNum (Pag_EXAMS,((struct Exa_Exams *) Exams)->CurrentPage); } } /*****************************************************************************/ -/***************** Free list of dates of exam announcements ******************/ +/****************************** Show one exam ******************************/ /*****************************************************************************/ -void Exa_FreeListExamAnnouncements (struct Exa_ExamAnnouncements *ExamAnns) +void Exa_SeeOneExam (void) { - if (ExamAnns->Lst) - { - free (ExamAnns->Lst); - ExamAnns->Lst = NULL; - ExamAnns->NumExaAnns = 0; - } + struct Exa_Exams Exams; + struct Exa_Exam Exam; + + /***** Reset exams *****/ + Exa_ResetExams (&Exams); + + /***** Get parameters *****/ + if ((Exam.ExaCod = Exa_GetParams (&Exams)) <= 0) + Lay_ShowErrorAndExit ("Code of exam is missing."); + Exa_GetDataOfExamByCod (&Exam); + + /***** Show exam *****/ + Exa_ShowOnlyOneExam (&Exams,&Exam, + false, // Do not list exam questions + false); // Do not put form to start new match } /*****************************************************************************/ -/******** Read the data of an exam announcement from the database ************/ +/******************************* Show one exam *******************************/ /*****************************************************************************/ -static void Exa_GetDataExamAnnouncementFromDB (struct Exa_ExamAnnouncements *ExamAnns, - long ExaCod) +void Exa_ShowOnlyOneExam (struct Exa_Exams *Exams, + struct Exa_Exam *Exam, + bool ListExamQuestions, + bool PutFormNewMatch) { - MYSQL_RES *mysql_res; - MYSQL_ROW row; - unsigned long NumExaAnns; - unsigned UnsignedNum; - unsigned Hour; - unsigned Minute; - unsigned Second; - - /***** Get data of an exam announcement from database *****/ - NumExaAnns = DB_QuerySELECT (&mysql_res,"can not get data" - " of an exam announcement", - "SELECT CrsCod,Status,CrsFullName,Year,ExamSession," - "CallDate,ExamDate,Duration,Place,ExamMode," - "Structure,DocRequired,MatRequired,MatAllowed,OtherInfo" - " FROM exam_announcements WHERE ExaCod=%ld", - ExaCod); - - /***** The result of the query must have one row *****/ - if (NumExaAnns != 1) - Lay_ShowErrorAndExit ("Error when getting data of an exam announcement."); - - /***** Get the data of the exam announcement *****/ - row = mysql_fetch_row (mysql_res); - - /* Code of the course in which the exam announcement is inserted (row[0]) */ - ExamAnns->ExamAnn.CrsCod = Str_ConvertStrCodToLongCod (row[0]); - - /* Status of the exam announcement (row[1]) */ - if (sscanf (row[1],"%u",&UnsignedNum) != 1) - Lay_ShowErrorAndExit ("Wrong status."); - if (UnsignedNum >= Exa_NUM_STATUS) - Lay_ShowErrorAndExit ("Wrong status."); - ExamAnns->ExamAnn.Status = (Exa_ExamAnnouncementStatus_t) UnsignedNum; - - /* Name of the course (row[2]) */ - Str_Copy (ExamAnns->ExamAnn.CrsFullName,row[2], - Hie_MAX_BYTES_FULL_NAME); - - /* Year (row[3]) */ - if (sscanf (row[3],"%u",&ExamAnns->ExamAnn.Year) != 1) - Lay_ShowErrorAndExit ("Wrong year."); - - /* Exam session (row[4]) */ - Str_Copy (ExamAnns->ExamAnn.Session,row[4], - Exa_MAX_BYTES_SESSION); - - /* Date of exam announcement (row[5]) */ - if (sscanf (row[5],"%04u-%02u-%02u %02u:%02u:%02u", - &ExamAnns->ExamAnn.CallDate.Year, - &ExamAnns->ExamAnn.CallDate.Month, - &ExamAnns->ExamAnn.CallDate.Day, - &Hour, - &Minute, - &Second) != 6) - Lay_ShowErrorAndExit ("Wrong date of exam announcement."); - - /* Date of exam (row[6]) */ - if (sscanf (row[6],"%04u-%02u-%02u %02u:%02u:%02u", - &ExamAnns->ExamAnn.ExamDate.Year, - &ExamAnns->ExamAnn.ExamDate.Month, - &ExamAnns->ExamAnn.ExamDate.Day, - &ExamAnns->ExamAnn.StartTime.Hour, - &ExamAnns->ExamAnn.StartTime.Minute, - &Second) != 6) - Lay_ShowErrorAndExit ("Wrong date of exam."); - snprintf (ExamAnns->ExamAnn.ExamDate.YYYYMMDD,sizeof (ExamAnns->ExamAnn.ExamDate.YYYYMMDD), - "%04u%02u%02u", - ExamAnns->ExamAnn.ExamDate.Year, - ExamAnns->ExamAnn.ExamDate.Month, - ExamAnns->ExamAnn.ExamDate.Day); - - /* Approximate duration (row[7]) */ - if (sscanf (row[7],"%02u:%02u:%02u",&ExamAnns->ExamAnn.Duration.Hour,&ExamAnns->ExamAnn.Duration.Minute,&Second) != 3) - Lay_ShowErrorAndExit ("Wrong duration of exam."); - - /* Place (row[8]) */ - Str_Copy (ExamAnns->ExamAnn.Place,row[8], - Cns_MAX_BYTES_TEXT); - - /* Exam mode (row[9]) */ - Str_Copy (ExamAnns->ExamAnn.Mode,row[9], - Cns_MAX_BYTES_TEXT); - - /* Structure (row[10]) */ - Str_Copy (ExamAnns->ExamAnn.Structure,row[10], - Cns_MAX_BYTES_TEXT); - - /* Documentation required (row[11]) */ - Str_Copy (ExamAnns->ExamAnn.DocRequired,row[11], - Cns_MAX_BYTES_TEXT); - - /* Material required (row[12]) */ - Str_Copy (ExamAnns->ExamAnn.MatRequired,row[12], - Cns_MAX_BYTES_TEXT); - - /* Material allowed (row[13]) */ - Str_Copy (ExamAnns->ExamAnn.MatAllowed,row[13], - Cns_MAX_BYTES_TEXT); - - /* Other information for students (row[14]) */ - Str_Copy (ExamAnns->ExamAnn.OtherInfo,row[14], - Cns_MAX_BYTES_TEXT); - - /***** Free structure that stores the query result *****/ - DB_FreeMySQLResult (&mysql_res); + Exa_ShowOnlyOneExamBegin (Exams,Exam,ListExamQuestions,PutFormNewMatch); + Exa_ShowOnlyOneExamEnd (); } -/*****************************************************************************/ -/************ Show a form with the data of an exam announcement **************/ -/*****************************************************************************/ - -static void Exa_ShowExamAnnouncement (struct Exa_ExamAnnouncements *ExamAnns, - long ExaCod, - Exa_TypeViewExamAnnouncement_t TypeViewExamAnnouncement, - bool HighLight) +void Exa_ShowOnlyOneExamBegin (struct Exa_Exams *Exams, + struct Exa_Exam *Exam, + bool ListExamQuestions, + bool PutFormNewMatch) { - extern const char *Hlp_ASSESSMENT_Announcements_new_announcement; - extern const char *Hlp_ASSESSMENT_Announcements_edit_announcement; - extern const char *Txt_YEAR_OF_DEGREE[1 + Deg_MAX_YEARS_PER_DEGREE]; - extern const char *Txt_EXAM_ANNOUNCEMENT; - extern const char *Txt_EXAM_ANNOUNCEMENT_Course; - extern const char *Txt_EXAM_ANNOUNCEMENT_Year_or_semester; - extern const char *Txt_EXAM_ANNOUNCEMENT_Session; - extern const char *Txt_EXAM_ANNOUNCEMENT_Exam_date; - extern const char *Txt_EXAM_ANNOUNCEMENT_Start_time; - extern const char *Txt_EXAM_ANNOUNCEMENT_Approximate_duration; - extern const char *Txt_EXAM_ANNOUNCEMENT_Place_of_exam; - extern const char *Txt_EXAM_ANNOUNCEMENT_Mode; - extern const char *Txt_EXAM_ANNOUNCEMENT_Structure_of_the_exam; - extern const char *Txt_EXAM_ANNOUNCEMENT_Documentation_required; - extern const char *Txt_EXAM_ANNOUNCEMENT_Material_required; - extern const char *Txt_EXAM_ANNOUNCEMENT_Material_allowed; - extern const char *Txt_EXAM_ANNOUNCEMENT_Other_information; - extern const char *Txt_hours_ABBREVIATION; - extern const char *Txt_hour; - extern const char *Txt_hours; - extern const char *Txt_minute; - extern const char *Txt_minutes; - extern const char *Txt_Publish_announcement_OF_EXAM; - struct Instit Ins; - char StrExamDate[Cns_MAX_BYTES_DATE + 1]; - unsigned Year; - unsigned Hour; - unsigned Minute; - char *Anchor = NULL; - const char *Width; - void (*FunctionToDrawContextualIcons) (void *Args); - const char *HelpLink; - static const char *ClassExaAnnouncement[Exa_NUM_VIEWS][Exa_NUM_STATUS] = - { - [Exa_NORMAL_VIEW][Exa_VISIBLE_EXAM_ANNOUNCEMENT] = "EXA_ANN_VISIBLE", - [Exa_NORMAL_VIEW][Exa_HIDDEN_EXAM_ANNOUNCEMENT ] = "EXA_ANN_HIDDEN", - [Exa_NORMAL_VIEW][Exa_DELETED_EXAM_ANNOUNCEMENT] = NULL, // Not applicable here - - [Exa_PRINT_VIEW ][Exa_VISIBLE_EXAM_ANNOUNCEMENT] = "EXA_ANN_VISIBLE", - [Exa_PRINT_VIEW ][Exa_HIDDEN_EXAM_ANNOUNCEMENT ] = "EXA_ANN_VISIBLE", - [Exa_PRINT_VIEW ][Exa_DELETED_EXAM_ANNOUNCEMENT] = NULL, // Not applicable here - - [Exa_FORM_VIEW ][Exa_VISIBLE_EXAM_ANNOUNCEMENT] = "EXA_ANN_VISIBLE", - [Exa_FORM_VIEW ][Exa_HIDDEN_EXAM_ANNOUNCEMENT ] = "EXA_ANN_VISIBLE", - [Exa_FORM_VIEW ][Exa_DELETED_EXAM_ANNOUNCEMENT] = NULL, // Not applicable here - }; - - /***** Get data of institution of this degree *****/ - Ins.InsCod = Gbl.Hierarchy.Ins.InsCod; - Ins_GetDataOfInstitutionByCod (&Ins); - - /***** Build anchor string *****/ - Frm_SetAnchorStr (ExaCod,&Anchor); - - /***** Begin article *****/ - if (TypeViewExamAnnouncement == Exa_NORMAL_VIEW) - HTM_ARTICLE_Begin (Anchor); + extern const char *Hlp_ASSESSMENT_Exams; + extern const char *Txt_Exam; /***** Begin box *****/ - Width = "625px"; - ExamAnns->Anchor = Anchor; // Used to put contextual icons - ExamAnns->ExaCod = ExaCod; // Used to put contextual icons - FunctionToDrawContextualIcons = TypeViewExamAnnouncement == Exa_NORMAL_VIEW ? Exa_PutIconsExamAnnouncement : - NULL; - HelpLink = TypeViewExamAnnouncement == Exa_FORM_VIEW ? ((ExaCod > 0) ? Hlp_ASSESSMENT_Announcements_edit_announcement : - Hlp_ASSESSMENT_Announcements_new_announcement) : - NULL; - if (HighLight) - { - /* Show pending alerts */ - Ale_ShowAlerts (Anchor); + Exams->ExaCod = Exam->ExaCod; + Box_BoxBegin (NULL,Txt_Exam, + Exa_PutIconToShowResultsOfExam,Exams, + Hlp_ASSESSMENT_Exams,Box_NOT_CLOSABLE); - /* Start highlighted box */ - Box_BoxShadowBegin (Width,NULL, - FunctionToDrawContextualIcons,ExamAnns, - HelpLink); - } - else // Don't highlight - /* Start normal box */ - Box_BoxBegin (Width,NULL, - FunctionToDrawContextualIcons,ExamAnns, - HelpLink,Box_NOT_CLOSABLE); + /***** Show exam *****/ + Exa_ShowOneExam (Exams, + Exam, + true); // Show only this exam - if (TypeViewExamAnnouncement == Exa_FORM_VIEW) + if (ListExamQuestions) + /***** Write questions of this exam *****/ + Exa_ListExamQuestions (Exams,Exam); + else + /***** List matches *****/ + ExaEvt_ListEvents (Exams,Exam,PutFormNewMatch); + } + +void Exa_ShowOnlyOneExamEnd (void) + { + /***** End box *****/ + Box_BoxEnd (); + } + +static void Exa_ShowOneExam (struct Exa_Exams *Exams, + struct Exa_Exam *Exam,bool ShowOnlyThisExam) + { + extern const char *Txt_View_exam; + extern const char *Txt_No_of_questions; + extern const char *Txt_Maximum_grade; + extern const char *Txt_Result_visibility; + extern const char *Txt_Matches; + char *Anchor = NULL; + static unsigned UniqueId = 0; + char *Id; + Dat_StartEndTime_t StartEndTime; + const char *Color; + char Txt[Cns_MAX_BYTES_TEXT + 1]; + + /***** Set anchor string *****/ + Frm_SetAnchorStr (Exam->ExaCod,&Anchor); + + /***** Begin box and table *****/ + if (ShowOnlyThisExam) + HTM_TABLE_BeginWidePadding (2); + + /***** Start first row of this exam *****/ + HTM_TR_Begin (NULL); + + /***** Icons related to this exam *****/ + if (Exa_CheckIfICanEditExams ()) { - /***** Begin form *****/ - Frm_StartFormAnchor (ActRcvExaAnn,Anchor); - if (ExaCod > 0) // Existing announcement of exam - Exa_PutHiddenParamExaCod (ExaCod); + if (ShowOnlyThisExam) + HTM_TD_Begin ("rowspan=\"2\" class=\"CONTEXT_COL\""); + else + HTM_TD_Begin ("rowspan=\"2\" class=\"CONTEXT_COL COLOR%u\"",Gbl.RowEvenOdd); + + /* Icons to remove/edit this exam */ + Exa_PutFormsToRemEditOneExam (Exams,Exam,Anchor); + + HTM_TD_End (); } - /***** Begin table *****/ - HTM_TABLE_Begin ("%s CELLS_PAD_2", - ClassExaAnnouncement[TypeViewExamAnnouncement][ExamAnns->ExamAnn.Status]); - - /***** Institution logo *****/ - HTM_TR_Begin (NULL); - HTM_TD_Begin ("colspan=\"2\" class=\"CM\""); - if (TypeViewExamAnnouncement == Exa_PRINT_VIEW) - HTM_SPAN_Begin ("class=\"EXAM_TIT\""); - else - HTM_A_Begin ("href=\"%s\" target=\"_blank\" class=\"EXAM_TIT\"", - Ins.WWW); - Lgo_DrawLogo (Hie_INS,Ins.InsCod,Ins.FullName,64,NULL,true); - HTM_BR (); - HTM_Txt (Ins.FullName); - if (TypeViewExamAnnouncement == Exa_PRINT_VIEW) - HTM_SPAN_End (); - else - HTM_A_End (); - HTM_TD_End (); - HTM_TR_End (); - - /***** Degree *****/ - HTM_TR_Begin (NULL); - HTM_TD_Begin ("colspan=\"2\" class=\"EXAM_TIT CM\""); - if (TypeViewExamAnnouncement == Exa_NORMAL_VIEW) - HTM_A_Begin ("href=\"%s\" target=\"_blank\" class=\"EXAM_TIT\"", - Gbl.Hierarchy.Deg.WWW); - HTM_Txt (Gbl.Hierarchy.Deg.FullName); - if (TypeViewExamAnnouncement == Exa_NORMAL_VIEW) - HTM_A_End (); - HTM_TD_End (); - HTM_TR_End (); - - /***** Title *****/ - HTM_TR_Begin (NULL); - HTM_TD_Begin ("colspan=\"2\" class=\"EXAM CM\""); - HTM_NBSP (); - HTM_BR (); - HTM_STRONG_Begin (); - HTM_Txt (Txt_EXAM_ANNOUNCEMENT); - HTM_STRONG_End (); - HTM_TD_End (); - HTM_TR_End (); - - HTM_TR_Begin (NULL); - HTM_TD_Begin ("colspan=\"2\" class=\"EXAM LM\""); - HTM_NBSP (); - HTM_TD_End (); - HTM_TR_End (); - - /***** Name of the course *****/ - HTM_TR_Begin (NULL); - - /* Label */ - Frm_LabelColumn ("RT", - TypeViewExamAnnouncement == Exa_FORM_VIEW ? "CrsName" : - NULL, - Txt_EXAM_ANNOUNCEMENT_Course); - - /* Data */ - HTM_TD_Begin ("class=\"EXAM LT\""); - if (TypeViewExamAnnouncement == Exa_FORM_VIEW) - HTM_INPUT_TEXT ("CrsName",Hie_MAX_CHARS_FULL_NAME,ExamAnns->ExamAnn.CrsFullName,false, - "id=\"CrsName\" size=\"30\""); - else + /***** Start/end date/time *****/ + UniqueId++; + for (StartEndTime = (Dat_StartEndTime_t) 0; + StartEndTime <= (Dat_StartEndTime_t) (Dat_NUM_START_END_TIME - 1); + StartEndTime++) { - HTM_STRONG_Begin (); - HTM_Txt (ExamAnns->ExamAnn.CrsFullName); - HTM_STRONG_End (); + if (asprintf (&Id,"exa_date_%u_%u",(unsigned) StartEndTime,UniqueId) < 0) + Lay_NotEnoughMemoryExit (); + Color = Exam->NumUnfinishedEves ? (Exam->Hidden ? "DATE_GREEN_LIGHT": + "DATE_GREEN") : + (Exam->Hidden ? "DATE_RED_LIGHT": + "DATE_RED"); + if (ShowOnlyThisExam) + HTM_TD_Begin ("id=\"%s\" class=\"%s LT\"", + Id,Color); + else + HTM_TD_Begin ("id=\"%s\" class=\"%s LT COLOR%u\"", + Id,Color,Gbl.RowEvenOdd); + if (Exam->TimeUTC[Dat_START_TIME]) + Dat_WriteLocalDateHMSFromUTC (Id,Exam->TimeUTC[StartEndTime], + Gbl.Prefs.DateFormat,Dat_SEPARATOR_BREAK, + true,true,true,0x7); + HTM_TD_End (); + free (Id); } - HTM_TD_End (); - HTM_TR_End (); - - /***** Year/semester (N.A., 1º, 2º, 3º, 4º, 5º...) *****/ - HTM_TR_Begin (NULL); - - /* Label */ - Frm_LabelColumn ("RT", - TypeViewExamAnnouncement == Exa_FORM_VIEW ? "Year" : - NULL, - Txt_EXAM_ANNOUNCEMENT_Year_or_semester); - - /* Data */ - HTM_TD_Begin ("class=\"EXAM LT\""); - if (TypeViewExamAnnouncement == Exa_FORM_VIEW) - { - HTM_SELECT_Begin (false, - "id=\"Year\" name=\"Year\""); - for (Year = 0; - Year <= Deg_MAX_YEARS_PER_DEGREE; - Year++) - HTM_OPTION (HTM_Type_UNSIGNED,&Year, - ExamAnns->ExamAnn.Year == Year,false, - "%s",Txt_YEAR_OF_DEGREE[Year]); - HTM_SELECT_End (); - } - else - HTM_Txt (Txt_YEAR_OF_DEGREE[ExamAnns->ExamAnn.Year]); - HTM_TD_End (); - - HTM_TR_End (); - - /***** Exam session *****/ - HTM_TR_Begin (NULL); - - /* Label */ - Frm_LabelColumn ("RT", - TypeViewExamAnnouncement == Exa_FORM_VIEW ? "ExamSession" : - NULL, - Txt_EXAM_ANNOUNCEMENT_Session); - - /* Data */ - HTM_TD_Begin ("class=\"EXAM LT\""); - if (TypeViewExamAnnouncement == Exa_FORM_VIEW) - HTM_INPUT_TEXT ("ExamSession",Exa_MAX_CHARS_SESSION,ExamAnns->ExamAnn.Session,false, - "id=\"ExamSession\" size=\"30\""); - else - HTM_Txt (ExamAnns->ExamAnn.Session); - HTM_TD_End (); - - HTM_TR_End (); - - /***** Date of the exam *****/ - HTM_TR_Begin (NULL); - - /* Label */ - Frm_LabelColumn ("RT",NULL,Txt_EXAM_ANNOUNCEMENT_Exam_date); - - /* Data */ - if (TypeViewExamAnnouncement == Exa_FORM_VIEW) - { + /***** Exam title and main data *****/ + if (ShowOnlyThisExam) HTM_TD_Begin ("class=\"LT\""); - Dat_WriteFormDate (ExamAnns->ExamAnn.ExamDate.Year < Gbl.Now.Date.Year ? ExamAnns->ExamAnn.ExamDate.Year : - Gbl.Now.Date.Year, - Gbl.Now.Date.Year + 1,"Exam", - &(ExamAnns->ExamAnn.ExamDate), - false,false); - HTM_TD_End (); - } else - { - Dat_ConvDateToDateStr (&ExamAnns->ExamAnn.ExamDate, - StrExamDate); - HTM_TD_Begin ("class=\"EXAM LT\""); - HTM_Txt (StrExamDate); - HTM_TD_End (); - } - HTM_TR_End (); + HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd); - /***** Start time *****/ - HTM_TR_Begin (NULL); + /* Exam title */ + Exams->ExaCod = Exam->ExaCod; + HTM_ARTICLE_Begin (Anchor); + Frm_StartForm (ActSeeExa); + Exa_PutParams (Exams); + HTM_BUTTON_SUBMIT_Begin (Txt_View_exam, + Exam->Hidden ? "BT_LINK LT ASG_TITLE_LIGHT": + "BT_LINK LT ASG_TITLE", + NULL); + HTM_Txt (Exam->Title); + HTM_BUTTON_End (); + Frm_EndForm (); + HTM_ARTICLE_End (); - /* Label */ - Frm_LabelColumn ("RT",NULL,Txt_EXAM_ANNOUNCEMENT_Start_time); + /* Number of questions, maximum grade, visibility of results */ + HTM_DIV_Begin ("class=\"%s\"",Exam->Hidden ? "ASG_GRP_LIGHT" : + "ASG_GRP"); + HTM_TxtColonNBSP (Txt_No_of_questions); + HTM_Unsigned (Exam->NumQsts); + HTM_BR (); + HTM_TxtColonNBSP (Txt_Maximum_grade); + HTM_Double (Exam->MaxGrade); + HTM_BR (); + HTM_TxtColonNBSP (Txt_Result_visibility); + TstVis_ShowVisibilityIcons (Exam->Visibility,Exam->Hidden); + HTM_DIV_End (); - /* Data */ - HTM_TD_Begin ("class=\"EXAM LT\""); - if (TypeViewExamAnnouncement == Exa_FORM_VIEW) - { - HTM_SELECT_Begin (false, - "name=\"ExamHour\""); - HTM_OPTION (HTM_Type_STRING,"0", - ExamAnns->ExamAnn.StartTime.Hour == 0,false, - "-"); - for (Hour = 7; - Hour <= 22; - Hour++) - HTM_OPTION (HTM_Type_UNSIGNED,&Hour, - ExamAnns->ExamAnn.StartTime.Hour == Hour,false, - "%02u %s",Hour,Txt_hours_ABBREVIATION); - HTM_SELECT_End (); + /***** Number of matches in exam *****/ + if (ShowOnlyThisExam) + HTM_TD_Begin ("class=\"RT\""); + else + HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd); + + Exams->ExaCod = Exam->ExaCod; + Frm_StartForm (ActSeeExa); + Exa_PutParams (Exams); + HTM_BUTTON_SUBMIT_Begin (Txt_Matches, + Exam->Hidden ? "BT_LINK LT ASG_TITLE_LIGHT" : + "BT_LINK LT ASG_TITLE", + NULL); + if (ShowOnlyThisExam) + HTM_TxtColonNBSP (Txt_Matches); + HTM_Unsigned (Exam->NumEves); + HTM_BUTTON_End (); + Frm_EndForm (); - HTM_SELECT_Begin (false, - "name=\"ExamMinute\""); - for (Minute = 0; - Minute <= 59; - Minute++) - HTM_OPTION (HTM_Type_UNSIGNED,&Minute, - ExamAnns->ExamAnn.StartTime.Minute == Minute,false, - "%02u ′",Minute); - HTM_SELECT_End (); - } - else if (ExamAnns->ExamAnn.StartTime.Hour) - HTM_TxtF ("%2u:%02u",ExamAnns->ExamAnn.StartTime.Hour, - ExamAnns->ExamAnn.StartTime.Minute); HTM_TD_End (); + /***** End 1st row of this exam *****/ HTM_TR_End (); - /***** Approximate duration of the exam *****/ + /***** Start 2nd row of this exam *****/ HTM_TR_Begin (NULL); - /* Label */ - Frm_LabelColumn ("RT",NULL,Txt_EXAM_ANNOUNCEMENT_Approximate_duration); - - /* Data */ - HTM_TD_Begin ("class=\"EXAM LT\""); - if (TypeViewExamAnnouncement == Exa_FORM_VIEW) - { - HTM_SELECT_Begin (false, - "name=\"DurationHour\""); - for (Hour = 0; - Hour <= 8; - Hour++) - HTM_OPTION (HTM_Type_UNSIGNED,&Hour, - ExamAnns->ExamAnn.Duration.Hour == Hour,false, - "%02u %s",Hour,Txt_hours_ABBREVIATION); - HTM_SELECT_End (); - - HTM_SELECT_Begin (false, - "name=\"DurationMinute\""); - for (Minute = 0; - Minute <= 59; - Minute++) - HTM_OPTION (HTM_Type_UNSIGNED,&Minute, - ExamAnns->ExamAnn.Duration.Minute == Minute,false, - "%02u ′",Minute); - HTM_SELECT_End (); - } - else if (ExamAnns->ExamAnn.Duration.Hour || - ExamAnns->ExamAnn.Duration.Minute) - { - if (ExamAnns->ExamAnn.Duration.Hour) - { - if (ExamAnns->ExamAnn.Duration.Minute) - HTM_TxtF ("%u %s %u ′",ExamAnns->ExamAnn.Duration.Hour, - Txt_hours_ABBREVIATION, - ExamAnns->ExamAnn.Duration.Minute); - else - HTM_TxtF ("%u %s",ExamAnns->ExamAnn.Duration.Hour, - ExamAnns->ExamAnn.Duration.Hour == 1 ? Txt_hour : - Txt_hours); - } - else if (ExamAnns->ExamAnn.Duration.Minute) - { - HTM_TxtF ("%u %s",ExamAnns->ExamAnn.Duration.Minute, - ExamAnns->ExamAnn.Duration.Minute == 1 ? Txt_minute : - Txt_minutes); - } - } + /***** Author of the exam *****/ + if (ShowOnlyThisExam) + HTM_TD_Begin ("colspan=\"2\" class=\"LT\""); + else + HTM_TD_Begin ("colspan=\"2\" class=\"LT COLOR%u\"",Gbl.RowEvenOdd); + Exa_WriteAuthor (Exam); HTM_TD_End (); - HTM_TR_End (); - - /***** Place where the exam will be made *****/ - HTM_TR_Begin (NULL); - - /* Label */ - Frm_LabelColumn ("RT", - TypeViewExamAnnouncement == Exa_FORM_VIEW ? "Place" : - NULL, - Txt_EXAM_ANNOUNCEMENT_Place_of_exam); - - /* Data */ - HTM_TD_Begin ("class=\"EXAM LT\""); - if (TypeViewExamAnnouncement == Exa_FORM_VIEW) - { - HTM_TEXTAREA_Begin ("id=\"Place\" name=\"Place\" cols=\"40\" rows=\"4\""); - HTM_Txt (ExamAnns->ExamAnn.Place); - HTM_TEXTAREA_End (); - } + /***** Text of the exam *****/ + if (ShowOnlyThisExam) + HTM_TD_Begin ("colspan=\"2\" class=\"LT\""); else - { - Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, - ExamAnns->ExamAnn.Place, - Cns_MAX_BYTES_TEXT,false); - HTM_Txt (ExamAnns->ExamAnn.Place); - } + HTM_TD_Begin ("colspan=\"2\" class=\"LT COLOR%u\"",Gbl.RowEvenOdd); + Exa_GetExamTxtFromDB (Exam->ExaCod,Txt); + Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, + Txt,Cns_MAX_BYTES_TEXT,false); // Convert from HTML to rigorous HTML + Str_InsertLinks (Txt,Cns_MAX_BYTES_TEXT,60); // Insert links + HTM_DIV_Begin ("class=\"PAR %s\"",Exam->Hidden ? "DAT_LIGHT" : + "DAT"); + HTM_Txt (Txt); + HTM_DIV_End (); HTM_TD_End (); + /***** End 2nd row of this exam *****/ HTM_TR_End (); - /***** Exam mode *****/ - HTM_TR_Begin (NULL); - - /* Label */ - Frm_LabelColumn ("RT", - TypeViewExamAnnouncement == Exa_FORM_VIEW ? "ExamMode" : - NULL, - Txt_EXAM_ANNOUNCEMENT_Mode); - - /* Data */ - HTM_TD_Begin ("class=\"EXAM LT\""); - if (TypeViewExamAnnouncement == Exa_FORM_VIEW) - { - HTM_TEXTAREA_Begin ("id=\"ExamMode\" name=\"ExamMode\" cols=\"40\" rows=\"2\""); - HTM_Txt (ExamAnns->ExamAnn.Mode); - HTM_TEXTAREA_End (); - } + /***** End table *****/ + if (ShowOnlyThisExam) + HTM_TABLE_End (); else - { - Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, - ExamAnns->ExamAnn.Mode, - Cns_MAX_BYTES_TEXT,false); - HTM_Txt (ExamAnns->ExamAnn.Mode); - } - HTM_TD_End (); - - HTM_TR_End (); - - /***** Structure of the exam *****/ - HTM_TR_Begin (NULL); - - /* Label */ - Frm_LabelColumn ("RT", - TypeViewExamAnnouncement == Exa_FORM_VIEW ? "Structure" : - NULL, - Txt_EXAM_ANNOUNCEMENT_Structure_of_the_exam); - - /* Data */ - HTM_TD_Begin ("class=\"EXAM LT\""); - if (TypeViewExamAnnouncement == Exa_FORM_VIEW) - { - HTM_TEXTAREA_Begin ("id=\"Structure\" name=\"Structure\" cols=\"40\" rows=\"8\""); - HTM_Txt (ExamAnns->ExamAnn.Structure); - HTM_TEXTAREA_End (); - } - else - { - Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, - ExamAnns->ExamAnn.Structure, - Cns_MAX_BYTES_TEXT,false); - HTM_Txt (ExamAnns->ExamAnn.Structure); - } - HTM_TD_End (); - - HTM_TR_End (); - - /***** Documentation required *****/ - HTM_TR_Begin (NULL); - - /* Label */ - Frm_LabelColumn ("RT", - TypeViewExamAnnouncement == Exa_FORM_VIEW ? "DocRequired" : - NULL, - Txt_EXAM_ANNOUNCEMENT_Documentation_required); - - /* Data */ - HTM_TD_Begin ("class=\"EXAM LT\""); - if (TypeViewExamAnnouncement == Exa_FORM_VIEW) - { - HTM_TEXTAREA_Begin ("id=\"DocRequired\" name=\"DocRequired\" cols=\"40\" rows=\"2\""); - HTM_Txt (ExamAnns->ExamAnn.DocRequired); - HTM_TEXTAREA_End (); - } - else - { - Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, - ExamAnns->ExamAnn.DocRequired, - Cns_MAX_BYTES_TEXT,false); - HTM_Txt (ExamAnns->ExamAnn.DocRequired); - } - HTM_TD_End (); - - HTM_TR_End (); - - /***** Material required *****/ - HTM_TR_Begin (NULL); - - /* Label */ - Frm_LabelColumn ("RT", - TypeViewExamAnnouncement == Exa_FORM_VIEW ? "MatRequired" : - NULL, - Txt_EXAM_ANNOUNCEMENT_Material_required); - - /* Data */ - HTM_TD_Begin ("class=\"EXAM LT\""); - if (TypeViewExamAnnouncement == Exa_FORM_VIEW) - { - HTM_TEXTAREA_Begin ("id=\"MatRequired\" name=\"MatRequired\" cols=\"40\" rows=\"4\""); - HTM_Txt (ExamAnns->ExamAnn.MatRequired); - HTM_TEXTAREA_End (); - } - else - { - Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, - ExamAnns->ExamAnn.MatRequired, - Cns_MAX_BYTES_TEXT,false); - HTM_Txt (ExamAnns->ExamAnn.MatRequired); - } - HTM_TD_End (); - - HTM_TR_End (); - - /***** Material allowed *****/ - HTM_TR_Begin (NULL); - - /* Label */ - Frm_LabelColumn ("RT", - TypeViewExamAnnouncement == Exa_FORM_VIEW ? "MatAllowed" : - NULL, - Txt_EXAM_ANNOUNCEMENT_Material_allowed); - - /* Data */ - HTM_TD_Begin ("class=\"EXAM LT\""); - if (TypeViewExamAnnouncement == Exa_FORM_VIEW) - { - HTM_TEXTAREA_Begin ("id=\"MatAllowed\" name=\"MatAllowed\" cols=\"40\" rows=\"4\""); - HTM_Txt (ExamAnns->ExamAnn.MatAllowed); - HTM_TEXTAREA_End (); - } - else - { - Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, - ExamAnns->ExamAnn.MatAllowed, - Cns_MAX_BYTES_TEXT,false); - HTM_Txt (ExamAnns->ExamAnn.MatAllowed); - } - HTM_TD_End (); - - HTM_TR_End (); - - /***** Other information to students *****/ - HTM_TR_Begin (NULL); - - /* Label */ - Frm_LabelColumn ("RT", - TypeViewExamAnnouncement == Exa_FORM_VIEW ? "OtherInfo" : - NULL, - Txt_EXAM_ANNOUNCEMENT_Other_information); - - /* Data */ - HTM_TD_Begin ("class=\"EXAM LT\""); - if (TypeViewExamAnnouncement == Exa_FORM_VIEW) - { - HTM_TEXTAREA_Begin ("id=\"OtherInfo\" name=\"OtherInfo\" cols=\"40\" rows=\"5\""); - HTM_Txt (ExamAnns->ExamAnn.OtherInfo); - HTM_TEXTAREA_End (); - } - else - { - Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, - ExamAnns->ExamAnn.OtherInfo, - Cns_MAX_BYTES_TEXT,false); - HTM_Txt (ExamAnns->ExamAnn.OtherInfo); - } - HTM_TD_End (); - - HTM_TR_End (); - - /***** End table, send button and end box *****/ - if (TypeViewExamAnnouncement == Exa_FORM_VIEW) - Box_BoxTableWithButtonEnd ((ExaCod > 0) ? Btn_CONFIRM_BUTTON : - Btn_CREATE_BUTTON, - Txt_Publish_announcement_OF_EXAM); - else - Box_BoxTableEnd (); - - /***** Show QR code *****/ - if (TypeViewExamAnnouncement == Exa_PRINT_VIEW) - QR_ExamAnnnouncement (); - - /***** End article *****/ - if (TypeViewExamAnnouncement == Exa_NORMAL_VIEW) - HTM_ARTICLE_End (); + Gbl.RowEvenOdd = 1 - Gbl.RowEvenOdd; /***** Free anchor string *****/ Frm_FreeAnchorStr (Anchor); } /*****************************************************************************/ -/********* Put icons to remove / edit / print an exam announcement ***********/ +/************* Put icon to show results of matches in an exam *****************/ /*****************************************************************************/ -static void Exa_PutIconsExamAnnouncement (void *ExamAnns) +static void Exa_PutIconToShowResultsOfExam (void *Exams) { - if (ExamAnns) + if (Exams) { - if (Gbl.Usrs.Me.Role.Logged == Rol_TCH || - Gbl.Usrs.Me.Role.Logged == Rol_SYS_ADM) + /***** Put icon to view matches results *****/ + switch (Gbl.Usrs.Me.Role.Logged) { - /***** Link to remove this exam announcement *****/ - Ico_PutContextualIconToRemove (ActReqRemExaAnn, - Exa_PutParamExaCodToEdit,&((struct Exa_ExamAnnouncements *) ExamAnns)->ExaCod); - - /***** Put form to hide/show exam announement *****/ - switch (((struct Exa_ExamAnnouncements *) ExamAnns)->ExamAnn.Status) - { - case Exa_VISIBLE_EXAM_ANNOUNCEMENT: - Ico_PutContextualIconToHide (ActHidExaAnn,((struct Exa_ExamAnnouncements *) ExamAnns)->Anchor, - Exa_PutParamExaCodToEdit,&((struct Exa_ExamAnnouncements *) ExamAnns)->ExaCod); - break; - case Exa_HIDDEN_EXAM_ANNOUNCEMENT: - Ico_PutContextualIconToUnhide (ActShoExaAnn,((struct Exa_ExamAnnouncements *) ExamAnns)->Anchor, - Exa_PutParamExaCodToEdit,&((struct Exa_ExamAnnouncements *) ExamAnns)->ExaCod); - break; - case Exa_DELETED_EXAM_ANNOUNCEMENT: // Not applicable here - break; - } - - /***** Link to edit this exam announcement *****/ - Ico_PutContextualIconToEdit (ActEdiExaAnn,NULL, - Exa_PutParamExaCodToEdit,&((struct Exa_ExamAnnouncements *) ExamAnns)->ExaCod); + case Rol_STD: + Ico_PutContextualIconToShowResults (ActSeeMyEveResExa,ExaRes_RESULTS_BOX_ID, + Exa_PutParams,Exams); + break; + case Rol_NET: + case Rol_TCH: + case Rol_SYS_ADM: + Ico_PutContextualIconToShowResults (ActSeeAllEveResExa,ExaRes_RESULTS_BOX_ID, + Exa_PutParams,Exams); + break; + default: + break; } - - /***** Link to print view *****/ - Ico_PutContextualIconToPrint (ActPrnExaAnn, - Exa_PutParamExaCodToEdit,&((struct Exa_ExamAnnouncements *) ExamAnns)->ExaCod); } } /*****************************************************************************/ -/*************** Param with the code of an exam announcement *****************/ +/*********************** Write the author of an exam ************************/ /*****************************************************************************/ -static void Exa_PutParamExaCodToEdit (void *ExaCod) +static void Exa_WriteAuthor (struct Exa_Exam *Exam) { - if (ExaCod) - Exa_PutHiddenParamExaCod (*((long *) ExaCod)); + Usr_WriteAuthor1Line (Exam->UsrCod,Exam->Hidden); } -void Exa_PutHiddenParamExaCod (long ExaCod) +/*****************************************************************************/ +/****** Put a hidden parameter with the type of order in list of exams *******/ +/*****************************************************************************/ + +static void Exa_PutHiddenParamExamOrder (Exa_Order_t SelectedOrder) + { + Par_PutHiddenParamUnsigned (NULL,"Order",(unsigned) SelectedOrder); + } + +/*****************************************************************************/ +/******************** Put a link (form) to edit one exam *********************/ +/*****************************************************************************/ + +static void Exa_PutFormsToRemEditOneExam (struct Exa_Exams *Exams, + const struct Exa_Exam *Exam, + const char *Anchor) + { + Exams->ExaCod = Exam->ExaCod; + + /***** Put icon to remove exam *****/ + Ico_PutContextualIconToRemove (ActReqRemExa, + Exa_PutParams,Exams); + + /***** Put icon to unhide/hide exam *****/ + if (Exam->Hidden) + Ico_PutContextualIconToUnhide (ActShoExa,Anchor, + Exa_PutParams,Exams); + else + Ico_PutContextualIconToHide (ActHidExa,Anchor, + Exa_PutParams,Exams); + + /***** Put icon to edit exam *****/ + Ico_PutContextualIconToEdit (ActEdiOneExa,NULL, + Exa_PutParams,Exams); + } + +/*****************************************************************************/ +/**************** Put parameter to move/remove one question ******************/ +/*****************************************************************************/ + +static void Exa_PutParamsOneQst (void *Exams) + { + if (Exams) + { + Exa_PutParams (Exams); + Exa_PutParamQstInd (((struct Exa_Exams *) Exams)->QstInd); + } + } + +/*****************************************************************************/ +/*********************** Params used to edit an exam **************************/ +/*****************************************************************************/ + +void Exa_PutParams (void *Exams) + { + Grp_WhichGroups_t WhichGroups; + + if (Exams) + { + if (((struct Exa_Exams *) Exams)->ExaCod > 0) + Exa_PutParamExamCod (((struct Exa_Exams *) Exams)->ExaCod); + Exa_PutHiddenParamOrder (((struct Exa_Exams *) Exams)->SelectedOrder); + WhichGroups = Grp_GetParamWhichGroups (); + Grp_PutParamWhichGroups (&WhichGroups); + Pag_PutHiddenParamPagNum (Pag_EXAMS,((struct Exa_Exams *) Exams)->CurrentPage); + } + } + +/*****************************************************************************/ +/******************** Write parameter with code of exam **********************/ +/*****************************************************************************/ + +void Exa_PutParamExamCod (long ExaCod) { Par_PutHiddenParamLong (NULL,"ExaCod",ExaCod); } /*****************************************************************************/ -/********** Get parameter with the code of an exam announcement **************/ +/********************* Get parameter with code of exam ***********************/ /*****************************************************************************/ -static long Exa_GetParamExaCod (void) +long Exa_GetParamExamCod (void) { - /* Get notice code */ + /***** Get code of exam *****/ return Par_GetParToLong ("ExaCod"); } /*****************************************************************************/ -/************ Get summary and content about an exam announcement *************/ +/******************* Get parameters used to edit an exam **********************/ /*****************************************************************************/ -void Exa_GetSummaryAndContentExamAnnouncement (char SummaryStr[Ntf_MAX_BYTES_SUMMARY + 1], - char **ContentStr, - long ExaCod,bool GetContent) +long Exa_GetParams (struct Exa_Exams *Exams) { - extern const char *Txt_hours_ABBREVIATION; - struct Exa_ExamAnnouncements ExamAnns; - char CrsNameAndDate[Hie_MAX_BYTES_FULL_NAME + (2 + Cns_MAX_BYTES_DATE + 7) + 1]; - char StrExamDate[Cns_MAX_BYTES_DATE + 1]; + /***** Get other parameters *****/ + Exams->SelectedOrder = Exa_GetParamOrder (); + Exams->CurrentPage = Pag_GetParamPagNum (Pag_EXAMS); - /***** Reset exam announcements context *****/ - Exa_ResetExamAnnouncements (&ExamAnns); - - /***** Initializations *****/ - SummaryStr[0] = '\0'; // Return nothing on error - - /***** Allocate memory for the exam announcement *****/ - Exa_AllocMemExamAnnouncement (&ExamAnns); - - /***** Get data of an exam announcement from database *****/ - Exa_GetDataExamAnnouncementFromDB (&ExamAnns,ExaCod); - - /***** Content *****/ - if (GetContent) - Exa_GetNotifContentExamAnnouncement (&ExamAnns,ContentStr); - - /***** Summary *****/ - /* Name of the course and date of exam */ - Dat_ConvDateToDateStr (&ExamAnns.ExamAnn.ExamDate,StrExamDate); - snprintf (CrsNameAndDate,sizeof (CrsNameAndDate), - "%s, %s, %2u:%02u", - ExamAnns.ExamAnn.CrsFullName, - StrExamDate, - ExamAnns.ExamAnn.StartTime.Hour, - ExamAnns.ExamAnn.StartTime.Minute); - Str_Copy (SummaryStr,CrsNameAndDate, - Ntf_MAX_BYTES_SUMMARY); - - /***** Free memory of the exam announcement *****/ - Exa_FreeMemExamAnnouncement (&ExamAnns); + /***** Get exam code *****/ + return Exa_GetParamExamCod (); } /*****************************************************************************/ -/************ Show a form with the data of an exam announcement **************/ +/****** Put a hidden parameter with the type of order in list of exams *******/ /*****************************************************************************/ -static void Exa_GetNotifContentExamAnnouncement (const struct Exa_ExamAnnouncements *ExamAnns, - char **ContentStr) +static void Exa_PutHiddenParamOrder (Exa_Order_t SelectedOrder) { - extern const char *Txt_Institution; - extern const char *Txt_Degree; - extern const char *Txt_YEAR_OF_DEGREE[1 + Deg_MAX_YEARS_PER_DEGREE]; - extern const char *Txt_EXAM_ANNOUNCEMENT_Course; - extern const char *Txt_EXAM_ANNOUNCEMENT_Year_or_semester; - extern const char *Txt_EXAM_ANNOUNCEMENT_Session; - extern const char *Txt_EXAM_ANNOUNCEMENT_Exam_date; - extern const char *Txt_EXAM_ANNOUNCEMENT_Start_time; - extern const char *Txt_EXAM_ANNOUNCEMENT_Approximate_duration; - extern const char *Txt_EXAM_ANNOUNCEMENT_Place_of_exam; - extern const char *Txt_EXAM_ANNOUNCEMENT_Mode; - extern const char *Txt_EXAM_ANNOUNCEMENT_Structure_of_the_exam; - extern const char *Txt_EXAM_ANNOUNCEMENT_Documentation_required; - extern const char *Txt_EXAM_ANNOUNCEMENT_Material_required; - extern const char *Txt_EXAM_ANNOUNCEMENT_Material_allowed; - extern const char *Txt_EXAM_ANNOUNCEMENT_Other_information; - extern const char *Txt_hours_ABBREVIATION; - struct Course Crs; - struct Degree Deg; - struct Instit Ins; - char StrExamDate[Cns_MAX_BYTES_DATE + 1]; + if (SelectedOrder != Exa_ORDER_DEFAULT) + Par_PutHiddenParamUnsigned (NULL,"Order",(unsigned) SelectedOrder); + } - /***** Get data of course *****/ - Crs.CrsCod = ExamAnns->ExamAnn.CrsCod; - Crs_GetDataOfCourseByCod (&Crs); +/*****************************************************************************/ +/********** Get parameter with the type or order in list of exams ************/ +/*****************************************************************************/ - /***** Get data of degree *****/ - Deg.DegCod = Crs.DegCod; - Deg_GetDataOfDegreeByCod (&Deg); +static Exa_Order_t Exa_GetParamOrder (void) + { + return (Exa_Order_t) Par_GetParToUnsignedLong ("Order", + 0, + Exa_NUM_ORDERS - 1, + (unsigned long) Exa_ORDER_DEFAULT); + } - /***** Get data of institution *****/ - Ins.InsCod = Deg_GetInsCodOfDegreeByCod (Deg.DegCod); - Ins_GetDataOfInstitutionByCod (&Ins); +/*****************************************************************************/ +/*********************** Get list of all the exams *************************/ +/*****************************************************************************/ - /***** Convert struct date to a date string *****/ - Dat_ConvDateToDateStr (&ExamAnns->ExamAnn.ExamDate,StrExamDate); +void Exa_GetListExams (struct Exa_Exams *Exams,Exa_Order_t SelectedOrder) + { + static const char *OrderBySubQuery[Exa_NUM_ORDERS] = + { + [Exa_ORDER_BY_START_DATE] = "StartTime DESC,EndTime DESC,exa_exams.Title DESC", + [Exa_ORDER_BY_END_DATE ] = "EndTime DESC,StartTime DESC,exa_exams.Title DESC", + [Exa_ORDER_BY_TITLE ] = "exa_exams.Title", + }; + MYSQL_RES *mysql_res; + MYSQL_ROW row; + char *HiddenSubQuery; + unsigned long NumRows = 0; // Initialized to avoid warning + unsigned NumExam; - /***** Fill content string *****/ - if (asprintf (ContentStr,"%s: %s
" - "%s: %s
" - "%s: %s
" - "%s: %s
" - "%s: %s
" - "%s: %s
" - "%s: %2u:%02u %s
" - "%s: %2u:%02u %s
" - "%s: %s
" - "%s: %s
" - "%s: %s
" - "%s: %s
" - "%s: %s
" - "%s: %s
" - "%s: %s", - Txt_Institution,Ins.FullName, - Txt_Degree,Deg.FullName, - Txt_EXAM_ANNOUNCEMENT_Course,ExamAnns->ExamAnn.CrsFullName, - Txt_EXAM_ANNOUNCEMENT_Year_or_semester,Txt_YEAR_OF_DEGREE[ExamAnns->ExamAnn.Year], - Txt_EXAM_ANNOUNCEMENT_Session,ExamAnns->ExamAnn.Session, - Txt_EXAM_ANNOUNCEMENT_Exam_date,StrExamDate, - Txt_EXAM_ANNOUNCEMENT_Start_time,ExamAnns->ExamAnn.StartTime.Hour, - ExamAnns->ExamAnn.StartTime.Minute, - Txt_hours_ABBREVIATION, - Txt_EXAM_ANNOUNCEMENT_Approximate_duration,ExamAnns->ExamAnn.Duration.Hour, - ExamAnns->ExamAnn.Duration.Minute, - Txt_hours_ABBREVIATION, - Txt_EXAM_ANNOUNCEMENT_Place_of_exam,ExamAnns->ExamAnn.Place, - Txt_EXAM_ANNOUNCEMENT_Mode,ExamAnns->ExamAnn.Mode, - Txt_EXAM_ANNOUNCEMENT_Structure_of_the_exam,ExamAnns->ExamAnn.Structure, - Txt_EXAM_ANNOUNCEMENT_Documentation_required,ExamAnns->ExamAnn.DocRequired, - Txt_EXAM_ANNOUNCEMENT_Material_required,ExamAnns->ExamAnn.MatRequired, - Txt_EXAM_ANNOUNCEMENT_Material_allowed,ExamAnns->ExamAnn.MatAllowed, - Txt_EXAM_ANNOUNCEMENT_Other_information,ExamAnns->ExamAnn.OtherInfo) < 0) + /***** Free list of exams *****/ + if (Exams->LstIsRead) + Exa_FreeListExams (Exams); + + /***** Subquery: get hidden exams depending on user's role *****/ + switch (Gbl.Usrs.Me.Role.Logged) + { + case Rol_STD: + if (asprintf (&HiddenSubQuery," AND exa_exams.Hidden='N'") < 0) + Lay_NotEnoughMemoryExit (); + break; + case Rol_NET: + case Rol_TCH: + case Rol_DEG_ADM: + case Rol_CTR_ADM: + case Rol_INS_ADM: + case Rol_SYS_ADM: + if (asprintf (&HiddenSubQuery,"%s","") < 0) + Lay_NotEnoughMemoryExit (); + break; + default: + Rol_WrongRoleExit (); + break; + } + + /***** Get list of exams from database *****/ + NumRows = DB_QuerySELECT (&mysql_res,"can not get exams", + "SELECT exa_exams.ExaCod," // row[0] + "MIN(exa_events.StartTime) AS StartTime," // row[1] + "MAX(exa_events.EndTime) AS EndTime" // row[2] + " FROM exa_exams" + " LEFT JOIN exa_events" + " ON exa_exams.ExaCod=exa_events.ExaCod" + " WHERE exa_exams.CrsCod=%ld" + "%s" + " GROUP BY exa_exams.ExaCod" + " ORDER BY %s", + Gbl.Hierarchy.Crs.CrsCod, + HiddenSubQuery, + OrderBySubQuery[SelectedOrder]); + + /***** Free allocated memory for subquery *****/ + free (HiddenSubQuery); + + if (NumRows) // Exams found... + { + Exams->Num = (unsigned) NumRows; + + /***** Create list of exams *****/ + if ((Exams->Lst = (struct Exa_ExamSelected *) malloc (NumRows * sizeof (struct Exa_ExamSelected))) == NULL) + Lay_NotEnoughMemoryExit (); + + /***** Get the exams codes *****/ + for (NumExam = 0; + NumExam < Exams->Num; + NumExam++) + { + /* Get next exam code (row[0]) */ + row = mysql_fetch_row (mysql_res); + if ((Exams->Lst[NumExam].ExaCod = Str_ConvertStrCodToLongCod (row[0])) <= 0) + Lay_ShowErrorAndExit ("Error: wrong exam code."); + } + } + else + Exams->Num = 0; + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + Exams->LstIsRead = true; + } + +/*****************************************************************************/ +/********************* Get list of exam events selected **********************/ +/*****************************************************************************/ + +void Exa_GetListSelectedExaCods (struct Exa_Exams *Exams) + { + unsigned MaxSizeListExaCodsSelected; + unsigned NumExam; + const char *Ptr; + long ExaCod; + char LongStr[Cns_MAX_DECIMAL_DIGITS_LONG + 1]; + + /***** Allocate memory for list of exams selected *****/ + MaxSizeListExaCodsSelected = Exams->Num * (Cns_MAX_DECIMAL_DIGITS_LONG + 1); + if ((Exams->ExaCodsSelected = (char *) malloc (MaxSizeListExaCodsSelected + 1)) == NULL) Lay_NotEnoughMemoryExit (); + + /***** Get parameter multiple with list of exams selected *****/ + Par_GetParMultiToText ("ExaCod",Exams->ExaCodsSelected,MaxSizeListExaCodsSelected); + + /***** Set which exams will be shown as selected (checkboxes on) *****/ + if (Exams->ExaCodsSelected[0]) // Some exams selected + { + /* Reset selection */ + for (NumExam = 0; + NumExam < Exams->Num; + NumExam++) + Exams->Lst[NumExam].Selected = false; + Exams->NumSelected = 0; + + /* Set some exams as selected */ + for (Ptr = Exams->ExaCodsSelected; + *Ptr; + ) + { + /* Get next exam selected */ + Par_GetNextStrUntilSeparParamMult (&Ptr,LongStr,Cns_MAX_DECIMAL_DIGITS_LONG); + ExaCod = Str_ConvertStrCodToLongCod (LongStr); + + /* Set each exam in *StrExaCodsSelected as selected */ + for (NumExam = 0; + NumExam < Exams->Num; + NumExam++) + if (Exams->Lst[NumExam].ExaCod == ExaCod) + { + Exams->Lst[NumExam].Selected = true; + Exams->NumSelected++; + break; + } + } + } + else // No exams selected + { + /***** Set all exams as selected *****/ + for (NumExam = 0; + NumExam < Exams->Num; + NumExam++) + Exams->Lst[NumExam].Selected = true; + Exams->NumSelected = Exams->Num; + } + } + +/*****************************************************************************/ +/********************** Get exam data using its code *************************/ +/*****************************************************************************/ + +void Exa_GetDataOfExamByCod (struct Exa_Exam *Exam) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned long NumRows; + + /***** Get data of exam from database *****/ + NumRows = DB_QuerySELECT (&mysql_res,"can not get exam data", + "SELECT exa_exams.ExaCod," // row[0] + "exa_exams.CrsCod," // row[1] + "exa_exams.Hidden," // row[2] + "exa_exams.UsrCod," // row[3] + "exa_exams.MaxGrade," // row[4] + "exa_exams.Visibility," // row[5] + "exa_exams.Title" // row[6] + " FROM exa_exams" + " LEFT JOIN exa_events" + " ON exa_exams.ExaCod=exa_events.ExaCod" + " WHERE exa_exams.ExaCod=%ld", + Exam->ExaCod); + if (NumRows) // Exam found... + { + /* Get row */ + row = mysql_fetch_row (mysql_res); + + /* Get code of the exam (row[0]) */ + Exam->ExaCod = Str_ConvertStrCodToLongCod (row[0]); + + /* Get code of the course (row[1]) */ + Exam->CrsCod = Str_ConvertStrCodToLongCod (row[1]); + + /* Get whether the exam is hidden (row[2]) */ + Exam->Hidden = (row[2][0] == 'Y'); + + /* Get author of the exam (row[3]) */ + Exam->UsrCod = Str_ConvertStrCodToLongCod (row[3]); + + /* Get maximum grade (row[4]) */ + Exam->MaxGrade = Str_GetDoubleFromStr (row[4]); + if (Exam->MaxGrade < 0.0) // Only positive values allowed + Exam->MaxGrade = 0.0; + + /* Get visibility (row[5]) */ + Exam->Visibility = TstVis_GetVisibilityFromStr (row[5]); + + /* Get the title of the exam (row[6]) */ + Str_Copy (Exam->Title,row[6], + Exa_MAX_BYTES_TITLE); + + /* Get number of questions */ + Exam->NumQsts = Exa_GetNumQstsExam (Exam->ExaCod); + + /* Get number of matches */ + Exam->NumEves = ExaEve_GetNumEvesInExam (Exam->ExaCod); + + /* Get number of unfinished matches */ + Exam->NumUnfinishedEves = ExaEvt_GetNumUnfinishedEvtsInExam (Exam->ExaCod); + } + else + /* Initialize to empty exam */ + Exa_ResetExam (Exam); + + /* Free structure that stores the query result */ + DB_FreeMySQLResult (&mysql_res); + + if (Exam->ExaCod > 0) + { + /***** Get start and end times from database *****/ + NumRows = DB_QuerySELECT (&mysql_res,"can not get exam data", + "SELECT UNIX_TIMESTAMP(MIN(StartTime))," // row[0] + "UNIX_TIMESTAMP(MAX(EndTime))" // row[1] + " FROM exa_events" + " WHERE ExaCod=%ld", + Exam->ExaCod); + if (NumRows) + { + /* Get row */ + row = mysql_fetch_row (mysql_res); + + /* Get start date (row[0] holds the start UTC time) */ + Exam->TimeUTC[Dat_START_TIME] = Dat_GetUNIXTimeFromStr (row[0]); + + /* Get end date (row[1] holds the end UTC time) */ + Exam->TimeUTC[Dat_END_TIME ] = Dat_GetUNIXTimeFromStr (row[1]); + } + + /* Free structure that stores the query result */ + DB_FreeMySQLResult (&mysql_res); + } + else + { + Exam->TimeUTC[Dat_START_TIME] = + Exam->TimeUTC[Dat_END_TIME ] = (time_t) 0; + } + } + +/*****************************************************************************/ +/*************************** Initialize exam to empty ************************/ +/*****************************************************************************/ + +static void Exa_ResetExam (struct Exa_Exam *Exam) + { + /***** Initialize to empty exam *****/ + Exam->ExaCod = -1L; + Exam->CrsCod = -1L; + Exam->UsrCod = -1L; + Exam->MaxGrade = Exa_MAX_GRADE_DEFAULT; + Exam->Visibility = TstVis_VISIBILITY_DEFAULT; + Exam->TimeUTC[Dat_START_TIME] = (time_t) 0; + Exam->TimeUTC[Dat_END_TIME ] = (time_t) 0; + Exam->Title[0] = '\0'; + Exam->NumQsts = 0; + Exam->NumEves = 0; + Exam->NumUnfinishedEves = 0; + Exam->Hidden = false; + } + +/*****************************************************************************/ +/***************************** Free list of exams ****************************/ +/*****************************************************************************/ + +void Exa_FreeListExams (struct Exa_Exams *Exams) + { + if (Exams->LstIsRead && Exams->Lst) + { + /***** Free memory used by the list of exams *****/ + free (Exams->Lst); + Exams->Lst = NULL; + Exams->Num = 0; + Exams->LstIsRead = false; + } + } + +/*****************************************************************************/ +/********************** Get exam text from database ************************/ +/*****************************************************************************/ + +static void Exa_GetExamTxtFromDB (long ExaCod,char Txt[Cns_MAX_BYTES_TEXT + 1]) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned long NumRows; + + /***** Get text of exam from database *****/ + NumRows = DB_QuerySELECT (&mysql_res,"can not get exam text", + "SELECT Txt FROM exa_exams WHERE ExaCod=%ld", + ExaCod); + + /***** The result of the query must have one row or none *****/ + if (NumRows == 1) + { + /* Get info text */ + row = mysql_fetch_row (mysql_res); + Str_Copy (Txt,row[0], + Cns_MAX_BYTES_TEXT); + } + else + Txt[0] = '\0'; + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + if (NumRows > 1) + Lay_ShowErrorAndExit ("Error when getting exam text."); + } + +/*****************************************************************************/ +/*************** Ask for confirmation of removing of an exam ******************/ +/*****************************************************************************/ + +void Exa_AskRemExam (void) + { + extern const char *Txt_Do_you_really_want_to_remove_the_exam_X; + extern const char *Txt_Remove_exam; + struct Exa_Exams Exams; + struct Exa_Exam Exam; + + /***** Reset exams *****/ + Exa_ResetExams (&Exams); + + /***** Get parameters *****/ + if ((Exam.ExaCod = Exa_GetParams (&Exams)) <= 0) + Lay_ShowErrorAndExit ("Code of exam is missing."); + + /***** Get data of the exam from database *****/ + Exa_GetDataOfExamByCod (&Exam); + if (!Exa_CheckIfICanEditExams ()) + Lay_NoPermissionExit (); + + /***** Show question and button to remove exam *****/ + Exams.ExaCod = Exam.ExaCod; + Ale_ShowAlertAndButton (ActRemExa,NULL,NULL, + Exa_PutParams,&Exams, + Btn_REMOVE_BUTTON,Txt_Remove_exam, + Ale_QUESTION,Txt_Do_you_really_want_to_remove_the_exam_X, + Exam.Title); + + /***** Show exams again *****/ + Exa_ListAllExams (&Exams); + } + +/*****************************************************************************/ +/******************************* Remove an exam *******************************/ +/*****************************************************************************/ + +void Exa_RemoveExam (void) + { + extern const char *Txt_Exam_X_removed; + struct Exa_Exams Exams; + struct Exa_Exam Exam; + + /***** Reset exams *****/ + Exa_ResetExams (&Exams); + + /***** Get exam code *****/ + if ((Exam.ExaCod = Exa_GetParamExamCod ()) == -1L) + Lay_ShowErrorAndExit ("Code of exam is missing."); + + /***** Get data of the exam from database *****/ + Exa_GetDataOfExamByCod (&Exam); + if (!Exa_CheckIfICanEditExams ()) + Lay_NoPermissionExit (); + + /***** Remove exam from all tables *****/ + Exa_RemoveExamFromAllTables (Exam.ExaCod); + + /***** Write message to show the change made *****/ + Ale_ShowAlert (Ale_SUCCESS,Txt_Exam_X_removed, + Exam.Title); + + /***** Show exams again *****/ + Exa_ListAllExams (&Exams); + } + +/*****************************************************************************/ +/*********************** Remove exam from all tables *************************/ +/*****************************************************************************/ + +static void Exa_RemoveExamFromAllTables (long ExaCod) + { + /***** Remove all matches in this exam *****/ + ExaEvt_RemoveEventsInExamFromAllTables (ExaCod); + + /***** Remove exam question *****/ + DB_QueryDELETE ("can not remove exam questions", + "DELETE FROM exa_questions WHERE ExaCod=%ld", + ExaCod); + + /***** Remove exam *****/ + DB_QueryDELETE ("can not remove exam", + "DELETE FROM exa_exams WHERE ExaCod=%ld", + ExaCod); + } + +/*****************************************************************************/ +/******************** Remove all the exams of a course ***********************/ +/*****************************************************************************/ + +void Exa_RemoveExamsCrs (long CrsCod) + { + /***** Remove all matches in this course *****/ + ExaEvt_RemoveEventInCourseFromAllTables (CrsCod); + + /***** Remove the questions in exams *****/ + DB_QueryDELETE ("can not remove questions in course exams", + "DELETE FROM exa_questions" + " USING exa_exams,exa_questions" + " WHERE exa_exams.CrsCod=%ld" + " AND exa_exams.ExaCod=exa_questions.ExaCod", + CrsCod); + + /***** Remove the exams *****/ + DB_QueryDELETE ("can not remove course exams", + "DELETE FROM exa_exams" + " WHERE CrsCod=%ld", + CrsCod); + } + +/*****************************************************************************/ +/******************************** Hide an exam ******************************/ +/*****************************************************************************/ + +void Exa_HideExam (void) + { + struct Exa_Exams Exams; + struct Exa_Exam Exam; + + /***** Reset exams *****/ + Exa_ResetExams (&Exams); + + /***** Get parameters *****/ + if ((Exam.ExaCod = Exa_GetParams (&Exams)) <= 0) + Lay_ShowErrorAndExit ("Code of exam is missing."); + + /***** Get data of the exam from database *****/ + Exa_GetDataOfExamByCod (&Exam); + if (!Exa_CheckIfICanEditExams ()) + Lay_NoPermissionExit (); + + /***** Hide exam *****/ + DB_QueryUPDATE ("can not hide exam", + "UPDATE exa_exams SET Hidden='Y' WHERE ExaCod=%ld", + Exam.ExaCod); + + /***** Show exams again *****/ + Exa_ListAllExams (&Exams); + } + +/*****************************************************************************/ +/******************************** Show an exam ******************************/ +/*****************************************************************************/ + +void Exa_UnhideExam (void) + { + struct Exa_Exams Exams; + struct Exa_Exam Exam; + + /***** Reset exams *****/ + Exa_ResetExams (&Exams); + + /***** Get parameters *****/ + if ((Exam.ExaCod = Exa_GetParams (&Exams)) <= 0) + Lay_ShowErrorAndExit ("Code of exam is missing."); + + /***** Get data of the exam from database *****/ + Exa_GetDataOfExamByCod (&Exam); + if (!Exa_CheckIfICanEditExams ()) + Lay_NoPermissionExit (); + + /***** Show exam *****/ + DB_QueryUPDATE ("can not show exam", + "UPDATE exa_exams SET Hidden='N' WHERE ExaCod=%ld", + Exam.ExaCod); + + /***** Show exams again *****/ + Exa_ListAllExams (&Exams); + } + +/*****************************************************************************/ +/******************* Check if the title of an exam exists *******************/ +/*****************************************************************************/ + +static bool Exa_CheckIfSimilarExamExists (const struct Exa_Exam *Exam) + { + /***** Get number of exams with a field value from database *****/ + return (DB_QueryCOUNT ("can not get similar exams", + "SELECT COUNT(*) FROM exa_exams" + " WHERE CrsCod=%ld AND Title='%s'" + " AND ExaCod<>%ld", + Gbl.Hierarchy.Crs.CrsCod,Exam->Title, + Exam->ExaCod) != 0); + } + +/*****************************************************************************/ +/**************** Request the creation or edition of an exam ******************/ +/*****************************************************************************/ + +void Exa_RequestCreatOrEditExam (void) + { + struct Exa_Exams Exams; + struct Exa_Exam Exam; + bool ItsANewExam; + char Txt[Cns_MAX_BYTES_TEXT + 1]; + + /***** Reset exams *****/ + Exa_ResetExams (&Exams); + + /***** Check if I can edit exams *****/ + if (!Exa_CheckIfICanEditExams ()) + Lay_NoPermissionExit (); + + /***** Get parameters *****/ + ItsANewExam = ((Exam.ExaCod = Exa_GetParams (&Exams)) <= 0); + + /***** Get exam data *****/ + if (ItsANewExam) + { + /* Initialize to empty exam */ + Exa_ResetExam (&Exam); + Txt[0] = '\0'; + } + else + { + /* Get exam data from database */ + Exa_GetDataOfExamByCod (&Exam); + Exa_GetExamTxtFromDB (Exam.ExaCod,Txt); + } + + /***** Put forms to create/edit an exam *****/ + Exa_PutFormsEditionExam (&Exams,&Exam,Txt,ItsANewExam); + + /***** Show exams or questions *****/ + if (ItsANewExam) + /* Show exams again */ + Exa_ListAllExams (&Exams); + else + /* Show questions of the exam ready to be edited */ + Exa_ListExamQuestions (&Exams,&Exam); + + } + +/*****************************************************************************/ +/********************* Put a form to create/edit an exam **********************/ +/*****************************************************************************/ + +static void Exa_PutFormsEditionExam (struct Exa_Exams *Exams, + struct Exa_Exam *Exam, + char Txt[Cns_MAX_BYTES_TEXT + 1], + bool ItsANewExam) + { + extern const char *Hlp_ASSESSMENT_Exams_new_exam; + extern const char *Hlp_ASSESSMENT_Exams_edit_exam; + extern const char *The_ClassFormInBox[The_NUM_THEMES]; + extern const char *Txt_New_exam; + extern const char *Txt_Edit_exam; + extern const char *Txt_Title; + extern const char *Txt_Maximum_grade; + extern const char *Txt_Result_visibility; + extern const char *Txt_Description; + extern const char *Txt_Create_exam; + extern const char *Txt_Save_changes; + + /***** Begin form *****/ + Exams->ExaCod = Exam->ExaCod; + Frm_StartForm (ItsANewExam ? ActNewExa : + ActChgExa); + Exa_PutParams (Exams); + + /***** Begin box and table *****/ + if (ItsANewExam) + Box_BoxTableBegin (NULL,Txt_New_exam, + NULL,NULL, + Hlp_ASSESSMENT_Exams_new_exam,Box_NOT_CLOSABLE,2); + else + Box_BoxTableBegin (NULL, + Exam->Title[0] ? Exam->Title : + Txt_Edit_exam, + NULL,NULL, + Hlp_ASSESSMENT_Exams_edit_exam,Box_NOT_CLOSABLE,2); + + /***** Exam title *****/ + HTM_TR_Begin (NULL); + + /* Label */ + Frm_LabelColumn ("RT","Title",Txt_Title); + + /* Data */ + HTM_TD_Begin ("class=\"LT\""); + HTM_INPUT_TEXT ("Title",Exa_MAX_CHARS_TITLE,Exam->Title,false, + "id=\"Title\" required=\"required\"" + " class=\"TITLE_DESCRIPTION_WIDTH\""); + HTM_TD_End (); + + HTM_TR_End (); + + /***** Maximum grade *****/ + HTM_TR_Begin (NULL); + + HTM_TD_Begin ("class=\"%s RM\"",The_ClassFormInBox[Gbl.Prefs.Theme]); + HTM_TxtF ("%s:",Txt_Maximum_grade); + HTM_TD_End (); + + HTM_TD_Begin ("class=\"LM\""); + HTM_INPUT_FLOAT ("MaxGrade",0.0,DBL_MAX,0.01,Exam->MaxGrade,false, + "required=\"required\""); + HTM_TD_End (); + + HTM_TR_End (); + + /***** Visibility of results *****/ + HTM_TR_Begin (NULL); + + HTM_TD_Begin ("class=\"%s RT\"",The_ClassFormInBox[Gbl.Prefs.Theme]); + HTM_TxtF ("%s:",Txt_Result_visibility); + HTM_TD_End (); + + HTM_TD_Begin ("class=\"LB\""); + TstVis_PutVisibilityCheckboxes (Exam->Visibility); + HTM_TD_End (); + + HTM_TR_End (); + + /***** Exam text *****/ + HTM_TR_Begin (NULL); + + /* Label */ + Frm_LabelColumn ("RT","Txt",Txt_Description); + + /* Data */ + HTM_TD_Begin ("class=\"LT\""); + HTM_TEXTAREA_Begin ("id=\"Txt\" name=\"Txt\" rows=\"5\"" + " class=\"TITLE_DESCRIPTION_WIDTH\""); + HTM_Txt (Txt); + HTM_TEXTAREA_End (); + HTM_TD_End (); + + HTM_TR_End (); + + /***** End table, send button and end box *****/ + if (ItsANewExam) + Box_BoxTableWithButtonEnd (Btn_CREATE_BUTTON,Txt_Create_exam); + else + Box_BoxTableWithButtonEnd (Btn_CONFIRM_BUTTON,Txt_Save_changes); + + /***** End form *****/ + Frm_EndForm (); + } + +/*****************************************************************************/ +/********************** Receive form to create a new exam ********************/ +/*****************************************************************************/ + +void Exa_RecFormExam (void) + { + struct Exa_Exams Exams; + struct Exa_Exam Exam; + bool ItsANewExam; + char Txt[Cns_MAX_BYTES_TEXT + 1]; + + /***** Reset exams *****/ + Exa_ResetExams (&Exams); + + /***** Check if I can edit exams *****/ + if (!Exa_CheckIfICanEditExams ()) + Lay_NoPermissionExit (); + + /***** Get parameters *****/ + ItsANewExam = ((Exam.ExaCod = Exa_GetParams (&Exams)) <= 0); + + /***** If I can edit exams ==> receive exam from form *****/ + if (Exa_CheckIfICanEditExams ()) + { + Exa_ReceiveExamFieldsFromForm (&Exam,Txt); + if (Exa_CheckExamFieldsReceivedFromForm (&Exam)) + { + /***** Create a new exam or update an existing one *****/ + if (ItsANewExam) + Exa_CreateExam (&Exam,Txt); // Add new exam to database + else + Exa_UpdateExam (&Exam,Txt); // Update exam data in database + + /***** Put forms to edit the exam created or updated *****/ + Exa_PutFormsEditionExam (&Exams,&Exam,Txt, + false); // No new exam + + /***** Show questions of the exam ready to be edited ******/ + Exa_ListExamQuestions (&Exams,&Exam); + } + else + { + /***** Put forms to create/edit the exam *****/ + Exa_PutFormsEditionExam (&Exams,&Exam,Txt,ItsANewExam); + + /***** Show exams or questions *****/ + if (ItsANewExam) + /* Show exams again */ + Exa_ListAllExams (&Exams); + else + /* Show questions of the exam ready to be edited */ + Exa_ListExamQuestions (&Exams,&Exam); + } + } + else + Lay_NoPermissionExit (); + } + +static void Exa_ReceiveExamFieldsFromForm (struct Exa_Exam *Exam, + char Txt[Cns_MAX_BYTES_TEXT + 1]) + { + char MaxGradeStr[64]; + + /***** Get exam title *****/ + Par_GetParToText ("Title",Exam->Title,Exa_MAX_BYTES_TITLE); + + /***** Get maximum grade *****/ + Par_GetParToText ("MaxGrade",MaxGradeStr,sizeof (MaxGradeStr) - 1); + Exam->MaxGrade = Str_GetDoubleFromStr (MaxGradeStr); + if (Exam->MaxGrade < 0.0) // Only positive values allowed + Exam->MaxGrade = 0.0; + + /***** Get visibility *****/ + Exam->Visibility = TstVis_GetVisibilityFromForm (); + + /***** Get exam text *****/ + Par_GetParToHTML ("Txt",Txt,Cns_MAX_BYTES_TEXT); // Store in HTML format (not rigorous) + } + +static bool Exa_CheckExamFieldsReceivedFromForm (const struct Exa_Exam *Exam) + { + extern const char *Txt_Already_existed_an_exam_with_the_title_X; + extern const char *Txt_You_must_specify_the_title_of_the_exam; + bool NewExamIsCorrect; + + /***** Check if title is correct *****/ + NewExamIsCorrect = true; + if (Exam->Title[0]) // If there's an exam title + { + /* If title of exam was in database... */ + if (Exa_CheckIfSimilarExamExists (Exam)) + { + NewExamIsCorrect = false; + Ale_ShowAlert (Ale_WARNING,Txt_Already_existed_an_exam_with_the_title_X, + Exam->Title); + } + } + else // If there is not an exam title + { + NewExamIsCorrect = false; + Ale_ShowAlert (Ale_WARNING,Txt_You_must_specify_the_title_of_the_exam); + } + + return NewExamIsCorrect; + } + +/*****************************************************************************/ +/**************************** Create a new exam ******************************/ +/*****************************************************************************/ + +static void Exa_CreateExam (struct Exa_Exam *Exam,const char *Txt) + { + extern const char *Txt_Created_new_exam_X; + + /***** Create a new exam *****/ + Str_SetDecimalPointToUS (); // To write the decimal point as a dot + Exam->ExaCod = + DB_QueryINSERTandReturnCode ("can not create new exam", + "INSERT INTO exa_exams" + " (CrsCod,Hidden,UsrCod,MaxGrade,Visibility,Title,Txt)" + " VALUES" + " (%ld,'N',%ld,%.15lg,%u,'%s','%s')", + Gbl.Hierarchy.Crs.CrsCod, + Gbl.Usrs.Me.UsrDat.UsrCod, + Exam->MaxGrade, + Exam->Visibility, + Exam->Title, + Txt); + Str_SetDecimalPointToLocal (); // Return to local system + + /***** Write success message *****/ + Ale_ShowAlert (Ale_SUCCESS,Txt_Created_new_exam_X, + Exam->Title); + } + +/*****************************************************************************/ +/************************* Update an existing exam *************************/ +/*****************************************************************************/ + +static void Exa_UpdateExam (struct Exa_Exam *Exam,const char *Txt) + { + extern const char *Txt_The_exam_has_been_modified; + + /***** Update the data of the exam *****/ + Str_SetDecimalPointToUS (); // To write the decimal point as a dot + DB_QueryUPDATE ("can not update exam", + "UPDATE exa_exams" + " SET CrsCod=%ld," + "MaxGrade=%.15lg," + "Visibility=%u," + "Title='%s'," + "Txt='%s'" + " WHERE ExaCod=%ld", + Gbl.Hierarchy.Crs.CrsCod, + Exam->MaxGrade, + Exam->Visibility, + Exam->Title, + Txt, + Exam->ExaCod); + Str_SetDecimalPointToLocal (); // Return to local system + + /***** Write success message *****/ + Ale_ShowAlert (Ale_SUCCESS,Txt_The_exam_has_been_modified); + } + +/*****************************************************************************/ +/******************* Get number of questions of an exam *********************/ +/*****************************************************************************/ + +unsigned Exa_GetNumQstsExam (long ExaCod) + { + /***** Get nuumber of questions in an exam from database *****/ + return + (unsigned) DB_QueryCOUNT ("can not get number of questions of an exam", + "SELECT COUNT(*) FROM exa_questions" + " WHERE ExaCod=%ld", + ExaCod); + } + +/*****************************************************************************/ +/*************** Put a form to edit/create a question in exam ****************/ +/*****************************************************************************/ + +void Exa_RequestNewQuestion (void) + { + struct Exa_Exams Exams; + struct Exa_Exam Exam; + + /***** Reset exams *****/ + Exa_ResetExams (&Exams); + + /***** Get parameters *****/ + if ((Exam.ExaCod = Exa_GetParams (&Exams)) <= 0) + Lay_ShowErrorAndExit ("Code of exam is missing."); + Exa_GetDataOfExamByCod (&Exam); + + /***** Check if exam has matches *****/ + if (Exa_CheckIfEditable (&Exam)) + { + /***** Show form to create a new question in this exam *****/ + Exams.ExaCod = Exam.ExaCod; + Tst_RequestSelectTestsForExam (&Exams); + } + else + Lay_NoPermissionExit (); + + /***** Show current exam *****/ + Exa_ShowOnlyOneExam (&Exams,&Exam, + true, // List exam questions + false); // Do not put form to start new match + } + +/*****************************************************************************/ +/**************** List several test questions for selection ******************/ +/*****************************************************************************/ + +void Exa_ListTstQuestionsToSelect (void) + { + struct Exa_Exams Exams; + struct Exa_Exam Exam; + + /***** Reset exams *****/ + Exa_ResetExams (&Exams); + + /***** Get parameters *****/ + if ((Exam.ExaCod = Exa_GetParams (&Exams)) <= 0) + Lay_ShowErrorAndExit ("Code of exam is missing."); + Exa_GetDataOfExamByCod (&Exam); + + /***** Check if exam has matches *****/ + if (Exa_CheckIfEditable (&Exam)) + { + /***** List several test questions for selection *****/ + Exams.ExaCod = Exam.ExaCod; + Tst_ListQuestionsToSelect (&Exams); + } + else + Lay_NoPermissionExit (); + } + +/*****************************************************************************/ +/****************** Write parameter with index of question *******************/ +/*****************************************************************************/ + +void Exa_PutParamQstInd (unsigned QstInd) + { + Par_PutHiddenParamUnsigned (NULL,"QstInd",QstInd); + } + +/*****************************************************************************/ +/******************* Get parameter with index of question ********************/ +/*****************************************************************************/ + +unsigned Exa_GetParamQstInd (void) + { + long QstInd; + + QstInd = Par_GetParToLong ("QstInd"); + if (QstInd < 0) + Lay_ShowErrorAndExit ("Wrong question index."); + + return (unsigned) QstInd; + } + +/*****************************************************************************/ +/********************** Remove answers of an exam question ********************/ +/*****************************************************************************/ + +static void Exa_RemAnswersOfAQuestion (long ExaCod,unsigned QstInd) + { + /***** Remove answers from all matches of this exam *****/ + DB_QueryDELETE ("can not remove the answers of a question", + "DELETE FROM exa_answers" + " USING exa_events,exa_answers" + " WHERE exa_events.ExaCod=%ld" // From all matches of this exam... + " AND exa_events.EvtCod=exa_answers.EvtCod" + " AND exa_answers.QstInd=%u", // ...remove only answers to this question + ExaCod,QstInd); + } + +/*****************************************************************************/ +/************ Get question code given exam and index of question *************/ +/*****************************************************************************/ + +long Exa_GetQstCodFromQstInd (long ExaCod,unsigned QstInd) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + long QstCod; + + /***** Get question code of thw question to be moved up *****/ + if (!DB_QuerySELECT (&mysql_res,"can not get question code", + "SELECT QstCod FROM exa_questions" + " WHERE ExaCod=%ld AND QstInd=%u", + ExaCod,QstInd)) + Lay_ShowErrorAndExit ("Error: wrong question index."); + + /***** Get question code (row[0]) *****/ + row = mysql_fetch_row (mysql_res); + if ((QstCod = Str_ConvertStrCodToLongCod (row[0])) <= 0) + Lay_ShowErrorAndExit ("Error: wrong question code."); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + return QstCod; + } + +/*****************************************************************************/ +/****************** Get maximum question index in an exam *********************/ +/*****************************************************************************/ +// Question index can be 1, 2, 3... +// Return 0 if no questions + +static unsigned Exa_GetMaxQuestionIndexInExam (long ExaCod) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned QstInd = 0; + + /***** Get maximum question index in an exam from database *****/ + DB_QuerySELECT (&mysql_res,"can not get last question index", + "SELECT MAX(QstInd)" + " FROM exa_questions" + " WHERE ExaCod=%ld", + ExaCod); + row = mysql_fetch_row (mysql_res); + if (row[0]) // There are questions + if (sscanf (row[0],"%u",&QstInd) != 1) + Lay_ShowErrorAndExit ("Error when getting last question index."); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + return QstInd; + } + +/*****************************************************************************/ +/*********** Get previous question index to a given index in an exam **********/ +/*****************************************************************************/ +// Input question index can be 1, 2, 3... n-1 +// Return question index will be 1, 2, 3... n if previous question exists, or 0 if no previous question + +unsigned Exa_GetPrevQuestionIndexInExam (long ExaCod,unsigned QstInd) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned PrevQstInd = 0; + + /***** Get previous question index in an exam from database *****/ + // Although indexes are always continuous... + // ...this implementation works even with non continuous indexes + if (!DB_QuerySELECT (&mysql_res,"can not get previous question index", + "SELECT MAX(QstInd) FROM exa_questions" + " WHERE ExaCod=%ld AND QstInd<%u", + ExaCod,QstInd)) + Lay_ShowErrorAndExit ("Error: previous question index not found."); + + /***** Get previous question index (row[0]) *****/ + row = mysql_fetch_row (mysql_res); + if (row) + if (row[0]) + if (sscanf (row[0],"%u",&PrevQstInd) != 1) + Lay_ShowErrorAndExit ("Error when getting previous question index."); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + return PrevQstInd; + } + +/*****************************************************************************/ +/************* Get next question index to a given index in an exam ************/ +/*****************************************************************************/ +// Input question index can be 0, 1, 2, 3... n-1 +// Return question index will be 1, 2, 3... n if next question exists, or 0 if no next question + +unsigned Exa_GetNextQuestionIndexInExam (long ExaCod,unsigned QstInd) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NextQstInd = ExaEvt_AFTER_LAST_QUESTION; // End of questions has been reached + + /***** Get next question index in an exam from database *****/ + // Although indexes are always continuous... + // ...this implementation works even with non continuous indexes + if (!DB_QuerySELECT (&mysql_res,"can not get next question index", + "SELECT MIN(QstInd) FROM exa_questions" + " WHERE ExaCod=%ld AND QstInd>%u", + ExaCod,QstInd)) + Lay_ShowErrorAndExit ("Error: next question index not found."); + + /***** Get next question index (row[0]) *****/ + row = mysql_fetch_row (mysql_res); + if (row) + if (row[0]) + if (sscanf (row[0],"%u",&NextQstInd) != 1) + Lay_ShowErrorAndExit ("Error when getting next question index."); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + return NextQstInd; + } + +/*****************************************************************************/ +/************************ List the questions of an exam ***********************/ +/*****************************************************************************/ + +static void Exa_ListExamQuestions (struct Exa_Exams *Exams,struct Exa_Exam *Exam) + { + extern const char *Hlp_ASSESSMENT_Exams_questions; + extern const char *Txt_Questions; + extern const char *Txt_This_exam_has_no_questions; + MYSQL_RES *mysql_res; + unsigned NumQsts; + bool ICanEditQuestions = Exa_CheckIfEditable (Exam); + + /***** Get data of questions from database *****/ + NumQsts = (unsigned) + DB_QuerySELECT (&mysql_res,"can not get exam questions", + "SELECT QstInd," // row[0] + "QstCod" // row[1] + " FROM exa_questions" + " WHERE ExaCod=%ld" + " ORDER BY QstInd", + Exam->ExaCod); + + /***** Begin box *****/ + Exams->ExaCod = Exam->ExaCod; + if (ICanEditQuestions) + Box_BoxBegin (NULL,Txt_Questions, + Exa_PutIconToAddNewQuestions,Exams, + Hlp_ASSESSMENT_Exams_questions,Box_NOT_CLOSABLE); + else + Box_BoxBegin (NULL,Txt_Questions, + NULL,NULL, + Hlp_ASSESSMENT_Exams_questions,Box_NOT_CLOSABLE); + + /***** Show table with questions *****/ + if (NumQsts) + Exa_ListOneOrMoreQuestionsForEdition (Exams, + Exam->ExaCod,NumQsts,mysql_res, + ICanEditQuestions); + else // This exam has no questions + Ale_ShowAlert (Ale_INFO,Txt_This_exam_has_no_questions); + + /***** Put button to add a new question in this exam *****/ + if (ICanEditQuestions) // I can edit questions + Exa_PutButtonToAddNewQuestions (Exams); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + /***** End box *****/ + Box_BoxEnd (); + } + +/*****************************************************************************/ +/********************* List exam questions for edition ***********************/ +/*****************************************************************************/ + +static void Exa_ListOneOrMoreQuestionsForEdition (struct Exa_Exams *Exams, + long ExaCod,unsigned NumQsts, + MYSQL_RES *mysql_res, + bool ICanEditQuestions) + { + extern const char *Txt_Questions; + extern const char *Txt_No_INDEX; + extern const char *Txt_Code; + extern const char *Txt_Tags; + extern const char *Txt_Question; + extern const char *Txt_Move_up_X; + extern const char *Txt_Move_down_X; + extern const char *Txt_Movement_not_allowed; + unsigned NumQst; + MYSQL_ROW row; + struct Tst_Question Question; + unsigned QstInd; + unsigned MaxQstInd; + char StrQstInd[Cns_MAX_DECIMAL_DIGITS_UINT + 1]; + bool QuestionExists; + + /***** Get maximum question index *****/ + MaxQstInd = Exa_GetMaxQuestionIndexInExam (ExaCod); + + /***** Write the heading *****/ + HTM_TABLE_BeginWideMarginPadding (2); + HTM_TR_Begin (NULL); + + HTM_TH_Empty (1); + + HTM_TH (1,1,"CT",Txt_No_INDEX); + HTM_TH (1,1,"CT",Txt_Code); + HTM_TH (1,1,"CT",Txt_Tags); + HTM_TH (1,1,"CT",Txt_Question); + + HTM_TR_End (); + + /***** Write rows *****/ + for (NumQst = 0; + NumQst < NumQsts; + NumQst++) + { + Gbl.RowEvenOdd = NumQst % 2; + + /***** Create test question *****/ + Tst_QstConstructor (&Question); + + /***** Get question data *****/ + row = mysql_fetch_row (mysql_res); + /* + row[0] QstInd + row[1] QstCod + */ + + /* Get question index (row[0]) */ + QstInd = Str_ConvertStrToUnsigned (row[0]); + snprintf (StrQstInd,sizeof (StrQstInd), + "%u", + QstInd); + + /* Get question code (row[1]) */ + Question.QstCod = Str_ConvertStrCodToLongCod (row[1]); + + /***** Icons *****/ + Exams->ExaCod = ExaCod; + Exams->QstInd = QstInd; + HTM_TR_Begin (NULL); + + HTM_TD_Begin ("class=\"BT%u\"",Gbl.RowEvenOdd); + + /* Put icon to remove the question */ + if (ICanEditQuestions) + { + Frm_StartForm (ActReqRemExaQst); + Exa_PutParams (Exams); + Exa_PutParamQstInd (QstInd); + Ico_PutIconRemove (); + Frm_EndForm (); + } + else + Ico_PutIconRemovalNotAllowed (); + + /* Put icon to move up the question */ + if (ICanEditQuestions && QstInd > 1) + { + Lay_PutContextualLinkOnlyIcon (ActUp_ExaQst,NULL, + Exa_PutParamsOneQst,Exams, + "arrow-up.svg", + Str_BuildStringStr (Txt_Move_up_X, + StrQstInd)); + Str_FreeString (); + } + else + Ico_PutIconOff ("arrow-up.svg",Txt_Movement_not_allowed); + + /* Put icon to move down the question */ + if (ICanEditQuestions && QstInd < MaxQstInd) + { + Lay_PutContextualLinkOnlyIcon (ActDwnExaQst,NULL, + Exa_PutParamsOneQst,Exams, + "arrow-down.svg", + Str_BuildStringStr (Txt_Move_down_X, + StrQstInd)); + Str_FreeString (); + } + else + Ico_PutIconOff ("arrow-down.svg",Txt_Movement_not_allowed); + + /* Put icon to edit the question */ + if (ICanEditQuestions) + Ico_PutContextualIconToEdit (ActEdiOneTstQst,NULL, + Tst_PutParamQstCod,&Question.QstCod); + + HTM_TD_End (); + + /***** Question *****/ + QuestionExists = Tst_GetQstDataFromDB (&Question); + Exa_ListQuestionForEdition (&Question,QstInd,QuestionExists); + + HTM_TR_End (); + + /***** Destroy test question *****/ + Tst_QstDestructor (&Question); + } + + /***** End table *****/ + HTM_TABLE_End (); + } + +/*****************************************************************************/ +/********************** List exam question for edition ***********************/ +/*****************************************************************************/ + +static void Exa_ListQuestionForEdition (const struct Tst_Question *Question, + unsigned QstInd,bool QuestionExists) + { + extern const char *Txt_Question_removed; + + /***** Number of question and answer type (row[1]) *****/ + HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd); + Tst_WriteNumQst (QstInd); + if (QuestionExists) + Tst_WriteAnswerType (Question->Answer.Type); + HTM_TD_End (); + + /***** Write question code *****/ + HTM_TD_Begin ("class=\"DAT_SMALL CT COLOR%u\"",Gbl.RowEvenOdd); + HTM_TxtF ("%ld ",Question->QstCod); + HTM_TD_End (); + + /***** Write the question tags *****/ + HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd); + if (QuestionExists) + Tst_GetAndWriteTagsQst (Question->QstCod); + HTM_TD_End (); + + /***** Write stem (row[3]) and media *****/ + HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd); + if (QuestionExists) + { + /* Write stem */ + Tst_WriteQstStem (Question->Stem,"TEST_EDI", + true); // Visible + + /* Show media */ + Med_ShowMedia (&Question->Media, + "TEST_MED_EDIT_LIST_STEM_CONTAINER", + "TEST_MED_EDIT_LIST_STEM"); + + /* Show feedback */ + Tst_WriteQstFeedback (Question->Feedback,"TEST_EDI_LIGHT"); + + /* Show answers */ + Tst_WriteAnswersListing (Question); + } + else + { + HTM_SPAN_Begin ("class=\"DAT_LIGHT\""); + HTM_Txt (Txt_Question_removed); + HTM_SPAN_End (); + } + HTM_TD_End (); + } + +/*****************************************************************************/ +/***************** Put icon to add a new questions to exam *******************/ +/*****************************************************************************/ + +static void Exa_PutIconToAddNewQuestions (void *Exams) + { + extern const char *Txt_Add_questions; + + /***** Put form to create a new question *****/ + Ico_PutContextualIconToAdd (ActAddOneExaQst,NULL, + Exa_PutParams,Exams, + Txt_Add_questions); + } + +/*****************************************************************************/ +/***************** Put button to add new questions to exam *******************/ +/*****************************************************************************/ + +static void Exa_PutButtonToAddNewQuestions (struct Exa_Exams *Exams) + { + extern const char *Txt_Add_questions; + + Frm_StartForm (ActAddOneExaQst); + Exa_PutParams (Exams); + Btn_PutConfirmButton (Txt_Add_questions); + Frm_EndForm (); + } + +/*****************************************************************************/ +/******************** Add selected test questions to exam ********************/ +/*****************************************************************************/ + +void Exa_AddTstQuestionsToExam (void) + { + extern const char *Txt_No_questions_have_been_added; + struct Exa_Exams Exams; + struct Exa_Exam Exam; + const char *Ptr; + char LongStr[Cns_MAX_DECIMAL_DIGITS_LONG + 1]; + long QstCod; + unsigned MaxQstInd; + + /***** Reset exams *****/ + Exa_ResetExams (&Exams); + + /***** Get parameters *****/ + if ((Exam.ExaCod = Exa_GetParams (&Exams)) <= 0) + Lay_ShowErrorAndExit ("Code of exam is missing."); + Exa_GetDataOfExamByCod (&Exam); + + /***** Check if exam has matches *****/ + if (Exa_CheckIfEditable (&Exam)) + { + /***** Get selected questions *****/ + /* Allocate space for selected question codes */ + Exa_AllocateListSelectedQuestions (&Exams); + + /* Get question codes */ + Par_GetParMultiToText ("QstCods",Exams.ListQuestions, + Exa_MAX_BYTES_LIST_SELECTED_QUESTIONS); + + /* Check number of questions */ + if (Exa_CountNumQuestionsInList (&Exams)) // If questions selected... + { + /***** Insert questions in database *****/ + Ptr = Exams.ListQuestions; + while (*Ptr) + { + /* Get next code */ + Par_GetNextStrUntilSeparParamMult (&Ptr,LongStr,Cns_MAX_DECIMAL_DIGITS_LONG); + if (sscanf (LongStr,"%ld",&QstCod) != 1) + Lay_ShowErrorAndExit ("Wrong question code."); + + /* Get current maximum index */ + MaxQstInd = Exa_GetMaxQuestionIndexInExam (Exam.ExaCod); // -1 if no questions + + /* Insert question in the table of questions */ + DB_QueryINSERT ("can not create question", + "INSERT INTO exa_questions" + " (ExaCod,QstCod,QstInd)" + " VALUES" + " (%ld,%ld,%u)", + Exam.ExaCod,QstCod,MaxQstInd + 1); + } + } + else + Ale_ShowAlert (Ale_WARNING,Txt_No_questions_have_been_added); + + /***** Free space for selected question codes *****/ + Exa_FreeListsSelectedQuestions (&Exams); + } + else + Lay_NoPermissionExit (); + + /***** Show current exam *****/ + Exa_ShowOnlyOneExam (&Exams,&Exam, + true, // List exam questions + false); // Do not put form to start new match + } + +/*****************************************************************************/ +/****************** Allocate memory for list of questions ********************/ +/*****************************************************************************/ + +static void Exa_AllocateListSelectedQuestions (struct Exa_Exams *Exams) + { + if (!Exams->ListQuestions) + { + if ((Exams->ListQuestions = (char *) malloc (Exa_MAX_BYTES_LIST_SELECTED_QUESTIONS + 1)) == NULL) + Lay_NotEnoughMemoryExit (); + Exams->ListQuestions[0] = '\0'; + } + } + +/*****************************************************************************/ +/*********** Free memory used by list of selected question codes *************/ +/*****************************************************************************/ + +static void Exa_FreeListsSelectedQuestions (struct Exa_Exams *Exams) + { + if (Exams->ListQuestions) + { + free (Exams->ListQuestions); + Exams->ListQuestions = NULL; + } + } + +/*****************************************************************************/ +/**** Count the number of questions in the list of selected question codes ***/ +/*****************************************************************************/ + +static unsigned Exa_CountNumQuestionsInList (const struct Exa_Exams *Exams) + { + const char *Ptr; + unsigned NumQuestions = 0; + char LongStr[Cns_MAX_DECIMAL_DIGITS_LONG + 1]; + long QstCod; + + /***** Go over list of questions counting the number of questions *****/ + Ptr = Exams->ListQuestions; + while (*Ptr) + { + Par_GetNextStrUntilSeparParamMult (&Ptr,LongStr,Cns_MAX_DECIMAL_DIGITS_LONG); + if (sscanf (LongStr,"%ld",&QstCod) != 1) + Lay_ShowErrorAndExit ("Wrong question code."); + NumQuestions++; + } + return NumQuestions; + } + +/*****************************************************************************/ +/********************** Request the removal of a question ********************/ +/*****************************************************************************/ + +void Exa_RequestRemoveQst (void) + { + extern const char *Txt_Do_you_really_want_to_remove_the_question_X; + extern const char *Txt_Remove_question; + struct Exa_Exams Exams; + struct Exa_Exam Exam; + unsigned QstInd; + + /***** Reset exams *****/ + Exa_ResetExams (&Exams); + + /***** Get parameters *****/ + if ((Exam.ExaCod = Exa_GetParams (&Exams)) <= 0) + Lay_ShowErrorAndExit ("Code of exam is missing."); + Exa_GetDataOfExamByCod (&Exam); + + /***** Check if exam has matches *****/ + if (Exa_CheckIfEditable (&Exam)) + { + /***** Get question index *****/ + QstInd = Exa_GetParamQstInd (); + + /***** Show question and button to remove question *****/ + Exams.ExaCod = Exam.ExaCod; + Exams.QstInd = QstInd; + Ale_ShowAlertAndButton (ActRemExaQst,NULL,NULL, + Exa_PutParamsOneQst,&Exams, + Btn_REMOVE_BUTTON,Txt_Remove_question, + Ale_QUESTION,Txt_Do_you_really_want_to_remove_the_question_X, + QstInd); + } + else + Lay_NoPermissionExit (); + + /***** Show current exam *****/ + Exa_ShowOnlyOneExam (&Exams,&Exam, + true, // List exam questions + false); // Do not put form to start new match + } + +/*****************************************************************************/ +/****************************** Remove a question ****************************/ +/*****************************************************************************/ + +void Exa_RemoveQst (void) + { + extern const char *Txt_Question_removed; + struct Exa_Exams Exams; + struct Exa_Exam Exam; + unsigned QstInd; + + /***** Reset exams *****/ + Exa_ResetExams (&Exams); + + /***** Get parameters *****/ + if ((Exam.ExaCod = Exa_GetParams (&Exams)) <= 0) + Lay_ShowErrorAndExit ("Code of exam is missing."); + Exa_GetDataOfExamByCod (&Exam); + + /***** Check if exam has matches *****/ + if (Exa_CheckIfEditable (&Exam)) + { + /***** Get question index *****/ + QstInd = Exa_GetParamQstInd (); + + /***** Remove the question from all the tables *****/ + /* Remove answers from this test question */ + Exa_RemAnswersOfAQuestion (Exam.ExaCod,QstInd); + + /* Remove the question itself */ + DB_QueryDELETE ("can not remove a question", + "DELETE FROM exa_questions" + " WHERE ExaCod=%ld AND QstInd=%u", + Exam.ExaCod,QstInd); + if (!mysql_affected_rows (&Gbl.mysql)) + Lay_ShowErrorAndExit ("The question to be removed does not exist."); + + /* Change index of questions greater than this */ + DB_QueryUPDATE ("can not update indexes of questions in table of answers", + "UPDATE exa_answers,exa_events" + " SET exa_answers.QstInd=exa_answers.QstInd-1" + " WHERE exa_events.ExaCod=%ld" + " AND exa_events.EvtCod=exa_answers.EvtCod" + " AND exa_answers.QstInd>%u", + Exam.ExaCod,QstInd); + DB_QueryUPDATE ("can not update indexes of questions", + "UPDATE exa_questions SET QstInd=QstInd-1" + " WHERE ExaCod=%ld AND QstInd>%u", + Exam.ExaCod,QstInd); + + /***** Write message *****/ + Ale_ShowAlert (Ale_SUCCESS,Txt_Question_removed); + } + else + Lay_NoPermissionExit (); + + /***** Show current exam *****/ + Exa_ShowOnlyOneExam (&Exams,&Exam, + true, // List exam questions + false); // Do not put form to start new match + } + +/*****************************************************************************/ +/***************** Move up position of a question in an exam ******************/ +/*****************************************************************************/ + +void Exa_MoveUpQst (void) + { + extern const char *Txt_The_question_has_been_moved_up; + extern const char *Txt_Movement_not_allowed; + struct Exa_Exams Exams; + struct Exa_Exam Exam; + unsigned QstIndTop; + unsigned QstIndBottom; + + /***** Reset exams *****/ + Exa_ResetExams (&Exams); + + /***** Get parameters *****/ + if ((Exam.ExaCod = Exa_GetParams (&Exams)) <= 0) + Lay_ShowErrorAndExit ("Code of exam is missing."); + Exa_GetDataOfExamByCod (&Exam); + + /***** Check if exam has matches *****/ + if (Exa_CheckIfEditable (&Exam)) + { + /***** Get question index *****/ + QstIndBottom = Exa_GetParamQstInd (); + + /***** Move up question *****/ + if (QstIndBottom > 1) + { + /* Indexes of questions to be exchanged */ + QstIndTop = Exa_GetPrevQuestionIndexInExam (Exam.ExaCod,QstIndBottom); + if (!QstIndTop) + Lay_ShowErrorAndExit ("Wrong index of question."); + + /* Exchange questions */ + Exa_ExchangeQuestions (Exam.ExaCod,QstIndTop,QstIndBottom); + + /* Success alert */ + Ale_ShowAlert (Ale_SUCCESS,Txt_The_question_has_been_moved_up); + } + else + Ale_ShowAlert (Ale_WARNING,Txt_Movement_not_allowed); + } + else + Lay_NoPermissionExit (); + + /***** Show current exam *****/ + Exa_ShowOnlyOneExam (&Exams,&Exam, + true, // List exam questions + false); // Do not put form to start new match + } + +/*****************************************************************************/ +/**************** Move down position of a question in an exam *****************/ +/*****************************************************************************/ + +void Exa_MoveDownQst (void) + { + extern const char *Txt_The_question_has_been_moved_down; + extern const char *Txt_Movement_not_allowed; + extern const char *Txt_This_exam_has_no_questions; + struct Exa_Exams Exams; + struct Exa_Exam Exam; + unsigned QstIndTop; + unsigned QstIndBottom; + unsigned MaxQstInd; // 0 if no questions + + /***** Reset exams *****/ + Exa_ResetExams (&Exams); + + /***** Get parameters *****/ + if ((Exam.ExaCod = Exa_GetParams (&Exams)) <= 0) + Lay_ShowErrorAndExit ("Code of exam is missing."); + Exa_GetDataOfExamByCod (&Exam); + + /***** Check if exam has matches *****/ + if (Exa_CheckIfEditable (&Exam)) + { + /***** Get question index *****/ + QstIndTop = Exa_GetParamQstInd (); + + /***** Get maximum question index *****/ + MaxQstInd = Exa_GetMaxQuestionIndexInExam (Exam.ExaCod); + + /***** Move down question *****/ + if (MaxQstInd) + { + if (QstIndTop < MaxQstInd) + { + /* Indexes of questions to be exchanged */ + QstIndBottom = Exa_GetNextQuestionIndexInExam (Exam.ExaCod,QstIndTop); + if (!QstIndBottom) + Lay_ShowErrorAndExit ("Wrong index of question."); + + /* Exchange questions */ + Exa_ExchangeQuestions (Exam.ExaCod,QstIndTop,QstIndBottom); + + /* Success alert */ + Ale_ShowAlert (Ale_SUCCESS,Txt_The_question_has_been_moved_down); + } + else + Ale_ShowAlert (Ale_WARNING,Txt_Movement_not_allowed); + } + else + Ale_ShowAlert (Ale_WARNING,Txt_This_exam_has_no_questions); + } + else + Lay_NoPermissionExit (); + + /***** Show current exam *****/ + Exa_ShowOnlyOneExam (&Exams,&Exam, + true, // List exam questions + false); // Do not put form to start new match + } + +/*****************************************************************************/ +/********* Exchange the order of two consecutive questions in an exam *********/ +/*****************************************************************************/ + +static void Exa_ExchangeQuestions (long ExaCod, + unsigned QstIndTop,unsigned QstIndBottom) + { + long QstCodTop; + long QstCodBottom; + + /***** Lock table to make the move atomic *****/ + DB_Query ("can not lock tables to move exam question", + "LOCK TABLES exa_questions WRITE"); + Gbl.DB.LockedTables = true; + + /***** Get question code of the questions to be moved *****/ + QstCodTop = Exa_GetQstCodFromQstInd (ExaCod,QstIndTop); + QstCodBottom = Exa_GetQstCodFromQstInd (ExaCod,QstIndBottom); + + /***** Exchange indexes of questions *****/ + /* + Example: + QstIndTop = 1; QstCodTop = 218 + QstIndBottom = 2; QstCodBottom = 220 + +--------+--------+ +--------+--------+ +--------+--------+ + | QstInd | QstCod | | QstInd | QstCod | | QstInd | QstCod | + +--------+--------+ +--------+--------+ +--------+--------+ + | 1 | 218 | -----> | 2 | 218 | = | 1 | 220 | + | 2 | 220 | | 1 | 220 | | 2 | 218 | + | 3 | 232 | | 3 | 232 | | 3 | 232 | + +--------+--------+ +--------+--------+ +--------+--------+ + */ + DB_QueryUPDATE ("can not exchange indexes of questions", + "UPDATE exa_questions SET QstInd=%u" + " WHERE ExaCod=%ld AND QstCod=%ld", + QstIndBottom, + ExaCod,QstCodTop); + + DB_QueryUPDATE ("can not exchange indexes of questions", + "UPDATE exa_questions SET QstInd=%u" + " WHERE ExaCod=%ld AND QstCod=%ld", + QstIndTop, + ExaCod,QstCodBottom); + + /***** Unlock table *****/ + 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 after moving exam questions", + "UNLOCK TABLES"); + } + +/*****************************************************************************/ +/*********** Get number of matches and check is edition is possible **********/ +/*****************************************************************************/ +// Before calling this function, number of matches must be calculated + +static bool Exa_CheckIfEditable (const struct Exa_Exam *Exam) + { + if (Exa_CheckIfICanEditExams ()) + /***** Questions are editable only if exam has no matches *****/ + return (bool) (Exam->NumEves == 0); // Exams with matches should not be edited + else + return false; // Questions are not editable + } + +/*****************************************************************************/ +/********************* Put button to create a new match **********************/ +/*****************************************************************************/ + +void Exa_PutButtonNewMatch (struct Exa_Exams *Exams,long ExaCod) + { + extern const char *Txt_New_match; + + Exams->ExaCod = ExaCod; + Frm_StartFormAnchor (ActReqNewExaEvt,ExaEvt_NEW_EVENT_SECTION_ID); + Exa_PutParams (Exams); + Btn_PutConfirmButton (Txt_New_match); + Frm_EndForm (); + } + +/*****************************************************************************/ +/************* Request the creation of a new match as a teacher **************/ +/*****************************************************************************/ + +void Exa_RequestNewMatch (void) + { + struct Exa_Exams Exams; + struct Exa_Exam Exam; + + /***** Reset exams *****/ + Exa_ResetExams (&Exams); + + /***** Get parameters *****/ + if ((Exam.ExaCod = Exa_GetParams (&Exams)) <= 0) + Lay_ShowErrorAndExit ("Code of exam is missing."); + Exa_GetDataOfExamByCod (&Exam); + + /***** Show exam *****/ + Exa_ShowOnlyOneExam (&Exams,&Exam, + false, // Do not list exam questions + true); // Put form to start new match + } + +/*****************************************************************************/ +/********************* Get number of courses with exams **********************/ +/*****************************************************************************/ +// Returns the number of courses with exams in this location + +unsigned Exa_GetNumCoursesWithExams (Hie_Level_t Scope) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NumCourses; + + /***** Get number of courses with exams from database *****/ + switch (Scope) + { + case Hie_SYS: + DB_QuerySELECT (&mysql_res,"can not get number of courses with exams", + "SELECT COUNT(DISTINCT CrsCod)" + " FROM exa_exams"); + break; + case Hie_CTY: + DB_QuerySELECT (&mysql_res,"can not get number of courses with exams", + "SELECT COUNT(DISTINCT exa_exams.CrsCod)" + " FROM institutions,centres,degrees,courses,exa_exams" + " WHERE institutions.CtyCod=%ld" + " AND institutions.InsCod=centres.InsCod" + " AND centres.CtrCod=degrees.CtrCod" + " AND degrees.DegCod=courses.DegCod" + " AND courses.CrsCod=exa_exams.CrsCod", + Gbl.Hierarchy.Ins.InsCod); + break; + case Hie_INS: + DB_QuerySELECT (&mysql_res,"can not get number of courses with exams", + "SELECT COUNT(DISTINCT exa_exams.CrsCod)" + " FROM centres,degrees,courses,exa_exams" + " WHERE centres.InsCod=%ld" + " AND centres.CtrCod=degrees.CtrCod" + " AND degrees.DegCod=courses.DegCod" + " AND courses.CrsCod=exa_exams.CrsCod", + Gbl.Hierarchy.Ins.InsCod); + break; + case Hie_CTR: + DB_QuerySELECT (&mysql_res,"can not get number of courses with exams", + "SELECT COUNT(DISTINCT exa_exams.CrsCod)" + " FROM degrees,courses,exa_exams" + " WHERE degrees.CtrCod=%ld" + " AND degrees.DegCod=courses.DegCod" + " AND courses.CrsCod=exa_exams.CrsCod", + Gbl.Hierarchy.Ctr.CtrCod); + break; + case Hie_DEG: + DB_QuerySELECT (&mysql_res,"can not get number of courses with exams", + "SELECT COUNT(DISTINCT exa_exams.CrsCod)" + " FROM courses,exa_exams" + " WHERE courses.DegCod=%ld" + " AND courses.CrsCod=exa_exams.CrsCod", + Gbl.Hierarchy.Deg.DegCod); + break; + case Hie_CRS: + DB_QuerySELECT (&mysql_res,"can not get number of courses with exams", + "SELECT COUNT(DISTINCT CrsCod)" + " FROM exa_exams" + " WHERE CrsCod=%ld", + Gbl.Hierarchy.Crs.CrsCod); + break; + default: + Lay_WrongScopeExit (); + break; + } + + /***** Get number of exams *****/ + row = mysql_fetch_row (mysql_res); + if (sscanf (row[0],"%u",&NumCourses) != 1) + Lay_ShowErrorAndExit ("Error when getting number of courses with exams."); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + return NumCourses; + } + +/*****************************************************************************/ +/**************************** Get number of exams ****************************/ +/*****************************************************************************/ +// Returns the number of exams in this location + +unsigned Exa_GetNumExams (Hie_Level_t Scope) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NumExams; + + /***** Get number of exams from database *****/ + switch (Scope) + { + case Hie_SYS: + DB_QuerySELECT (&mysql_res,"can not get number of exams", + "SELECT COUNT(*)" + " FROM exa_exams"); + break; + case Hie_CTY: + DB_QuerySELECT (&mysql_res,"can not get number of exams", + "SELECT COUNT(*)" + " FROM institutions,centres,degrees,courses,exa_exams" + " WHERE institutions.CtyCod=%ld" + " AND institutions.InsCod=centres.InsCod" + " AND centres.CtrCod=degrees.CtrCod" + " AND degrees.DegCod=courses.DegCod" + " AND courses.CrsCod=exa_exams.CrsCod", + Gbl.Hierarchy.Cty.CtyCod); + break; + case Hie_INS: + DB_QuerySELECT (&mysql_res,"can not get number of exams", + "SELECT COUNT(*)" + " FROM centres,degrees,courses,exa_exams" + " WHERE centres.InsCod=%ld" + " AND centres.CtrCod=degrees.CtrCod" + " AND degrees.DegCod=courses.DegCod" + " AND courses.CrsCod=exa_exams.CrsCod", + Gbl.Hierarchy.Ins.InsCod); + break; + case Hie_CTR: + DB_QuerySELECT (&mysql_res,"can not get number of exams", + "SELECT COUNT(*)" + " FROM degrees,courses,exa_exams" + " WHERE degrees.CtrCod=%ld" + " AND degrees.DegCod=courses.DegCod" + " AND courses.CrsCod=exa_exams.CrsCod", + Gbl.Hierarchy.Ctr.CtrCod); + break; + case Hie_DEG: + DB_QuerySELECT (&mysql_res,"can not get number of exams", + "SELECT COUNT(*)" + " FROM courses,exa_exams" + " WHERE courses.DegCod=%ld" + " AND courses.CrsCod=exa_exams.CrsCod", + Gbl.Hierarchy.Deg.DegCod); + break; + case Hie_CRS: + DB_QuerySELECT (&mysql_res,"can not get number of exams", + "SELECT COUNT(*)" + " FROM exa_exams" + " WHERE CrsCod=%ld", + Gbl.Hierarchy.Crs.CrsCod); + break; + default: + Lay_WrongScopeExit (); + break; + } + + /***** Get number of exams *****/ + row = mysql_fetch_row (mysql_res); + if (sscanf (row[0],"%u",&NumExams) != 1) + Lay_ShowErrorAndExit ("Error when getting number of exams."); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + return NumExams; + } + +/*****************************************************************************/ +/************* Get average number of questions per course exam ***************/ +/*****************************************************************************/ + +double Exa_GetNumQstsPerCrsExam (Hie_Level_t Scope) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + double NumQstsPerExam; + + /***** Get number of questions per exam from database *****/ + switch (Scope) + { + case Hie_SYS: + DB_QuerySELECT (&mysql_res,"can not get number of questions per exam", + "SELECT AVG(NumQsts) FROM" + " (SELECT COUNT(exa_questions.QstCod) AS NumQsts" + " FROM exa_exams,exa_questions" + " WHERE exa_exams.ExaCod=exa_questions.ExaCod" + " GROUP BY exa_questions.ExaCod) AS NumQstsTable"); + break; + case Hie_CTY: + DB_QuerySELECT (&mysql_res,"can not get number of questions per exam", + "SELECT AVG(NumQsts) FROM" + " (SELECT COUNT(exa_questions.QstCod) AS NumQsts" + " FROM institutions,centres,degrees,courses,exa_exams,exa_questions" + " WHERE institutions.CtyCod=%ld" + " AND institutions.InsCod=centres.InsCod" + " AND centres.CtrCod=degrees.CtrCod" + " AND degrees.DegCod=courses.DegCod" + " AND courses.CrsCod=exa_exams.CrsCod" + " AND exa_exams.ExaCod=exa_questions.ExaCod" + " GROUP BY exa_questions.ExaCod) AS NumQstsTable", + Gbl.Hierarchy.Cty.CtyCod); + break; + case Hie_INS: + DB_QuerySELECT (&mysql_res,"can not get number of questions per exam", + "SELECT AVG(NumQsts) FROM" + " (SELECT COUNT(exa_questions.QstCod) AS NumQsts" + " FROM centres,degrees,courses,exa_exams,exa_questions" + " WHERE centres.InsCod=%ld" + " AND centres.CtrCod=degrees.CtrCod" + " AND degrees.DegCod=courses.DegCod" + " AND courses.CrsCod=exa_exams.CrsCod" + " AND exa_exams.ExaCod=exa_questions.ExaCod" + " GROUP BY exa_questions.ExaCod) AS NumQstsTable", + Gbl.Hierarchy.Ins.InsCod); + break; + case Hie_CTR: + DB_QuerySELECT (&mysql_res,"can not get number of questions per exam", + "SELECT AVG(NumQsts) FROM" + " (SELECT COUNT(exa_questions.QstCod) AS NumQsts" + " FROM degrees,courses,exa_exams,exa_questions" + " WHERE degrees.CtrCod=%ld" + " AND degrees.DegCod=courses.DegCod" + " AND courses.CrsCod=exa_exams.CrsCod" + " AND exa_exams.ExaCod=exa_questions.ExaCod" + " GROUP BY exa_questions.ExaCod) AS NumQstsTable", + Gbl.Hierarchy.Ctr.CtrCod); + break; + case Hie_DEG: + DB_QuerySELECT (&mysql_res,"can not get number of questions per exam", + "SELECT AVG(NumQsts) FROM" + " (SELECT COUNT(exa_questions.QstCod) AS NumQsts" + " FROM courses,exa_exams,exa_questions" + " WHERE courses.DegCod=%ld" + " AND courses.CrsCod=exa_exams.CrsCod" + " AND exa_exams.ExaCod=exa_questions.ExaCod" + " GROUP BY exa_questions.ExaCod) AS NumQstsTable", + Gbl.Hierarchy.Deg.DegCod); + break; + case Hie_CRS: + DB_QuerySELECT (&mysql_res,"can not get number of questions per exam", + "SELECT AVG(NumQsts) FROM" + " (SELECT COUNT(exa_questions.QstCod) AS NumQsts" + " FROM exa_exams,exa_questions" + " WHERE exa_exams.Cod=%ld" + " AND exa_exams.ExaCod=exa_questions.ExaCod" + " GROUP BY exa_questions.ExaCod) AS NumQstsTable", + Gbl.Hierarchy.Crs.CrsCod); + break; + default: + Lay_WrongScopeExit (); + break; + } + + /***** Get average number of questions per exam *****/ + row = mysql_fetch_row (mysql_res); + NumQstsPerExam = Str_GetDoubleFromStr (row[0]); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + return NumQstsPerExam; + } + +/*****************************************************************************/ +/************************* Show test tags in an exam **************************/ +/*****************************************************************************/ + +void Exa_ShowTstTagsPresentInAnExam (long ExaCod) + { + MYSQL_RES *mysql_res; + unsigned long NumTags; + + /***** Get all tags of questions in this exam *****/ + NumTags = (unsigned) + DB_QuerySELECT (&mysql_res,"can not get tags" + " present in a match result", + "SELECT tst_tags.TagTxt" // row[0] + " FROM" + " (SELECT DISTINCT(tst_question_tags.TagCod)" + " FROM tst_question_tags,exa_questions" + " WHERE exa_questions.ExaCod=%ld" + " AND exa_questions.QstCod=tst_question_tags.QstCod)" + " AS TagsCods,tst_tags" + " WHERE TagsCods.TagCod=tst_tags.TagCod" + " ORDER BY tst_tags.TagTxt", + ExaCod); + Tst_ShowTagList (NumTags,mysql_res); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/*************** Get maximum score of an exam from database *******************/ +/*****************************************************************************/ + +void Exa_GetScoreRange (long ExaCod,double *MinScore,double *MaxScore) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NumRows; + unsigned NumRow; + unsigned NumAnswers; + + /***** Get maximum score of an exam from database *****/ + NumRows = (unsigned) + DB_QuerySELECT (&mysql_res,"can not get data of a question", + "SELECT COUNT(tst_answers.AnsInd) AS N" + " FROM tst_answers,exa_questions" + " WHERE exa_questions.ExaCod=%ld" + " AND exa_questions.QstCod=tst_answers.QstCod" + " GROUP BY tst_answers.QstCod", + ExaCod); + for (NumRow = 0, *MinScore = *MaxScore = 0.0; + NumRow < NumRows; + NumRow++) + { + row = mysql_fetch_row (mysql_res); + + /* Get min answers (row[0]) */ + if (sscanf (row[0],"%u",&NumAnswers) != 1) + NumAnswers = 0; + + /* Accumulate minimum and maximum score */ + if (NumAnswers < 2) + Lay_ShowErrorAndExit ("Wrong number of answers."); + *MinScore += -1.0 / (double) (NumAnswers - 1); + *MaxScore += 1.0; + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); } diff --git a/swad_exam.h b/swad_exam.h index 65c819c1..a9d3f8db 100644 --- a/swad_exam.h +++ b/swad_exam.h @@ -1,9 +1,9 @@ -// swad_exam.h: exam announcements +// swad_exam.h: exams #ifndef _SWAD_EXA #define _SWAD_EXA /* - SWAD (Shared Workspace At a Distance in Spanish), + SWAD (Shared Workspace At a Distance), is a web platform developed at the University of Granada (Spain), and used to support university teaching. @@ -11,7 +11,7 @@ Copyright (C) 1999-2020 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 + it under the terms of the GNU Affero General 3 License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. @@ -23,106 +23,147 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ + /*****************************************************************************/ /********************************* Headers ***********************************/ /*****************************************************************************/ -#include // For boolean type - -#include "swad_constant.h" -#include "swad_course.h" #include "swad_date.h" -#include "swad_notification.h" +#include "swad_scope.h" /*****************************************************************************/ /************************** Public types and constants ***********************/ /*****************************************************************************/ -#define Exa_NUM_VIEWS 3 +#define Exa_MAX_CHARS_TITLE (128 - 1) // 127 +#define Exa_MAX_BYTES_TITLE ((Exa_MAX_CHARS_TITLE + 1) * Str_MAX_BYTES_PER_CHAR - 1) // 2047 + +#define Exa_NUM_ORDERS 3 typedef enum { - Exa_NORMAL_VIEW, - Exa_PRINT_VIEW, - Exa_FORM_VIEW, - } Exa_TypeViewExamAnnouncement_t; + Exa_ORDER_BY_START_DATE = 0, + Exa_ORDER_BY_END_DATE = 1, + Exa_ORDER_BY_TITLE = 2, + } Exa_Order_t; +#define Exa_ORDER_DEFAULT Exa_ORDER_BY_START_DATE -#define Exa_NUM_STATUS 3 +#define Exa_NUM_ANS_TYPES 2 typedef enum { - Exa_VISIBLE_EXAM_ANNOUNCEMENT = 0, - Exa_HIDDEN_EXAM_ANNOUNCEMENT = 1, - Exa_DELETED_EXAM_ANNOUNCEMENT = 2, - } Exa_ExamAnnouncementStatus_t; // Don't change these numbers because they are used in database + Exa_ANS_UNIQUE_CHOICE = 0, + Exa_ANS_MULTIPLE_CHOICE = 1, + } Exa_AnswerType_t; +#define Exa_ANSWER_TYPE_DEFAULT Exa_ANS_UNIQUE_CHOICE -#define Exa_MAX_CHARS_SESSION (128 - 1) // 127 -#define Exa_MAX_BYTES_SESSION ((Exa_MAX_CHARS_SESSION + 1) * Str_MAX_BYTES_PER_CHAR - 1) // 2047 - -struct Exa_ExamAnnouncement +struct Exa_ExamSelected { - long CrsCod; - Exa_ExamAnnouncementStatus_t Status; - char CrsFullName[Hie_MAX_BYTES_FULL_NAME + 1]; - unsigned Year; // Number of year (0 (N.A.), 1, 2, 3, 4, 5, 6) in the degree - char Session[Exa_MAX_BYTES_SESSION + 1]; // Exam session is june, september, etc. - struct Date CallDate; - struct Date ExamDate; - struct Hour StartTime; - struct Hour Duration; - char *Place; - char *Mode; - char *Structure; - char *DocRequired; - char *MatRequired; - char *MatAllowed; - char *OtherInfo; + long ExaCod; // Exam code + bool Selected; // Is this exam selected when seeing match results? }; -struct Exa_ExamCodeAndDate +struct Exa_Exams { - long ExaCod; - struct Date ExamDate; + bool LstIsRead; // Is the list already read from database... + // ...or it needs to be read? + unsigned Num; // Total number of exams + unsigned NumSelected; // Number of exams selected + struct Exa_ExamSelected *Lst;// List of exams + Exa_Order_t SelectedOrder; + unsigned CurrentPage; + char *ListQuestions; + char *ExaCodsSelected; // String with selected exam codes separated by separator multiple + long ExaCod; // Selected/current exam code + long EvtCod; // Selected/current match code + unsigned QstInd; // Current question index }; -struct Exa_ExamAnnouncements +struct Exa_Exam { - unsigned NumExaAnns; // Number of announcements of exam in the list - struct Exa_ExamCodeAndDate *Lst; // List of exam announcements - long NewExaCod; // New exam announcement just created - long HighlightExaCod; // Exam announcement to be highlighted - char HighlightDate[4 + 2 + 2 + 1]; // Date with exam announcements to be highlighted (in YYYYMMDD format) - long ExaCod; // Used to put contextual icons - const char *Anchor; // Used to put contextual icons - struct Exa_ExamAnnouncement ExamAnn; + long ExaCod; // Exam code + long CrsCod; // Course code + long UsrCod; // Author code + double MaxGrade; // Score range [0...max.score] + // will be converted to + // grade range [0...max.grade] + unsigned Visibility; // Visibility of results + char Title[Exa_MAX_BYTES_TITLE + 1]; + time_t TimeUTC[Dat_NUM_START_END_TIME]; + bool Hidden; // Exam is hidden + unsigned NumQsts; // Number of questions in the exam + unsigned NumEves; // Number of events in the exam + unsigned NumUnfinishedEves; // Number of unfinished events in the exam }; /*****************************************************************************/ /***************************** Public prototypes *****************************/ /*****************************************************************************/ -void Exa_ResetExamAnnouncements (struct Exa_ExamAnnouncements *ExamAnns); +void Exa_ResetExams (struct Exa_Exams *Exams); -void Exa_PutFrmEditAExamAnnouncement (void); -void Exa_ReceiveExamAnnouncement1 (void); -void Exa_ReceiveExamAnnouncement2 (void); -void Exa_PrintExamAnnouncement (void); -void Exa_ReqRemoveExamAnnouncement (void); -void Exa_RemoveExamAnnouncement1 (void); -void Exa_RemoveExamAnnouncement2 (void); -void Exa_HideExamAnnouncement (void); -void Exa_UnhideExamAnnouncement (void); +void Exa_SeeAllExams (void); +void Exa_SeeOneExam (void); +void Exa_ShowOnlyOneExam (struct Exa_Exams *Exams, + struct Exa_Exam *Exam, + bool ListExamQuestions, + bool PutFormNewMatch); +void Exa_ShowOnlyOneExamBegin (struct Exa_Exams *Exams, + struct Exa_Exam *Exam, + bool ListExamQuestions, + bool PutFormNewMatch); +void Exa_ShowOnlyOneExamEnd (void); -void Exa_FreeListExamAnnouncements (struct Exa_ExamAnnouncements *ExamAnns); -void Exa_ListExamAnnouncementsSee (void); -void Exa_ListExamAnnouncementsEdit (void); +void Exa_SetCurrentExaCod (long ExaCod); +void Exa_PutParams (void *Exams); +void Exa_PutParamExamCod (long ExaCod); +long Exa_GetParamExamCod (void); +long Exa_GetParams (struct Exa_Exams *Exams); -void Exa_ListExamAnnouncementsCod (void); -void Exa_ListExamAnnouncementsDay (void); +void Exa_GetListExams (struct Exa_Exams *Exams,Exa_Order_t SelectedOrder); +void Exa_GetListSelectedExaCods (struct Exa_Exams *Exams); +void Exa_GetDataOfExamByCod (struct Exa_Exam *Exam); +void Exa_GetDataOfExamByFolder (struct Exa_Exam *Exam); +void Exa_FreeListExams (struct Exa_Exams *Exams); -void Exa_CreateListExamAnnouncements (struct Exa_ExamAnnouncements *ExamAnns); -void Exa_PutHiddenParamExaCod (long ExaCod); +void Exa_AskRemExam (void); +void Exa_RemoveExam (void); +void Exa_RemoveExamsCrs (long CrsCod); -void Exa_GetSummaryAndContentExamAnnouncement (char SummaryStr[Ntf_MAX_BYTES_SUMMARY + 1], - char **ContentStr, - long ExaCod,bool GetContent); +void Exa_HideExam (void); +void Exa_UnhideExam (void); + +void Exa_RequestCreatOrEditExam (void); + +void Exa_RecFormExam (void); +bool Mch_CheckIfMatchIsAssociatedToGrp (long EvtCod,long GrpCod); + +unsigned Exa_GetNumQstsExam (long ExaCod); + +void Exa_RequestNewQuestion (void); +void Exa_ListTstQuestionsToSelect (void); + +void Exa_PutParamQstInd (unsigned QstInd); +unsigned Exa_GetParamQstInd (void); +long Exa_GetQstCodFromQstInd (long ExaCod,unsigned QstInd); +unsigned Exa_GetPrevQuestionIndexInExam (long ExaCod,unsigned QstInd); +unsigned Exa_GetNextQuestionIndexInExam (long ExaCod,unsigned QstInd); + +void Exa_AddTstQuestionsToExam (void); + +void Exa_RequestRemoveQst (void); +void Exa_RemoveQst (void); + +void Exa_MoveUpQst (void); +void Exa_MoveDownQst (void); + +void Exa_PutButtonNewMatch (struct Exa_Exams *Exams,long ExaCod); +void Exa_RequestNewMatch (void); + +unsigned Exa_GetNumCoursesWithExams (Hie_Level_t Scope); +unsigned Exa_GetNumExams (Hie_Level_t Scope); +double Exa_GetNumQstsPerCrsExam (Hie_Level_t Scope); + +void Exa_ShowTstTagsPresentInAnExam (long ExaCod); + +void Exa_GetScoreRange (long ExaCod,double *MinScore,double *MaxScore); #endif diff --git a/swad_exam_announcement.c b/swad_exam_announcement.c new file mode 100644 index 00000000..d8f20a11 --- /dev/null +++ b/swad_exam_announcement.c @@ -0,0 +1,1793 @@ +// swad_exam_announcement.c: exam announcements + +/* + 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-2020 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 3 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 NULL +#include // For sscanf, asprintf, etc. +#include // For exit, system, malloc, calloc, free, etc. +#include // For string functions + +#include "swad_box.h" +#include "swad_config.h" +#include "swad_database.h" +#include "swad_exam_announcement.h" +#include "swad_form.h" +#include "swad_global.h" +#include "swad_HTML.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_timeline.h" + +/*****************************************************************************/ +/************** External global variables from others modules ****************/ +/*****************************************************************************/ + +extern struct Globals Gbl; + +/*****************************************************************************/ +/***************************** Private constants *****************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/******************************* Private types *******************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/***************************** Private variables *****************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/***************************** Private prototypes ****************************/ +/*****************************************************************************/ + +static struct ExaAnn_ExamAnnouncements *ExaAnn_GetGlobalExamAnnouncements (void); + +static long ExaAnn_GetParamsExamAnnouncement (struct ExaAnn_ExamAnnouncements *ExamAnns); + +static void ExaAnn_AllocMemExamAnnouncement (struct ExaAnn_ExamAnnouncements *ExamAnns); +static void ExaAnn_FreeMemExamAnnouncement (struct ExaAnn_ExamAnnouncements *ExamAnns); + +static void ExaAnn_UpdateNumUsrsNotifiedByEMailAboutExamAnnouncement (long ExaCod,unsigned NumUsrsToBeNotifiedByEMail); + +static void ExaAnn_GetExaCodToHighlight (struct ExaAnn_ExamAnnouncements *ExamAnns); +static void ExaAnn_GetDateToHighlight (struct ExaAnn_ExamAnnouncements *ExamAnns); + +static void ExaAnn_ListExamAnnouncements (struct ExaAnn_ExamAnnouncements *ExamAnns, + ExaAnn_TypeViewExamAnnouncement_t TypeViewExamAnnouncement); +static void ExaAnn_PutIconToCreateNewExamAnnouncement (__attribute__((unused)) void *Args); +static void ExaAnn_PutButtonToCreateNewExamAnnouncement (void); + +static long ExaAnn_AddExamAnnouncementToDB (const struct ExaAnn_ExamAnnouncements *ExamAnns); +static void ExaAnn_ModifyExamAnnouncementInDB (const struct ExaAnn_ExamAnnouncements *ExamAnns, + long ExaCod); +static void ExaAnn_GetDataExamAnnouncementFromDB (struct ExaAnn_ExamAnnouncements *ExamAnns, + long ExaCod); +static void ExaAnn_ShowExamAnnouncement (struct ExaAnn_ExamAnnouncements *ExamAnns, + long ExaCod, + ExaAnn_TypeViewExamAnnouncement_t TypeViewExamAnnouncement, + bool HighLight); +static void ExaAnn_PutIconsExamAnnouncement (void *ExamAnns); +static void ExaAnn_PutParamExaCodToEdit (void *ExaCod); +static long ExaAnn_GetParamExaCod (void); + +static void ExaAnn_GetNotifContentExamAnnouncement (const struct ExaAnn_ExamAnnouncements *ExamAnns, + char **ContentStr); + +/*****************************************************************************/ +/******************* Get global exam announcements context *******************/ +/*****************************************************************************/ + +static struct ExaAnn_ExamAnnouncements *ExaAnn_GetGlobalExamAnnouncements (void) + { + static struct ExaAnn_ExamAnnouncements ExaAnn_GlobalExamAnns; // Used to preserve information between priori and posteriori functions + + return &ExaAnn_GlobalExamAnns; + } + +/*****************************************************************************/ +/********************** Reset exam announcements context *********************/ +/*****************************************************************************/ + +void ExaAnn_ResetExamAnnouncements (struct ExaAnn_ExamAnnouncements *ExamAnns) + { + ExamAnns->NumExaAnns = 0; + ExamAnns->Lst = NULL; + ExamAnns->NewExaCod = -1L; + ExamAnns->HighlightExaCod = -1L; + ExamAnns->HighlightDate[0] = '\0'; // No exam announcements highlighted + ExamAnns->ExaCod = -1L; + ExamAnns->Anchor = NULL; + } + +/*****************************************************************************/ +/********************** Form to edit an exam announcement ********************/ +/*****************************************************************************/ + +void ExaAnn_PutFrmEditAExamAnnouncement (void) + { + struct ExaAnn_ExamAnnouncements ExamAnns; + long ExaCod; + + /***** Reset exam announcements context *****/ + ExaAnn_ResetExamAnnouncements (&ExamAnns); + + /***** Allocate memory for the exam announcement *****/ + ExaAnn_AllocMemExamAnnouncement (&ExamAnns); + + /***** Get the code of the exam announcement *****/ + ExaCod = ExaAnn_GetParamsExamAnnouncement (&ExamAnns); + + if (ExaCod > 0) // -1 indicates that this is a new exam announcement + /***** Read exam announcement from the database *****/ + ExaAnn_GetDataExamAnnouncementFromDB (&ExamAnns,ExaCod); + + /***** Show exam announcement *****/ + ExaAnn_ShowExamAnnouncement (&ExamAnns,ExaCod,ExaAnn_FORM_VIEW, + false); // Don't highlight + + /***** Free memory of the exam announcement *****/ + ExaAnn_FreeMemExamAnnouncement (&ExamAnns); + } + +/*****************************************************************************/ +/**************** Get parameters of an exam announcement *********************/ +/*****************************************************************************/ + +static long ExaAnn_GetParamsExamAnnouncement (struct ExaAnn_ExamAnnouncements *ExamAnns) + { + long ExaCod; + + /***** Get the code of the exam announcement *****/ + ExaCod = ExaAnn_GetParamExaCod (); + + /***** Get the name of the course (it is allowed to be different from the official name of the course) *****/ + Par_GetParToText ("CrsName",ExamAnns->ExamAnn.CrsFullName,Hie_MAX_BYTES_FULL_NAME); + // If the parameter is not present or is empty, initialize the string to the full name of the current course + if (!ExamAnns->ExamAnn.CrsFullName[0]) + Str_Copy (ExamAnns->ExamAnn.CrsFullName,Gbl.Hierarchy.Crs.FullName, + Hie_MAX_BYTES_FULL_NAME); + + /***** Get the year *****/ + ExamAnns->ExamAnn.Year = (unsigned) + Par_GetParToUnsignedLong ("Year", + 0, // N.A. + Deg_MAX_YEARS_PER_DEGREE, + (unsigned long) Gbl.Hierarchy.Crs.Year); + + /***** Get the type of exam announcement *****/ + Par_GetParToText ("ExamSession",ExamAnns->ExamAnn.Session,ExaAnn_MAX_BYTES_SESSION); + + /***** Get the data of the exam *****/ + Dat_GetDateFromForm ("ExamDay","ExamMonth","ExamYear", + &ExamAnns->ExamAnn.ExamDate.Day, + &ExamAnns->ExamAnn.ExamDate.Month, + &ExamAnns->ExamAnn.ExamDate.Year); + if (ExamAnns->ExamAnn.ExamDate.Day == 0 || + ExamAnns->ExamAnn.ExamDate.Month == 0 || + ExamAnns->ExamAnn.ExamDate.Year == 0) + { + ExamAnns->ExamAnn.ExamDate.Day = Gbl.Now.Date.Day; + ExamAnns->ExamAnn.ExamDate.Month = Gbl.Now.Date.Month; + ExamAnns->ExamAnn.ExamDate.Year = Gbl.Now.Date.Year; + } + + /***** Get the hour of the exam *****/ + ExamAnns->ExamAnn.StartTime.Hour = (unsigned) Par_GetParToUnsignedLong ("ExamHour", + 0,23,0); + ExamAnns->ExamAnn.StartTime.Minute = (unsigned) Par_GetParToUnsignedLong ("ExamMinute", + 0,59,0); + + /***** Get the duration of the exam *****/ + ExamAnns->ExamAnn.Duration.Hour = (unsigned) Par_GetParToUnsignedLong ("DurationHour", + 0,23,0); + ExamAnns->ExamAnn.Duration.Minute = (unsigned) Par_GetParToUnsignedLong ("DurationMinute", + 0,59,0); + + /***** Get the place where the exam will happen *****/ + Par_GetParToHTML ("Place",ExamAnns->ExamAnn.Place,Cns_MAX_BYTES_TEXT); + + /***** Get the modality of exam *****/ + Par_GetParToHTML ("ExamMode",ExamAnns->ExamAnn.Mode,Cns_MAX_BYTES_TEXT); + + /***** Get the structure of exam *****/ + Par_GetParToHTML ("Structure",ExamAnns->ExamAnn.Structure,Cns_MAX_BYTES_TEXT); + + /***** Get the mandatory documentation *****/ + Par_GetParToHTML ("DocRequired",ExamAnns->ExamAnn.DocRequired,Cns_MAX_BYTES_TEXT); + + /***** Get the mandatory material *****/ + Par_GetParToHTML ("MatRequired",ExamAnns->ExamAnn.MatRequired,Cns_MAX_BYTES_TEXT); + + /***** Get the allowed material *****/ + Par_GetParToHTML ("MatAllowed",ExamAnns->ExamAnn.MatAllowed,Cns_MAX_BYTES_TEXT); + + /***** Get other information *****/ + Par_GetParToHTML ("OtherInfo",ExamAnns->ExamAnn.OtherInfo,Cns_MAX_BYTES_TEXT); + + return ExaCod; + } + +/*****************************************************************************/ +/* Allocate memory for those parameters of an exam anno. with a lot of text **/ +/*****************************************************************************/ + +static void ExaAnn_AllocMemExamAnnouncement (struct ExaAnn_ExamAnnouncements *ExamAnns) + { + if ((ExamAnns->ExamAnn.Place = (char *) malloc (Cns_MAX_BYTES_TEXT + 1)) == NULL) + Lay_NotEnoughMemoryExit (); + + if ((ExamAnns->ExamAnn.Mode = (char *) malloc (Cns_MAX_BYTES_TEXT + 1)) == NULL) + Lay_NotEnoughMemoryExit (); + + if ((ExamAnns->ExamAnn.Structure = (char *) malloc (Cns_MAX_BYTES_TEXT + 1)) == NULL) + Lay_NotEnoughMemoryExit (); + + if ((ExamAnns->ExamAnn.DocRequired = (char *) malloc (Cns_MAX_BYTES_TEXT + 1)) == NULL) + Lay_NotEnoughMemoryExit (); + + if ((ExamAnns->ExamAnn.MatRequired = (char *) malloc (Cns_MAX_BYTES_TEXT + 1)) == NULL) + Lay_NotEnoughMemoryExit (); + + if ((ExamAnns->ExamAnn.MatAllowed = (char *) malloc (Cns_MAX_BYTES_TEXT + 1)) == NULL) + Lay_NotEnoughMemoryExit (); + + if ((ExamAnns->ExamAnn.OtherInfo = (char *) malloc (Cns_MAX_BYTES_TEXT + 1)) == NULL) + Lay_NotEnoughMemoryExit (); + } + +/*****************************************************************************/ +/* Free memory of those parameters of an exam announcem. with a lot of text **/ +/*****************************************************************************/ + +static void ExaAnn_FreeMemExamAnnouncement (struct ExaAnn_ExamAnnouncements *ExamAnns) + { + if (ExamAnns->ExamAnn.Place) + { + free (ExamAnns->ExamAnn.Place); + ExamAnns->ExamAnn.Place = NULL; + } + if (ExamAnns->ExamAnn.Mode) + { + free (ExamAnns->ExamAnn.Mode); + ExamAnns->ExamAnn.Mode = NULL; + } + if (ExamAnns->ExamAnn.Structure) + { + free (ExamAnns->ExamAnn.Structure); + ExamAnns->ExamAnn.Structure = NULL; + } + if (ExamAnns->ExamAnn.DocRequired) + { + free (ExamAnns->ExamAnn.DocRequired); + ExamAnns->ExamAnn.DocRequired = NULL; + } + if (ExamAnns->ExamAnn.MatRequired) + { + free (ExamAnns->ExamAnn.MatRequired); + ExamAnns->ExamAnn.MatRequired = NULL; + } + if (ExamAnns->ExamAnn.MatAllowed) + { + free (ExamAnns->ExamAnn.MatAllowed); + ExamAnns->ExamAnn.MatAllowed = NULL; + } + if (ExamAnns->ExamAnn.OtherInfo) + { + free (ExamAnns->ExamAnn.OtherInfo); + ExamAnns->ExamAnn.OtherInfo = NULL; + } + } + +/*****************************************************************************/ +/************************ Receive an exam announcement ***********************/ +/*****************************************************************************/ +// This function is splitted into a-priori and a-posteriori functions +// in order to view updated links in month of left column + +void ExaAnn_ReceiveExamAnnouncement1 (void) + { + extern const char *Txt_Created_new_announcement_of_exam; + extern const char *Txt_The_announcement_of_exam_has_been_successfully_updated; + struct ExaAnn_ExamAnnouncements *ExamAnns = ExaAnn_GetGlobalExamAnnouncements (); + long ExaCod; + bool NewExamAnnouncement; + char *Anchor = NULL; + + /***** Reset exam announcements context *****/ + ExaAnn_ResetExamAnnouncements (ExamAnns); + + /***** Allocate memory for the exam announcement *****/ + ExaAnn_AllocMemExamAnnouncement (ExamAnns); + + /***** Get parameters of the exam announcement *****/ + ExaCod = ExaAnn_GetParamsExamAnnouncement (ExamAnns); + NewExamAnnouncement = (ExaCod < 0); + + /***** Add the exam announcement to the database and read it again from the database *****/ + if (NewExamAnnouncement) + ExamAnns->NewExaCod = ExaCod = ExaAnn_AddExamAnnouncementToDB (ExamAnns); + else + ExaAnn_ModifyExamAnnouncementInDB (ExamAnns,ExaCod); + + /***** Free memory of the exam announcement *****/ + ExaAnn_FreeMemExamAnnouncement (ExamAnns); + + /***** Create alert to show the change made *****/ + Frm_SetAnchorStr (ExaCod,&Anchor); + Ale_CreateAlert (Ale_SUCCESS,Anchor, + NewExamAnnouncement ? Txt_Created_new_announcement_of_exam : + Txt_The_announcement_of_exam_has_been_successfully_updated); + Frm_FreeAnchorStr (Anchor); + + /***** Set exam to be highlighted *****/ + ExamAnns->HighlightExaCod = ExaCod; + } + +void ExaAnn_ReceiveExamAnnouncement2 (void) + { + struct ExaAnn_ExamAnnouncements *ExamAnns = ExaAnn_GetGlobalExamAnnouncements (); + unsigned NumUsrsToBeNotifiedByEMail; + struct TL_Publication SocPub; + + /***** Notify by email about the new exam announcement *****/ + if ((NumUsrsToBeNotifiedByEMail = Ntf_StoreNotifyEventsToAllUsrs (Ntf_EVENT_EXAM_ANNOUNCEMENT,ExamAnns->HighlightExaCod))) + ExaAnn_UpdateNumUsrsNotifiedByEMailAboutExamAnnouncement (ExamAnns->HighlightExaCod,NumUsrsToBeNotifiedByEMail); + + /***** Create a new social note about the new exam announcement *****/ + TL_StoreAndPublishNote (TL_NOTE_EXAM_ANNOUNCEMENT,ExamAnns->HighlightExaCod,&SocPub); + + /***** Update RSS of current course *****/ + RSS_UpdateRSSFileForACrs (&Gbl.Hierarchy.Crs); + + /***** Show exam announcements *****/ + ExaAnn_ListExamAnnouncementsEdit (); + } + +/*****************************************************************************/ +/***** Update number of users notified in table of exam announcements ********/ +/*****************************************************************************/ + +static void ExaAnn_UpdateNumUsrsNotifiedByEMailAboutExamAnnouncement (long ExaCod,unsigned NumUsrsToBeNotifiedByEMail) + { + /***** Update number of users notified *****/ + DB_QueryUPDATE ("can not update the number of notifications" + " of an exam announcement", + "UPDATE exam_announcements SET NumNotif=NumNotif+%u" + " WHERE ExaCod=%ld", + NumUsrsToBeNotifiedByEMail,ExaCod); + } + +/*****************************************************************************/ +/************************* Print an exam announcement ************************/ +/*****************************************************************************/ + +void ExaAnn_PrintExamAnnouncement (void) + { + struct ExaAnn_ExamAnnouncements ExamAnns; + long ExaCod; + + /***** Reset exam announcements context *****/ + ExaAnn_ResetExamAnnouncements (&ExamAnns); + + /***** Allocate memory for the exam announcement *****/ + ExaAnn_AllocMemExamAnnouncement (&ExamAnns); + + /***** Get the code of the exam announcement *****/ + if ((ExaCod = ExaAnn_GetParamExaCod ()) <= 0) + Lay_ShowErrorAndExit ("Code of exam announcement is missing."); + + /***** Read exam announcement from the database *****/ + ExaAnn_GetDataExamAnnouncementFromDB (&ExamAnns,ExaCod); + + /***** Show exam announcement *****/ + ExaAnn_ShowExamAnnouncement (&ExamAnns,ExaCod,ExaAnn_PRINT_VIEW, + false); // Don't highlight + + /***** Free memory of the exam announcement *****/ + ExaAnn_FreeMemExamAnnouncement (&ExamAnns); + } + +/*****************************************************************************/ +/************************ Remove an exam announcement ************************/ +/*****************************************************************************/ + +void ExaAnn_ReqRemoveExamAnnouncement (void) + { + extern const char *Txt_Do_you_really_want_to_remove_the_following_announcement_of_exam; + extern const char *Txt_Remove; + struct ExaAnn_ExamAnnouncements ExamAnns; + long ExaCod; + + /***** Reset exam announcements context *****/ + ExaAnn_ResetExamAnnouncements (&ExamAnns); + + /***** Get the code of the exam announcement *****/ + if ((ExaCod = ExaAnn_GetParamExaCod ()) <= 0) + Lay_ShowErrorAndExit ("Code of exam announcement is missing."); + + /***** Show question and button to remove exam announcement *****/ + /* Start alert */ + Ale_ShowAlertAndButton1 (Ale_QUESTION,Txt_Do_you_really_want_to_remove_the_following_announcement_of_exam); + + /* Show announcement */ + ExaAnn_AllocMemExamAnnouncement (&ExamAnns); + ExaAnn_GetDataExamAnnouncementFromDB (&ExamAnns,ExaCod); + ExaAnn_ShowExamAnnouncement (&ExamAnns,ExaCod,ExaAnn_NORMAL_VIEW, + false); // Don't highlight + ExaAnn_FreeMemExamAnnouncement (&ExamAnns); + + /* End alert */ + + Ale_ShowAlertAndButton2 (ActRemExaAnn,NULL,NULL, + ExaAnn_PutParamExaCodToEdit,&ExamAnns.ExaCod, + Btn_REMOVE_BUTTON,Txt_Remove); + } + +/*****************************************************************************/ +/************************ Remove an exam announcement ************************/ +/*****************************************************************************/ +// This function is splitted into a-priori and a-posteriori functions +// in order to view updated links in month of left column + +void ExaAnn_RemoveExamAnnouncement1 (void) + { + struct ExaAnn_ExamAnnouncements *ExamAnns = ExaAnn_GetGlobalExamAnnouncements (); + long ExaCod; + + /***** Reset exam announcements context *****/ + ExaAnn_ResetExamAnnouncements (ExamAnns); + + /***** Get the code of the exam announcement *****/ + if ((ExaCod = ExaAnn_GetParamExaCod ()) <= 0) + Lay_ShowErrorAndExit ("Code of exam announcement is missing."); + + /***** Mark the exam announcement as deleted in the database *****/ + DB_QueryUPDATE ("can not remove exam announcement", + "UPDATE exam_announcements SET Status=%u" + " WHERE ExaCod=%ld AND CrsCod=%ld", + (unsigned) ExaAnn_DELETED_EXAM_ANNOUNCEMENT, + ExaCod,Gbl.Hierarchy.Crs.CrsCod); + + /***** Mark possible notifications as removed *****/ + Ntf_MarkNotifAsRemoved (Ntf_EVENT_EXAM_ANNOUNCEMENT,ExaCod); + + /***** Mark possible social note as unavailable *****/ + TL_MarkNoteAsUnavailableUsingNoteTypeAndCod (TL_NOTE_EXAM_ANNOUNCEMENT,ExaCod); + + /***** Update RSS of current course *****/ + RSS_UpdateRSSFileForACrs (&Gbl.Hierarchy.Crs); + } + +void ExaAnn_RemoveExamAnnouncement2 (void) + { + extern const char *Txt_Announcement_of_exam_removed; + + /***** Write message *****/ + Ale_ShowAlert (Ale_SUCCESS,Txt_Announcement_of_exam_removed); + + /***** List again all the remaining exam announcements *****/ + ExaAnn_ListExamAnnouncementsEdit (); + } + +/*****************************************************************************/ +/************************ Hide an exam announcement **************************/ +/*****************************************************************************/ +// This function is splitted into a-priori and a-posteriori functions +// in order to view updated links in month of left column + +void ExaAnn_HideExamAnnouncement (void) + { + struct ExaAnn_ExamAnnouncements *ExamAnns = ExaAnn_GetGlobalExamAnnouncements (); + long ExaCod; + + /***** Reset exam announcements context *****/ + ExaAnn_ResetExamAnnouncements (ExamAnns); + + /***** Get the code of the exam announcement *****/ + if ((ExaCod = ExaAnn_GetParamExaCod ()) <= 0) + Lay_ShowErrorAndExit ("Code of exam announcement is missing."); + + /***** Mark the exam announcement as hidden in the database *****/ + DB_QueryUPDATE ("can not hide exam announcement", + "UPDATE exam_announcements SET Status=%u" + " WHERE ExaCod=%ld AND CrsCod=%ld", + (unsigned) ExaAnn_HIDDEN_EXAM_ANNOUNCEMENT, + ExaCod,Gbl.Hierarchy.Crs.CrsCod); + + /***** Set exam to be highlighted *****/ + ExamAnns->HighlightExaCod = ExaCod; + } + +/*****************************************************************************/ +/************************ Unhide an exam announcement ************************/ +/*****************************************************************************/ +// This function is splitted into a-priori and a-posteriori functions +// in order to view updated links in month of left column + +void ExaAnn_UnhideExamAnnouncement (void) + { + struct ExaAnn_ExamAnnouncements *ExamAnns = ExaAnn_GetGlobalExamAnnouncements (); + long ExaCod; + + /***** Reset exam announcements context *****/ + ExaAnn_ResetExamAnnouncements (ExamAnns); + + /***** Get the code of the exam announcement *****/ + if ((ExaCod = ExaAnn_GetParamExaCod ()) <= 0) + Lay_ShowErrorAndExit ("Code of exam announcement is missing."); + + /***** Mark the exam announcement as visible in the database *****/ + DB_QueryUPDATE ("can not unhide exam announcement", + "UPDATE exam_announcements SET Status=%u" + " WHERE ExaCod=%ld AND CrsCod=%ld", + (unsigned) ExaAnn_VISIBLE_EXAM_ANNOUNCEMENT, + ExaCod,Gbl.Hierarchy.Crs.CrsCod); + + /***** Set exam to be highlighted *****/ + ExamAnns->HighlightExaCod = ExaCod; + } + +/*****************************************************************************/ +/*************** List all the exam announcements to see them *****************/ +/*****************************************************************************/ + +void ExaAnn_ListExamAnnouncementsSee (void) + { + struct ExaAnn_ExamAnnouncements ExamAnns; + + /***** Reset exam announcements context *****/ + ExaAnn_ResetExamAnnouncements (&ExamAnns); + + /***** List all exam announcements *****/ + ExaAnn_ListExamAnnouncements (&ExamAnns,ExaAnn_NORMAL_VIEW); + + /***** Mark possible notifications as seen *****/ + Ntf_MarkNotifAsSeen (Ntf_EVENT_EXAM_ANNOUNCEMENT, + -1L,Gbl.Hierarchy.Crs.CrsCod, + Gbl.Usrs.Me.UsrDat.UsrCod); + } + +/*****************************************************************************/ +/********** List all the exam announcements to edit or remove them ***********/ +/*****************************************************************************/ + +void ExaAnn_ListExamAnnouncementsEdit (void) + { + struct ExaAnn_ExamAnnouncements *ExamAnns = ExaAnn_GetGlobalExamAnnouncements (); + + ExaAnn_ListExamAnnouncements (ExamAnns,ExaAnn_NORMAL_VIEW); + } + +/*****************************************************************************/ +/********** List exam announcement given an exam announcement code ***********/ +/*****************************************************************************/ + +void ExaAnn_ListExamAnnouncementsCod (void) + { + struct ExaAnn_ExamAnnouncements ExamAnns; + + /***** Reset exam announcements context *****/ + ExaAnn_ResetExamAnnouncements (&ExamAnns); + + /***** Get exam announcement code *****/ + ExaAnn_GetExaCodToHighlight (&ExamAnns); + + /***** List all exam announcements *****/ + ExaAnn_ListExamAnnouncements (&ExamAnns,ExaAnn_NORMAL_VIEW); + } + +/*****************************************************************************/ +/***************** List exam announcements on a given date *******************/ +/*****************************************************************************/ + +void ExaAnn_ListExamAnnouncementsDay (void) + { + struct ExaAnn_ExamAnnouncements ExamAnns; + + /***** Reset exam announcements context *****/ + ExaAnn_ResetExamAnnouncements (&ExamAnns); + + /***** Get date *****/ + ExaAnn_GetDateToHighlight (&ExamAnns); + + /***** List all exam announcements *****/ + ExaAnn_ListExamAnnouncements (&ExamAnns,ExaAnn_NORMAL_VIEW); + } + +/*****************************************************************************/ +/*********** Get date of exam announcements to show highlighted **************/ +/*****************************************************************************/ + +static void ExaAnn_GetExaCodToHighlight (struct ExaAnn_ExamAnnouncements *ExamAnns) + { + /***** Get the exam announcement code + of the exam announcement to highlight *****/ + ExamAnns->HighlightExaCod = ExaAnn_GetParamExaCod (); + } + +/*****************************************************************************/ +/*********** Get date of exam announcements to show highlighted **************/ +/*****************************************************************************/ + +static void ExaAnn_GetDateToHighlight (struct ExaAnn_ExamAnnouncements *ExamAnns) + { + /***** Get the date (in YYYYMMDD format) + of the exam announcements to highlight *****/ + Par_GetParToText ("Date",ExamAnns->HighlightDate,4 + 2 + 2); + } + +/*****************************************************************************/ +/******************** List all the exam announcements ************************/ +/*****************************************************************************/ + +static void ExaAnn_ListExamAnnouncements (struct ExaAnn_ExamAnnouncements *ExamAnns, + ExaAnn_TypeViewExamAnnouncement_t TypeViewExamAnnouncement) + { + extern const char *Hlp_ASSESSMENT_Announcements; + extern const char *Txt_Announcements_of_exams; + extern const char *Txt_No_announcements_of_exams_of_X; + char SubQueryStatus[64]; + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned long NumExaAnn; + unsigned long NumExaAnns; + long ExaCod; + bool HighLight; + bool ICanEdit = (Gbl.Usrs.Me.Role.Logged == Rol_TCH || + Gbl.Usrs.Me.Role.Logged == Rol_SYS_ADM); + + /***** Build subquery about status depending on my role *****/ + if (ICanEdit) + sprintf (SubQueryStatus,"Status<>%u", + (unsigned) ExaAnn_DELETED_EXAM_ANNOUNCEMENT); + else + sprintf (SubQueryStatus,"Status=%u", + (unsigned) ExaAnn_VISIBLE_EXAM_ANNOUNCEMENT); + + /***** Get exam announcements (the most recent first) + in current course from database *****/ + NumExaAnns = DB_QuerySELECT (&mysql_res,"can not get exam announcements" + " in this course for listing", + "SELECT ExaCod" + " FROM exam_announcements" + " WHERE CrsCod=%ld AND %s" + " ORDER BY ExamDate DESC", + Gbl.Hierarchy.Crs.CrsCod,SubQueryStatus); + + /***** Begin box *****/ + if (ICanEdit) + Box_BoxBegin (NULL,Txt_Announcements_of_exams, + ExaAnn_PutIconToCreateNewExamAnnouncement,NULL, + Hlp_ASSESSMENT_Announcements,Box_NOT_CLOSABLE); + else + Box_BoxBegin (NULL,Txt_Announcements_of_exams, + NULL,NULL, + Hlp_ASSESSMENT_Announcements,Box_NOT_CLOSABLE); + + /***** The result of the query may be empty *****/ + if (!NumExaAnns) + Ale_ShowAlert (Ale_INFO,Txt_No_announcements_of_exams_of_X, + Gbl.Hierarchy.Crs.FullName); + + /***** List the existing exam announcements *****/ + for (NumExaAnn = 0; + NumExaAnn < NumExaAnns; + NumExaAnn++) + { + /***** Get the code of the exam announcement (row[0]) *****/ + row = mysql_fetch_row (mysql_res); + + if (sscanf (row[0],"%ld",&ExaCod) != 1) + Lay_ShowErrorAndExit ("Wrong code of exam announcement."); + + /***** Allocate memory for the exam announcement *****/ + ExaAnn_AllocMemExamAnnouncement (ExamAnns); + + /***** Read the data of the exam announcement *****/ + ExaAnn_GetDataExamAnnouncementFromDB (ExamAnns,ExaCod); + + /***** Show exam announcement *****/ + HighLight = false; + if (ExaCod == ExamAnns->HighlightExaCod) + HighLight = true; + else if (ExamAnns->HighlightDate[0]) + { + if (!strcmp (ExamAnns->ExamAnn.ExamDate.YYYYMMDD, + ExamAnns->HighlightDate)) + HighLight = true; + } + ExaAnn_ShowExamAnnouncement (ExamAnns,ExaCod,TypeViewExamAnnouncement, + HighLight); + + /***** Free memory of the exam announcement *****/ + ExaAnn_FreeMemExamAnnouncement (ExamAnns); + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + /***** Button to create a new exam announcement *****/ + if (ICanEdit) + ExaAnn_PutButtonToCreateNewExamAnnouncement (); + + /***** End box *****/ + Box_BoxEnd (); + } + +/*****************************************************************************/ +/***************** Put icon to create a new exam announcement ****************/ +/*****************************************************************************/ + +static void ExaAnn_PutIconToCreateNewExamAnnouncement (__attribute__((unused)) void *Args) + { + extern const char *Txt_New_announcement_OF_EXAM; + + Ico_PutContextualIconToAdd (ActEdiExaAnn,NULL, + NULL,NULL, + Txt_New_announcement_OF_EXAM); + } + +/*****************************************************************************/ +/**************** Put button to create a new exam announcement ***************/ +/*****************************************************************************/ + +static void ExaAnn_PutButtonToCreateNewExamAnnouncement (void) + { + extern const char *Txt_New_announcement_OF_EXAM; + + Frm_StartForm (ActEdiExaAnn); + Btn_PutConfirmButton (Txt_New_announcement_OF_EXAM); + Frm_EndForm (); + } + +/*****************************************************************************/ +/****************** Add an exam announcement to the database *****************/ +/*****************************************************************************/ +// Return the code of the exam announcement just added + +static long ExaAnn_AddExamAnnouncementToDB (const struct ExaAnn_ExamAnnouncements *ExamAnns) + { + long ExaCod; + + /***** Add exam announcement *****/ + ExaCod = + DB_QueryINSERTandReturnCode ("can not create a new exam announcement", + "INSERT INTO exam_announcements " + "(CrsCod,Status,NumNotif,CrsFullName,Year,ExamSession," + "CallDate,ExamDate,Duration," + "Place,ExamMode,Structure,DocRequired,MatRequired,MatAllowed,OtherInfo)" + " VALUES " + "(%ld,%u,0,'%s',%u,'%s'," + "NOW(),'%04u-%02u-%02u %02u:%02u:00','%02u:%02u:00','%s'," + "'%s','%s','%s','%s','%s','%s')", + Gbl.Hierarchy.Crs.CrsCod, + (unsigned) ExaAnn_VISIBLE_EXAM_ANNOUNCEMENT, + ExamAnns->ExamAnn.CrsFullName, + ExamAnns->ExamAnn.Year, + ExamAnns->ExamAnn.Session, + ExamAnns->ExamAnn.ExamDate.Year, + ExamAnns->ExamAnn.ExamDate.Month, + ExamAnns->ExamAnn.ExamDate.Day, + ExamAnns->ExamAnn.StartTime.Hour, + ExamAnns->ExamAnn.StartTime.Minute, + ExamAnns->ExamAnn.Duration.Hour, + ExamAnns->ExamAnn.Duration.Minute, + ExamAnns->ExamAnn.Place, + ExamAnns->ExamAnn.Mode, + ExamAnns->ExamAnn.Structure, + ExamAnns->ExamAnn.DocRequired, + ExamAnns->ExamAnn.MatRequired, + ExamAnns->ExamAnn.MatAllowed, + ExamAnns->ExamAnn.OtherInfo); + + return ExaCod; + } + +/*****************************************************************************/ +/*************** Modify an exam announcement in the database *****************/ +/*****************************************************************************/ + +static void ExaAnn_ModifyExamAnnouncementInDB (const struct ExaAnn_ExamAnnouncements *ExamAnns, + long ExaCod) + { + /***** Modify exam announcement *****/ + DB_QueryUPDATE ("can not update an exam announcement", + "UPDATE exam_announcements" + " SET CrsFullName='%s',Year=%u,ExamSession='%s'," + "ExamDate='%04u-%02u-%02u %02u:%02u:00'," + "Duration='%02u:%02u:00'," + "Place='%s',ExamMode='%s',Structure='%s'," + "DocRequired='%s',MatRequired='%s',MatAllowed='%s',OtherInfo='%s'" + " WHERE ExaCod=%ld", + ExamAnns->ExamAnn.CrsFullName, + ExamAnns->ExamAnn.Year, + ExamAnns->ExamAnn.Session, + ExamAnns->ExamAnn.ExamDate.Year, + ExamAnns->ExamAnn.ExamDate.Month, + ExamAnns->ExamAnn.ExamDate.Day, + ExamAnns->ExamAnn.StartTime.Hour, + ExamAnns->ExamAnn.StartTime.Minute, + ExamAnns->ExamAnn.Duration.Hour, + ExamAnns->ExamAnn.Duration.Minute, + ExamAnns->ExamAnn.Place, + ExamAnns->ExamAnn.Mode, + ExamAnns->ExamAnn.Structure, + ExamAnns->ExamAnn.DocRequired, + ExamAnns->ExamAnn.MatRequired, + ExamAnns->ExamAnn.MatAllowed, + ExamAnns->ExamAnn.OtherInfo, + ExaCod); + } + +/*****************************************************************************/ +/******* Create a list with the dates of all the exam announcements **********/ +/*****************************************************************************/ + +void ExaAnn_CreateListExamAnnouncements (struct ExaAnn_ExamAnnouncements *ExamAnns) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned long NumExaAnn; + unsigned long NumExaAnns; + + if (Gbl.DB.DatabaseIsOpen) + { + /***** Get exam dates (ordered from more recent to older) + of visible exam announcements + in current course from database *****/ + NumExaAnns = DB_QuerySELECT (&mysql_res,"can not get exam announcements" + " in this course", + "SELECT ExaCod,DATE(ExamDate)" + " FROM exam_announcements" + " WHERE CrsCod=%ld AND Status=%u" + " ORDER BY ExamDate DESC", + Gbl.Hierarchy.Crs.CrsCod, + (unsigned) ExaAnn_VISIBLE_EXAM_ANNOUNCEMENT); + + /***** The result of the query may be empty *****/ + ExamAnns->Lst = NULL; + ExamAnns->NumExaAnns = 0; + if (NumExaAnns) + { + /***** Allocate memory for the list *****/ + if ((ExamAnns->Lst = (struct ExaAnn_ExamCodeAndDate *) calloc (NumExaAnns,sizeof (struct ExaAnn_ExamCodeAndDate))) == NULL) + Lay_NotEnoughMemoryExit (); + + /***** Get the dates of the existing exam announcements *****/ + for (NumExaAnn = 0; + NumExaAnn < NumExaAnns; + NumExaAnn++) + { + /***** Get next exam announcement *****/ + row = mysql_fetch_row (mysql_res); + + /* Get exam code (row[0]) */ + ExamAnns->Lst[ExamAnns->NumExaAnns].ExaCod = Str_ConvertStrCodToLongCod (row[0]); + + /* Read the date of the exam (row[1]) */ + if (sscanf (row[1],"%04u-%02u-%02u", + &ExamAnns->Lst[ExamAnns->NumExaAnns].ExamDate.Year, + &ExamAnns->Lst[ExamAnns->NumExaAnns].ExamDate.Month, + &ExamAnns->Lst[ExamAnns->NumExaAnns].ExamDate.Day) != 3) + Lay_ShowErrorAndExit ("Wrong date of exam."); + + /***** Increment number of elements in list *****/ + ExamAnns->NumExaAnns++; + } + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + } + +/*****************************************************************************/ +/***************** Free list of dates of exam announcements ******************/ +/*****************************************************************************/ + +void ExaAnn_FreeListExamAnnouncements (struct ExaAnn_ExamAnnouncements *ExamAnns) + { + if (ExamAnns->Lst) + { + free (ExamAnns->Lst); + ExamAnns->Lst = NULL; + ExamAnns->NumExaAnns = 0; + } + } + +/*****************************************************************************/ +/******** Read the data of an exam announcement from the database ************/ +/*****************************************************************************/ + +static void ExaAnn_GetDataExamAnnouncementFromDB (struct ExaAnn_ExamAnnouncements *ExamAnns, + long ExaCod) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned long NumExaAnns; + unsigned UnsignedNum; + unsigned Hour; + unsigned Minute; + unsigned Second; + + /***** Get data of an exam announcement from database *****/ + NumExaAnns = DB_QuerySELECT (&mysql_res,"can not get data" + " of an exam announcement", + "SELECT CrsCod,Status,CrsFullName,Year,ExamSession," + "CallDate,ExamDate,Duration,Place,ExamMode," + "Structure,DocRequired,MatRequired,MatAllowed,OtherInfo" + " FROM exam_announcements WHERE ExaCod=%ld", + ExaCod); + + /***** The result of the query must have one row *****/ + if (NumExaAnns != 1) + Lay_ShowErrorAndExit ("Error when getting data of an exam announcement."); + + /***** Get the data of the exam announcement *****/ + row = mysql_fetch_row (mysql_res); + + /* Code of the course in which the exam announcement is inserted (row[0]) */ + ExamAnns->ExamAnn.CrsCod = Str_ConvertStrCodToLongCod (row[0]); + + /* Status of the exam announcement (row[1]) */ + if (sscanf (row[1],"%u",&UnsignedNum) != 1) + Lay_ShowErrorAndExit ("Wrong status."); + if (UnsignedNum >= ExaAnn_NUM_STATUS) + Lay_ShowErrorAndExit ("Wrong status."); + ExamAnns->ExamAnn.Status = (ExaAnn_Status_t) UnsignedNum; + + /* Name of the course (row[2]) */ + Str_Copy (ExamAnns->ExamAnn.CrsFullName,row[2], + Hie_MAX_BYTES_FULL_NAME); + + /* Year (row[3]) */ + if (sscanf (row[3],"%u",&ExamAnns->ExamAnn.Year) != 1) + Lay_ShowErrorAndExit ("Wrong year."); + + /* Exam session (row[4]) */ + Str_Copy (ExamAnns->ExamAnn.Session,row[4], + ExaAnn_MAX_BYTES_SESSION); + + /* Date of exam announcement (row[5]) */ + if (sscanf (row[5],"%04u-%02u-%02u %02u:%02u:%02u", + &ExamAnns->ExamAnn.CallDate.Year, + &ExamAnns->ExamAnn.CallDate.Month, + &ExamAnns->ExamAnn.CallDate.Day, + &Hour, + &Minute, + &Second) != 6) + Lay_ShowErrorAndExit ("Wrong date of exam announcement."); + + /* Date of exam (row[6]) */ + if (sscanf (row[6],"%04u-%02u-%02u %02u:%02u:%02u", + &ExamAnns->ExamAnn.ExamDate.Year, + &ExamAnns->ExamAnn.ExamDate.Month, + &ExamAnns->ExamAnn.ExamDate.Day, + &ExamAnns->ExamAnn.StartTime.Hour, + &ExamAnns->ExamAnn.StartTime.Minute, + &Second) != 6) + Lay_ShowErrorAndExit ("Wrong date of exam."); + snprintf (ExamAnns->ExamAnn.ExamDate.YYYYMMDD,sizeof (ExamAnns->ExamAnn.ExamDate.YYYYMMDD), + "%04u%02u%02u", + ExamAnns->ExamAnn.ExamDate.Year, + ExamAnns->ExamAnn.ExamDate.Month, + ExamAnns->ExamAnn.ExamDate.Day); + + /* Approximate duration (row[7]) */ + if (sscanf (row[7],"%02u:%02u:%02u",&ExamAnns->ExamAnn.Duration.Hour,&ExamAnns->ExamAnn.Duration.Minute,&Second) != 3) + Lay_ShowErrorAndExit ("Wrong duration of exam."); + + /* Place (row[8]) */ + Str_Copy (ExamAnns->ExamAnn.Place,row[8], + Cns_MAX_BYTES_TEXT); + + /* Exam mode (row[9]) */ + Str_Copy (ExamAnns->ExamAnn.Mode,row[9], + Cns_MAX_BYTES_TEXT); + + /* Structure (row[10]) */ + Str_Copy (ExamAnns->ExamAnn.Structure,row[10], + Cns_MAX_BYTES_TEXT); + + /* Documentation required (row[11]) */ + Str_Copy (ExamAnns->ExamAnn.DocRequired,row[11], + Cns_MAX_BYTES_TEXT); + + /* Material required (row[12]) */ + Str_Copy (ExamAnns->ExamAnn.MatRequired,row[12], + Cns_MAX_BYTES_TEXT); + + /* Material allowed (row[13]) */ + Str_Copy (ExamAnns->ExamAnn.MatAllowed,row[13], + Cns_MAX_BYTES_TEXT); + + /* Other information for students (row[14]) */ + Str_Copy (ExamAnns->ExamAnn.OtherInfo,row[14], + Cns_MAX_BYTES_TEXT); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/************ Show a form with the data of an exam announcement **************/ +/*****************************************************************************/ + +static void ExaAnn_ShowExamAnnouncement (struct ExaAnn_ExamAnnouncements *ExamAnns, + long ExaCod, + ExaAnn_TypeViewExamAnnouncement_t TypeViewExamAnnouncement, + bool HighLight) + { + extern const char *Hlp_ASSESSMENT_Announcements_new_announcement; + extern const char *Hlp_ASSESSMENT_Announcements_edit_announcement; + extern const char *Txt_YEAR_OF_DEGREE[1 + Deg_MAX_YEARS_PER_DEGREE]; + extern const char *Txt_EXAM_ANNOUNCEMENT; + extern const char *Txt_EXAM_ANNOUNCEMENT_Course; + extern const char *Txt_EXAM_ANNOUNCEMENT_Year_or_semester; + extern const char *Txt_EXAM_ANNOUNCEMENT_Session; + extern const char *Txt_EXAM_ANNOUNCEMENT_Exam_date; + extern const char *Txt_EXAM_ANNOUNCEMENT_Start_time; + extern const char *Txt_EXAM_ANNOUNCEMENT_Approximate_duration; + extern const char *Txt_EXAM_ANNOUNCEMENT_Place_of_exam; + extern const char *Txt_EXAM_ANNOUNCEMENT_Mode; + extern const char *Txt_EXAM_ANNOUNCEMENT_Structure_of_the_exam; + extern const char *Txt_EXAM_ANNOUNCEMENT_Documentation_required; + extern const char *Txt_EXAM_ANNOUNCEMENT_Material_required; + extern const char *Txt_EXAM_ANNOUNCEMENT_Material_allowed; + extern const char *Txt_EXAM_ANNOUNCEMENT_Other_information; + extern const char *Txt_hours_ABBREVIATION; + extern const char *Txt_hour; + extern const char *Txt_hours; + extern const char *Txt_minute; + extern const char *Txt_minutes; + extern const char *Txt_Publish_announcement_OF_EXAM; + struct Instit Ins; + char StrExamDate[Cns_MAX_BYTES_DATE + 1]; + unsigned Year; + unsigned Hour; + unsigned Minute; + char *Anchor = NULL; + const char *Width; + void (*FunctionToDrawContextualIcons) (void *Args); + const char *HelpLink; + static const char *ClassExaAnnouncement[ExaAnn_NUM_VIEWS][ExaAnn_NUM_STATUS] = + { + [ExaAnn_NORMAL_VIEW][ExaAnn_VISIBLE_EXAM_ANNOUNCEMENT] = "EXA_ANN_VISIBLE", + [ExaAnn_NORMAL_VIEW][ExaAnn_HIDDEN_EXAM_ANNOUNCEMENT ] = "EXA_ANN_HIDDEN", + [ExaAnn_NORMAL_VIEW][ExaAnn_DELETED_EXAM_ANNOUNCEMENT] = NULL, // Not applicable here + + [ExaAnn_PRINT_VIEW ][ExaAnn_VISIBLE_EXAM_ANNOUNCEMENT] = "EXA_ANN_VISIBLE", + [ExaAnn_PRINT_VIEW ][ExaAnn_HIDDEN_EXAM_ANNOUNCEMENT ] = "EXA_ANN_VISIBLE", + [ExaAnn_PRINT_VIEW ][ExaAnn_DELETED_EXAM_ANNOUNCEMENT] = NULL, // Not applicable here + + [ExaAnn_FORM_VIEW ][ExaAnn_VISIBLE_EXAM_ANNOUNCEMENT] = "EXA_ANN_VISIBLE", + [ExaAnn_FORM_VIEW ][ExaAnn_HIDDEN_EXAM_ANNOUNCEMENT ] = "EXA_ANN_VISIBLE", + [ExaAnn_FORM_VIEW ][ExaAnn_DELETED_EXAM_ANNOUNCEMENT] = NULL, // Not applicable here + }; + + /***** Get data of institution of this degree *****/ + Ins.InsCod = Gbl.Hierarchy.Ins.InsCod; + Ins_GetDataOfInstitutionByCod (&Ins); + + /***** Build anchor string *****/ + Frm_SetAnchorStr (ExaCod,&Anchor); + + /***** Begin article *****/ + if (TypeViewExamAnnouncement == ExaAnn_NORMAL_VIEW) + HTM_ARTICLE_Begin (Anchor); + + /***** Begin box *****/ + Width = "625px"; + ExamAnns->Anchor = Anchor; // Used to put contextual icons + ExamAnns->ExaCod = ExaCod; // Used to put contextual icons + FunctionToDrawContextualIcons = TypeViewExamAnnouncement == ExaAnn_NORMAL_VIEW ? ExaAnn_PutIconsExamAnnouncement : + NULL; + HelpLink = TypeViewExamAnnouncement == ExaAnn_FORM_VIEW ? ((ExaCod > 0) ? Hlp_ASSESSMENT_Announcements_edit_announcement : + Hlp_ASSESSMENT_Announcements_new_announcement) : + NULL; + if (HighLight) + { + /* Show pending alerts */ + Ale_ShowAlerts (Anchor); + + /* Start highlighted box */ + Box_BoxShadowBegin (Width,NULL, + FunctionToDrawContextualIcons,ExamAnns, + HelpLink); + } + else // Don't highlight + /* Start normal box */ + Box_BoxBegin (Width,NULL, + FunctionToDrawContextualIcons,ExamAnns, + HelpLink,Box_NOT_CLOSABLE); + + if (TypeViewExamAnnouncement == ExaAnn_FORM_VIEW) + { + /***** Begin form *****/ + Frm_StartFormAnchor (ActRcvExaAnn,Anchor); + if (ExaCod > 0) // Existing announcement of exam + ExaAnn_PutHiddenParamExaCod (ExaCod); + } + + /***** Begin table *****/ + HTM_TABLE_Begin ("%s CELLS_PAD_2", + ClassExaAnnouncement[TypeViewExamAnnouncement][ExamAnns->ExamAnn.Status]); + + /***** Institution logo *****/ + HTM_TR_Begin (NULL); + HTM_TD_Begin ("colspan=\"2\" class=\"CM\""); + if (TypeViewExamAnnouncement == ExaAnn_PRINT_VIEW) + HTM_SPAN_Begin ("class=\"EXAM_TIT\""); + else + HTM_A_Begin ("href=\"%s\" target=\"_blank\" class=\"EXAM_TIT\"", + Ins.WWW); + Lgo_DrawLogo (Hie_INS,Ins.InsCod,Ins.FullName,64,NULL,true); + HTM_BR (); + HTM_Txt (Ins.FullName); + if (TypeViewExamAnnouncement == ExaAnn_PRINT_VIEW) + HTM_SPAN_End (); + else + HTM_A_End (); + HTM_TD_End (); + HTM_TR_End (); + + /***** Degree *****/ + HTM_TR_Begin (NULL); + HTM_TD_Begin ("colspan=\"2\" class=\"EXAM_TIT CM\""); + if (TypeViewExamAnnouncement == ExaAnn_NORMAL_VIEW) + HTM_A_Begin ("href=\"%s\" target=\"_blank\" class=\"EXAM_TIT\"", + Gbl.Hierarchy.Deg.WWW); + HTM_Txt (Gbl.Hierarchy.Deg.FullName); + if (TypeViewExamAnnouncement == ExaAnn_NORMAL_VIEW) + HTM_A_End (); + HTM_TD_End (); + HTM_TR_End (); + + /***** Title *****/ + HTM_TR_Begin (NULL); + HTM_TD_Begin ("colspan=\"2\" class=\"EXAM CM\""); + HTM_NBSP (); + HTM_BR (); + HTM_STRONG_Begin (); + HTM_Txt (Txt_EXAM_ANNOUNCEMENT); + HTM_STRONG_End (); + HTM_TD_End (); + HTM_TR_End (); + + HTM_TR_Begin (NULL); + HTM_TD_Begin ("colspan=\"2\" class=\"EXAM LM\""); + HTM_NBSP (); + HTM_TD_End (); + HTM_TR_End (); + + /***** Name of the course *****/ + HTM_TR_Begin (NULL); + + /* Label */ + Frm_LabelColumn ("RT", + TypeViewExamAnnouncement == ExaAnn_FORM_VIEW ? "CrsName" : + NULL, + Txt_EXAM_ANNOUNCEMENT_Course); + + /* Data */ + HTM_TD_Begin ("class=\"EXAM LT\""); + if (TypeViewExamAnnouncement == ExaAnn_FORM_VIEW) + HTM_INPUT_TEXT ("CrsName",Hie_MAX_CHARS_FULL_NAME,ExamAnns->ExamAnn.CrsFullName,false, + "id=\"CrsName\" size=\"30\""); + else + { + HTM_STRONG_Begin (); + HTM_Txt (ExamAnns->ExamAnn.CrsFullName); + HTM_STRONG_End (); + } + HTM_TD_End (); + + HTM_TR_End (); + + /***** Year/semester (N.A., 1º, 2º, 3º, 4º, 5º...) *****/ + HTM_TR_Begin (NULL); + + /* Label */ + Frm_LabelColumn ("RT", + TypeViewExamAnnouncement == ExaAnn_FORM_VIEW ? "Year" : + NULL, + Txt_EXAM_ANNOUNCEMENT_Year_or_semester); + + /* Data */ + HTM_TD_Begin ("class=\"EXAM LT\""); + if (TypeViewExamAnnouncement == ExaAnn_FORM_VIEW) + { + HTM_SELECT_Begin (false, + "id=\"Year\" name=\"Year\""); + for (Year = 0; + Year <= Deg_MAX_YEARS_PER_DEGREE; + Year++) + HTM_OPTION (HTM_Type_UNSIGNED,&Year, + ExamAnns->ExamAnn.Year == Year,false, + "%s",Txt_YEAR_OF_DEGREE[Year]); + HTM_SELECT_End (); + } + else + HTM_Txt (Txt_YEAR_OF_DEGREE[ExamAnns->ExamAnn.Year]); + HTM_TD_End (); + + HTM_TR_End (); + + /***** Exam session *****/ + HTM_TR_Begin (NULL); + + /* Label */ + Frm_LabelColumn ("RT", + TypeViewExamAnnouncement == ExaAnn_FORM_VIEW ? "ExamSession" : + NULL, + Txt_EXAM_ANNOUNCEMENT_Session); + + /* Data */ + HTM_TD_Begin ("class=\"EXAM LT\""); + if (TypeViewExamAnnouncement == ExaAnn_FORM_VIEW) + HTM_INPUT_TEXT ("ExamSession",ExaAnn_MAX_CHARS_SESSION,ExamAnns->ExamAnn.Session,false, + "id=\"ExamSession\" size=\"30\""); + else + HTM_Txt (ExamAnns->ExamAnn.Session); + HTM_TD_End (); + + HTM_TR_End (); + + /***** Date of the exam *****/ + HTM_TR_Begin (NULL); + + /* Label */ + Frm_LabelColumn ("RT",NULL,Txt_EXAM_ANNOUNCEMENT_Exam_date); + + /* Data */ + if (TypeViewExamAnnouncement == ExaAnn_FORM_VIEW) + { + HTM_TD_Begin ("class=\"LT\""); + Dat_WriteFormDate (ExamAnns->ExamAnn.ExamDate.Year < Gbl.Now.Date.Year ? ExamAnns->ExamAnn.ExamDate.Year : + Gbl.Now.Date.Year, + Gbl.Now.Date.Year + 1,"Exam", + &(ExamAnns->ExamAnn.ExamDate), + false,false); + HTM_TD_End (); + } + else + { + Dat_ConvDateToDateStr (&ExamAnns->ExamAnn.ExamDate, + StrExamDate); + HTM_TD_Begin ("class=\"EXAM LT\""); + HTM_Txt (StrExamDate); + HTM_TD_End (); + } + HTM_TR_End (); + + /***** Start time *****/ + HTM_TR_Begin (NULL); + + /* Label */ + Frm_LabelColumn ("RT",NULL,Txt_EXAM_ANNOUNCEMENT_Start_time); + + /* Data */ + HTM_TD_Begin ("class=\"EXAM LT\""); + if (TypeViewExamAnnouncement == ExaAnn_FORM_VIEW) + { + HTM_SELECT_Begin (false, + "name=\"ExamHour\""); + HTM_OPTION (HTM_Type_STRING,"0", + ExamAnns->ExamAnn.StartTime.Hour == 0,false, + "-"); + for (Hour = 7; + Hour <= 22; + Hour++) + HTM_OPTION (HTM_Type_UNSIGNED,&Hour, + ExamAnns->ExamAnn.StartTime.Hour == Hour,false, + "%02u %s",Hour,Txt_hours_ABBREVIATION); + HTM_SELECT_End (); + + HTM_SELECT_Begin (false, + "name=\"ExamMinute\""); + for (Minute = 0; + Minute <= 59; + Minute++) + HTM_OPTION (HTM_Type_UNSIGNED,&Minute, + ExamAnns->ExamAnn.StartTime.Minute == Minute,false, + "%02u ′",Minute); + HTM_SELECT_End (); + } + else if (ExamAnns->ExamAnn.StartTime.Hour) + HTM_TxtF ("%2u:%02u",ExamAnns->ExamAnn.StartTime.Hour, + ExamAnns->ExamAnn.StartTime.Minute); + HTM_TD_End (); + + HTM_TR_End (); + + /***** Approximate duration of the exam *****/ + HTM_TR_Begin (NULL); + + /* Label */ + Frm_LabelColumn ("RT",NULL,Txt_EXAM_ANNOUNCEMENT_Approximate_duration); + + /* Data */ + HTM_TD_Begin ("class=\"EXAM LT\""); + if (TypeViewExamAnnouncement == ExaAnn_FORM_VIEW) + { + HTM_SELECT_Begin (false, + "name=\"DurationHour\""); + for (Hour = 0; + Hour <= 8; + Hour++) + HTM_OPTION (HTM_Type_UNSIGNED,&Hour, + ExamAnns->ExamAnn.Duration.Hour == Hour,false, + "%02u %s",Hour,Txt_hours_ABBREVIATION); + HTM_SELECT_End (); + + HTM_SELECT_Begin (false, + "name=\"DurationMinute\""); + for (Minute = 0; + Minute <= 59; + Minute++) + HTM_OPTION (HTM_Type_UNSIGNED,&Minute, + ExamAnns->ExamAnn.Duration.Minute == Minute,false, + "%02u ′",Minute); + HTM_SELECT_End (); + } + else if (ExamAnns->ExamAnn.Duration.Hour || + ExamAnns->ExamAnn.Duration.Minute) + { + if (ExamAnns->ExamAnn.Duration.Hour) + { + if (ExamAnns->ExamAnn.Duration.Minute) + HTM_TxtF ("%u %s %u ′",ExamAnns->ExamAnn.Duration.Hour, + Txt_hours_ABBREVIATION, + ExamAnns->ExamAnn.Duration.Minute); + else + HTM_TxtF ("%u %s",ExamAnns->ExamAnn.Duration.Hour, + ExamAnns->ExamAnn.Duration.Hour == 1 ? Txt_hour : + Txt_hours); + } + else if (ExamAnns->ExamAnn.Duration.Minute) + { + HTM_TxtF ("%u %s",ExamAnns->ExamAnn.Duration.Minute, + ExamAnns->ExamAnn.Duration.Minute == 1 ? Txt_minute : + Txt_minutes); + } + } + HTM_TD_End (); + + HTM_TR_End (); + + /***** Place where the exam will be made *****/ + HTM_TR_Begin (NULL); + + /* Label */ + Frm_LabelColumn ("RT", + TypeViewExamAnnouncement == ExaAnn_FORM_VIEW ? "Place" : + NULL, + Txt_EXAM_ANNOUNCEMENT_Place_of_exam); + + /* Data */ + HTM_TD_Begin ("class=\"EXAM LT\""); + if (TypeViewExamAnnouncement == ExaAnn_FORM_VIEW) + { + HTM_TEXTAREA_Begin ("id=\"Place\" name=\"Place\" cols=\"40\" rows=\"4\""); + HTM_Txt (ExamAnns->ExamAnn.Place); + HTM_TEXTAREA_End (); + } + else + { + Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, + ExamAnns->ExamAnn.Place, + Cns_MAX_BYTES_TEXT,false); + HTM_Txt (ExamAnns->ExamAnn.Place); + } + HTM_TD_End (); + + HTM_TR_End (); + + /***** Exam mode *****/ + HTM_TR_Begin (NULL); + + /* Label */ + Frm_LabelColumn ("RT", + TypeViewExamAnnouncement == ExaAnn_FORM_VIEW ? "ExamMode" : + NULL, + Txt_EXAM_ANNOUNCEMENT_Mode); + + /* Data */ + HTM_TD_Begin ("class=\"EXAM LT\""); + if (TypeViewExamAnnouncement == ExaAnn_FORM_VIEW) + { + HTM_TEXTAREA_Begin ("id=\"ExamMode\" name=\"ExamMode\" cols=\"40\" rows=\"2\""); + HTM_Txt (ExamAnns->ExamAnn.Mode); + HTM_TEXTAREA_End (); + } + else + { + Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, + ExamAnns->ExamAnn.Mode, + Cns_MAX_BYTES_TEXT,false); + HTM_Txt (ExamAnns->ExamAnn.Mode); + } + HTM_TD_End (); + + HTM_TR_End (); + + /***** Structure of the exam *****/ + HTM_TR_Begin (NULL); + + /* Label */ + Frm_LabelColumn ("RT", + TypeViewExamAnnouncement == ExaAnn_FORM_VIEW ? "Structure" : + NULL, + Txt_EXAM_ANNOUNCEMENT_Structure_of_the_exam); + + /* Data */ + HTM_TD_Begin ("class=\"EXAM LT\""); + if (TypeViewExamAnnouncement == ExaAnn_FORM_VIEW) + { + HTM_TEXTAREA_Begin ("id=\"Structure\" name=\"Structure\" cols=\"40\" rows=\"8\""); + HTM_Txt (ExamAnns->ExamAnn.Structure); + HTM_TEXTAREA_End (); + } + else + { + Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, + ExamAnns->ExamAnn.Structure, + Cns_MAX_BYTES_TEXT,false); + HTM_Txt (ExamAnns->ExamAnn.Structure); + } + HTM_TD_End (); + + HTM_TR_End (); + + /***** Documentation required *****/ + HTM_TR_Begin (NULL); + + /* Label */ + Frm_LabelColumn ("RT", + TypeViewExamAnnouncement == ExaAnn_FORM_VIEW ? "DocRequired" : + NULL, + Txt_EXAM_ANNOUNCEMENT_Documentation_required); + + /* Data */ + HTM_TD_Begin ("class=\"EXAM LT\""); + if (TypeViewExamAnnouncement == ExaAnn_FORM_VIEW) + { + HTM_TEXTAREA_Begin ("id=\"DocRequired\" name=\"DocRequired\" cols=\"40\" rows=\"2\""); + HTM_Txt (ExamAnns->ExamAnn.DocRequired); + HTM_TEXTAREA_End (); + } + else + { + Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, + ExamAnns->ExamAnn.DocRequired, + Cns_MAX_BYTES_TEXT,false); + HTM_Txt (ExamAnns->ExamAnn.DocRequired); + } + HTM_TD_End (); + + HTM_TR_End (); + + /***** Material required *****/ + HTM_TR_Begin (NULL); + + /* Label */ + Frm_LabelColumn ("RT", + TypeViewExamAnnouncement == ExaAnn_FORM_VIEW ? "MatRequired" : + NULL, + Txt_EXAM_ANNOUNCEMENT_Material_required); + + /* Data */ + HTM_TD_Begin ("class=\"EXAM LT\""); + if (TypeViewExamAnnouncement == ExaAnn_FORM_VIEW) + { + HTM_TEXTAREA_Begin ("id=\"MatRequired\" name=\"MatRequired\" cols=\"40\" rows=\"4\""); + HTM_Txt (ExamAnns->ExamAnn.MatRequired); + HTM_TEXTAREA_End (); + } + else + { + Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, + ExamAnns->ExamAnn.MatRequired, + Cns_MAX_BYTES_TEXT,false); + HTM_Txt (ExamAnns->ExamAnn.MatRequired); + } + HTM_TD_End (); + + HTM_TR_End (); + + /***** Material allowed *****/ + HTM_TR_Begin (NULL); + + /* Label */ + Frm_LabelColumn ("RT", + TypeViewExamAnnouncement == ExaAnn_FORM_VIEW ? "MatAllowed" : + NULL, + Txt_EXAM_ANNOUNCEMENT_Material_allowed); + + /* Data */ + HTM_TD_Begin ("class=\"EXAM LT\""); + if (TypeViewExamAnnouncement == ExaAnn_FORM_VIEW) + { + HTM_TEXTAREA_Begin ("id=\"MatAllowed\" name=\"MatAllowed\" cols=\"40\" rows=\"4\""); + HTM_Txt (ExamAnns->ExamAnn.MatAllowed); + HTM_TEXTAREA_End (); + } + else + { + Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, + ExamAnns->ExamAnn.MatAllowed, + Cns_MAX_BYTES_TEXT,false); + HTM_Txt (ExamAnns->ExamAnn.MatAllowed); + } + HTM_TD_End (); + + HTM_TR_End (); + + /***** Other information to students *****/ + HTM_TR_Begin (NULL); + + /* Label */ + Frm_LabelColumn ("RT", + TypeViewExamAnnouncement == ExaAnn_FORM_VIEW ? "OtherInfo" : + NULL, + Txt_EXAM_ANNOUNCEMENT_Other_information); + + /* Data */ + HTM_TD_Begin ("class=\"EXAM LT\""); + if (TypeViewExamAnnouncement == ExaAnn_FORM_VIEW) + { + HTM_TEXTAREA_Begin ("id=\"OtherInfo\" name=\"OtherInfo\" cols=\"40\" rows=\"5\""); + HTM_Txt (ExamAnns->ExamAnn.OtherInfo); + HTM_TEXTAREA_End (); + } + else + { + Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, + ExamAnns->ExamAnn.OtherInfo, + Cns_MAX_BYTES_TEXT,false); + HTM_Txt (ExamAnns->ExamAnn.OtherInfo); + } + HTM_TD_End (); + + HTM_TR_End (); + + /***** End table, send button and end box *****/ + if (TypeViewExamAnnouncement == ExaAnn_FORM_VIEW) + Box_BoxTableWithButtonEnd ((ExaCod > 0) ? Btn_CONFIRM_BUTTON : + Btn_CREATE_BUTTON, + Txt_Publish_announcement_OF_EXAM); + else + Box_BoxTableEnd (); + + /***** Show QR code *****/ + if (TypeViewExamAnnouncement == ExaAnn_PRINT_VIEW) + QR_ExamAnnnouncement (); + + /***** End article *****/ + if (TypeViewExamAnnouncement == ExaAnn_NORMAL_VIEW) + HTM_ARTICLE_End (); + + /***** Free anchor string *****/ + Frm_FreeAnchorStr (Anchor); + } + +/*****************************************************************************/ +/********* Put icons to remove / edit / print an exam announcement ***********/ +/*****************************************************************************/ + +static void ExaAnn_PutIconsExamAnnouncement (void *ExamAnns) + { + if (ExamAnns) + { + if (Gbl.Usrs.Me.Role.Logged == Rol_TCH || + Gbl.Usrs.Me.Role.Logged == Rol_SYS_ADM) + { + /***** Link to remove this exam announcement *****/ + Ico_PutContextualIconToRemove (ActReqRemExaAnn, + ExaAnn_PutParamExaCodToEdit,&((struct ExaAnn_ExamAnnouncements *) ExamAnns)->ExaCod); + + /***** Put form to hide/show exam announement *****/ + switch (((struct ExaAnn_ExamAnnouncements *) ExamAnns)->ExamAnn.Status) + { + case ExaAnn_VISIBLE_EXAM_ANNOUNCEMENT: + Ico_PutContextualIconToHide (ActHidExaAnn,((struct ExaAnn_ExamAnnouncements *) ExamAnns)->Anchor, + ExaAnn_PutParamExaCodToEdit,&((struct ExaAnn_ExamAnnouncements *) ExamAnns)->ExaCod); + break; + case ExaAnn_HIDDEN_EXAM_ANNOUNCEMENT: + Ico_PutContextualIconToUnhide (ActShoExaAnn,((struct ExaAnn_ExamAnnouncements *) ExamAnns)->Anchor, + ExaAnn_PutParamExaCodToEdit,&((struct ExaAnn_ExamAnnouncements *) ExamAnns)->ExaCod); + break; + case ExaAnn_DELETED_EXAM_ANNOUNCEMENT: // Not applicable here + break; + } + + /***** Link to edit this exam announcement *****/ + Ico_PutContextualIconToEdit (ActEdiExaAnn,NULL, + ExaAnn_PutParamExaCodToEdit,&((struct ExaAnn_ExamAnnouncements *) ExamAnns)->ExaCod); + } + + /***** Link to print view *****/ + Ico_PutContextualIconToPrint (ActPrnExaAnn, + ExaAnn_PutParamExaCodToEdit,&((struct ExaAnn_ExamAnnouncements *) ExamAnns)->ExaCod); + } + } + +/*****************************************************************************/ +/*************** Param with the code of an exam announcement *****************/ +/*****************************************************************************/ + +static void ExaAnn_PutParamExaCodToEdit (void *ExaCod) + { + if (ExaCod) + ExaAnn_PutHiddenParamExaCod (*((long *) ExaCod)); + } + +void ExaAnn_PutHiddenParamExaCod (long ExaCod) + { + Par_PutHiddenParamLong (NULL,"ExaCod",ExaCod); + } + +/*****************************************************************************/ +/********** Get parameter with the code of an exam announcement **************/ +/*****************************************************************************/ + +static long ExaAnn_GetParamExaCod (void) + { + /* Get notice code */ + return Par_GetParToLong ("ExaCod"); + } + +/*****************************************************************************/ +/************ Get summary and content about an exam announcement *************/ +/*****************************************************************************/ + +void ExaAnn_GetSummaryAndContentExamAnnouncement (char SummaryStr[Ntf_MAX_BYTES_SUMMARY + 1], + char **ContentStr, + long ExaCod,bool GetContent) + { + extern const char *Txt_hours_ABBREVIATION; + struct ExaAnn_ExamAnnouncements ExamAnns; + char CrsNameAndDate[Hie_MAX_BYTES_FULL_NAME + (2 + Cns_MAX_BYTES_DATE + 7) + 1]; + char StrExamDate[Cns_MAX_BYTES_DATE + 1]; + + /***** Reset exam announcements context *****/ + ExaAnn_ResetExamAnnouncements (&ExamAnns); + + /***** Initializations *****/ + SummaryStr[0] = '\0'; // Return nothing on error + + /***** Allocate memory for the exam announcement *****/ + ExaAnn_AllocMemExamAnnouncement (&ExamAnns); + + /***** Get data of an exam announcement from database *****/ + ExaAnn_GetDataExamAnnouncementFromDB (&ExamAnns,ExaCod); + + /***** Content *****/ + if (GetContent) + ExaAnn_GetNotifContentExamAnnouncement (&ExamAnns,ContentStr); + + /***** Summary *****/ + /* Name of the course and date of exam */ + Dat_ConvDateToDateStr (&ExamAnns.ExamAnn.ExamDate,StrExamDate); + snprintf (CrsNameAndDate,sizeof (CrsNameAndDate), + "%s, %s, %2u:%02u", + ExamAnns.ExamAnn.CrsFullName, + StrExamDate, + ExamAnns.ExamAnn.StartTime.Hour, + ExamAnns.ExamAnn.StartTime.Minute); + Str_Copy (SummaryStr,CrsNameAndDate, + Ntf_MAX_BYTES_SUMMARY); + + /***** Free memory of the exam announcement *****/ + ExaAnn_FreeMemExamAnnouncement (&ExamAnns); + } + +/*****************************************************************************/ +/************ Show a form with the data of an exam announcement **************/ +/*****************************************************************************/ + +static void ExaAnn_GetNotifContentExamAnnouncement (const struct ExaAnn_ExamAnnouncements *ExamAnns, + char **ContentStr) + { + extern const char *Txt_Institution; + extern const char *Txt_Degree; + extern const char *Txt_YEAR_OF_DEGREE[1 + Deg_MAX_YEARS_PER_DEGREE]; + extern const char *Txt_EXAM_ANNOUNCEMENT_Course; + extern const char *Txt_EXAM_ANNOUNCEMENT_Year_or_semester; + extern const char *Txt_EXAM_ANNOUNCEMENT_Session; + extern const char *Txt_EXAM_ANNOUNCEMENT_Exam_date; + extern const char *Txt_EXAM_ANNOUNCEMENT_Start_time; + extern const char *Txt_EXAM_ANNOUNCEMENT_Approximate_duration; + extern const char *Txt_EXAM_ANNOUNCEMENT_Place_of_exam; + extern const char *Txt_EXAM_ANNOUNCEMENT_Mode; + extern const char *Txt_EXAM_ANNOUNCEMENT_Structure_of_the_exam; + extern const char *Txt_EXAM_ANNOUNCEMENT_Documentation_required; + extern const char *Txt_EXAM_ANNOUNCEMENT_Material_required; + extern const char *Txt_EXAM_ANNOUNCEMENT_Material_allowed; + extern const char *Txt_EXAM_ANNOUNCEMENT_Other_information; + extern const char *Txt_hours_ABBREVIATION; + struct Course Crs; + struct Degree Deg; + struct Instit Ins; + char StrExamDate[Cns_MAX_BYTES_DATE + 1]; + + /***** Get data of course *****/ + Crs.CrsCod = ExamAnns->ExamAnn.CrsCod; + Crs_GetDataOfCourseByCod (&Crs); + + /***** Get data of degree *****/ + Deg.DegCod = Crs.DegCod; + Deg_GetDataOfDegreeByCod (&Deg); + + /***** Get data of institution *****/ + Ins.InsCod = Deg_GetInsCodOfDegreeByCod (Deg.DegCod); + Ins_GetDataOfInstitutionByCod (&Ins); + + /***** Convert struct date to a date string *****/ + Dat_ConvDateToDateStr (&ExamAnns->ExamAnn.ExamDate,StrExamDate); + + /***** Fill content string *****/ + if (asprintf (ContentStr,"%s: %s
" + "%s: %s
" + "%s: %s
" + "%s: %s
" + "%s: %s
" + "%s: %s
" + "%s: %2u:%02u %s
" + "%s: %2u:%02u %s
" + "%s: %s
" + "%s: %s
" + "%s: %s
" + "%s: %s
" + "%s: %s
" + "%s: %s
" + "%s: %s", + Txt_Institution,Ins.FullName, + Txt_Degree,Deg.FullName, + Txt_EXAM_ANNOUNCEMENT_Course,ExamAnns->ExamAnn.CrsFullName, + Txt_EXAM_ANNOUNCEMENT_Year_or_semester,Txt_YEAR_OF_DEGREE[ExamAnns->ExamAnn.Year], + Txt_EXAM_ANNOUNCEMENT_Session,ExamAnns->ExamAnn.Session, + Txt_EXAM_ANNOUNCEMENT_Exam_date,StrExamDate, + Txt_EXAM_ANNOUNCEMENT_Start_time,ExamAnns->ExamAnn.StartTime.Hour, + ExamAnns->ExamAnn.StartTime.Minute, + Txt_hours_ABBREVIATION, + Txt_EXAM_ANNOUNCEMENT_Approximate_duration,ExamAnns->ExamAnn.Duration.Hour, + ExamAnns->ExamAnn.Duration.Minute, + Txt_hours_ABBREVIATION, + Txt_EXAM_ANNOUNCEMENT_Place_of_exam,ExamAnns->ExamAnn.Place, + Txt_EXAM_ANNOUNCEMENT_Mode,ExamAnns->ExamAnn.Mode, + Txt_EXAM_ANNOUNCEMENT_Structure_of_the_exam,ExamAnns->ExamAnn.Structure, + Txt_EXAM_ANNOUNCEMENT_Documentation_required,ExamAnns->ExamAnn.DocRequired, + Txt_EXAM_ANNOUNCEMENT_Material_required,ExamAnns->ExamAnn.MatRequired, + Txt_EXAM_ANNOUNCEMENT_Material_allowed,ExamAnns->ExamAnn.MatAllowed, + Txt_EXAM_ANNOUNCEMENT_Other_information,ExamAnns->ExamAnn.OtherInfo) < 0) + Lay_NotEnoughMemoryExit (); + } diff --git a/swad_exam_announcement.h b/swad_exam_announcement.h new file mode 100644 index 00000000..a98788e3 --- /dev/null +++ b/swad_exam_announcement.h @@ -0,0 +1,128 @@ +// swad_exam_announcement.h: exam announcements + +#ifndef _SWAD_EXA_ANN +#define _SWAD_EXA_ANN +/* + 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-2020 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 3 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 boolean type + +#include "swad_constant.h" +#include "swad_course.h" +#include "swad_date.h" +#include "swad_notification.h" + +/*****************************************************************************/ +/************************** Public types and constants ***********************/ +/*****************************************************************************/ + +#define ExaAnn_NUM_VIEWS 3 +typedef enum + { + ExaAnn_NORMAL_VIEW, + ExaAnn_PRINT_VIEW, + ExaAnn_FORM_VIEW, + } ExaAnn_TypeViewExamAnnouncement_t; + +#define ExaAnn_NUM_STATUS 3 +typedef enum + { + ExaAnn_VISIBLE_EXAM_ANNOUNCEMENT = 0, + ExaAnn_HIDDEN_EXAM_ANNOUNCEMENT = 1, + ExaAnn_DELETED_EXAM_ANNOUNCEMENT = 2, + } ExaAnn_Status_t; // Don't change these numbers because they are used in database + +#define ExaAnn_MAX_CHARS_SESSION (128 - 1) // 127 +#define ExaAnn_MAX_BYTES_SESSION ((ExaAnn_MAX_CHARS_SESSION + 1) * Str_MAX_BYTES_PER_CHAR - 1) // 2047 + +struct ExaAnn_ExamAnnouncement + { + long CrsCod; + ExaAnn_Status_t Status; + char CrsFullName[Hie_MAX_BYTES_FULL_NAME + 1]; + unsigned Year; // Number of year (0 (N.A.), 1, 2, 3, 4, 5, 6) in the degree + char Session[ExaAnn_MAX_BYTES_SESSION + 1]; // Exam session is june, september, etc. + struct Date CallDate; + struct Date ExamDate; + struct Hour StartTime; + struct Hour Duration; + char *Place; + char *Mode; + char *Structure; + char *DocRequired; + char *MatRequired; + char *MatAllowed; + char *OtherInfo; + }; + +struct ExaAnn_ExamCodeAndDate + { + long ExaCod; + struct Date ExamDate; + }; + +struct ExaAnn_ExamAnnouncements + { + unsigned NumExaAnns; // Number of announcements of exam in the list + struct ExaAnn_ExamCodeAndDate *Lst; // List of exam announcements + long NewExaCod; // New exam announcement just created + long HighlightExaCod; // Exam announcement to be highlighted + char HighlightDate[4 + 2 + 2 + 1]; // Date with exam announcements to be highlighted (in YYYYMMDD format) + long ExaCod; // Used to put contextual icons + const char *Anchor; // Used to put contextual icons + struct ExaAnn_ExamAnnouncement ExamAnn; + }; + +/*****************************************************************************/ +/***************************** Public prototypes *****************************/ +/*****************************************************************************/ + +void ExaAnn_ResetExamAnnouncements (struct ExaAnn_ExamAnnouncements *ExamAnns); + +void ExaAnn_PutFrmEditAExamAnnouncement (void); +void ExaAnn_ReceiveExamAnnouncement1 (void); +void ExaAnn_ReceiveExamAnnouncement2 (void); +void ExaAnn_PrintExamAnnouncement (void); +void ExaAnn_ReqRemoveExamAnnouncement (void); +void ExaAnn_RemoveExamAnnouncement1 (void); +void ExaAnn_RemoveExamAnnouncement2 (void); +void ExaAnn_HideExamAnnouncement (void); +void ExaAnn_UnhideExamAnnouncement (void); + +void ExaAnn_FreeListExamAnnouncements (struct ExaAnn_ExamAnnouncements *ExamAnns); +void ExaAnn_ListExamAnnouncementsSee (void); +void ExaAnn_ListExamAnnouncementsEdit (void); + +void ExaAnn_ListExamAnnouncementsCod (void); +void ExaAnn_ListExamAnnouncementsDay (void); + +void ExaAnn_CreateListExamAnnouncements (struct ExaAnn_ExamAnnouncements *ExamAnns); +void ExaAnn_PutHiddenParamExaCod (long ExaCod); + +void ExaAnn_GetSummaryAndContentExamAnnouncement (char SummaryStr[Ntf_MAX_BYTES_SUMMARY + 1], + char **ContentStr, + long ExaCod,bool GetContent); + +#endif diff --git a/swad_exam_event.c b/swad_exam_event.c new file mode 100644 index 00000000..2f421cee --- /dev/null +++ b/swad_exam_event.c @@ -0,0 +1,4069 @@ +// swad_exam_event.c: exam events (each ocurrence of an exam) + +/* + 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-2020 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 PATH_MAX +#include // For NULL +#include // For asprintf +#include // For calloc +#include // For string functions + +#include "swad_database.h" +#include "swad_date.h" +#include "swad_exam.h" +#include "swad_exam_event.h" +#include "swad_exam_result.h" +#include "swad_form.h" +#include "swad_global.h" +#include "swad_HTML.h" +#include "swad_role.h" +#include "swad_setting.h" +#include "swad_test.h" + +/*****************************************************************************/ +/************** External global variables from others modules ****************/ +/*****************************************************************************/ + +extern struct Globals Gbl; + +/*****************************************************************************/ +/***************************** Private constants *****************************/ +/*****************************************************************************/ + +#define ExaEvt_ICON_CLOSE "fas fa-times" +#define ExaEvt_ICON_PLAY "fas fa-play" +#define ExaEvt_ICON_PAUSE "fas fa-pause" +#define ExaEvt_ICON_PREVIOUS "fas fa-step-backward" +#define ExaEvt_ICON_NEXT "fas fa-step-forward" +#define ExaEvt_ICON_RESULTS "fas fa-chart-bar" + +#define ExaEvt_COUNTDOWN_SECONDS_LARGE 60 +#define ExaEvt_COUNTDOWN_SECONDS_MEDIUM 30 +#define ExaEvt_COUNTDOWN_SECONDS_SMALL 10 + +/*****************************************************************************/ +/******************************* Private types *******************************/ +/*****************************************************************************/ + +typedef enum + { + ExaEvt_CHANGE_STATUS_BY_STUDENT, + ExaEvt_REFRESH_STATUS_BY_SERVER, + } ExaEvt_Update_t; + +/*****************************************************************************/ +/***************************** Private constants *****************************/ +/*****************************************************************************/ + +const char *ExaEvt_ShowingStringsDB[ExaEvt_NUM_SHOWING] = + { + [ExaEvt_START ] = "start", + [ExaEvt_STEM ] = "stem", + [ExaEvt_ANSWERS] = "answers", + [ExaEvt_RESULTS] = "results", + [ExaEvt_END ] = "end", + }; + +#define ExaEvt_MAX_COLS 4 +#define ExaEvt_NUM_COLS_DEFAULT 1 + +/*****************************************************************************/ +/***************************** Private variables *****************************/ +/*****************************************************************************/ + +long ExaEvt_EvtCodBeingPlayed; // Used to refresh exam via AJAX + +/*****************************************************************************/ +/***************************** Private prototypes ****************************/ +/*****************************************************************************/ + +static void ExaEvt_SetEvtCodBeingPlayed (long EvtCod); + +static void ExaEvt_PutIconsInListOfEvents (void *Exams); +static void ExaEvt_PutIconToCreateNewEvent (struct Exa_Exams *Exams); + +static void ExaEvt_ListOneOrMoreEvents (struct Exa_Exams *Exams, + const struct Exa_Exam *Exam, + unsigned NumEvents, + MYSQL_RES *mysql_res); +static void ExaEvt_ListOneOrMoreEventsHeading (bool ICanEditEvents); +static bool ExaEvt_CheckIfICanEditEvents (void); +static bool ExaEvt_CheckIfICanEditThisEvent (const struct ExaEvt_Match *Event); +static void ExaEvt_ListOneOrMoreEventsIcons (struct Exa_Exams *Exams, + const struct ExaEvt_Match *Event); +static void ExaEvt_ListOneOrMoreEventsAuthor (const struct ExaEvt_Match *Event); +static void ExaEvt_ListOneOrMoreEventsTimes (const struct ExaEvt_Match *Event,unsigned UniqueId); +static void ExaEvt_ListOneOrMoreEventsTitleGrps (const struct ExaEvt_Match *Event); +static void ExaEvt_GetAndWriteNamesOfGrpsAssociatedToEvent (const struct ExaEvt_Match *Event); +static void ExaEvt_ListOneOrMoreEventsNumPlayers (const struct ExaEvt_Match *Event); +static void ExaEvt_ListOneOrMoreEventsStatus (struct ExaEvt_Match *Event,unsigned NumQsts); +static void ExaEvt_ListOneOrMoreEventsResult (struct Exa_Exams *Exams, + const struct ExaEvt_Match *Event); +static void ExaEvt_ListOneOrMoreEventsResultStd (struct Exa_Exams *Exams, + const struct ExaEvt_Match *Event); +static void ExaEvt_ListOneOrMoreEventsResultTch (struct Exa_Exams *Exams, + const struct ExaEvt_Match *Event); + +static void ExaEvt_GetEventDataFromRow (MYSQL_RES *mysql_res, + struct ExaEvt_Match *Event); +static ExaEvt_Showing_t ExaEvt_GetShowingFromStr (const char *Str); + +static void ExaEvt_RemoveEventFromAllTables (long EvtCod); +static void ExaEvt_RemoveEventFromTable (long EvtCod,const char *TableName); +static void ExaEvt_RemoveEventsInExamFromTable (long ExaCod,const char *TableName); +static void ExaEvt_RemoveEventInCourseFromTable (long CrsCod,const char *TableName); +static void ExaEvt_RemoveUsrEvtResultsInCrs (long UsrCod,long CrsCod,const char *TableName); + +static void ExaEvt_PutParamsPlay (void *EvtCod); +static void ExaEvt_PutParamEvtCod (long EvtCod); + +static void ExaEvt_PutFormNewMatch (const struct Exa_Exam *Exam); +static void ExaEvt_ShowLstGrpsToCreateEvent (void); + +static long ExaEvt_CreateEvent (long ExaCod,char Title[Exa_MAX_BYTES_TITLE + 1]); +static void ExaEvt_CreateIndexes (long ExaCod,long EvtCod); +static void ExaEvt_ReorderAnswer (long EvtCod,unsigned QstInd, + const struct Tst_Question *Question); +static void ExaEvt_CreateGrps (long EvtCod); +static void ExaEvt_UpdateEventStatusInDB (const struct ExaEvt_Match *Event); + +static void ExaEvt_UpdateElapsedTimeInQuestion (const struct ExaEvt_Match *Event); +static void ExaEvt_GetElapsedTimeInQuestion (const struct ExaEvt_Match *Event, + struct Time *Time); +static void ExaEvt_GetElapsedTimeInMatch (const struct ExaEvt_Match *Event, + struct Time *Time); +static void ExaEvt_GetElapsedTime (unsigned NumRows,MYSQL_RES *mysql_res, + struct Time *Time); + +static void ExaEvt_SetMatchStatusToPrev (struct ExaEvt_Match *Event); +static void ExaEvt_SetMatchStatusToPrevQst (struct ExaEvt_Match *Event); +static void ExaEvt_SetMatchStatusToStart (struct ExaEvt_Match *Event); + +static void ExaEvt_SetMatchStatusToNext (struct ExaEvt_Match *Event); +static void ExaEvt_SetMatchStatusToNextQst (struct ExaEvt_Match *Event); +static void ExaEvt_SetMatchStatusToEnd (struct ExaEvt_Match *Event); + +static void ExaEvt_ShowMatchStatusForTch (struct ExaEvt_Match *Event); +static void ExaEvt_ShowMatchStatusForStd (struct ExaEvt_Match *Event,ExaEvt_Update_t Update); + +static void ExaEvt_ShowLeftColumnTch (struct ExaEvt_Match *Event); +static void ExaEvt_ShowRefreshablePartTch (struct ExaEvt_Match *Event); +static void ExaEvt_WriteElapsedTimeInEvt (struct ExaEvt_Match *Event); +static void ExaEvt_WriteElapsedTimeInQst (struct ExaEvt_Match *Event); +static void ExaEvt_WriteNumRespondersQst (struct ExaEvt_Match *Event); +static void ExaEvt_PutFormCountdown (struct ExaEvt_Match *Event,long Seconds,const char *Color); +static void ExaEvt_PutCountdownAndHourglassIcon (struct ExaEvt_Match *Event); +static void ExaEvt_PutFormsCountdown (struct ExaEvt_Match *Event); + +static void ExaEvt_ShowRightColumnTch (const struct ExaEvt_Match *Event); +static void ExaEvt_ShowLeftColumnStd (const struct ExaEvt_Match *Event, + const struct ExaEvt_UsrAnswer *UsrAnswer); +static void ExaEvt_ShowRightColumnStd (struct ExaEvt_Match *Event, + const struct ExaEvt_UsrAnswer *UsrAnswer, + ExaEvt_Update_t Update); + +static void ExaEvt_ShowNumQstInEvt (const struct ExaEvt_Match *Event); +static void ExaEvt_PutMatchControlButtons (const struct ExaEvt_Match *Event); +static void ExaEvt_ShowFormColumns (const struct ExaEvt_Match *Event); +static void ExaEvt_PutParamNumCols (unsigned NumCols); + +static void ExaEvt_ShowEventTitleTch (const struct ExaEvt_Match *Event); +static void ExaEvt_ShowEventTitleStd (const struct ExaEvt_Match *Event); + +static void ExaEvt_PutCheckboxResult (const struct ExaEvt_Match *Event); +static void ExaEvt_PutIfAnswered (const struct ExaEvt_Match *Event,bool Answered); +static void ExaEvt_PutIconToRemoveMyAnswer (const struct ExaEvt_Match *Event); +static void ExaEvt_ShowQuestionAndAnswersTch (const struct ExaEvt_Match *Event); +static void ExaEvt_WriteAnswersEventResult (const struct ExaEvt_Match *Event, + const struct Tst_Question *Question, + const char *Class,bool ShowResult); +static bool ExaEvt_ShowQuestionAndAnswersStd (const struct ExaEvt_Match *Event, + const struct ExaEvt_UsrAnswer *UsrAnswer, + ExaEvt_Update_t Update); + +static void ExaEvt_ShowEventScore (const struct ExaEvt_Match *Event); +static void ExaEvt_DrawEmptyScoreRow (unsigned NumRow,double MinScore,double MaxScore); +static void ExaEvt_DrawScoreRow (double Score,double MinScore,double MaxScore, + unsigned NumRow,unsigned NumUsrs,unsigned MaxUsrs); +static const char *ExaEvt_GetClassBorder (unsigned NumRow); + +static void ExaEvt_PutParamNumOpt (unsigned NumOpt); +static unsigned ExaEvt_GetParamNumOpt (void); + +static void ExaEvt_PutBigButton (Act_Action_t NextAction,const char *Id, + long EvtCod,const char *Icon,const char *Txt); +static void ExaEvt_PutBigButtonHidden (const char *Icon); +static void ExaEvt_PutBigButtonClose (void); + +static void ExaEvt_ShowWaitImage (const char *Txt); + +static void ExaEvt_RemoveOldPlayers (void); +static void ExaEvt_UpdateEventAsBeingPlayed (long EvtCod); +static void ExaEvt_SetEventAsNotBeingPlayed (long EvtCod); +static bool ExaEvt_GetIfEventIsBeingPlayed (long EvtCod); +static void ExaEvt_GetNumPlayers (struct ExaEvt_Match *Event); + +static void ExaEvt_RemoveMyAnswerToEventQuestion (const struct ExaEvt_Match *Event); + +static void ExaEvt_ComputeScore (struct TstExa_Exam *Result); + +static unsigned ExaEvt_GetNumUsrsWhoHaveAnswerEvt (long EvtCod); + +/*****************************************************************************/ +/********** Set/Get exam event code of the exam event being played ***********/ +/*****************************************************************************/ + +static void ExaEvt_SetEvtCodBeingPlayed (long EvtCod) + { + ExaEvt_EvtCodBeingPlayed = EvtCod; + } + +long ExaEvt_GetEvtCodBeingPlayed (void) + { + return ExaEvt_EvtCodBeingPlayed; + } + +/*****************************************************************************/ +/************************* List the events of an exam ************************/ +/*****************************************************************************/ + +void ExaEvt_ListEvents (struct Exa_Exams *Exams, + struct Exa_Exam *Exam, + bool PutFormNewEvent) + { + extern const char *Hlp_ASSESSMENT_Games_matches; + extern const char *Txt_Matches; + char *SubQuery; + MYSQL_RES *mysql_res; + unsigned NumEvents; + + /***** Get data of events from database *****/ + /* Fill subquery for exam */ + if (Gbl.Crs.Grps.WhichGrps == Grp_MY_GROUPS) + { + if (asprintf (&SubQuery," AND" + "(EvtCod NOT IN" + " (SELECT EvtCod FROM exa_groups)" + " OR" + " EvtCod IN" + " (SELECT exa_groups.EvtCod" + " FROM exa_groups,crs_grp_usr" + " WHERE crs_grp_usr.UsrCod=%ld" + " AND exa_groups.GrpCod=crs_grp_usr.GrpCod))", + Gbl.Usrs.Me.UsrDat.UsrCod) < 0) + Lay_NotEnoughMemoryExit (); + } + else // Gbl.Crs.Grps.WhichGrps == Grp_ALL_GROUPS + if (asprintf (&SubQuery,"%s","") < 0) + Lay_NotEnoughMemoryExit (); + + /* Make query */ + NumEvents = (unsigned) DB_QuerySELECT (&mysql_res,"can not get events", + "SELECT EvtCod," // row[ 0] + "ExaCod," // row[ 1] + "UsrCod," // row[ 2] + "UNIX_TIMESTAMP(StartTime)," // row[ 3] + "UNIX_TIMESTAMP(EndTime)," // row[ 4] + "Title," // row[ 5] + "QstInd," // row[ 6] + "QstCod," // row[ 7] + "Showing," // row[ 8] + "Countdown," // row[ 9] + "NumCols," // row[10] + "ShowQstResults," // row[11] + "ShowUsrResults" // row[12] + " FROM exa_events" + " WHERE ExaCod=%ld%s" + " ORDER BY EvtCod", + Exam->ExaCod, + SubQuery); + + /* Free allocated memory for subquery */ + free (SubQuery); + + /***** Begin box *****/ + Exams->ExaCod = Exam->ExaCod; + Box_BoxBegin ("100%",Txt_Matches, + ExaEvt_PutIconsInListOfEvents,Exams, + Hlp_ASSESSMENT_Games_matches,Box_NOT_CLOSABLE); + + /***** Select whether show only my groups or all groups *****/ + if (Gbl.Crs.Grps.NumGrps) + { + Set_StartSettingsHead (); + Grp_ShowFormToSelWhichGrps (ActSeeGam, + Gam_PutParams,Exams); + Set_EndSettingsHead (); + } + + if (NumEvents) + /***** Show the table with the events *****/ + ExaEvt_ListOneOrMoreEvents (Exams,Exam,NumEvents,mysql_res); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + /***** Put button to play a new exam event in this exam *****/ + switch (Gbl.Usrs.Me.Role.Logged) + { + case Rol_NET: + case Rol_TCH: + case Rol_SYS_ADM: + if (PutFormNewEvent) + ExaEvt_PutFormNewMatch (Exam); // Form to fill in data and start playing a new exam event + else + Gam_PutButtonNewMatch (Exams,Exam->ExaCod); // Button to create a new exam event + break; + default: + break; + } + + /***** End box *****/ + Box_BoxEnd (); + } + +/*****************************************************************************/ +/******************** Get exam event data using its code *********************/ +/*****************************************************************************/ + +void ExaEvt_GetDataOfEventByCod (struct ExaEvt_Match *Event) + { + MYSQL_RES *mysql_res; + unsigned long NumRows; + Dat_StartEndTime_t StartEndTime; + + /***** Get data of exam event from database *****/ + NumRows = (unsigned) DB_QuerySELECT (&mysql_res,"can not get events", + "SELECT EvtCod," // row[ 0] + "ExaCod," // row[ 1] + "UsrCod," // row[ 2] + "UNIX_TIMESTAMP(StartTime)," // row[ 3] + "UNIX_TIMESTAMP(EndTime)," // row[ 4] + "Title," // row[ 5] + "QstInd," // row[ 6] + "QstCod," // row[ 7] + "Showing," // row[ 8] + "Countdown," // row[ 9] + "NumCols," // row[10] + "ShowQstResults," // row[11] + "ShowUsrResults" // row[12] + " FROM exa_events" + " WHERE EvtCod=%ld" + " AND ExaCod IN" // Extra check + " (SELECT ExaCod FROM exa_exams" + " WHERE CrsCod='%ld')", + Event->EvtCod, + Gbl.Hierarchy.Crs.CrsCod); + if (NumRows) // Event found... + /***** Get exam event data from row *****/ + ExaEvt_GetEventDataFromRow (mysql_res,Event); + else + { + /* Initialize to empty exam event */ + Event->EvtCod = -1L; + Event->ExaCod = -1L; + Event->UsrCod = -1L; + for (StartEndTime = (Dat_StartEndTime_t) 0; + StartEndTime <= (Dat_StartEndTime_t) (Dat_NUM_START_END_TIME - 1); + StartEndTime++) + Event->TimeUTC[StartEndTime] = (time_t) 0; + Event->Title[0] = '\0'; + Event->Status.QstInd = 0; + Event->Status.QstCod = -1L; + Event->Status.QstStartTimeUTC = (time_t) 0; + Event->Status.Showing = ExaEvt_START; + Event->Status.Countdown = -1L; + Event->Status.Playing = false; + Event->Status.NumPlayers = 0; + Event->Status.NumCols = ExaEvt_NUM_COLS_DEFAULT; + Event->Status.ShowQstResults = false; + Event->Status.ShowUsrResults = false; + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/****************** Put icons in list of events of an exam *******************/ +/*****************************************************************************/ + +static void ExaEvt_PutIconsInListOfEvents (void *Exams) + { + bool ICanEditEvents; + + if (Exams) + { + /***** Put icon to create a new exam event in current exam *****/ + ICanEditEvents = ExaEvt_CheckIfICanEditEvents (); + if (ICanEditEvents) + ExaEvt_PutIconToCreateNewEvent ((struct Exa_Exams *) Exams); + } + } + +/*****************************************************************************/ +/******************* Put icon to create a new exam event *********************/ +/*****************************************************************************/ + +static void ExaEvt_PutIconToCreateNewEvent (struct Exa_Exams *Exams) + { + extern const char *Txt_New_match; + + /***** Put form to create a new exam event *****/ + Ico_PutContextualIconToAdd (ActReqNewExaEvt,ExaEvt_NEW_MATCH_SECTION_ID, + Gam_PutParams,Exams, + Txt_New_match); + } + +/*****************************************************************************/ +/*********************** List exam events for edition ************************/ +/*****************************************************************************/ + +static void ExaEvt_ListOneOrMoreEvents (struct Exa_Exams *Exams, + const struct Exa_Exam *Exam, + unsigned NumEvents, + MYSQL_RES *mysql_res) + { + unsigned NumEvent; + unsigned UniqueId; + struct ExaEvt_Match Event; + bool ICanEditEvents = ExaEvt_CheckIfICanEditEvents (); + + /***** Write the heading *****/ + HTM_TABLE_BeginWidePadding (2); + ExaEvt_ListOneOrMoreEventsHeading (ICanEditEvents); + + /***** Write rows *****/ + for (NumEvent = 0, UniqueId = 1; + NumEvent < NumEvents; + NumEvent++, UniqueId++) + { + Gbl.RowEvenOdd = NumEvent % 2; + + /***** Get exam event data from row *****/ + ExaEvt_GetEventDataFromRow (mysql_res,&Event); + + if (ExaEvt_CheckIfICanPlayThisEventBasedOnGrps (&Event)) + { + /***** Write row for this exam event ****/ + HTM_TR_Begin (NULL); + + /* Icons */ + if (ICanEditEvents) + ExaEvt_ListOneOrMoreEventsIcons (Exams,&Event); + + /* Event player */ + ExaEvt_ListOneOrMoreEventsAuthor (&Event); + + /* Start/end date/time */ + ExaEvt_ListOneOrMoreEventsTimes (&Event,UniqueId); + + /* Title and groups */ + ExaEvt_ListOneOrMoreEventsTitleGrps (&Event); + + /* Number of players who have answered any question in the exam event */ + ExaEvt_ListOneOrMoreEventsNumPlayers (&Event); + + /* Event status */ + ExaEvt_ListOneOrMoreEventsStatus (&Event,Exam->NumQsts); + + /* Event result visible? */ + ExaEvt_ListOneOrMoreEventsResult (Exams,&Event); + } + } + + /***** End table *****/ + HTM_TABLE_End (); + } + +/*****************************************************************************/ +/************** Put a column for exam event start and end times **************/ +/*****************************************************************************/ + +static void ExaEvt_ListOneOrMoreEventsHeading (bool ICanEditEvents) + { + extern const char *Txt_ROLES_SINGUL_Abc[Rol_NUM_ROLES][Usr_NUM_SEXS]; + extern const char *Txt_START_END_TIME[Dat_NUM_START_END_TIME]; + extern const char *Txt_Match; + extern const char *Txt_Players; + extern const char *Txt_Status; + extern const char *Txt_Results; + + /***** Start row *****/ + HTM_TR_Begin (NULL); + + /***** Column for icons *****/ + if (ICanEditEvents) + HTM_TH_Empty (1); + + /***** The rest of columns *****/ + HTM_TH (1,1,"LT",Txt_ROLES_SINGUL_Abc[Rol_TCH][Usr_SEX_UNKNOWN]); + HTM_TH (1,1,"LT",Txt_START_END_TIME[Gam_ORDER_BY_START_DATE]); + HTM_TH (1,1,"LT",Txt_START_END_TIME[Gam_ORDER_BY_END_DATE ]); + HTM_TH (1,1,"LT",Txt_Match); + HTM_TH (1,1,"RT",Txt_Players); + HTM_TH (1,1,"CT",Txt_Status); + HTM_TH (1,1,"CT",Txt_Results); + + /***** End row *****/ + HTM_TR_End (); + } + +/*****************************************************************************/ +/*********************** Check if I can edit events **************************/ +/*****************************************************************************/ + +static bool ExaEvt_CheckIfICanEditEvents (void) + { + switch (Gbl.Usrs.Me.Role.Logged) + { + case Rol_NET: + case Rol_TCH: + case Rol_SYS_ADM: + return true; + default: + return false; + } + } + +/*****************************************************************************/ +/************** Check if I can edit (remove/resume) an exam event ************/ +/*****************************************************************************/ + +static bool ExaEvt_CheckIfICanEditThisEvent (const struct ExaEvt_Match *Event) + { + switch (Gbl.Usrs.Me.Role.Logged) + { + case Rol_NET: + return (Event->UsrCod == Gbl.Usrs.Me.UsrDat.UsrCod); // Only if I am the creator + case Rol_TCH: + case Rol_SYS_ADM: + return true; + default: + return false; + } + } + +/*****************************************************************************/ +/************************* Put a column for icons ****************************/ +/*****************************************************************************/ + +static void ExaEvt_ListOneOrMoreEventsIcons (struct Exa_Exams *Exams, + const struct ExaEvt_Match *Event) + { + HTM_TD_Begin ("class=\"BT%u\"",Gbl.RowEvenOdd); + + /***** Put icon to remove the exam event *****/ + if (ExaEvt_CheckIfICanEditThisEvent (Event)) + { + Exams->ExaCod = Event->ExaCod; + Exams->EvtCod = Event->EvtCod; + Frm_StartForm (ActReqRemExaEvt); + ExaEvt_PutParamsEdit (Exams); + Ico_PutIconRemove (); + Frm_EndForm (); + } + else + Ico_PutIconRemovalNotAllowed (); + + HTM_TD_End (); + } + +/*****************************************************************************/ +/*********** Put a column for teacher who created the exam event *************/ +/*****************************************************************************/ + +static void ExaEvt_ListOneOrMoreEventsAuthor (const struct ExaEvt_Match *Event) + { + /***** Event author (teacher) *****/ + HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd); + Usr_WriteAuthor1Line (Event->UsrCod,false); + HTM_TD_End (); + } + +/*****************************************************************************/ +/*************** Put a column for exam event start and end times *************/ +/*****************************************************************************/ + +static void ExaEvt_ListOneOrMoreEventsTimes (const struct ExaEvt_Match *Event,unsigned UniqueId) + { + Dat_StartEndTime_t StartEndTime; + char *Id; + + for (StartEndTime = (Dat_StartEndTime_t) 0; + StartEndTime <= (Dat_StartEndTime_t) (Dat_NUM_START_END_TIME - 1); + StartEndTime++) + { + if (asprintf (&Id,"exa_time_%u_%u",(unsigned) StartEndTime,UniqueId) < 0) + Lay_NotEnoughMemoryExit (); + HTM_TD_Begin ("id=\"%s\" class=\"%s LT COLOR%u\"", + Id, + Event->Status.Showing == ExaEvt_END ? "DATE_RED" : + "DATE_GREEN", + Gbl.RowEvenOdd); + Dat_WriteLocalDateHMSFromUTC (Id,Event->TimeUTC[StartEndTime], + Gbl.Prefs.DateFormat,Dat_SEPARATOR_BREAK, + true,true,true,0x7); + HTM_TD_End (); + free (Id); + } + } + +/*****************************************************************************/ +/*************** Put a column for exam event title and grous *****************/ +/*****************************************************************************/ + +static void ExaEvt_ListOneOrMoreEventsTitleGrps (const struct ExaEvt_Match *Event) + { + extern const char *Txt_Play; + extern const char *Txt_Resume; + + HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd); + + /***** Event title *****/ + Frm_StartForm (Gbl.Usrs.Me.Role.Logged == Rol_STD ? ActJoiExaEvt : + ActResExaEvt); + ExaEvt_PutParamEvtCod (Event->EvtCod); + HTM_BUTTON_SUBMIT_Begin (Gbl.Usrs.Me.Role.Logged == Rol_STD ? Txt_Play : + Txt_Resume, + "BT_LINK LT ASG_TITLE",NULL); + HTM_Txt (Event->Title); + HTM_BUTTON_End (); + Frm_EndForm (); + + /***** Groups whose students can answer this exam event *****/ + if (Gbl.Crs.Grps.NumGrps) + ExaEvt_GetAndWriteNamesOfGrpsAssociatedToEvent (Event); + + HTM_TD_End (); + } + +/*****************************************************************************/ +/********** Get and write the names of the groups of an exam event ***********/ +/*****************************************************************************/ + +static void ExaEvt_GetAndWriteNamesOfGrpsAssociatedToEvent (const struct ExaEvt_Match *Event) + { + extern const char *Txt_Group; + extern const char *Txt_Groups; + extern const char *Txt_and; + extern const char *Txt_The_whole_course; + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned long NumRow; + unsigned long NumRows; + + /***** Get groups associated to an exam event from database *****/ + NumRows = DB_QuerySELECT (&mysql_res,"can not get groups of an exam event", + "SELECT crs_grp_types.GrpTypName,crs_grp.GrpName" + " FROM exa_groups,crs_grp,crs_grp_types" + " WHERE exa_groups.EvtCod=%ld" + " AND exa_groups.GrpCod=crs_grp.GrpCod" + " AND crs_grp.GrpTypCod=crs_grp_types.GrpTypCod" + " ORDER BY crs_grp_types.GrpTypName,crs_grp.GrpName", + Event->EvtCod); + + /***** Write heading *****/ + HTM_DIV_Begin ("class=\"ASG_GRP\""); + HTM_TxtColonNBSP (NumRows == 1 ? Txt_Group : + Txt_Groups); + + /***** Write groups *****/ + if (NumRows) // Groups found... + { + /* Get and write the group types and names */ + for (NumRow = 0; + NumRow < NumRows; + NumRow++) + { + /* Get next group */ + row = mysql_fetch_row (mysql_res); + + /* Write group type name and group name */ + HTM_TxtF ("%s %s",row[0],row[1]); + + if (NumRows >= 2) + { + if (NumRow == NumRows-2) + HTM_TxtF (" %s ",Txt_and); + if (NumRows >= 3) + if (NumRow < NumRows-2) + HTM_Txt (", "); + } + } + } + else + HTM_TxtF ("%s %s",Txt_The_whole_course,Gbl.Hierarchy.Crs.ShrtName); + + HTM_DIV_End (); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/********** Check if an exam event is associated to a given group ************/ +/*****************************************************************************/ + +bool ExaEvt_CheckIfMatchIsAssociatedToGrp (long EvtCod,long GrpCod) + { + /***** Get if an exam event is associated to a group from database *****/ + return (DB_QueryCOUNT ("can not check if an exam event is associated to a group", + "SELECT COUNT(*) FROM exa_groups" + " WHERE EvtCod=%ld AND GrpCod=%ld", + EvtCod,GrpCod) != 0); + } + +/*****************************************************************************/ +/******************* Put a column for number of players **********************/ +/*****************************************************************************/ + +static void ExaEvt_ListOneOrMoreEventsNumPlayers (const struct ExaEvt_Match *Event) + { + /***** Number of players who have answered any question in the exam event ******/ + HTM_TD_Begin ("class=\"DAT RT COLOR%u\"",Gbl.RowEvenOdd); + HTM_Unsigned (ExaEvt_GetNumUsrsWhoHaveAnswerEvt (Event->EvtCod)); + HTM_TD_End (); + } + +/*****************************************************************************/ +/******************** Put a column for exam event status *********************/ +/*****************************************************************************/ + +static void ExaEvt_ListOneOrMoreEventsStatus (struct ExaEvt_Match *Event,unsigned NumQsts) + { + extern const char *Txt_Play; + extern const char *Txt_Resume; + + HTM_TD_Begin ("class=\"DAT CT COLOR%u\"",Gbl.RowEvenOdd); + + if (Event->Status.Showing != ExaEvt_END) // Event not over + { + /* Current question index / total of questions */ + HTM_DIV_Begin ("class=\"DAT\""); + HTM_TxtF ("%u/%u",Event->Status.QstInd,NumQsts); + HTM_DIV_End (); + } + + /* Icon to join exam event or resume exam event */ + Lay_PutContextualLinkOnlyIcon (Gbl.Usrs.Me.Role.Logged == Rol_STD ? ActJoiExaEvt : + ActResExaEvt, + NULL, + ExaEvt_PutParamsPlay,&Event->EvtCod, + Event->Status.Showing == ExaEvt_END ? "flag-checkered.svg" : + "play.svg", + Gbl.Usrs.Me.Role.Logged == Rol_STD ? Txt_Play : + Txt_Resume); + + HTM_TD_End (); + } + +/*****************************************************************************/ +/************* Put a column for visibility of exam event result **************/ +/*****************************************************************************/ + +static void ExaEvt_ListOneOrMoreEventsResult (struct Exa_Exams *Exams, + const struct ExaEvt_Match *Event) + { + HTM_TD_Begin ("class=\"DAT CT COLOR%u\"",Gbl.RowEvenOdd); + + switch (Gbl.Usrs.Me.Role.Logged) + { + case Rol_STD: + ExaEvt_ListOneOrMoreEventsResultStd (Exams,Event); + break; + case Rol_NET: + case Rol_TCH: + case Rol_SYS_ADM: + ExaEvt_ListOneOrMoreEventsResultTch (Exams,Event); + break; + default: + Rol_WrongRoleExit (); + break; + } + + HTM_TD_End (); + } + +static void ExaEvt_ListOneOrMoreEventsResultStd (struct Exa_Exams *Exams, + const struct ExaEvt_Match *Event) + { + extern const char *Txt_Results; + + /***** Is exam event result visible or hidden? *****/ + if (Event->Status.ShowUsrResults) + { + /* Result is visible by me */ + Exams->ExaCod = Event->ExaCod; + Exams->EvtCod = Event->EvtCod; + Lay_PutContextualLinkOnlyIcon (ActSeeMyExaEvtResEvt,ExaRes_RESULTS_BOX_ID, + ExaEvt_PutParamsEdit,Exams, + "trophy.svg", + Txt_Results); + } + else + /* Result is forbidden to me */ + Ico_PutIconNotVisible (); + } + +static void ExaEvt_ListOneOrMoreEventsResultTch (struct Exa_Exams *Exams, + const struct ExaEvt_Match *Event) + { + extern const char *Txt_Visible_results; + extern const char *Txt_Hidden_results; + extern const char *Txt_Results; + + /***** Can I edit exam event vivibility? *****/ + if (ExaEvt_CheckIfICanEditThisEvent (Event)) + { + Exams->ExaCod = Event->ExaCod; + Exams->EvtCod = Event->EvtCod; + + /* Show exam event results */ + Lay_PutContextualLinkOnlyIcon (ActSeeAllExaEvtResEvt,ExaRes_RESULTS_BOX_ID, + ExaEvt_PutParamsEdit,Exams, + "trophy.svg", + Txt_Results); + + /* I can edit visibility */ + Lay_PutContextualLinkOnlyIcon (ActChgVisResExaEvtUsr,NULL, + ExaEvt_PutParamsEdit,Exams, + Event->Status.ShowUsrResults ? "eye-green.svg" : + "eye-slash-red.svg", + Event->Status.ShowUsrResults ? Txt_Visible_results : + Txt_Hidden_results); + } + else + /* I can not edit visibility */ + Ico_PutIconOff (Event->Status.ShowUsrResults ? "eye-green.svg" : + "eye-slash-red.svg", + Event->Status.ShowUsrResults ? Txt_Visible_results : + Txt_Hidden_results); + } + +/*****************************************************************************/ +/****************** Toggle visibility of exam event results ******************/ +/*****************************************************************************/ + +void ExaEvt_ToggleVisibilResultsEvtUsr (void) + { + struct Exa_Exams Exams; + struct Exa_Exam Exam; + struct ExaEvt_Match Event; + + /***** Reset games *****/ + Gam_ResetGames (&Exams); + + /***** Get and check parameters *****/ + ExaEvt_GetAndCheckParameters (&Exams,&Exam,&Event); + + /***** Check if I have permission to change visibility *****/ + if (!ExaEvt_CheckIfICanEditThisEvent (&Event)) + Lay_NoPermissionExit (); + + /***** Toggle visibility of exam event results *****/ + Event.Status.ShowUsrResults = !Event.Status.ShowUsrResults; + DB_QueryUPDATE ("can not toggle visibility of exam event results", + "UPDATE exa_events" + " SET ShowUsrResults='%c'" + " WHERE EvtCod=%ld", + Event.Status.ShowUsrResults ? 'Y' : + 'N', + Event.EvtCod); + + /***** Show current exam *****/ + Gam_ShowOnlyOneGame (&Exams,&Exam, + false, // Do not list exam questions + false); // Do not put form to start new exam event + } + +/*****************************************************************************/ +/******************** Get exam data from a database row **********************/ +/*****************************************************************************/ + +static void ExaEvt_GetEventDataFromRow (MYSQL_RES *mysql_res, + struct ExaEvt_Match *Event) + { + MYSQL_ROW row; + Dat_StartEndTime_t StartEndTime; + long LongNum; + + /***** Get exam event data *****/ + row = mysql_fetch_row (mysql_res); + /* + row[ 0] EvtCod + row[ 1] ExaCod + row[ 2] UsrCod + row[ 3] UNIX_TIMESTAMP(StartTime) + row[ 4] UNIX_TIMESTAMP(EndTime) + row[ 5] Title + */ + /***** Get exam event data *****/ + /* Code of the exam event (row[0]) */ + if ((Event->EvtCod = Str_ConvertStrCodToLongCod (row[0])) <= 0) + Lay_ShowErrorAndExit ("Wrong code of exam event."); + + /* Code of the exam (row[1]) */ + if ((Event->ExaCod = Str_ConvertStrCodToLongCod (row[1])) <= 0) + Lay_ShowErrorAndExit ("Wrong code of exam."); + + /* Get exam event teacher (row[2]) */ + Event->UsrCod = Str_ConvertStrCodToLongCod (row[2]); + + /* Get start/end times (row[3], row[4] hold start/end UTC times) */ + for (StartEndTime = (Dat_StartEndTime_t) 0; + StartEndTime <= (Dat_StartEndTime_t) (Dat_NUM_START_END_TIME - 1); + StartEndTime++) + Event->TimeUTC[StartEndTime] = Dat_GetUNIXTimeFromStr (row[3 + StartEndTime]); + + /* Get the title of the exam (row[5]) */ + if (row[5]) + Str_Copy (Event->Title,row[5], + Exa_MAX_BYTES_TITLE); + else + Event->Title[0] = '\0'; + + /***** Get current exam event status *****/ + /* + row[ 6] QstInd + row[ 7] QstCod + row[ 8] Showing + row[ 9] Countdown + row[10] NumCols + row[11] ShowQstResults + row[12] ShowUsrResults + */ + /* Current question index (row[6]) */ + Event->Status.QstInd = Str_ConvertStrToUnsigned (row[6]); + + /* Current question code (row[7]) */ + Event->Status.QstCod = Str_ConvertStrCodToLongCod (row[7]); + + /* Get what to show (stem, answers, results) (row(8)) */ + Event->Status.Showing = ExaEvt_GetShowingFromStr (row[8]); + + /* Get countdown (row[9]) */ + Event->Status.Countdown = Str_ConvertStrCodToLongCod (row[9]); + + /* Get number of columns (row[10]) */ + LongNum = Str_ConvertStrCodToLongCod (row[10]); + Event->Status.NumCols = (LongNum <= 1 ) ? 1 : + ((LongNum >= ExaEvt_MAX_COLS) ? ExaEvt_MAX_COLS : + (unsigned) LongNum); + + /* Get whether to show question results or not (row(11)) */ + Event->Status.ShowQstResults = (row[11][0] == 'Y'); + + /* Get whether to show user results or not (row(12)) */ + Event->Status.ShowUsrResults = (row[12][0] == 'Y'); + + /***** Get whether the exam event is being played or not *****/ + if (Event->Status.Showing == ExaEvt_END) // Event over + Event->Status.Playing = false; + else // Event not over + Event->Status.Playing = ExaEvt_GetIfEventIsBeingPlayed (Event->EvtCod); + } + +/*****************************************************************************/ +/****************** Get parameter with what is being shown *******************/ +/*****************************************************************************/ + +static ExaEvt_Showing_t ExaEvt_GetShowingFromStr (const char *Str) + { + ExaEvt_Showing_t Showing; + + for (Showing = (ExaEvt_Showing_t) 0; + Showing <= (ExaEvt_Showing_t) (ExaEvt_NUM_SHOWING - 1); + Showing++) + if (!strcmp (Str,ExaEvt_ShowingStringsDB[Showing])) + return Showing; + + return (ExaEvt_Showing_t) ExaEvt_SHOWING_DEFAULT; + } + +/*****************************************************************************/ +/*********** Request the removal of an exam event (exam instance) ************/ +/*****************************************************************************/ + +void ExaEvt_RequestRemoveEvent (void) + { + extern const char *Txt_Do_you_really_want_to_remove_the_event_X; + extern const char *Txt_Remove_event; + struct Exa_Exams Exams; + struct Exa_Exam Exam; + struct ExaEvt_Match Event; + + /***** Reset games *****/ + Gam_ResetGames (&Exams); + + /***** Get and check parameters *****/ + ExaEvt_GetAndCheckParameters (&Exams,&Exam,&Event); + + /***** Show question and button to remove question *****/ + Exams.ExaCod = Event.ExaCod; + Exams.EvtCod = Event.EvtCod; + Ale_ShowAlertAndButton (ActRemExaEvt,NULL,NULL, + ExaEvt_PutParamsEdit,&Exams, + Btn_REMOVE_BUTTON,Txt_Remove_event, + Ale_QUESTION,Txt_Do_you_really_want_to_remove_the_event_X, + Event.Title); + + /***** Show current exam *****/ + Gam_ShowOnlyOneGame (&Exams,&Exam, + false, // Do not list exam questions + false); // Do not put form to start new exam event + } + +/*****************************************************************************/ +/******************* Remove an exam event (exam instance) ********************/ +/*****************************************************************************/ + +void ExaEvt_RemoveEvent (void) + { + extern const char *Txt_Match_X_removed; + struct Exa_Exams Exams; + struct Exa_Exam Exam; + struct ExaEvt_Match Event; + + /***** Reset games *****/ + Gam_ResetGames (&Exams); + + /***** Get and check parameters *****/ + ExaEvt_GetAndCheckParameters (&Exams,&Exam,&Event); + + /***** Check if I can remove this exam event *****/ + if (!ExaEvt_CheckIfICanEditThisEvent (&Event)) + Lay_NoPermissionExit (); + + /***** Remove the exam event from all database tables *****/ + ExaEvt_RemoveEventFromAllTables (Event.EvtCod); + + /***** Write message *****/ + Ale_ShowAlert (Ale_SUCCESS,Txt_Match_X_removed, + Event.Title); + + /***** Show current exam *****/ + Gam_ShowOnlyOneGame (&Exams,&Exam, + false, // Do not list exam questions + false); // Do not put form to start new exam event + } + +/*****************************************************************************/ +/******************** Remove exam event from all tables **********************/ +/*****************************************************************************/ +/* +mysql> SELECT table_name FROM information_schema.tables WHERE table_name LIKE 'exa%'; +*/ +static void ExaEvt_RemoveEventFromAllTables (long EvtCod) + { + /***** Remove exam event from secondary tables *****/ + ExaEvt_RemoveEventFromTable (EvtCod,"exa_players"); + ExaEvt_RemoveEventFromTable (EvtCod,"exa_playing"); + ExaEvt_RemoveEventFromTable (EvtCod,"exa_results"); + ExaEvt_RemoveEventFromTable (EvtCod,"exa_answers"); + ExaEvt_RemoveEventFromTable (EvtCod,"exa_times"); + ExaEvt_RemoveEventFromTable (EvtCod,"exa_groups"); + ExaEvt_RemoveEventFromTable (EvtCod,"exa_indexes"); + + /***** Remove exam event from main table *****/ + DB_QueryDELETE ("can not remove exam event", + "DELETE FROM exa_events WHERE EvtCod=%ld", + EvtCod); + } + +static void ExaEvt_RemoveEventFromTable (long EvtCod,const char *TableName) + { + /***** Remove exam event from secondary table *****/ + DB_QueryDELETE ("can not remove exam event from table", + "DELETE FROM %s WHERE EvtCod=%ld", + TableName, + EvtCod); + } + +/*****************************************************************************/ +/****************** Remove exam event in exam from all tables ****************/ +/*****************************************************************************/ + +void ExaEvt_RemoveEventsInExamFromAllTables (long ExaCod) + { + /***** Remove events from secondary tables *****/ + ExaEvt_RemoveEventsInExamFromTable (ExaCod,"exa_players"); + ExaEvt_RemoveEventsInExamFromTable (ExaCod,"exa_playing"); + ExaEvt_RemoveEventsInExamFromTable (ExaCod,"exa_results"); + ExaEvt_RemoveEventsInExamFromTable (ExaCod,"exa_answers"); + ExaEvt_RemoveEventsInExamFromTable (ExaCod,"exa_times"); + ExaEvt_RemoveEventsInExamFromTable (ExaCod,"exa_groups"); + ExaEvt_RemoveEventsInExamFromTable (ExaCod,"exa_indexes"); + + /***** Remove events from main table *****/ + DB_QueryDELETE ("can not remove events of an exam", + "DELETE FROM exa_events WHERE ExaCod=%ld", + ExaCod); + } + +static void ExaEvt_RemoveEventsInExamFromTable (long ExaCod,const char *TableName) + { + /***** Remove events in exam from secondary table *****/ + DB_QueryDELETE ("can not remove events of an exam from table", + "DELETE FROM %s" + " USING exa_events,%s" + " WHERE exa_events.ExaCod=%ld" + " AND exa_events.EvtCod=%s.EvtCod", + TableName, + TableName, + ExaCod, + TableName); + } + +/*****************************************************************************/ +/***************** Remove exam event in course from all tables ***************/ +/*****************************************************************************/ + +void ExaEvt_RemoveEventInCourseFromAllTables (long CrsCod) + { + /***** Remove events from secondary tables *****/ + ExaEvt_RemoveEventInCourseFromTable (CrsCod,"exa_players"); + ExaEvt_RemoveEventInCourseFromTable (CrsCod,"exa_playing"); + ExaEvt_RemoveEventInCourseFromTable (CrsCod,"exa_results"); + ExaEvt_RemoveEventInCourseFromTable (CrsCod,"exa_answers"); + ExaEvt_RemoveEventInCourseFromTable (CrsCod,"exa_times"); + ExaEvt_RemoveEventInCourseFromTable (CrsCod,"exa_groups"); + ExaEvt_RemoveEventInCourseFromTable (CrsCod,"exa_indexes"); + + /***** Remove events from main table *****/ + DB_QueryDELETE ("can not remove events of a course", + "DELETE FROM exa_events" + " USING exa_exams,exa_events" + " WHERE exa_exams.CrsCod=%ld" + " AND exa_exams.ExaCod=exa_events.ExaCod", + CrsCod); + } + +static void ExaEvt_RemoveEventInCourseFromTable (long CrsCod,const char *TableName) + { + /***** Remove events in course from secondary table *****/ + DB_QueryDELETE ("can not remove events of a course from table", + "DELETE FROM %s" + " USING exa_exams,exa_events,%s" + " WHERE exa_exams.CrsCod=%ld" + " AND exa_exams.ExaCod=exa_events.ExaCod" + " AND exa_events.EvtCod=%s.EvtCod", + TableName, + TableName, + CrsCod, + TableName); + } + +/*****************************************************************************/ +/************** Remove user from secondary exam event tables *****************/ +/*****************************************************************************/ + +void ExaEvt_RemoveUsrFromEventTablesInCrs (long UsrCod,long CrsCod) + { + /***** Remove student from secondary tables *****/ + ExaEvt_RemoveUsrEvtResultsInCrs (UsrCod,CrsCod,"exa_players"); + ExaEvt_RemoveUsrEvtResultsInCrs (UsrCod,CrsCod,"exa_results"); + ExaEvt_RemoveUsrEvtResultsInCrs (UsrCod,CrsCod,"exa_answers"); + } + +static void ExaEvt_RemoveUsrEvtResultsInCrs (long UsrCod,long CrsCod,const char *TableName) + { + /***** Remove events in course from secondary table *****/ + DB_QueryDELETE ("can not remove events of a user from table", + "DELETE FROM %s" + " USING exa_exams,exa_events,%s" + " WHERE exa_exams.CrsCod=%ld" + " AND exa_exams.ExaCod=exa_events.ExaCod" + " AND exa_events.EvtCod=%s.EvtCod" + " AND %s.UsrCod=%ld", + TableName, + TableName, + CrsCod, + TableName, + TableName, + UsrCod); + } + +/*****************************************************************************/ +/******************** Params used to edit an exam event **********************/ +/*****************************************************************************/ + +void ExaEvt_PutParamsEdit (void *Exams) + { + if (Exams) + { + Gam_PutParams (Exams); + ExaEvt_PutParamEvtCod (((struct Exa_Exams *) Exams)->EvtCod); + } + } + +/*****************************************************************************/ +/********************* Params used to edit an exam event *********************/ +/*****************************************************************************/ + +static void ExaEvt_PutParamsPlay (void *EvtCod) + { + if (EvtCod) + { + if (*((long *) EvtCod) > 0) + ExaEvt_PutParamEvtCod (*((long *) EvtCod)); + } + } + +/*****************************************************************************/ +/***************** Write parameter with code of exam event *******************/ +/*****************************************************************************/ + +static void ExaEvt_PutParamEvtCod (long EvtCod) + { + Par_PutHiddenParamLong (NULL,"EvtCod",EvtCod); + } + +/*****************************************************************************/ +/************************** Get and check parameters *************************/ +/*****************************************************************************/ + +void ExaEvt_GetAndCheckParameters (struct Exa_Exams *Exams, + struct Exa_Exam *Exam, + struct ExaEvt_Match *Event) + { + /***** Get parameters *****/ + /* Get parameters of exam */ + if ((Exam->ExaCod = Gam_GetParams (Exams)) <= 0) + Lay_ShowErrorAndExit ("Code of exam is missing."); + Grp_GetParamWhichGroups (); + Gam_GetDataOfGameByCod (Exam); + + /* Get exam event code */ + if ((Event->EvtCod = ExaEvt_GetParamEvtCod ()) <= 0) + Lay_ShowErrorAndExit ("Code of exam event is missing."); + ExaEvt_GetDataOfEventByCod (Event); + + /***** Ensure parameters are correct *****/ + if (Exam->ExaCod != Event->ExaCod) + Lay_ShowErrorAndExit ("Wrong exam code."); + if (Exam->CrsCod != Gbl.Hierarchy.Crs.CrsCod) + Lay_ShowErrorAndExit ("Event does not belong to this course."); + } + +/*****************************************************************************/ +/***************** Get parameter with code of exam event *********************/ +/*****************************************************************************/ + +long ExaEvt_GetParamEvtCod (void) + { + /***** Get code of exam event *****/ + return Par_GetParToLong ("EvtCod"); + } + +/*****************************************************************************/ +/* Put a big button to play exam event (start a new exam event) as a teacher */ +/*****************************************************************************/ + +static void ExaEvt_PutFormNewMatch (const struct Exa_Exam *Exam) + { + extern const char *Hlp_ASSESSMENT_Games_matches; + extern const char *Txt_New_match; + extern const char *Txt_Title; + extern const char *Txt_Play; + + /***** Start section for a new exam event *****/ + HTM_SECTION_Begin (ExaEvt_NEW_MATCH_SECTION_ID); + + /***** Begin form *****/ + Frm_StartForm (ActNewExaEvt); + Gam_PutParamGameCod (Exam->ExaCod); + Gam_PutParamQstInd (0); // Start by first question in exam + + /***** Begin box and table *****/ + Box_BoxTableBegin (NULL,Txt_New_match, + NULL,NULL, + Hlp_ASSESSMENT_Games_matches,Box_NOT_CLOSABLE,2); + + /***** Event title *****/ + HTM_TR_Begin (NULL); + + /* Label */ + Frm_LabelColumn ("RT","Title",Txt_Title); + + /* Data */ + HTM_TD_Begin ("class=\"LT\""); + HTM_INPUT_TEXT ("Title",Gam_MAX_CHARS_TITLE,Exam->Title,false, + "id=\"Title\" size=\"45\" required=\"required\""); + HTM_TD_End (); + + HTM_TR_End (); + + /***** Groups *****/ + ExaEvt_ShowLstGrpsToCreateEvent (); + + /***** End table *****/ + HTM_TABLE_End (); + + /***** Put icon to submit the form *****/ + HTM_INPUT_IMAGE (Cfg_URL_ICON_PUBLIC,"play.svg", + Txt_Play,"CONTEXT_OPT ICO_HIGHLIGHT ICO64x64"); + + /***** End box *****/ + Box_BoxEnd (); + + /***** End form *****/ + Frm_EndForm (); + + /***** End section for a new exam event *****/ + HTM_SECTION_End (); + } + +/*****************************************************************************/ +/************** Show list of groups to create a new exam event ***************/ +/*****************************************************************************/ + +static void ExaEvt_ShowLstGrpsToCreateEvent (void) + { + extern const char *The_ClassFormInBox[The_NUM_THEMES]; + extern const char *Txt_Groups; + extern const char *Txt_The_whole_course; + unsigned NumGrpTyp; + + /***** Get list of groups types and groups in this course *****/ + Grp_GetListGrpTypesAndGrpsInThisCrs (Grp_ONLY_GROUP_TYPES_WITH_GROUPS); + + if (Gbl.Crs.Grps.GrpTypes.Num) + { + /***** Begin box and table *****/ + HTM_TR_Begin (NULL); + + HTM_TD_Begin ("class=\"%s RT\"",The_ClassFormInBox[Gbl.Prefs.Theme]); + HTM_TxtF ("%s:",Txt_Groups); + HTM_TD_End (); + + HTM_TD_Begin ("class=\"LT\""); + Box_BoxTableBegin ("95%",NULL, + NULL,NULL, + NULL,Box_NOT_CLOSABLE,0); + + /***** First row: checkbox to select the whole course *****/ + HTM_TR_Begin (NULL); + + HTM_TD_Begin ("colspan=\"7\" class=\"DAT LM\""); + HTM_LABEL_Begin (NULL); + HTM_INPUT_CHECKBOX ("WholeCrs",HTM_SUBMIT_ON_CHANGE, + "id=\"WholeCrs\" value=\"Y\" checked=\"checked\"" + " onclick=\"uncheckChildren(this,'GrpCods')\""); + HTM_TxtF ("%s %s",Txt_The_whole_course,Gbl.Hierarchy.Crs.ShrtName); + HTM_LABEL_End (); + HTM_TD_End (); + + HTM_TR_End (); + + /***** List the groups for each group type *****/ + for (NumGrpTyp = 0; + NumGrpTyp < Gbl.Crs.Grps.GrpTypes.Num; + NumGrpTyp++) + if (Gbl.Crs.Grps.GrpTypes.LstGrpTypes[NumGrpTyp].NumGrps) + Grp_ListGrpsToEditAsgAttSvyMch (&Gbl.Crs.Grps.GrpTypes.LstGrpTypes[NumGrpTyp], + -1L, // -1 means "New exam event" + Grp_MATCH); + + /***** End table and box *****/ + Box_BoxTableEnd (); + HTM_TD_End (); + HTM_TR_End (); + } + + /***** Free list of groups types and groups in this course *****/ + Grp_FreeListGrpTypesAndGrps (); + } + +/*****************************************************************************/ +/******************* Create a new exam event (by a teacher) ******************/ +/*****************************************************************************/ + +void ExaEvt_CreateNewEventTch (void) + { + long ExaCod; + char Title[Exa_MAX_BYTES_TITLE + 1]; + + /***** Get form parameters *****/ + /* Get exam event code */ + if ((ExaCod = Gam_GetParamGameCod ()) == -1L) + Lay_ShowErrorAndExit ("Code of exam is missing."); + + /* Get exam event title */ + Par_GetParToText ("Title",Title,Exa_MAX_BYTES_TITLE); + + /* Get groups for this games */ + Grp_GetParCodsSeveralGrps (); + + /***** Create a new exam event *****/ + ExaEvt_SetEvtCodBeingPlayed (ExaEvt_CreateEvent (ExaCod,Title)); + + /***** Free memory for list of selected groups *****/ + Grp_FreeListCodSelectedGrps (); + } + +/*****************************************************************************/ +/**** Show button to actually start / resume an exam event (by a teacher) ****/ +/*****************************************************************************/ + +void ExaEvt_ResumeEvent (void) + { + struct ExaEvt_Match Event; + + /***** Remove old players. + This function must be called by a teacher + before getting exam event status. *****/ + ExaEvt_RemoveOldPlayers (); + + /***** Get data of the exam event from database *****/ + Event.EvtCod = ExaEvt_GetEvtCodBeingPlayed (); + ExaEvt_GetDataOfEventByCod (&Event); + + /***** Check if I have permission to resume exam event *****/ + if (!ExaEvt_CheckIfICanEditThisEvent (&Event)) + Lay_NoPermissionExit (); + + /***** Update exam event status in database *****/ + ExaEvt_UpdateEventStatusInDB (&Event); + + /***** Show current exam event status *****/ + HTM_DIV_Begin ("id=\"exam event\" class=\"EXA_CONT\""); + ExaEvt_ShowMatchStatusForTch (&Event); + HTM_DIV_End (); + } + +/*****************************************************************************/ +/******************* Create a new exam event in an exam **********************/ +/*****************************************************************************/ + +static long ExaEvt_CreateEvent (long ExaCod,char Title[Exa_MAX_BYTES_TITLE + 1]) + { + long EvtCod; + + /***** Insert this new exam event into database *****/ + EvtCod = DB_QueryINSERTandReturnCode ("can not create exam event", + "INSERT exa_events " + "(ExaCod,UsrCod,StartTime,EndTime,Title," + "QstInd,QstCod,Showing,Countdown," + "NumCols,ShowQstResults,ShowUsrResults)" + " VALUES " + "(%ld," // ExaCod + "%ld," // UsrCod + "NOW()," // StartTime + "NOW()," // EndTime + "'%s'," // Title + "0," // QstInd: Event has not started, so not the first question yet + "-1," // QstCod: Non-existent question + "'%s'," // Showing: What is being shown + "-1," // Countdown: No countdown + "%u," // NumCols: Number of columns in answers + "'N'," // ShowQstResults: Don't show question results initially + "'N')", // ShowUsrResults: Don't show user results initially + ExaCod, + Gbl.Usrs.Me.UsrDat.UsrCod, // Exam creator + Title, + ExaEvt_ShowingStringsDB[ExaEvt_SHOWING_DEFAULT], + ExaEvt_NUM_COLS_DEFAULT); + + /***** Create indexes for answers *****/ + ExaEvt_CreateIndexes (ExaCod,EvtCod); + + /***** Create groups associated to the exam event *****/ + if (Gbl.Crs.Grps.LstGrpsSel.NumGrps) + ExaEvt_CreateGrps (EvtCod); + + return EvtCod; + } + +/*****************************************************************************/ +/******************** Create indexes for an exam event ***********************/ +/*****************************************************************************/ +/* Everytime a new exam event is created, + the answers of each shufflable question are shuffled. + The shuffling is stored in a table of indexes + that will be read when showing an exam event */ + +static void ExaEvt_CreateIndexes (long ExaCod,long EvtCod) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NumQsts; + unsigned NumQst; + struct Tst_Question Question; + long LongNum; + unsigned QstInd; + + /***** Get questions of the exam *****/ + NumQsts = (unsigned) + DB_QuerySELECT (&mysql_res,"can not get questions of an exam", + "SELECT exa_questions.QstCod," // row[0] + "exa_questions.QstInd," // row[1] + "tst_questions.AnsType," // row[2] + "tst_questions.Shuffle" // row[3] + " FROM exa_questions,tst_questions" + " WHERE exa_questions.ExaCod=%ld" + " AND exa_questions.QstCod=tst_questions.QstCod" + " ORDER BY exa_questions.QstInd", + ExaCod); + + /***** For each question in exam... *****/ + for (NumQst = 0; + NumQst < NumQsts; + NumQst++) + { + /***** Create test question *****/ + Tst_QstConstructor (&Question); + + /***** Get question data *****/ + row = mysql_fetch_row (mysql_res); + /* + exa_questions.QstCod row[0] + exa_questions.QstInd row[1] + tst_questions.AnsType row[2] + tst_questions.Shuffle row[3] + */ + + /* Get question code (row[0]) */ + if ((Question.QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0) + Lay_ShowErrorAndExit ("Wrong code of question."); + + /* Get question index (row[1]) */ + if ((LongNum = Str_ConvertStrCodToLongCod (row[1])) < 0) + Lay_ShowErrorAndExit ("Wrong question index."); + QstInd = (unsigned) LongNum; + + /* Get answer type (row[2]) */ + Question.Answer.Type = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[2]); + if (Question.Answer.Type != Tst_ANS_UNIQUE_CHOICE) + Lay_ShowErrorAndExit ("Wrong answer type."); + + /* Get shuffle (row[3]) */ + Question.Answer.Shuffle = (row[3][0] == 'Y'); + + /***** Reorder answer *****/ + ExaEvt_ReorderAnswer (EvtCod,QstInd,&Question); + + /***** Destroy test question *****/ + Tst_QstDestructor (&Question); + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/**************** Reorder answers of an exam event question ******************/ +/*****************************************************************************/ + +static void ExaEvt_ReorderAnswer (long EvtCod,unsigned QstInd, + const struct Tst_Question *Question) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NumAnss; + unsigned NumAns; + long LongNum; + unsigned AnsInd; + char StrOneAnswer[Cns_MAX_DECIMAL_DIGITS_UINT + 1]; + char StrAnswersOneQst[TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1]; + + /***** Initialize list of answers to empty string *****/ + StrAnswersOneQst[0] = '\0'; + + /***** Get questions of the exam *****/ + NumAnss = (unsigned) + DB_QuerySELECT (&mysql_res,"can not get questions of an exam", + "SELECT AnsInd" // row[0] + " FROM tst_answers" + " WHERE QstCod=%ld" + " ORDER BY %s", + Question->QstCod, + Question->Answer.Shuffle ? "RAND()" : // Use RAND() because is really random; RAND(NOW()) repeats order + "AnsInd"); + + /***** For each answer in question... *****/ + for (NumAns = 0; + NumAns < NumAnss; + NumAns++) + { + row = mysql_fetch_row (mysql_res); + + /* Get answer index (row[0]) */ + if ((LongNum = Str_ConvertStrCodToLongCod (row[0])) < 0) + Lay_ShowErrorAndExit ("Wrong answer index."); + AnsInd = (unsigned) LongNum; + snprintf (StrOneAnswer,sizeof (StrOneAnswer), + "%u",AnsInd); + + /* Concatenate answer index to list of answers */ + if (NumAns) + Str_Concat (StrAnswersOneQst,",", + TstExa_MAX_BYTES_ANSWERS_ONE_QST); + Str_Concat (StrAnswersOneQst,StrOneAnswer, + TstExa_MAX_BYTES_ANSWERS_ONE_QST); + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + /***** Create entry for this question in table of exam event indexes *****/ + DB_QueryINSERT ("can not create exam event indexes", + "INSERT INTO exa_indexes" + " (EvtCod,QstInd,Indexes)" + " VALUES" + " (%ld,%u,'%s')", + EvtCod,QstInd,StrAnswersOneQst); + } + +/*****************************************************************************/ +/***************** Get indexes for a question from database ******************/ +/*****************************************************************************/ + +void ExaEvt_GetIndexes (long EvtCod,unsigned QstInd, + unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION]) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + char StrIndexesOneQst[TstExa_MAX_BYTES_INDEXES_ONE_QST + 1]; + + /***** Get indexes for a question from database *****/ + if (!DB_QuerySELECT (&mysql_res,"can not get data of a question", + "SELECT Indexes" // row[0] + " FROM exa_indexes" + " WHERE EvtCod=%ld AND QstInd=%u", + EvtCod,QstInd)) + Lay_ShowErrorAndExit ("No indexes found for a question."); + row = mysql_fetch_row (mysql_res); + + /* Get indexes (row[0]) */ + Str_Copy (StrIndexesOneQst,row[0], + TstExa_MAX_BYTES_INDEXES_ONE_QST); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + /***** Get indexes from string *****/ + Par_ReplaceCommaBySeparatorMultiple (StrIndexesOneQst); + TstExa_GetIndexesFromStr (StrIndexesOneQst,Indexes); + } + +/*****************************************************************************/ +/**************** Create groups associated to an exam event ******************/ +/*****************************************************************************/ + +static void ExaEvt_CreateGrps (long EvtCod) + { + unsigned NumGrpSel; + + /***** Create groups associated to the exam event *****/ + for (NumGrpSel = 0; + NumGrpSel < Gbl.Crs.Grps.LstGrpsSel.NumGrps; + NumGrpSel++) + /* Create group */ + DB_QueryINSERT ("can not associate a group to an exam event", + "INSERT INTO exa_groups" + " (EvtCod,GrpCod)" + " VALUES" + " (%ld,%ld)", + EvtCod,Gbl.Crs.Grps.LstGrpsSel.GrpCods[NumGrpSel]); + } + +/*****************************************************************************/ +/********************* Remove one group from all events **********************/ +/*****************************************************************************/ + +void ExaEvt_RemoveGroup (long GrpCod) + { + /***** Remove group from all the events *****/ + DB_QueryDELETE ("can not remove group" + " from the associations between events and groups", + "DELETE FROM exa_groups WHERE GrpCod=%ld", + GrpCod); + } + +/*****************************************************************************/ +/***************** Remove groups of one type from all events *****************/ +/*****************************************************************************/ + +void ExaEvt_RemoveGroupsOfType (long GrpTypCod) + { + /***** Remove group from all the events *****/ + DB_QueryDELETE ("can not remove groups of a type" + " from the associations between events and groups", + "DELETE FROM exa_groups" + " USING crs_grp,exa_groups" + " WHERE crs_grp.GrpTypCod=%ld" + " AND crs_grp.GrpCod=exa_groups.GrpCod", + GrpTypCod); + } + +/*****************************************************************************/ +/************** Insert/update an exam exam event being played ****************/ +/*****************************************************************************/ + +static void ExaEvt_UpdateEventStatusInDB (const struct ExaEvt_Match *Event) + { + char *EvtSubQuery; + + /***** Update end time only if exam event is currently being played *****/ + if (Event->Status.Playing) // Event is being played + { + if (asprintf (&EvtSubQuery,"exa_events.EndTime=NOW(),") < 0) + Lay_NotEnoughMemoryExit (); + } + else // Event is paused, not being played + { + if (asprintf (&EvtSubQuery,"%s","") < 0) + Lay_NotEnoughMemoryExit (); + } + + /***** Update exam event status in database *****/ + DB_QueryUPDATE ("can not update exam event being played", + "UPDATE exa_events,exa_exams" + " SET %s" + "exa_events.QstInd=%u," + "exa_events.QstCod=%ld," + "exa_events.Showing='%s'," + "exa_events.Countdown=%ld," + "exa_events.NumCols=%u," + "exa_events.ShowQstResults='%c'" + " WHERE exa_events.EvtCod=%ld" + " AND exa_events.ExaCod=exa_exams.ExaCod" + " AND exa_exams.CrsCod=%ld", // Extra check + EvtSubQuery, + Event->Status.QstInd,Event->Status.QstCod, + ExaEvt_ShowingStringsDB[Event->Status.Showing], + Event->Status.Countdown, + Event->Status.NumCols, + Event->Status.ShowQstResults ? 'Y' : 'N', + Event->EvtCod,Gbl.Hierarchy.Crs.CrsCod); + free (EvtSubQuery); + + if (Event->Status.Playing) // Event is being played + /* Update exam event as being played */ + ExaEvt_UpdateEventAsBeingPlayed (Event->EvtCod); + else // Event is paused, not being played + /* Update exam event as not being played */ + ExaEvt_SetEventAsNotBeingPlayed (Event->EvtCod); + } + +/*****************************************************************************/ +/********** Update elapsed time in current question (by a teacher) ***********/ +/*****************************************************************************/ + +static void ExaEvt_UpdateElapsedTimeInQuestion (const struct ExaEvt_Match *Event) + { + /***** Update elapsed time in current question in database *****/ + if (Event->Status.Playing && // Event is being played + Event->Status.Showing != ExaEvt_START && + Event->Status.Showing != ExaEvt_END) + DB_QueryINSERT ("can not update elapsed time in question", + "INSERT INTO exa_times (EvtCod,QstInd,ElapsedTime)" + " VALUES (%ld,%u,SEC_TO_TIME(%u))" + " ON DUPLICATE KEY" + " UPDATE ElapsedTime=ADDTIME(ElapsedTime,SEC_TO_TIME(%u))", + Event->EvtCod,Event->Status.QstInd, + Cfg_SECONDS_TO_REFRESH_MATCH_TCH, + Cfg_SECONDS_TO_REFRESH_MATCH_TCH); + } + +/*****************************************************************************/ +/**************** Get elapsed time in an exam event question *****************/ +/*****************************************************************************/ + +static void ExaEvt_GetElapsedTimeInQuestion (const struct ExaEvt_Match *Event, + struct Time *Time) + { + MYSQL_RES *mysql_res; + unsigned NumRows; + + /***** Query database *****/ + NumRows = (unsigned) DB_QuerySELECT (&mysql_res,"can not get elapsed time", + "SELECT ElapsedTime" + " FROM exa_times" + " WHERE EvtCod=%ld AND QstInd=%u", + Event->EvtCod,Event->Status.QstInd); + + /***** Get elapsed time from query result *****/ + ExaEvt_GetElapsedTime (NumRows,mysql_res,Time); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/******************** Get elapsed time in an exam event **********************/ +/*****************************************************************************/ + +static void ExaEvt_GetElapsedTimeInMatch (const struct ExaEvt_Match *Event, + struct Time *Time) + { + MYSQL_RES *mysql_res; + unsigned NumRows; + + /***** Query database *****/ + NumRows = (unsigned) DB_QuerySELECT (&mysql_res,"can not get elapsed time", + "SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(ElapsedTime)))" + " FROM exa_times WHERE EvtCod=%ld", + Event->EvtCod); + + /***** Get elapsed time from query result *****/ + ExaEvt_GetElapsedTime (NumRows,mysql_res,Time); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/******************** Get elapsed time in an exam event **********************/ +/*****************************************************************************/ + +static void ExaEvt_GetElapsedTime (unsigned NumRows,MYSQL_RES *mysql_res, + struct Time *Time) + { + MYSQL_ROW row; + bool ElapsedTimeGotFromDB = false; + + /***** Get time from H...H:MM:SS string *****/ + if (NumRows) + { + row = mysql_fetch_row (mysql_res); + + if (row[0]) + /* Get the elapsed time (row[0]) */ + if (sscanf (row[0],"%u:%02u:%02u",&Time->Hour,&Time->Minute,&Time->Second) == 3) + ElapsedTimeGotFromDB = true; + } + + /***** Initialize time to default value (0) *****/ + if (!ElapsedTimeGotFromDB) + Time->Hour = + Time->Minute = + Time->Second = 0; + } + +/*****************************************************************************/ +/******************* Play/pause exam event (by a teacher) ********************/ +/*****************************************************************************/ + +void ExaEvt_PlayPauseEvent (void) + { + struct ExaEvt_Match Event; + + /***** Remove old players. + This function must be called by a teacher + before getting exam event status. *****/ + ExaEvt_RemoveOldPlayers (); + + /***** Get data of the exam event from database *****/ + Event.EvtCod = ExaEvt_GetEvtCodBeingPlayed (); + ExaEvt_GetDataOfEventByCod (&Event); + + /***** Update status *****/ + if (Event.Status.Playing) // Event is being played ==> pause it + Event.Status.Playing = false; // Pause exam event + else // Event is paused, not being played ==> play it + /* If not over, update status */ + if (Event.Status.Showing != ExaEvt_END) // Event not over + Event.Status.Playing = true; // Start/resume exam event + + /***** Update exam event status in database *****/ + ExaEvt_UpdateEventStatusInDB (&Event); + + /***** Show current exam event status *****/ + HTM_DIV_Begin ("id=\"exam event\" class=\"EXA_CONT\""); + ExaEvt_ShowMatchStatusForTch (&Event); + HTM_DIV_End (); + } + +/*****************************************************************************/ +/**** Change number of columns in answers of an exam event (by a teacher) ****/ +/*****************************************************************************/ + +void ExaEvt_ChangeNumColsEvt (void) + { + struct ExaEvt_Match Event; + + /***** Remove old players. + This function must be called by a teacher + before getting exam event status. *****/ + ExaEvt_RemoveOldPlayers (); + + /***** Get data of the exam event from database *****/ + Event.EvtCod = ExaEvt_GetEvtCodBeingPlayed (); + ExaEvt_GetDataOfEventByCod (&Event); + + /***** Get number of columns *****/ + Event.Status.NumCols = (unsigned) + Par_GetParToUnsignedLong ("NumCols", + 1, + ExaEvt_MAX_COLS, + ExaEvt_NUM_COLS_DEFAULT); + + /***** Update exam event status in database *****/ + ExaEvt_UpdateEventStatusInDB (&Event); + + /***** Show current exam event status *****/ + HTM_DIV_Begin ("id=\"exam event\" class=\"EXA_CONT\""); + ExaEvt_ShowMatchStatusForTch (&Event); + HTM_DIV_End (); + } + +/*****************************************************************************/ +/****** Toggle the display of results in an exam event (by a teacher) ********/ +/*****************************************************************************/ + +void ExaEvt_ToggleVisibilResultsEvtQst (void) + { + struct ExaEvt_Match Event; + + /***** Remove old players. + This function must be called by a teacher + before getting exam event status. *****/ + ExaEvt_RemoveOldPlayers (); + + /***** Get data of the exam event from database *****/ + Event.EvtCod = ExaEvt_GetEvtCodBeingPlayed (); + ExaEvt_GetDataOfEventByCod (&Event); + + /***** Update status *****/ + Event.Status.ShowQstResults = !Event.Status.ShowQstResults; // Toggle display + if (Event.Status.Showing == ExaEvt_RESULTS && + !Event.Status.ShowQstResults) + Event.Status.Showing = ExaEvt_ANSWERS; // Hide results + + /***** Update exam event status in database *****/ + ExaEvt_UpdateEventStatusInDB (&Event); + + /***** Show current exam event status *****/ + HTM_DIV_Begin ("id=\"exam event\" class=\"EXA_CONT\""); + ExaEvt_ShowMatchStatusForTch (&Event); + HTM_DIV_End (); + } + +/*****************************************************************************/ +/********** Show previous question in an exam event (by a teacher) ***********/ +/*****************************************************************************/ + +void ExaEvt_BackEvent (void) + { + struct ExaEvt_Match Event; + + /***** Remove old players. + This function must be called by a teacher + before getting exam event status. *****/ + ExaEvt_RemoveOldPlayers (); + + /***** Get data of the exam event from database *****/ + Event.EvtCod = ExaEvt_GetEvtCodBeingPlayed (); + ExaEvt_GetDataOfEventByCod (&Event); + + /***** Update status *****/ + ExaEvt_SetMatchStatusToPrev (&Event); + + /***** Update exam event status in database *****/ + ExaEvt_UpdateEventStatusInDB (&Event); + + /***** Show current exam event status *****/ + HTM_DIV_Begin ("id=\"exam event\" class=\"EXA_CONT\""); + ExaEvt_ShowMatchStatusForTch (&Event); + HTM_DIV_End (); + } + +/*****************************************************************************/ +/************ Show next question in an exam event (by a teacher) *************/ +/*****************************************************************************/ + +void ExaEvt_ForwardEvent (void) + { + struct ExaEvt_Match Event; + + /***** Remove old players. + This function must be called by a teacher + before getting exam event status. *****/ + ExaEvt_RemoveOldPlayers (); + + /***** Get data of the exam event from database *****/ + Event.EvtCod = ExaEvt_GetEvtCodBeingPlayed (); + ExaEvt_GetDataOfEventByCod (&Event); + + /***** Update status *****/ + ExaEvt_SetMatchStatusToNext (&Event); + + /***** Update exam event status in database *****/ + ExaEvt_UpdateEventStatusInDB (&Event); + + /***** Show current exam event status *****/ + HTM_DIV_Begin ("id=\"exam event\" class=\"EXA_CONT\""); + ExaEvt_ShowMatchStatusForTch (&Event); + HTM_DIV_End (); + } + +/*****************************************************************************/ +/************ Set exam event status to previous (backward) status ************/ +/*****************************************************************************/ + +static void ExaEvt_SetMatchStatusToPrev (struct ExaEvt_Match *Event) + { + /***** What to show *****/ + switch (Event->Status.Showing) + { + case ExaEvt_START: + ExaEvt_SetMatchStatusToStart (Event); + break; + case ExaEvt_STEM: + case ExaEvt_END: + ExaEvt_SetMatchStatusToPrevQst (Event); + break; + case ExaEvt_ANSWERS: + Event->Status.Showing = ExaEvt_STEM; + break; + case ExaEvt_RESULTS: + Event->Status.Showing = ExaEvt_ANSWERS; + break; + } + Event->Status.Countdown = -1L; // No countdown + } + +/*****************************************************************************/ +/**************** Set exam event status to previous question *****************/ +/*****************************************************************************/ + +static void ExaEvt_SetMatchStatusToPrevQst (struct ExaEvt_Match *Event) + { + /***** Get index of the previous question *****/ + Event->Status.QstInd = Gam_GetPrevQuestionIndexInGame (Event->ExaCod, + Event->Status.QstInd); + if (Event->Status.QstInd) // Start of questions not reached + { + Event->Status.QstCod = Gam_GetQstCodFromQstInd (Event->ExaCod, + Event->Status.QstInd); + Event->Status.Showing = Event->Status.ShowQstResults ? ExaEvt_RESULTS : + ExaEvt_ANSWERS; + } + else // Start of questions reached + ExaEvt_SetMatchStatusToStart (Event); + } + +/*****************************************************************************/ +/********************** Set exam event status to start ***********************/ +/*****************************************************************************/ + +static void ExaEvt_SetMatchStatusToStart (struct ExaEvt_Match *Event) + { + Event->Status.QstInd = 0; // Before first question + Event->Status.QstCod = -1L; + Event->Status.Playing = false; + Event->Status.Showing = ExaEvt_START; + } + +/*****************************************************************************/ +/************** Set exam event status to next (forward) status ***************/ +/*****************************************************************************/ + +static void ExaEvt_SetMatchStatusToNext (struct ExaEvt_Match *Event) + { + /***** What to show *****/ + switch (Event->Status.Showing) + { + case ExaEvt_START: + ExaEvt_SetMatchStatusToNextQst (Event); + break; + case ExaEvt_STEM: + Event->Status.Showing = ExaEvt_ANSWERS; + break; + case ExaEvt_ANSWERS: + if (Event->Status.ShowQstResults) + Event->Status.Showing = ExaEvt_RESULTS; + else + ExaEvt_SetMatchStatusToNextQst (Event); + break; + case ExaEvt_RESULTS: + ExaEvt_SetMatchStatusToNextQst (Event); + break; + case ExaEvt_END: + ExaEvt_SetMatchStatusToEnd (Event); + break; + } + Event->Status.Countdown = -1L; // No countdown + } + +/*****************************************************************************/ +/**************** Set exam event status to next question *********************/ +/*****************************************************************************/ + +static void ExaEvt_SetMatchStatusToNextQst (struct ExaEvt_Match *Event) + { + /***** Get index of the next question *****/ + Event->Status.QstInd = Gam_GetNextQuestionIndexInGame (Event->ExaCod, + Event->Status.QstInd); + + /***** Get question code *****/ + if (Event->Status.QstInd < ExaEvt_AFTER_LAST_QUESTION) // End of questions not reached + { + Event->Status.QstCod = Gam_GetQstCodFromQstInd (Event->ExaCod, + Event->Status.QstInd); + Event->Status.Showing = ExaEvt_STEM; + } + else // End of questions reached + ExaEvt_SetMatchStatusToEnd (Event); + } + +/*****************************************************************************/ +/********************** Set exam event status to end *************************/ +/*****************************************************************************/ + +static void ExaEvt_SetMatchStatusToEnd (struct ExaEvt_Match *Event) + { + Event->Status.QstInd = ExaEvt_AFTER_LAST_QUESTION; // After last question + Event->Status.QstCod = -1L; + Event->Status.Playing = false; + Event->Status.Showing = ExaEvt_END; + } + +/*****************************************************************************/ +/***** Show current exam event status (number, question, answers, button) ****/ +/*****************************************************************************/ + +static void ExaEvt_ShowMatchStatusForTch (struct ExaEvt_Match *Event) + { + /***** Left column *****/ + ExaEvt_ShowLeftColumnTch (Event); + + /***** Right column *****/ + ExaEvt_ShowRightColumnTch (Event); + } + +/*****************************************************************************/ +/************ Show current question being played for a student ***************/ +/*****************************************************************************/ + +static void ExaEvt_ShowMatchStatusForStd (struct ExaEvt_Match *Event,ExaEvt_Update_t Update) + { + bool ICanPlayThisMatchBasedOnGrps; + struct ExaEvt_UsrAnswer UsrAnswer; + + /***** Can I play this exam event? *****/ + ICanPlayThisMatchBasedOnGrps = ExaEvt_CheckIfICanPlayThisEventBasedOnGrps (Event); + if (!ICanPlayThisMatchBasedOnGrps) + Lay_NoPermissionExit (); + + /***** Get student's answer to this question + (<0 ==> no answer) *****/ + ExaEvt_GetQstAnsFromDB (Event->EvtCod, + Gbl.Usrs.Me.UsrDat.UsrCod, + Event->Status.QstInd, + &UsrAnswer); + + /***** Left column *****/ + ExaEvt_ShowLeftColumnStd (Event,&UsrAnswer); + + /***** Right column *****/ + ExaEvt_ShowRightColumnStd (Event,&UsrAnswer,Update); + } + +/*****************************************************************************/ +/********************** Get number of events in an exam **********************/ +/*****************************************************************************/ + +unsigned ExaEvt_GetNumEventsInExam (long ExaCod) + { + /***** Trivial check *****/ + if (ExaCod < 0) // A non-existing exam... + return 0; // ...has no events + + /***** Get number of events in an exam from database *****/ + return + (unsigned) DB_QueryCOUNT ("can not get number of events of an exam", + "SELECT COUNT(*) FROM exa_events" + " WHERE ExaCod=%ld", + ExaCod); + } + +/*****************************************************************************/ +/*************** Get number of unfinished events in an exam ******************/ +/*****************************************************************************/ + +unsigned ExaEvt_GetNumUnfinishedEventsInExam (long ExaCod) + { + /***** Trivial check *****/ + if (ExaCod < 0) // A non-existing exam... + return 0; // ...has no events + + /***** Get number of events in an exam from database *****/ + return + (unsigned) DB_QueryCOUNT ("can not get number of unfinished events of an exam", + "SELECT COUNT(*) FROM exa_events" + " WHERE ExaCod=%ld AND Showing<>'%s'", + ExaCod,ExaEvt_ShowingStringsDB[ExaEvt_END]); + } + +/*****************************************************************************/ +/********* Check if I belong to any of the groups of an exam event ***********/ +/*****************************************************************************/ + +bool ExaEvt_CheckIfICanPlayThisEventBasedOnGrps (const struct ExaEvt_Match *Event) + { + switch (Gbl.Usrs.Me.Role.Logged) + { + case Rol_STD: + /***** Check if I belong to any of the groups + associated to the exam event *****/ + return (DB_QueryCOUNT ("can not check if I can play an exam event", + "SELECT COUNT(*) FROM exa_events" + " WHERE EvtCod=%ld" + " AND" + "(EvtCod NOT IN" + " (SELECT EvtCod FROM exa_groups)" + " OR" + " EvtCod IN" + " (SELECT exa_groups.EvtCod" + " FROM exa_groups,crs_grp_usr" + " WHERE crs_grp_usr.UsrCod=%ld" + " AND exa_groups.GrpCod=crs_grp_usr.GrpCod))", + Event->EvtCod,Gbl.Usrs.Me.UsrDat.UsrCod) != 0); + break; + case Rol_NET: + /***** Only if I am the creator *****/ + return (Event->UsrCod == Gbl.Usrs.Me.UsrDat.UsrCod); + case Rol_TCH: + case Rol_SYS_ADM: + return true; + default: + return false; + } + } + +/*****************************************************************************/ +/******** Show left column when playing an exam event (as a teacher) *********/ +/*****************************************************************************/ + +static void ExaEvt_ShowLeftColumnTch (struct ExaEvt_Match *Event) + { + /***** Start left container *****/ + HTM_DIV_Begin ("class=\"EXA_LEFT_TCH\""); + + /***** Refreshable part *****/ + HTM_DIV_Begin ("id=\"match_left\" class=\"EXA_REFRESHABLE_TEACHER\""); + ExaEvt_ShowRefreshablePartTch (Event); + HTM_DIV_End (); + + /***** Put forms to start countdown *****/ + ExaEvt_PutFormsCountdown (Event); + + /***** Buttons *****/ + ExaEvt_PutMatchControlButtons (Event); + + /***** Put forms to choice which projects to show *****/ + Set_StartSettingsHead (); + ExaEvt_ShowFormColumns (Event); + Set_EndSettingsHead (); + + /***** Write button to request viewing results *****/ + ExaEvt_PutCheckboxResult (Event); + + /***** End left container *****/ + HTM_DIV_End (); + } + +/*****************************************************************************/ +/***************** Show left refreshable part for teachers *******************/ +/*****************************************************************************/ + +static void ExaEvt_ShowRefreshablePartTch (struct ExaEvt_Match *Event) + { + /***** Write elapsed time in exam event *****/ + ExaEvt_WriteElapsedTimeInEvt (Event); + + /***** Write number of question *****/ + ExaEvt_ShowNumQstInEvt (Event); + + /***** Write elapsed time in question *****/ + ExaEvt_WriteElapsedTimeInQst (Event); + + /***** Number of users who have responded this question *****/ + ExaEvt_WriteNumRespondersQst (Event); + + /***** Write hourglass *****/ + ExaEvt_PutCountdownAndHourglassIcon (Event); + } + +/*****************************************************************************/ +/****************** Write elapsed time in current exam event *****************/ +/*****************************************************************************/ + +static void ExaEvt_WriteElapsedTimeInEvt (struct ExaEvt_Match *Event) + { + struct Time Time; + + HTM_DIV_Begin ("class=\"EXA_TOP CT\""); + + /***** Get elapsed time in exam event *****/ + ExaEvt_GetElapsedTimeInMatch (Event,&Time); + + /***** Write elapsed time in hh:mm'ss" format *****/ + Dat_WriteHoursMinutesSeconds (&Time); + + HTM_DIV_End (); + } + +/*****************************************************************************/ +/****************** Write elapsed time in current question *******************/ +/*****************************************************************************/ + +static void ExaEvt_WriteElapsedTimeInQst (struct ExaEvt_Match *Event) + { + struct Time Time; + + HTM_DIV_Begin ("class=\"EXA_TIME_QST\""); + + switch (Event->Status.Showing) + { + case ExaEvt_START: + case ExaEvt_END: + HTM_Hyphen (); // Do not write elapsed time + break; + default: + ExaEvt_GetElapsedTimeInQuestion (Event,&Time); + Dat_WriteHoursMinutesSeconds (&Time); + break; + } + + HTM_DIV_End (); + } + +/*****************************************************************************/ +/************ Write number of responders to an exam event question ***********/ +/*****************************************************************************/ + +static void ExaEvt_WriteNumRespondersQst (struct ExaEvt_Match *Event) + { + extern const char *Txt_MATCH_respond; + + /***** Begin block *****/ + HTM_DIV_Begin ("class=\"EXA_NUM_ANSWERERS\""); + HTM_Txt (Txt_MATCH_respond); + HTM_BR (); + HTM_STRONG_Begin (); + + /***** Write number of responders *****/ + switch (Event->Status.Showing) + { + case ExaEvt_START: + case ExaEvt_END: + HTM_Hyphen (); // Do not write number of responders + break; + default: + HTM_Unsigned (ExaEvt_GetNumUsrsWhoAnsweredQst (Event->EvtCod, + Event->Status.QstInd)); + break; + } + + /***** Write number of players *****/ + if (Event->Status.Playing) // Event is being played + { + /* Get current number of players */ + ExaEvt_GetNumPlayers (Event); + + /* Show current number of players */ + HTM_TxtF ("/%u",Event->Status.NumPlayers); + } + + /***** End block *****/ + HTM_STRONG_End (); + HTM_DIV_End (); + } + +/*****************************************************************************/ +/*************** Write current countdown and hourglass icon ******************/ +/*****************************************************************************/ + +static void ExaEvt_PutCountdownAndHourglassIcon (struct ExaEvt_Match *Event) + { + extern const char *Txt_Countdown; + const char *Class; + const char *Icon; + + /***** Set hourglass icon depending on countdown *****/ + if (Event->Status.Showing == ExaEvt_END) // Event over + { + Class = "BT_LINK_OFF EXA_BUTTON_HIDDEN EXA_GREEN"; + Icon = "fa-hourglass-start"; + } + else if (Event->Status.Countdown < 0) // No countdown + { + Class = "BT_LINK_OFF EXA_BUTTON_OFF EXA_GREEN"; + Icon = "fa-hourglass-start"; + } + else if (Event->Status.Countdown > ExaEvt_COUNTDOWN_SECONDS_MEDIUM) // Countdown in progress + { + Class = "BT_LINK_OFF EXA_BUTTON_OFF EXA_LIMEGREEN"; + Icon = "fa-hourglass-start"; + } + else if (Event->Status.Countdown > ExaEvt_COUNTDOWN_SECONDS_SMALL) // Countdown in progress + { + Class = "BT_LINK_OFF EXA_BUTTON_OFF EXA_YELLOW"; + Icon = "fa-hourglass-half"; + } + else // Countdown about to end + { + Class = "BT_LINK_OFF EXA_BUTTON_OFF EXA_RED"; + Icon = "fa-hourglass-end"; + } + + /***** Write countdown and put hourglass icon *****/ + HTM_DIV_Begin ("class=\"EXA_SHOW_HOURGLASS\""); + HTM_DIV_Begin ("class=\"EXA_BIGBUTTON_CONT\""); + HTM_BUTTON_BUTTON_Begin (Txt_Countdown,Class,NULL); + + /* Countdown */ + if (Event->Status.Countdown > 0) + HTM_TxtF (" %02ld″",Event->Status.Countdown); + else + HTM_NBSP (); + HTM_BR (); + + /* Icon */ + HTM_TxtF ("",Icon); + + HTM_BUTTON_End (); + HTM_DIV_End (); + HTM_DIV_End (); + } + +/*****************************************************************************/ +/******************** Put all forms to start countdowns **********************/ +/*****************************************************************************/ + +static void ExaEvt_PutFormsCountdown (struct ExaEvt_Match *Event) + { + /***** Start container *****/ + HTM_DIV_Begin ("class=\"EXA_SHOW_HOURGLASS\""); + + /***** Put forms to start countdown *****/ + ExaEvt_PutFormCountdown (Event,-1 ,"EXA_GREEN" ); + ExaEvt_PutFormCountdown (Event,ExaEvt_COUNTDOWN_SECONDS_LARGE ,"EXA_LIMEGREEN"); + ExaEvt_PutFormCountdown (Event,ExaEvt_COUNTDOWN_SECONDS_MEDIUM,"EXA_YELLOW" ); + ExaEvt_PutFormCountdown (Event,ExaEvt_COUNTDOWN_SECONDS_SMALL ,"EXA_RED" ); + + /***** End container *****/ + HTM_DIV_End (); + } + +/*****************************************************************************/ +/****** Put a form to start a countdown with a given number of seconds *******/ +/*****************************************************************************/ + +static void ExaEvt_PutFormCountdown (struct ExaEvt_Match *Event,long Seconds,const char *Color) + { + extern const char *Txt_Countdown; + char *OnSubmit; + bool PutForm = Event->Status.Showing != ExaEvt_END; + + if (PutForm) + { + /***** Start form *****/ + if (asprintf (&OnSubmit,"updateMatchTch('match_left'," + "'act=%ld&ses=%s&EvtCod=%ld&Countdown=%ld');" + " return false;", // return false is necessary to not submit form + Act_GetActCod (ActExaEvtCntDwn),Gbl.Session.Id, + Event->EvtCod,Seconds) < 0) + Lay_NotEnoughMemoryExit (); + Frm_StartFormOnSubmit (ActUnk,OnSubmit); + + } + else + { + } + + /***** Put icon *****/ + HTM_DIV_Begin ("class=\"EXA_SMALLBUTTON_CONT\""); + + HTM_BUTTON_SUBMIT_Begin (PutForm ? Txt_Countdown : + NULL, + Str_BuildStringStr (PutForm ? "BT_LINK EXA_BUTTON_ON %s" : + "BT_LINK_OFF EXA_BUTTON_HIDDEN %s", + Color), + NULL); + Str_FreeString (); + + HTM_NBSP (); + if (Seconds >= 0) + HTM_TxtF ("%ld″",Seconds); + else + { + HTM_Txt ("∞"); + HTM_NBSP (); + } + + HTM_BUTTON_End (); + + HTM_DIV_End (); + + /***** End form *****/ + if (PutForm) + { + Frm_EndForm (); + free (OnSubmit); + } + } + +/*****************************************************************************/ +/******* Show right column when playing an exam event (as a teacher) *********/ +/*****************************************************************************/ + +static void ExaEvt_ShowRightColumnTch (const struct ExaEvt_Match *Event) + { + /***** Start right container *****/ + HTM_DIV_Begin ("class=\"EXA_RIGHT_TCH\""); + + /***** Top row: exam event title *****/ + ExaEvt_ShowEventTitleTch (Event); + + /***** Bottom row: current question and possible answers *****/ + if (Event->Status.Showing == ExaEvt_END) // Event over + ExaEvt_ShowEventScore (Event); + else // Event not over + ExaEvt_ShowQuestionAndAnswersTch (Event); + + /***** End right container *****/ + HTM_DIV_End (); + } + +/*****************************************************************************/ +/******** Show left column when playing an exam event (as a student) *********/ +/*****************************************************************************/ + +static void ExaEvt_ShowLeftColumnStd (const struct ExaEvt_Match *Event, + const struct ExaEvt_UsrAnswer *UsrAnswer) + { + bool Answered = UsrAnswer->NumOpt >= 0; + + /***** Start left container *****/ + HTM_DIV_Begin ("class=\"EXA_LEFT_STD\""); + + /***** Top *****/ + HTM_DIV_Begin ("class=\"EXA_TOP CT\""); + HTM_DIV_End (); + + /***** Write number of question *****/ + ExaEvt_ShowNumQstInEvt (Event); + + switch (Event->Status.Showing) + { + case ExaEvt_START: + case ExaEvt_END: + break; + default: + /***** Write whether question is answered or not *****/ + ExaEvt_PutIfAnswered (Event,Answered); + + if (Event->Status.Playing && // Event is being played + Event->Status.Showing == ExaEvt_ANSWERS && // Teacher's screen is showing question answers + Answered) // I have answered this question + /***** Put icon to remove my answet *****/ + ExaEvt_PutIconToRemoveMyAnswer (Event); + break; + } + + /***** End left container *****/ + HTM_DIV_End (); + } + +/*****************************************************************************/ +/******* Show right column when playing an exam event (as a student) *********/ +/*****************************************************************************/ + +static void ExaEvt_ShowRightColumnStd (struct ExaEvt_Match *Event, + const struct ExaEvt_UsrAnswer *UsrAnswer, + ExaEvt_Update_t Update) + { + extern const char *Txt_Please_wait_; + + /***** Start right container *****/ + HTM_DIV_Begin ("class=\"EXA_RIGHT_STD\""); + + /***** Top row *****/ + ExaEvt_ShowEventTitleStd (Event); + + /***** Bottom row *****/ + if (Event->Status.Playing) // Event is being played + { + if (Event->Status.Showing == ExaEvt_END) // Event over + ExaEvt_ShowWaitImage (Txt_Please_wait_); + else // Event not over + { + HTM_DIV_Begin ("class=\"EXA_BOTTOM\""); + + /***** Update players ******/ + if (ExaEvt_RegisterMeAsPlayerInEvent (Event)) + { + if (Event->Status.Showing == ExaEvt_ANSWERS) // Teacher's screen is showing question answers + /* Show current question and possible answers */ + if (!ExaEvt_ShowQuestionAndAnswersStd (Event,UsrAnswer,Update)) + Ale_ShowAlert (Ale_ERROR,"Wrong question."); + } + else + Ale_ShowAlert (Ale_ERROR,"You can not join this exam event."); + + HTM_DIV_End (); + } + } + else // Event is not being played + ExaEvt_ShowWaitImage (Txt_Please_wait_); + + /***** End right container *****/ + HTM_DIV_End (); + } + +/*****************************************************************************/ +/********************* Show number of question in exam ***********************/ +/*****************************************************************************/ + +static void ExaEvt_ShowNumQstInEvt (const struct ExaEvt_Match *Event) + { + extern const char *Txt_MATCH_Start; + extern const char *Txt_MATCH_End; + unsigned NumQsts = Gam_GetNumQstsGame (Event->ExaCod); + + HTM_DIV_Begin ("class=\"EXA_NUM_QST\""); + switch (Event->Status.Showing) + { + case ExaEvt_START: // Not started + HTM_Txt (Txt_MATCH_Start); + break; + case ExaEvt_END: // Event over + HTM_Txt (Txt_MATCH_End); + break; + default: + HTM_TxtF ("%u/%u",Event->Status.QstInd,NumQsts); + break; + } + HTM_DIV_End (); + } + +/*****************************************************************************/ +/******************* Put buttons to control an exam event ********************/ +/*****************************************************************************/ + +static void ExaEvt_PutMatchControlButtons (const struct ExaEvt_Match *Event) + { + extern const char *Txt_Go_back; + extern const char *Txt_Go_forward; + extern const char *Txt_Pause; + extern const char *Txt_Start; + extern const char *Txt_Resume; + + /***** Start buttons container *****/ + HTM_DIV_Begin ("class=\"EXA_BUTTONS_CONT\""); + + /***** Left button *****/ + HTM_DIV_Begin ("class=\"EXA_BUTTON_LEFT_CONT\""); + switch (Event->Status.Showing) + { + case ExaEvt_START: + /* Put button to close browser tab */ + ExaEvt_PutBigButtonClose (); + break; + default: + /* Put button to go back */ + ExaEvt_PutBigButton (ActBckExaEvt,"backward",Event->EvtCod, + ExaEvt_ICON_PREVIOUS,Txt_Go_back); + break; + } + HTM_DIV_End (); + + /***** Center button *****/ + HTM_DIV_Begin ("class=\"EXA_BUTTON_CENTER_CONT\""); + if (Event->Status.Playing) // Event is being played + /* Put button to pause exam event */ + ExaEvt_PutBigButton (ActPlyPauExaEvt,"play_pause",Event->EvtCod, + ExaEvt_ICON_PAUSE,Txt_Pause); + else // Event is paused, not being played + { + switch (Event->Status.Showing) + { + case ExaEvt_START: // Event just started, before first question + /* Put button to start playing exam event */ + ExaEvt_PutBigButton (ActPlyPauExaEvt,"play_pause",Event->EvtCod, + ExaEvt_ICON_PLAY,Txt_Start); + break; + case ExaEvt_END: // Event over + /* Put disabled button to play exam event */ + ExaEvt_PutBigButtonHidden (ExaEvt_ICON_PLAY); + break; + default: + /* Put button to resume exam event */ + ExaEvt_PutBigButton (ActPlyPauExaEvt,"play_pause",Event->EvtCod, + ExaEvt_ICON_PLAY,Txt_Resume); + } + } + HTM_DIV_End (); + + /***** Right button *****/ + HTM_DIV_Begin ("class=\"EXA_BUTTON_RIGHT_CONT\""); + if (Event->Status.Showing == ExaEvt_END) // Event over + /* Put button to close browser tab */ + ExaEvt_PutBigButtonClose (); + else // Event not over + /* Put button to show answers */ + ExaEvt_PutBigButton (ActFwdExaEvt,"forward",Event->EvtCod, + ExaEvt_ICON_NEXT,Txt_Go_forward); + HTM_DIV_End (); + + /***** End buttons container *****/ + HTM_DIV_End (); + } + +/*****************************************************************************/ +/** Show form to choice whether to show answers in one column or two columns */ +/*****************************************************************************/ + +static void ExaEvt_ShowFormColumns (const struct ExaEvt_Match *Event) + { + extern const char *Txt_column; + extern const char *Txt_columns; + unsigned NumCols; + static const char *NumColsIcon[1 + ExaEvt_MAX_COLS] = + { + "", // Not used + "1col.png", // 1 column + "2col.png", // 2 columns + "3col.png", // 3 columns + "4col.png", // 4 columns + }; + + /***** Begin selector *****/ + Set_StartOneSettingSelector (); + + for (NumCols = 1; + NumCols <= ExaEvt_MAX_COLS; + NumCols++) + { + /* Begin container for this option */ + HTM_DIV_Begin ("class=\"%s\"", + (Event->Status.NumCols == NumCols) ? "EXA_NUM_COL_ON" : + "EXA_NUM_COL_OFF"); + + /* Begin form */ + Frm_StartForm (ActChgNumColExaEvt); + ExaEvt_PutParamEvtCod (Event->EvtCod); // Current exam event being played + ExaEvt_PutParamNumCols (NumCols); // Number of columns + + /* Number of columns */ + Ico_PutSettingIconLink (NumColsIcon[NumCols], + Str_BuildStringLongStr ((long) NumCols, + NumCols == 1 ? Txt_column : + Txt_columns)); + Str_FreeString (); + + /* End form */ + Frm_EndForm (); + + /* End container for this option */ + HTM_DIV_End (); + } + + /***** End selector *****/ + Set_EndOneSettingSelector (); + } + +/*****************************************************************************/ +/******** Write parameter with number of columns in answers of exam event *********/ +/*****************************************************************************/ + +static void ExaEvt_PutParamNumCols (unsigned NumCols) // Number of columns + { + Par_PutHiddenParamUnsigned (NULL,"NumCols",NumCols); + } + +/*****************************************************************************/ +/***************** Put checkbox to select if show results ********************/ +/*****************************************************************************/ + +static void ExaEvt_PutCheckboxResult (const struct ExaEvt_Match *Event) + { + extern const char *Txt_View_results; + + /***** Begin container *****/ + HTM_DIV_Begin ("class=\"EXA_SHOW_RESULTS\""); + + /***** Begin form *****/ + Frm_StartForm (ActChgVisResExaEvtQst); + ExaEvt_PutParamEvtCod (Event->EvtCod); // Current exam event being played + + /***** Put icon with link *****/ + HTM_BUTTON_SUBMIT_Begin (Txt_View_results,"BT_LINK DAT ICO_HIGHLIGHT",NULL); + HTM_TxtF ("", + Event->Status.ShowQstResults ? "fas fa-toggle-on" : + "fas fa-toggle-off"); + HTM_TxtF (" %s",Txt_View_results); + HTM_BUTTON_End (); + + /***** End form *****/ + Frm_EndForm (); + + /***** End container *****/ + HTM_DIV_End (); + } + +/*****************************************************************************/ +/***************** Put checkbox to select if show results ********************/ +/*****************************************************************************/ + +static void ExaEvt_PutIfAnswered (const struct ExaEvt_Match *Event,bool Answered) + { + extern const char *Txt_View_my_answer; + extern const char *Txt_MATCH_QUESTION_Answered; + extern const char *Txt_MATCH_QUESTION_Unanswered; + + /***** Start container *****/ + HTM_DIV_Begin ("class=\"EXA_SHOW_ANSWERED\""); + + /***** Put icon with link *****/ + if (Event->Status.Playing && // Event is being played + Event->Status.Showing == ExaEvt_ANSWERS && // Teacher's screen is showing question answers + Answered) // I have answered this question + { + /* Start form */ + Frm_StartForm (ActSeeExaEvtAnsQstStd); + ExaEvt_PutParamEvtCod (Event->EvtCod); // Current exam event being played + + HTM_BUTTON_OnMouseDown_Begin (Txt_View_my_answer,"BT_LINK DAT_SMALL_GREEN"); + HTM_TxtF ("","fas fa-check-circle"); + HTM_TxtF (" %s",Txt_MATCH_QUESTION_Answered); + HTM_BUTTON_End (); + + /* End form */ + Frm_EndForm (); + } + else + { + HTM_DIV_Begin ("class=\"%s\"",Answered ? "DAT_SMALL_GREEN" : + "DAT_SMALL_RED"); + HTM_TxtF ("", + Answered ? "fas fa-check-circle" : + "fas fa-exclamation-circle", + Answered ? Txt_MATCH_QUESTION_Answered : + Txt_MATCH_QUESTION_Unanswered); + HTM_TxtF (" %s",Answered ? Txt_MATCH_QUESTION_Answered : + Txt_MATCH_QUESTION_Unanswered); + HTM_DIV_End (); + } + + /***** End container *****/ + HTM_DIV_End (); + } + +/*****************************************************************************/ +/***************** Put checkbox to select if show results ********************/ +/*****************************************************************************/ + +static void ExaEvt_PutIconToRemoveMyAnswer (const struct ExaEvt_Match *Event) + { + extern const char *Txt_Delete_my_answer; + + /***** Start container *****/ + HTM_DIV_Begin ("class=\"EXA_REM_MY_ANS\""); + + /***** Start form *****/ + Frm_StartForm (ActRemExaEvtAnsQstStd); + ExaEvt_PutParamEvtCod (Event->EvtCod); // Current exam event being played + Gam_PutParamQstInd (Event->Status.QstInd); // Current question index shown + + /***** Put icon with link *****/ + HTM_DIV_Begin ("class=\"EXA_BIGBUTTON_CONT\""); + HTM_BUTTON_OnMouseDown_Begin (Txt_Delete_my_answer,"BT_LINK EXA_BUTTON_ON ICO_DARKRED"); + HTM_Txt (""); + HTM_BUTTON_End (); + HTM_DIV_End (); + + /***** End form *****/ + Frm_EndForm (); + + /***** End container *****/ + HTM_DIV_End (); + } + +/*****************************************************************************/ +/***************************** Show exam event title ******************************/ +/*****************************************************************************/ + +static void ExaEvt_ShowEventTitleTch (const struct ExaEvt_Match *Event) + { + /***** Event title *****/ + HTM_DIV_Begin ("class=\"EXA_TOP LT\""); + HTM_Txt (Event->Title); + HTM_DIV_End (); + } + +static void ExaEvt_ShowEventTitleStd (const struct ExaEvt_Match *Event) + { + /***** Event title *****/ + HTM_DIV_Begin ("class=\"EXA_TOP CT\""); + HTM_Txt (Event->Title); + HTM_DIV_End (); + } + +/*****************************************************************************/ +/***** Show question and its answers when playing an exam event (as a teacher) *****/ +/*****************************************************************************/ + +static void ExaEvt_ShowQuestionAndAnswersTch (const struct ExaEvt_Match *Event) + { + extern const char *Txt_MATCH_Paused; + extern const char *Txt_Question_removed; + struct Tst_Question Question; + + /***** Create test question *****/ + Tst_QstConstructor (&Question); + Question.QstCod = Event->Status.QstCod; + + /***** Trivial check: do not show anything on exam event start and end *****/ + switch (Event->Status.Showing) + { + case ExaEvt_START: + case ExaEvt_END: + return; + default: + break; + } + + /***** Get data of question from database *****/ + if (Tst_GetQstDataFromDB (&Question)) + { + /***** Show question *****/ + /* Check answer type */ + if (Question.Answer.Type != Tst_ANS_UNIQUE_CHOICE) + Lay_ShowErrorAndExit ("Wrong answer type."); + + /* Begin container */ + HTM_DIV_Begin ("class=\"EXA_BOTTOM\""); // Bottom + + /* Write stem */ + Tst_WriteQstStem (Question.Stem,"EXA_TCH_STEM", + true); // Visible + + /* Show media */ + Med_ShowMedia (&Question.Media, + "TEST_MED_EDIT_LIST_STEM_CONTAINER", + "TEST_MED_EDIT_LIST_STEM"); + + /***** Write answers? *****/ + switch (Event->Status.Showing) + { + case ExaEvt_ANSWERS: + if (Event->Status.Playing) // Event is being played + /* Write answers */ + ExaEvt_WriteAnswersEventResult (Event, + &Question, + "EXA_TCH_ANS", + false); // Don't show result + else // Event is paused, not being played + ExaEvt_ShowWaitImage (Txt_MATCH_Paused); + break; + case ExaEvt_RESULTS: + /* Write answers with results */ + ExaEvt_WriteAnswersEventResult (Event, + &Question, + "EXA_TCH_ANS", + true); // Show result + break; + default: + /* Don't write anything */ + break; + } + + /* End container */ + HTM_DIV_End (); // Bottom + } + else + Ale_ShowAlert (Ale_WARNING,Txt_Question_removed); + + /***** Destroy test question *****/ + Tst_QstDestructor (&Question); + } + +/*****************************************************************************/ +/************* Write answers of a question when seeing an exam event ***************/ +/*****************************************************************************/ + +static void ExaEvt_WriteAnswersEventResult (const struct ExaEvt_Match *Event, + const struct Tst_Question *Question, + const char *Class,bool ShowResult) + { + /***** Write answer depending on type *****/ + if (Question->Answer.Type == Tst_ANS_UNIQUE_CHOICE) + ExaEvt_WriteChoiceAnsViewEvent (Event, + Question, + Class,ShowResult); + else + Ale_ShowAlert (Ale_ERROR,"Type of answer not valid in an exam."); + } + +/*****************************************************************************/ +/******** Write single or multiple choice answer when seeing an exam event *********/ +/*****************************************************************************/ + +void ExaEvt_WriteChoiceAnsViewEvent (const struct ExaEvt_Match *Event, + const struct Tst_Question *Question, + const char *Class,bool ShowResult) + { + unsigned NumOpt; + bool RowIsOpen = false; + unsigned NumRespondersQst; + unsigned NumRespondersAns; + unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION]; // Indexes of all answers of this question + + /***** Get number of users who have answered this question from database *****/ + NumRespondersQst = ExaEvt_GetNumUsrsWhoAnsweredQst (Event->EvtCod,Event->Status.QstInd); + + /***** Get indexes for this question in exam event *****/ + ExaEvt_GetIndexes (Event->EvtCod,Event->Status.QstInd,Indexes); + + /***** Begin table *****/ + HTM_TABLE_BeginWidePadding (0); + + /***** Show options distributed in columns *****/ + for (NumOpt = 0; + NumOpt < Question->Answer.NumOptions; + NumOpt++) + { + /***** Start row? *****/ + if (NumOpt % Event->Status.NumCols == 0) + { + HTM_TR_Begin (NULL); + RowIsOpen = true; + } + + /***** Write letter for this option *****/ + HTM_TD_Begin ("class=\"EXA_TCH_BUTTON_TD\""); + HTM_DIV_Begin ("class=\"EXA_TCH_BUTTON BT_%c\"",'A' + (char) NumOpt); + HTM_TxtF ("%c",'a' + (char) NumOpt); + HTM_DIV_End (); + HTM_TD_End (); + + /***** Write the option text and the result *****/ + HTM_TD_Begin ("class=\"LT\""); + HTM_LABEL_Begin ("for=\"Ans%06u_%u\" class=\"%s\"",Event->Status.QstInd,NumOpt,Class); + + /* Convert text, that is in HTML, to rigorous HTML */ + Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, + Question->Answer.Options[NumOpt].Text, + Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false); + HTM_Txt (Question->Answer.Options[Indexes[NumOpt]].Text); + + HTM_LABEL_End (); + Med_ShowMedia (&Question->Answer.Options[Indexes[NumOpt]].Media, + "TEST_MED_SHOW_CONT", + "TEST_MED_SHOW"); + + /* Show result (number of users who answered? */ + if (ShowResult) + { + /* Get number of users who selected this answer */ + NumRespondersAns = ExaEvt_GetNumUsrsWhoHaveChosenAns (Event->EvtCod,Event->Status.QstInd,Indexes[NumOpt]); + + /* Draw proportional bar for this answer */ + ExaEvt_DrawBarNumUsrs (NumRespondersAns,NumRespondersQst, + Question->Answer.Options[Indexes[NumOpt]].Correct); + } + else + /* Draw empty bar for this answer + in order to show the same layout that the one shown with results */ + ExaEvt_DrawBarNumUsrs (0,0, + false); // Not used when length of bar is 0 + + HTM_TD_End (); + + /***** End row? *****/ + if (NumOpt % Event->Status.NumCols == Event->Status.NumCols - 1) + { + HTM_TR_End (); + RowIsOpen = false; + } + } + + /***** End row? *****/ + if (RowIsOpen) + HTM_TR_End (); + + /***** End table *****/ + HTM_TABLE_End (); + } + +/*****************************************************************************/ +/***** Show question and its answers when playing an exam event (as a student) *****/ +/*****************************************************************************/ +// Return true on valid question, false on invalid question + +static bool ExaEvt_ShowQuestionAndAnswersStd (const struct ExaEvt_Match *Event, + const struct ExaEvt_UsrAnswer *UsrAnswer, + ExaEvt_Update_t Update) + { + unsigned NumOptions; + unsigned NumOpt; + char *Class; + + /***** Trivial check: this question must be valid for games *****/ + if (!Tst_CheckIfQuestionIsValidForGame (Event->Status.QstCod)) + return false; + + /***** Get number of options in this question *****/ + NumOptions = Tst_GetNumAnswersQst (Event->Status.QstCod); + + /***** Begin table *****/ + HTM_TABLE_BeginWidePadding (8); + + for (NumOpt = 0; + NumOpt < NumOptions; + NumOpt++) + { + /***** Start row *****/ + HTM_TR_Begin (NULL); + + /***** Write letter for this option *****/ + /* Begin table cell */ + HTM_TD_Begin ("class=\"EXA_STD_CELL\""); + + /* Form with button. + Sumitting onmousedown instead of default onclick + is necessary in order to be fast + and not lose clicks due to refresh */ + Frm_StartForm (ActAnsExaEvtQstStd); + ExaEvt_PutParamEvtCod (Event->EvtCod); // Current exam event being played + Gam_PutParamQstInd (Event->Status.QstInd); // Current question index shown + ExaEvt_PutParamNumOpt (NumOpt); // Number of button + + if (asprintf (&Class,"EXA_STD_BUTTON%s BT_%c", + UsrAnswer->NumOpt == (int) NumOpt && // Student's answer + Update == ExaEvt_CHANGE_STATUS_BY_STUDENT ? " EXA_STD_ANSWER_SELECTED" : + "", + 'A' + (char) NumOpt) < 0) + Lay_NotEnoughMemoryExit (); + HTM_BUTTON_OnMouseDown_Begin (NULL,Class); + HTM_TxtF ("%c",'a' + (char) NumOpt); + HTM_BUTTON_End (); + free (Class); + + Frm_EndForm (); + + /* End table cell */ + HTM_TD_End (); + + /***** End row *****/ + HTM_TR_End (); + } + + /***** End table *****/ + HTM_TABLE_End (); + + return true; + } + +/*****************************************************************************/ +/***************************** Show exam event scores *****************************/ +/*****************************************************************************/ + +#define ExaEvt_NUM_ROWS_SCORE 50 + +static void ExaEvt_ShowEventScore (const struct ExaEvt_Match *Event) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NumScores; + unsigned NumScore; + double MinScore; + double MaxScore; + double Range; + double NumRowsPerScorePoint; + double Score; + unsigned MaxUsrs = 0; + unsigned NumUsrs; + unsigned NumRowForThisScore; + unsigned NumRow; + + /***** Get minimum and maximum scores *****/ + Gam_GetScoreRange (Event->ExaCod,&MinScore,&MaxScore); + Range = MaxScore - MinScore; + if (Range == 0.0) + return; + NumRowsPerScorePoint = (double) ExaEvt_NUM_ROWS_SCORE / Range; + + /***** Get maximum number of users *****/ + if (DB_QuerySELECT (&mysql_res,"can not get max users", + "SELECT MAX(NumUsrs)" + " FROM " + "(SELECT COUNT(*) AS NumUsrs" // row[1] + " FROM exa_results" + " WHERE EvtCod=%ld" + " GROUP BY Score" + " ORDER BY Score) AS Scores", + Event->EvtCod)) + { + row = mysql_fetch_row (mysql_res); + + /* Get maximum number of users (row[0]) *****/ + if (row) + if (row[0]) + if (sscanf (row[0],"%u",&MaxUsrs) != 1) + MaxUsrs = 0; + } + + /* Free structure that stores the query result */ + DB_FreeMySQLResult (&mysql_res); + + /***** Get scores from database *****/ + NumScores = (unsigned) + DB_QuerySELECT (&mysql_res,"can not get scores", + "SELECT Score," // row[0] + "COUNT(*) AS NumUsrs" // row[1] + " FROM exa_results" + " WHERE EvtCod=%ld" + " GROUP BY Score" + " ORDER BY Score DESC", + Event->EvtCod); + + /***** Begin table ****/ + HTM_TABLE_BeginWide (); + + /***** Get and draw scores *****/ + for (NumScore = 0, NumRow = 0; + NumScore < NumScores; + NumScore++) + { + /***** Get score and number of users from database *****/ + row = mysql_fetch_row (mysql_res); + + /* Get score (row[0]) */ + Str_SetDecimalPointToUS (); // To get the decimal point as a dot + if (sscanf (row[0],"%lf",&Score) != 1) + Score = 0.0; + Str_SetDecimalPointToLocal (); // Return to local system + + /* Get number of users (row[1]) *****/ + if (sscanf (row[1],"%u",&NumUsrs) != 1) + NumUsrs = 0; + + /***** Draw empty rows until reaching the adequate row *****/ + NumRowForThisScore = (unsigned) ((MaxScore - Score) * NumRowsPerScorePoint); + if (NumRowForThisScore == ExaEvt_NUM_ROWS_SCORE) + NumRowForThisScore = ExaEvt_NUM_ROWS_SCORE - 1; + for (; + NumRow < NumRowForThisScore; + NumRow++) + ExaEvt_DrawEmptyScoreRow (NumRow,MinScore,MaxScore); + + /***** Draw row for this score *****/ + ExaEvt_DrawScoreRow (Score,MinScore,MaxScore,NumRow,NumUsrs,MaxUsrs); + NumRow++; + } + + /***** Draw final empty rows *****/ + for (; + NumRow < ExaEvt_NUM_ROWS_SCORE; + NumRow++) + ExaEvt_DrawEmptyScoreRow (NumRow,MinScore,MaxScore); + + /***** End table *****/ + HTM_TABLE_End (); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/*************************** Draw empty score row ****************************/ +/*****************************************************************************/ + +static void ExaEvt_DrawEmptyScoreRow (unsigned NumRow,double MinScore,double MaxScore) + { + /***** Draw row *****/ + HTM_TR_Begin (NULL); + + /* Write score */ + HTM_TD_Begin ("class=\"EXA_SCO_SCO\""); + if (NumRow == 0) + { + HTM_DoubleFewDigits (MaxScore); + HTM_NBSP (); + } + else if (NumRow == ExaEvt_NUM_ROWS_SCORE - 1) + { + HTM_DoubleFewDigits (MinScore); + HTM_NBSP (); + } + HTM_TD_End (); + + /* Empty column with bar and number of users */ + HTM_TD_Begin ("class=\"EXA_SCO_NUM%s\"",ExaEvt_GetClassBorder (NumRow)); + HTM_TD_End (); + + HTM_TR_End (); + } + +/*****************************************************************************/ +/******************************* Draw score row ******************************/ +/*****************************************************************************/ + +static void ExaEvt_DrawScoreRow (double Score,double MinScore,double MaxScore, + unsigned NumRow,unsigned NumUsrs,unsigned MaxUsrs) + { + extern const char *Txt_ROLES_SINGUL_abc[Rol_NUM_ROLES][Usr_NUM_SEXS]; + extern const char *Txt_ROLES_PLURAL_abc[Rol_NUM_ROLES][Usr_NUM_SEXS]; + unsigned Color; + unsigned BarWidth; + char *Icon; + + /***** Compute color *****/ + /* + +----------------- MaxScore + | score9_1x1.png + +----------------- + | score8_1x1.png + +----------------- + | score7_1x1.png + +----------------- + | score6_1x1.png + +----------------- + | score5_1x1.png + +----------------- + | score4_1x1.png + +----------------- + | score3_1x1.png + +----------------- + | score2_1x1.png + +----------------- + | score1_1x1.png + +----------------- + | score0_1x1.png + +----------------- MinScore + */ + Color = (unsigned) (((Score - MinScore) / (MaxScore - MinScore)) * 10.0); + if (Color == 10) + Color = 9; + + /***** Compute bar width *****/ + if (MaxUsrs > 0) + { + BarWidth = (unsigned) (((NumUsrs * 95.0) / MaxUsrs) + 0.5); + if (BarWidth == 0) + BarWidth = 1; + } + else + BarWidth = 0; + + /***** Draw row *****/ + HTM_TR_Begin (NULL); + + /* Write score */ + HTM_TD_Begin ("class=\"EXA_SCO_SCO\""); + HTM_DoubleFewDigits (Score); + HTM_NBSP (); + HTM_TD_End (); + + /* Draw bar and write number of users for this score */ + HTM_TD_Begin ("class=\"EXA_SCO_NUM%s\"",ExaEvt_GetClassBorder (NumRow)); + if (asprintf (&Icon,"score%u_1x1.png",Color) < 0) // Background + Lay_NotEnoughMemoryExit (); + HTM_IMG (Cfg_URL_ICON_PUBLIC,Icon, + Str_BuildStringLongStr ((long) NumUsrs, + NumUsrs == 1 ? Txt_ROLES_SINGUL_abc[Rol_STD][Usr_SEX_UNKNOWN] : + Txt_ROLES_PLURAL_abc[Rol_STD][Usr_SEX_UNKNOWN]), + "class=\"EXA_SCO_BAR\" style=\"width:%u%%;\"",BarWidth); + Str_FreeString (); + free (Icon); + HTM_TxtF (" %u",NumUsrs); + HTM_TD_End (); + + HTM_TR_End (); + } + +/*****************************************************************************/ +/****** Write parameter with number of option (button) pressed by user *******/ +/*****************************************************************************/ + +static const char *ExaEvt_GetClassBorder (unsigned NumRow) + { + return NumRow == 0 ? " EXA_SCO_TOP" : + (NumRow == ExaEvt_NUM_ROWS_SCORE - 1 ? " EXA_SCO_BOT" : + " EXA_SCO_MID"); + } + +/*****************************************************************************/ +/****** Write parameter with number of option (button) pressed by user *******/ +/*****************************************************************************/ + +static void ExaEvt_PutParamNumOpt (unsigned NumOpt) + { + Par_PutHiddenParamUnsigned (NULL,"NumOpt",NumOpt); + } + +/*****************************************************************************/ +/******* Get parameter with number of option (button) pressed by user ********/ +/*****************************************************************************/ + +static unsigned ExaEvt_GetParamNumOpt (void) + { + long NumOpt; + + NumOpt = Par_GetParToLong ("NumOpt"); + if (NumOpt < 0) + Lay_ShowErrorAndExit ("Wrong number of option."); + + return (unsigned) NumOpt; + } + +/*****************************************************************************/ +/*********************** Put a big button to do action ***********************/ +/*****************************************************************************/ + +static void ExaEvt_PutBigButton (Act_Action_t NextAction,const char *Id, + long EvtCod,const char *Icon,const char *Txt) + { + /***** Begin form *****/ + Frm_StartFormId (NextAction,Id); + ExaEvt_PutParamEvtCod (EvtCod); + + /***** Put icon with link *****/ + HTM_DIV_Begin ("class=\"EXA_BIGBUTTON_CONT\""); + HTM_BUTTON_SUBMIT_Begin (Txt,"BT_LINK EXA_BUTTON_ON ICO_BLACK",NULL); + HTM_TxtF ("",Icon); + HTM_BUTTON_End (); + HTM_DIV_End (); + + /***** End form *****/ + Frm_EndForm (); + } + +/*****************************************************************************/ +/************************** Put a big button hidden **************************/ +/*****************************************************************************/ + +static void ExaEvt_PutBigButtonHidden (const char *Icon) + { + /***** Put inactive icon *****/ + HTM_DIV_Begin ("class=\"EXA_BIGBUTTON_CONT\""); + HTM_BUTTON_BUTTON_Begin (NULL,"BT_LINK_OFF EXA_BUTTON_HIDDEN ICO_BLACK",NULL); + HTM_TxtF ("",Icon); + HTM_BUTTON_End (); + HTM_DIV_End (); + } +/*****************************************************************************/ +/********************** Put a big button to close window *********************/ +/*****************************************************************************/ + +static void ExaEvt_PutBigButtonClose (void) + { + extern const char *Txt_Close; + + /***** Put icon with link *****/ + HTM_DIV_Begin ("class=\"EXA_BIGBUTTON_CONT\""); + HTM_BUTTON_BUTTON_Begin (Txt_Close,"BT_LINK EXA_BUTTON_ON ICO_DARKRED","window.close();"); + HTM_TxtF ("",ExaEvt_ICON_CLOSE); + HTM_BUTTON_End (); + HTM_DIV_End (); + } + +/*****************************************************************************/ +/****************************** Show wait image ******************************/ +/*****************************************************************************/ + +static void ExaEvt_ShowWaitImage (const char *Txt) + { + HTM_DIV_Begin ("class=\"EXA_WAIT_CONT\""); + Ico_PutIcon ("Spin-1s-200px.gif",Txt,"EXA_WAIT_IMG"); + HTM_DIV_End (); + } + +/*****************************************************************************/ +/**************************** Remove old players *****************************/ +/*****************************************************************************/ + +static void ExaEvt_RemoveOldPlayers (void) + { + /***** Delete events not being played by teacher *****/ + DB_QueryDELETE ("can not update events as not being played", + "DELETE FROM exa_playing" + " WHERE TSStatus.NumPlayers = + (unsigned) DB_QueryCOUNT ("can not get number of players", + "SELECT COUNT(*) FROM exa_players" + " WHERE EvtCod=%ld", + Event->EvtCod); + } + +/*****************************************************************************/ +/******************* Register me as a player in an exam event **********************/ +/*****************************************************************************/ +// Return true on success + +bool ExaEvt_RegisterMeAsPlayerInEvent (struct ExaEvt_Match *Event) + { + /***** Trivial check: exam event code must be > 0 *****/ + if (Event->EvtCod <= 0) + return false; + + /***** Trivial check: exam event must be being played *****/ + if (!Event->Status.Playing) // Event is paused, not being played + return false; + + /***** Trivial check: exam event must not be over *****/ + if (Event->Status.Showing == ExaEvt_END) // Event over + return false; + + /***** Trivial check: only a student can join an exam event *****/ + if (Gbl.Usrs.Me.Role.Logged != Rol_STD) // I am not logged as student + return false; + + /***** Insert me as exam event player *****/ + DB_QueryREPLACE ("can not insert exam event player", + "REPLACE exa_players (EvtCod,UsrCod) VALUES (%ld,%ld)", + Event->EvtCod,Gbl.Usrs.Me.UsrDat.UsrCod); + return true; + } + +/*****************************************************************************/ +/********************** Get code of exam event being played ***********************/ +/*****************************************************************************/ + +void ExaEvt_GetEventBeingPlayed (void) + { + long EvtCodBeingPlayed; + + /***** Get exam event code ****/ + if ((EvtCodBeingPlayed = ExaEvt_GetParamEvtCod ()) <= 0) + Lay_ShowErrorAndExit ("Code of exam event is missing."); + + ExaEvt_SetEvtCodBeingPlayed (EvtCodBeingPlayed); + } + +/*****************************************************************************/ +/********************* Show exam event being played as student ********************/ +/*****************************************************************************/ + +void ExaEvt_JoinEventAsStd (void) + { + struct ExaEvt_Match Event; + + /***** Get data of the exam event from database *****/ + Event.EvtCod = ExaEvt_GetEvtCodBeingPlayed (); + ExaEvt_GetDataOfEventByCod (&Event); + + /***** Show current exam event status *****/ + HTM_DIV_Begin ("id=\"exam event\" class=\"EXA_CONT\""); + ExaEvt_ShowMatchStatusForStd (&Event,ExaEvt_CHANGE_STATUS_BY_STUDENT); + HTM_DIV_End (); + } + +/*****************************************************************************/ +/****** Remove student's answer to a question and show exam event as student ******/ +/*****************************************************************************/ + +void ExaEvt_RemoveMyQuestionAnswer (void) + { + struct ExaEvt_Match Event; + unsigned QstInd; + + /***** Get data of the exam event from database *****/ + Event.EvtCod = ExaEvt_GetEvtCodBeingPlayed (); + ExaEvt_GetDataOfEventByCod (&Event); + + /***** Get question index from form *****/ + QstInd = Exa_GetParamQstInd (); + + /***** Check that teacher's screen is showing answers + and question index is the current one being played *****/ + if (Event.Status.Playing && // Event is being played + Event.Status.Showing == ExaEvt_ANSWERS && // Teacher's screen is showing answers + QstInd == Event.Status.QstInd) // Removing answer to the current question being played + /***** Remove answer to this question *****/ + ExaEvt_RemoveMyAnswerToEventQuestion (&Event); + + /***** Show current exam event status *****/ + HTM_DIV_Begin ("id=\"exam event\" class=\"EXA_CONT\""); + ExaEvt_ShowMatchStatusForStd (&Event,ExaEvt_CHANGE_STATUS_BY_STUDENT); + HTM_DIV_End (); + } + +/*****************************************************************************/ +/******************** Start exam event countdown (by a teacher) *******************/ +/*****************************************************************************/ + +void ExaEvt_StartCountdown (void) + { + struct ExaEvt_Match Event; + long NewCountdown; + + /***** Get countdown parameter ****/ + NewCountdown = Par_GetParToLong ("Countdown"); + + /***** Remove old players. + This function must be called by a teacher + before getting exam event status. *****/ + ExaEvt_RemoveOldPlayers (); + + /***** Get data of the exam event from database *****/ + Event.EvtCod = ExaEvt_GetEvtCodBeingPlayed (); + ExaEvt_GetDataOfEventByCod (&Event); + + /***** Start countdown *****/ + Event.Status.Countdown = NewCountdown; + + /***** Update exam event status in database *****/ + ExaEvt_UpdateEventStatusInDB (&Event); + + /***** Show current exam event status *****/ + ExaEvt_ShowRefreshablePartTch (&Event); + } + +/*****************************************************************************/ +/****************** Refresh exam event for a teacher via AJAX *********************/ +/*****************************************************************************/ + +void ExaEvt_RefreshEventTch (void) + { + struct ExaEvt_Match Event; + enum {REFRESH_LEFT,REFRESH_ALL} WhatToRefresh; + + if (!Gbl.Session.IsOpen) // If session has been closed, do not write anything + return; + + /***** Remove old players. + This function must be called by a teacher + before getting exam event status. *****/ + ExaEvt_RemoveOldPlayers (); + + /***** Get data of the exam event from database *****/ + Event.EvtCod = ExaEvt_GetEvtCodBeingPlayed (); + ExaEvt_GetDataOfEventByCod (&Event); + + /***** Update countdown *****/ + // If current countdown is < 0 ==> no countdown in progress + WhatToRefresh = REFRESH_LEFT; + if (Event.Status.Playing && // Event is being played + Event.Status.Countdown >= 0) // Countdown in progress + { + /* Decrease countdown */ + Event.Status.Countdown -= Cfg_SECONDS_TO_REFRESH_MATCH_TCH; + + /* On countdown reached, set exam event status to next (forward) status */ + if (Event.Status.Countdown <= 0) // End of countdown reached + { + ExaEvt_SetMatchStatusToNext (&Event); + WhatToRefresh = REFRESH_ALL; // Refresh the whole page + } + } + + /***** Update exam event status in database *****/ + ExaEvt_UpdateEventStatusInDB (&Event); + + /***** Update elapsed time in this question *****/ + ExaEvt_UpdateElapsedTimeInQuestion (&Event); + + /***** Show current exam event status *****/ + switch (WhatToRefresh) + { + case REFRESH_LEFT: // Refresh only left part + HTM_Txt ("match_left|0|"); // 0 ==> do not evaluate MatJax scripts after updating HTML + ExaEvt_ShowRefreshablePartTch (&Event); + break; + case REFRESH_ALL: // Refresh the whole page + HTM_Txt ("exam event|1|"); // 1 ==> evaluate MatJax scripts after updating HTML + ExaEvt_ShowMatchStatusForTch (&Event); + break; + } + } + +/*****************************************************************************/ +/*************** Refresh current exam for a student via AJAX *****************/ +/*****************************************************************************/ + +void ExaEvt_RefreshEventStd (void) + { + struct ExaEvt_Match Event; + + if (!Gbl.Session.IsOpen) // If session has been closed, do not write anything + return; + + /***** Get data of the exam event from database *****/ + Event.EvtCod = ExaEvt_GetEvtCodBeingPlayed (); + ExaEvt_GetDataOfEventByCod (&Event); + + /***** Show current exam event status *****/ + ExaEvt_ShowMatchStatusForStd (&Event,ExaEvt_REFRESH_STATUS_BY_SERVER); + } + +/*****************************************************************************/ +/**** Receive previous question answer in an exam event question from database *****/ +/*****************************************************************************/ + +void ExaEvt_GetQstAnsFromDB (long EvtCod,long UsrCod,unsigned QstInd, + struct ExaEvt_UsrAnswer *UsrAnswer) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NumRows; + + /***** Set default values for number of option and answer index *****/ + UsrAnswer->NumOpt = -1; // < 0 ==> no answer selected + UsrAnswer->AnsInd = -1; // < 0 ==> no answer selected + + /***** Get student's answer *****/ + NumRows = (unsigned) DB_QuerySELECT (&mysql_res,"can not get user's answer to an exam event question", + "SELECT NumOpt," // row[0] + "AnsInd" // row[1] + " FROM exa_answers" + " WHERE EvtCod=%ld" + " AND UsrCod=%ld" + " AND QstInd=%u", + EvtCod,UsrCod,QstInd); + if (NumRows) // Answer found... + { + row = mysql_fetch_row (mysql_res); + + /***** Get number of option index (row[0]) *****/ + if (sscanf (row[0],"%d",&(UsrAnswer->NumOpt)) != 1) + Lay_ShowErrorAndExit ("Error when getting student's answer to an exam event question."); + + /***** Get answer index (row[1]) *****/ + if (sscanf (row[1],"%d",&(UsrAnswer->AnsInd)) != 1) + Lay_ShowErrorAndExit ("Error when getting student's answer to an exam event question."); + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/********* Receive question answer from student when playing an exam event *********/ +/*****************************************************************************/ + +void ExaEvt_ReceiveQuestionAnswer (void) + { + struct ExaEvt_Match Event; + unsigned QstInd; + unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION]; + struct ExaEvt_UsrAnswer PreviousUsrAnswer; + struct ExaEvt_UsrAnswer UsrAnswer; + struct TstExa_Exam Result; + + /***** Get data of the exam event from database *****/ + Event.EvtCod = ExaEvt_GetEvtCodBeingPlayed (); + ExaEvt_GetDataOfEventByCod (&Event); + + /***** Get question index from form *****/ + QstInd = Exa_GetParamQstInd (); + + /***** Check that teacher's screen is showing answers + and question index is the current one being played *****/ + if (Event.Status.Showing == ExaEvt_ANSWERS && // Teacher's screen is showing answers + QstInd == Event.Status.QstInd) // Receiving an answer to the current question being played + { + /***** Get indexes for this question from database *****/ + ExaEvt_GetIndexes (Event.EvtCod,Event.Status.QstInd,Indexes); + + /***** Get answer index *****/ + /* + Indexes[4] = {0,3,1,2} + +-------+--------+----------+---------+ + | Button | Option | Answer | Correct | + | letter | number | index | | + | screen | screen | database | | + +--------+--------+----------+---------+ + | a | 0 | 0 | | + | b | 1 | 3 | | + | c | 2 | 1 | Y | <---- User press button #2 (index = 1, correct) + | d | 3 | 2 | | + +--------+--------+----------+---------+ + UsrAnswer.NumOpt = 2 + UsrAnswer.AnsInd = 1 + */ + UsrAnswer.NumOpt = ExaEvt_GetParamNumOpt (); + UsrAnswer.AnsInd = Indexes[UsrAnswer.NumOpt]; + + /***** Get previous student's answer to this question + (<0 ==> no answer) *****/ + ExaEvt_GetQstAnsFromDB (Event.EvtCod,Gbl.Usrs.Me.UsrDat.UsrCod,Event.Status.QstInd, + &PreviousUsrAnswer); + + /***** Store student's answer *****/ + if (UsrAnswer.NumOpt >= 0 && + UsrAnswer.AnsInd >= 0 && + UsrAnswer.AnsInd != PreviousUsrAnswer.AnsInd) + DB_QueryREPLACE ("can not register your answer to the exam event question", + "REPLACE exa_answers" + " (EvtCod,UsrCod,QstInd,NumOpt,AnsInd)" + " VALUES" + " (%ld,%ld,%u,%d,%d)", + Event.EvtCod,Gbl.Usrs.Me.UsrDat.UsrCod,Event.Status.QstInd, + UsrAnswer.NumOpt, + UsrAnswer.AnsInd); + + /***** Update student's exam event result *****/ + ExaRes_GetExamResultQuestionsFromDB (Event.EvtCod,Gbl.Usrs.Me.UsrDat.UsrCod, + &Result); + ExaEvt_ComputeScore (&Result); + + Str_SetDecimalPointToUS (); // To print the floating point as a dot + if (DB_QueryCOUNT ("can not get if exam event result exists", + "SELECT COUNT(*) FROM exa_results" + " WHERE EvtCod=%ld AND UsrCod=%ld", + Event.EvtCod,Gbl.Usrs.Me.UsrDat.UsrCod)) // Result exists + /* Update result */ + DB_QueryUPDATE ("can not update exam event result", + "UPDATE exa_results" + " SET EndTime=NOW()," + "NumQsts=%u," + "NumQstsNotBlank=%u," + "Score='%.15lg'" + " WHERE EvtCod=%ld AND UsrCod=%ld", + Result.NumQsts, + Result.NumQstsNotBlank, + Result.Score, + Event.EvtCod,Gbl.Usrs.Me.UsrDat.UsrCod); + else // Result doesn't exist + /* Create result */ + DB_QueryINSERT ("can not create exam event result", + "INSERT exa_results " + "(EvtCod,UsrCod,StartTime,EndTime,NumQsts,NumQstsNotBlank,Score)" + " VALUES " + "(%ld," // EvtCod + "%ld," // UsrCod + "NOW()," // StartTime + "NOW()," // EndTime + "%u," // NumQsts + "%u," // NumQstsNotBlank + "'%.15lg')", // Score + Event.EvtCod,Gbl.Usrs.Me.UsrDat.UsrCod, + Result.NumQsts, + Result.NumQstsNotBlank, + Result.Score); + Str_SetDecimalPointToLocal (); // Return to local system + } + + /***** Show current exam event status *****/ + HTM_DIV_Begin ("id=\"exam event\" class=\"EXA_CONT\""); + ExaEvt_ShowMatchStatusForStd (&Event,ExaEvt_CHANGE_STATUS_BY_STUDENT); + HTM_DIV_End (); + } + +/*****************************************************************************/ +/********************* Remove answer to exam event question ***********************/ +/*****************************************************************************/ + +static void ExaEvt_RemoveMyAnswerToEventQuestion (const struct ExaEvt_Match *Event) + { + DB_QueryDELETE ("can not remove your answer to the exam event question", + "DELETE FROM exa_answers" + " WHERE EvtCod=%ld AND UsrCod=%ld AND QstInd=%u", + Event->EvtCod,Gbl.Usrs.Me.UsrDat.UsrCod,Event->Status.QstInd); + } + +/*****************************************************************************/ +/******************** Compute exam event score for a student **********************/ +/*****************************************************************************/ + +static void ExaEvt_ComputeScore (struct TstExa_Exam *Result) + { + unsigned NumQst; + struct Tst_Question Question; + + for (NumQst = 0, Result->Score = 0.0; + NumQst < Result->NumQsts; + NumQst++) + { + /***** Create test question *****/ + Tst_QstConstructor (&Question); + Question.QstCod = Result->Questions[NumQst].QstCod; + Question.Answer.Type = Tst_ANS_UNIQUE_CHOICE; + + /***** Compute score for this answer ******/ + TstExa_ComputeChoiceAnsScore (Result,NumQst,&Question); + + /***** Update total score *****/ + Result->Score += Result->Questions[NumQst].Score; + + /***** Destroy test question *****/ + Tst_QstDestructor (&Question); + } + } + +/*****************************************************************************/ +/********** Get number of users who answered a question in an exam event ***********/ +/*****************************************************************************/ + +unsigned ExaEvt_GetNumUsrsWhoAnsweredQst (long EvtCod,unsigned QstInd) + { + /***** Get number of users who answered + a question in an exam event from database *****/ + return + (unsigned) DB_QueryCOUNT ("can not get number of users who answered a question", + "SELECT COUNT(*) FROM exa_answers" + " WHERE EvtCod=%ld AND QstInd=%u", + EvtCod,QstInd); + } + +/*****************************************************************************/ +/** Get number of users who have chosen a given answer of an exam question ***/ +/*****************************************************************************/ + +unsigned ExaEvt_GetNumUsrsWhoHaveChosenAns (long EvtCod,unsigned QstInd,unsigned AnsInd) + { + /***** Get number of users who have chosen + an answer of a question from database *****/ + return + (unsigned) DB_QueryCOUNT ("can not get number of users who have chosen an answer", + "SELECT COUNT(*) FROM exa_answers" + " WHERE EvtCod=%ld AND QstInd=%u AND AnsInd=%u", + EvtCod,QstInd,AnsInd); + } + +/*****************************************************************************/ +/**** Get number of users who have answered any question in an exam event ****/ +/*****************************************************************************/ + +static unsigned ExaEvt_GetNumUsrsWhoHaveAnswerEvt (long EvtCod) + { + /***** Get number of users who have answered + any question in exam event from database *****/ + return + (unsigned) DB_QueryCOUNT ("can not get number of users who have answered an exam event", + "SELECT COUNT(DISTINCT UsrCod) FROM exa_answers" + " WHERE EvtCod=%ld", + EvtCod); + } + +/*****************************************************************************/ +/***************** Draw a bar with the percentage of answers *****************/ +/*****************************************************************************/ + +#define ExaEvt_MAX_BAR_WIDTH 100 + +void ExaEvt_DrawBarNumUsrs (unsigned NumRespondersAns,unsigned NumRespondersQst,bool Correct) + { + extern const char *Txt_of_PART_OF_A_TOTAL; + unsigned i; + unsigned BarWidth = 0; + + /***** Start container *****/ + HTM_DIV_Begin ("class=\"EXA_RESULT\""); + + /***** Draw bar with a with proportional to the number of clicks *****/ + if (NumRespondersAns && NumRespondersQst) + BarWidth = (unsigned) ((((double) NumRespondersAns * (double) ExaEvt_MAX_BAR_WIDTH) / + (double) NumRespondersQst) + 0.5); + + /***** Bar proportional to number of users *****/ + HTM_TABLE_BeginWide (); + HTM_TR_Begin ("class=\"EXA_RES_TR\""); + for (i = 0; + i < 100; + i++) + { + HTM_TD_Begin ("class=\"%s\"", + (i < BarWidth) ? (Correct ? "EXA_RES_CORRECT" : + "EXA_RES_WRONG") : + "EXA_RES_VOID"); + HTM_TD_End (); + } + HTM_TR_End (); + HTM_TABLE_End (); + + /***** Write the number of users *****/ + if (NumRespondersAns && NumRespondersQst) + HTM_TxtF ("%u (%u%% %s %u)", + NumRespondersAns, + (unsigned) ((((double) NumRespondersAns * 100.0) / (double) NumRespondersQst) + 0.5), + Txt_of_PART_OF_A_TOTAL,NumRespondersQst); + else + HTM_NBSP (); + + /***** End container *****/ + HTM_DIV_End (); + } diff --git a/swad_exam_event.h b/swad_exam_event.h new file mode 100644 index 00000000..5be9425e --- /dev/null +++ b/swad_exam_event.h @@ -0,0 +1,149 @@ +// swad_exam_event.h: exam events (each ocurrence of an exam) + +#ifndef _SWAD_EXA_EVE +#define _SWAD_EXA_EVE +/* + SWAD (Shared Workspace At a Distance in Spanish), + 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-2020 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_scope.h" +#include "swad_test.h" + +/*****************************************************************************/ +/************************** Public types and constants ***********************/ +/*****************************************************************************/ + +#define ExaEvt_NEW_MATCH_SECTION_ID "new_match" + +#define ExaEvt_AFTER_LAST_QUESTION ((unsigned)((1UL << 31) - 1)) // 2^31 - 1, don't change this number because it is used in database to indicate that a event is finished + +#define ExaEvt_NUM_SHOWING 5 +typedef enum + { + ExaEvt_START, // Start: don't show anything + ExaEvt_STEM, // Showing only the question stem + ExaEvt_ANSWERS, // Showing the question stem and the answers + ExaEvt_RESULTS, // Showing the results + ExaEvt_END, // End: don't show anything + } ExaEvt_Showing_t; +#define ExaEvt_SHOWING_DEFAULT ExaEvt_START + +struct ExaEvt_Match + { + long EvtCod; + long ExaCod; + long UsrCod; + time_t TimeUTC[Dat_NUM_START_END_TIME]; + char Title[Exa_MAX_BYTES_TITLE + 1]; + struct + { + unsigned QstInd; // 0 means that the exam has not started. First question has index 1. + long QstCod; + time_t QstStartTimeUTC; + ExaEvt_Showing_t Showing; // What is shown on teacher's screen + long Countdown; // > 0 ==> countdown in progress + // = 0 ==> countdown over ==> go to next step + // < 0 ==> no countdown at this time + unsigned NumCols; // Number of columns for answers on teacher's screen + bool ShowQstResults; // Show global results of current question while playing + bool ShowUsrResults; // Show exam with results of all questions for the student + bool Playing; // Is being played now? + unsigned NumPlayers; + } Status; // Status related to event playing + }; + +struct ExaEvt_UsrAnswer + { + int NumOpt; // < 0 ==> no answer selected + int AnsInd; // < 0 ==> no answer selected + }; + +/*****************************************************************************/ +/***************************** Public prototypes *****************************/ +/*****************************************************************************/ + +long ExaEvt_GetEvtCodBeingPlayed (void); + +void ExaEvt_ListEvents (struct Exa_Exams *Exams, + struct Exa_Exam *Exam, + bool PutFormNewEvent); +void ExaEvt_GetDataOfEventByCod (struct ExaEvt_Match *Event); + +void ExaEvt_ToggleVisibilResultsEvtUsr (void); + +void ExaEvt_RequestRemoveEvent (void); +void ExaEvt_RemoveEvent (void); + +void ExaEvt_RemoveEventsInExamFromAllTables (long ExaCod); +void ExaEvt_RemoveEventInCourseFromAllTables (long CrsCod); +void ExaEvt_RemoveUsrFromEventTablesInCrs (long UsrCod,long CrsCod); + +void ExaEvt_PutParamsEdit (void *Exams); +void ExaEvt_GetAndCheckParameters (struct Exa_Exams *Exams, + struct Exa_Exam *Exam, + struct ExaEvt_Match *Event); +long ExaEvt_GetParamEvtCod (void); + +void ExaEvt_CreateNewEventTch (void); +void ExaEvt_ResumeEvent (void); +void ExaEvt_GetIndexes (long EvtCod,unsigned QstInd, + unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION]); + +void ExaEvt_RemoveGroup (long GrpCod); +void ExaEvt_RemoveGroupsOfType (long GrpTypCod); + +void ExaEvt_PlayPauseEvent (void); +void ExaEvt_ChangeNumColsEvt (void); +void ExaEvt_ToggleVisibilResultsEvtQst (void); +void ExaEvt_BackEvent (void); +void ExaEvt_ForwardEvent (void); + +unsigned ExaEvt_GetNumEventsInExam (long ExaCod); +unsigned ExaEvt_GetNumUnfinishedEventsInExam (long ExaCod); + +bool ExaEvt_CheckIfICanPlayThisEventBasedOnGrps (const struct ExaEvt_Match *Event); + +void ExaEvt_WriteChoiceAnsViewEvent (const struct ExaEvt_Match *Event, + const struct Tst_Question *Question, + const char *Class,bool ShowResult); + +bool ExaEvt_RegisterMeAsPlayerInEvent (struct ExaEvt_Match *Event); + +void ExaEvt_GetEventBeingPlayed (void); +void ExaEvt_JoinEventAsStd (void); +void ExaEvt_RemoveMyQuestionAnswer (void); + +void ExaEvt_StartCountdown (void); +void ExaEvt_RefreshEventTch (void); +void ExaEvt_RefreshEventStd (void); + +void ExaEvt_GetQstAnsFromDB (long EvtCod,long UsrCod,unsigned QstInd, + struct ExaEvt_UsrAnswer *UsrAnswer); +void ExaEvt_ReceiveQuestionAnswer (void); + +unsigned ExaEvt_GetNumUsrsWhoAnsweredQst (long EvtCod,unsigned QstInd); +unsigned ExaEvt_GetNumUsrsWhoHaveChosenAns (long EvtCod,unsigned QstInd,unsigned AnsInd); +void ExaEvt_DrawBarNumUsrs (unsigned NumRespondersAns,unsigned NumRespondersQst,bool Correct); + +#endif diff --git a/swad_exam_result.c b/swad_exam_result.c new file mode 100644 index 00000000..7aa4044f --- /dev/null +++ b/swad_exam_result.c @@ -0,0 +1,1457 @@ +// swad_exam_result.c: exams results + +/* + 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-2020 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 PATH_MAX +#include // For NULL +#include // For asprintf +#include // For calloc +#include // For string functions + +#include "swad_action.h" +#include "swad_database.h" +#include "swad_date.h" +#include "swad_exam.h" +#include "swad_exam_result.h" +#include "swad_form.h" +#include "swad_global.h" +#include "swad_HTML.h" +#include "swad_ID.h" +#include "swad_photo.h" +#include "swad_test_visibility.h" +#include "swad_user.h" + +/*****************************************************************************/ +/************** External global variables from others modules ****************/ +/*****************************************************************************/ + +extern struct Globals Gbl; + +/*****************************************************************************/ +/***************************** Private constants *****************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/******************************* Private types *******************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/***************************** Private constants *****************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/***************************** Private variables *****************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/***************************** Private prototypes ****************************/ +/*****************************************************************************/ + +static void ExaRes_PutFormToSelUsrsToViewEvtResults (void *Exams); + +static void ExaRes_ListMyEvtResultsInCrs (struct Exa_Exams *Exams); +static void ExaRes_ListMyEvtResultsInGam (struct Exa_Exams *Exams,long ExaCod); +static void ExaRes_ListMyEvtResultsInMch (struct Exa_Exams *Exams,long EvtCod); +static void ExaRes_ShowAllEvtResultsInSelectedExams (void *Exams); +static void ExaRes_ListAllEvtResultsInSelectedExams (struct Exa_Exams *Exams); +static void ExaRes_ListAllEvtResultsInExa (struct Exa_Exams *Exams,long ExaCod); +static void ExaRes_ListAllEvtResultsInEvt (struct Exa_Exams *Exams,long EvtCod); + +static void ExaRes_ShowResultsBegin (struct Exa_Exams *Exams, + const char *Title,bool ListGamesToSelect); +static void ExaRes_ShowResultsEnd (void); + +static void ExaRes_ListExamsToSelect (struct Exa_Exams *Exams); +static void ExaRes_ShowHeaderEvtResults (Usr_MeOrOther_t MeOrOther); + +static void ExaRes_BuildExamsSelectedCommas (struct Exa_Exams *Exams, + char **ExamsSelectedCommas); +static void ExaRes_ShowEvtResults (struct Exa_Exams *Exams, + Usr_MeOrOther_t MeOrOther, + long EvtCod, // <= 0 ==> any + long ExaCod, // <= 0 ==> any + const char *ExamsSelectedCommas); +static void ExaRes_ShowEvtResultsSummaryRow (unsigned NumResults, + unsigned NumTotalQsts, + unsigned NumTotalQstsNotBlank, + double TotalScoreOfAllResults, + double TotalGrade); +static void ExaRes_GetEventResultDataByEvtCod (long EvtCod,long UsrCod, + struct TstExa_Exam *Exam); + +static bool ExaRes_CheckIfICanSeeEventResult (struct ExaEvt_Event *Event,long UsrCod); +static bool ExaRes_CheckIfICanViewScore (bool ICanViewResult,unsigned Visibility); + +/*****************************************************************************/ +/*************************** Show my events results *************************/ +/*****************************************************************************/ + +void ExaRes_ShowMyExaResultsInCrs (void) + { + extern const char *Txt_Results; + struct Exa_Exams Exams; + + /***** Reset games *****/ + Gam_ResetGames (&Exams); + + /***** Get list of games *****/ + Gam_GetListGames (&Exams,Gam_ORDER_BY_TITLE); + Gam_GetListSelectedGamCods (&Exams); + + /***** List my events results in the current course *****/ + ExaRes_ShowResultsBegin (&Exams,Txt_Results,true); // List games to select + ExaRes_ListMyEvtResultsInCrs (&Exams); + ExaRes_ShowResultsEnd (); + + /***** Free list of games *****/ + free (Exams.GamCodsSelected); + Gam_FreeListGames (&Exams); + } + +static void ExaRes_ListMyEvtResultsInCrs (struct Exa_Exams *Exams) + { + char *ExamsSelectedCommas = NULL; // Initialized to avoid warning + + /***** Table header *****/ + ExaRes_ShowHeaderEvtResults (Usr_ME); + + /***** List my events results in the current course *****/ + TstCfg_GetConfigFromDB (); // Get feedback type + ExaRes_BuildExamsSelectedCommas (Exams,&ExamsSelectedCommas); + ExaRes_ShowEvtResults (Exams,Usr_ME,-1L,-1L,ExamsSelectedCommas); + free (ExamsSelectedCommas); + } + +/*****************************************************************************/ +/***************** Show my events results in a given exam *******************/ +/*****************************************************************************/ + +void ExaRes_ShowMyExaResultsInExa (void) + { + extern const char *Txt_Results_of_game_X; + struct Exa_Exams Exams; + struct Exa_Exam Exam; + + /***** Reset games *****/ + Gam_ResetGames (&Exams); + + /***** Get parameters *****/ + if ((Exam.ExaCod = Gam_GetParams (&Exams)) <= 0) + Lay_ShowErrorAndExit ("Code of exam is missing."); + Gam_GetDataOfGameByCod (&Exam); + + /***** Exam begin *****/ + Gam_ShowOnlyOneGameBegin (&Exams,&Exam, + false, // Do not list exam questions + false); // Do not put form to start new event + + /***** List my events results in exam *****/ + ExaRes_ShowResultsBegin (&Exams, + Str_BuildStringStr (Txt_Results_of_game_X,Exam.Title), + false); // Do not list games to select + Str_FreeString (); + ExaRes_ListMyEvtResultsInGam (&Exams,Exam.ExaCod); + ExaRes_ShowResultsEnd (); + + /***** Exam end *****/ + Gam_ShowOnlyOneGameEnd (); + } + +static void ExaRes_ListMyEvtResultsInGam (struct Exa_Exams *Exams,long ExaCod) + { + /***** Table header *****/ + ExaRes_ShowHeaderEvtResults (Usr_ME); + + /***** List my events results in exam *****/ + TstCfg_GetConfigFromDB (); // Get feedback type + ExaRes_ShowEvtResults (Exams,Usr_ME,-1L,ExaCod,NULL); + } + +/*****************************************************************************/ +/***************** Show my events results in a given event ******************/ +/*****************************************************************************/ + +void ExaRes_ShowMyExaResultsInEve (void) + { + extern const char *Txt_Results_of_match_X; + struct Exa_Exams Exams; + struct Exa_Exam Exam; + struct ExaEvt_Event Event; + + /***** Reset games *****/ + Gam_ResetGames (&Exams); + + /***** Get parameters *****/ + if ((Exam.ExaCod = Gam_GetParams (&Exams)) <= 0) + Lay_ShowErrorAndExit ("Code of exam is missing."); + if ((Event.EvtCod = Mch_GetParamEvtCod ()) <= 0) + Lay_ShowErrorAndExit ("Code of event is missing."); + Gam_GetDataOfGameByCod (&Exam); + ExaEvt_GetDataOfEventByCod (&Event); + + /***** Exam begin *****/ + Gam_ShowOnlyOneGameBegin (&Exams,&Exam, + false, // Do not list exam questions + false); // Do not put form to start new event + + /***** List my events results in event *****/ + ExaRes_ShowResultsBegin (&Exams,Str_BuildStringStr (Txt_Results_of_match_X,Event.Title), + false); // Do not list games to select + Str_FreeString (); + ExaRes_ListMyEvtResultsInMch (&Exams,Event.EvtCod); + ExaRes_ShowResultsEnd (); + + /***** Exam end *****/ + Gam_ShowOnlyOneGameEnd (); + } + +static void ExaRes_ListMyEvtResultsInMch (struct Exa_Exams *Exams,long EvtCod) + { + /***** Table header *****/ + ExaRes_ShowHeaderEvtResults (Usr_ME); + + /***** List my events results in exam *****/ + TstCfg_GetConfigFromDB (); // Get feedback type + ExaRes_ShowEvtResults (Exams,Usr_ME,EvtCod,-1L,NULL); + } + +/*****************************************************************************/ +/****************** Get users and show their events results *****************/ +/*****************************************************************************/ + +void ExaRes_ShowAllExaResultsInCrs (void) + { + struct Exa_Exams Exams; + + /***** Reset games *****/ + Gam_ResetGames (&Exams); + + /***** Get users and show their events results *****/ + Usr_GetSelectedUsrsAndGoToAct (&Gbl.Usrs.Selected, + ExaRes_ShowAllEvtResultsInSelectedExams,&Exams, + ExaRes_PutFormToSelUsrsToViewEvtResults,&Exams); + } + +/*****************************************************************************/ +/****************** Show events results for several users *******************/ +/*****************************************************************************/ + +static void ExaRes_ShowAllEvtResultsInSelectedExams (void *Exams) + { + extern const char *Txt_Results; + + if (!Exams) + return; + + /***** Get list of games *****/ + Gam_GetListGames ((struct Exa_Exams *) Exams,Gam_ORDER_BY_TITLE); + Gam_GetListSelectedGamCods ((struct Exa_Exams *) Exams); + + /***** List the events results of the selected users *****/ + ExaRes_ShowResultsBegin ((struct Exa_Exams *) Exams, + Txt_Results, + true); // List games to select + ExaRes_ListAllEvtResultsInSelectedExams ((struct Exa_Exams *) Exams); + ExaRes_ShowResultsEnd (); + + /***** Free list of games *****/ + free (((struct Exa_Exams *) Exams)->GamCodsSelected); + Gam_FreeListGames ((struct Exa_Exams *) Exams); + } + +static void ExaRes_ListAllEvtResultsInSelectedExams (struct Exa_Exams *Exams) + { + char *ExamsSelectedCommas = NULL; // Initialized to avoid warning + const char *Ptr; + + /***** Table head *****/ + ExaRes_ShowHeaderEvtResults (Usr_OTHER); + + /***** List the events results of the selected users *****/ + ExaRes_BuildExamsSelectedCommas (Exams,&ExamsSelectedCommas); + Ptr = Gbl.Usrs.Selected.List[Rol_UNK]; + while (*Ptr) + { + Par_GetNextStrUntilSeparParamMult (&Ptr,Gbl.Usrs.Other.UsrDat.EncryptedUsrCod, + Cry_BYTES_ENCRYPTED_STR_SHA256_BASE64); + Usr_GetUsrCodFromEncryptedUsrCod (&Gbl.Usrs.Other.UsrDat); + if (Usr_ChkUsrCodAndGetAllUsrDataFromUsrCod (&Gbl.Usrs.Other.UsrDat,Usr_DONT_GET_PREFS)) + if (Usr_CheckIfICanViewMch (&Gbl.Usrs.Other.UsrDat)) + { + /***** Show events results *****/ + Gbl.Usrs.Other.UsrDat.Accepted = Usr_CheckIfUsrHasAcceptedInCurrentCrs (&Gbl.Usrs.Other.UsrDat); + ExaRes_ShowEvtResults (Exams,Usr_OTHER,-1L,-1L,ExamsSelectedCommas); + } + } + free (ExamsSelectedCommas); + } + +/*****************************************************************************/ +/**************** Select users to show their events results *****************/ +/*****************************************************************************/ + +void ExaRes_SelUsrsToViewExaResults (void) + { + struct Exa_Exams Exams; + + /***** Reset games *****/ + Gam_ResetGames (&Exams); + + /***** Put form to select users *****/ + ExaRes_PutFormToSelUsrsToViewEvtResults (&Exams); + } + +static void ExaRes_PutFormToSelUsrsToViewEvtResults (void *Exams) + { + extern const char *Hlp_ASSESSMENT_Games_results; + extern const char *Txt_Results; + extern const char *Txt_View_matches_results; + + if (Exams) // Not used + Usr_PutFormToSelectUsrsToGoToAct (&Gbl.Usrs.Selected, + ActSeeAllMchResCrs, + NULL,NULL, + Txt_Results, + Hlp_ASSESSMENT_Games_results, + Txt_View_matches_results, + false); // Do not put form with date range + } + +/*****************************************************************************/ +/*** Show events results of a exam for the users who answered in that exam **/ +/*****************************************************************************/ + +void ExaRes_ShowAllExaResultsInExa (void) + { + extern const char *Txt_Results_of_game_X; + struct Exa_Exams Exams; + struct Exa_Exam Exam; + + /***** Reset games *****/ + Gam_ResetGames (&Exams); + + /***** Get parameters *****/ + if ((Exam.ExaCod = Gam_GetParams (&Exams)) <= 0) + Lay_ShowErrorAndExit ("Code of exam is missing."); + Gam_GetDataOfGameByCod (&Exam); + + /***** Exam begin *****/ + Gam_ShowOnlyOneGameBegin (&Exams,&Exam, + false, // Do not list exam questions + false); // Do not put form to start new event + + /***** List events results in exam *****/ + ExaRes_ShowResultsBegin (&Exams, + Str_BuildStringStr (Txt_Results_of_game_X,Exam.Title), + false); // Do not list games to select + Str_FreeString (); + ExaRes_ListAllEvtResultsInExa (&Exams,Exam.ExaCod); + ExaRes_ShowResultsEnd (); + + /***** Exam end *****/ + Gam_ShowOnlyOneGameEnd (); + } + +static void ExaRes_ListAllEvtResultsInExa (struct Exa_Exams *Exams,long ExaCod) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned long NumUsrs; + unsigned long NumUsr; + + /***** Table head *****/ + ExaRes_ShowHeaderEvtResults (Usr_OTHER); + + /***** Get all users who have answered any event question in this exam *****/ + NumUsrs = DB_QuerySELECT (&mysql_res,"can not get users in exam", + "SELECT users.UsrCod FROM" + " (SELECT DISTINCT exa_results.UsrCod AS UsrCod" // row[0] + " FROM exa_results,exa_events,exa_exams" + " WHERE exa_events.ExaCod=%ld" + " AND exa_events.EvtCod=exa_results.EvtCod" + " AND exa_events.ExaCod=exa_exams.ExaCod" + " AND exa_exams.CrsCod=%ld)" // Extra check + " AS users,usr_data" + " WHERE users.UsrCod=usr_data.UsrCod" + " ORDER BY usr_data.Surname1," + "usr_data.Surname2," + "usr_data.FirstName", + ExaCod, + Gbl.Hierarchy.Crs.CrsCod); + if (NumUsrs) + { + /***** List events results for each user *****/ + for (NumUsr = 0; + NumUsr < NumUsrs; + NumUsr++) + { + row = mysql_fetch_row (mysql_res); + + /* Get event code (row[0]) */ + if ((Gbl.Usrs.Other.UsrDat.UsrCod = Str_ConvertStrCodToLongCod (row[0])) > 0) + if (Usr_ChkUsrCodAndGetAllUsrDataFromUsrCod (&Gbl.Usrs.Other.UsrDat,Usr_DONT_GET_PREFS)) + if (Usr_CheckIfICanViewMch (&Gbl.Usrs.Other.UsrDat)) + { + /***** Show events results *****/ + Gbl.Usrs.Other.UsrDat.Accepted = Usr_CheckIfUsrHasAcceptedInCurrentCrs (&Gbl.Usrs.Other.UsrDat); + ExaRes_ShowEvtResults (Exams,Usr_OTHER,-1L,ExaCod,NULL); + } + } + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/** Show events results of a event for the users who answered in that event */ +/*****************************************************************************/ + +void ExaRes_ShowAllExaResultsInEve (void) + { + extern const char *Txt_Results_of_match_X; + struct Exa_Exams Exams; + struct Exa_Exam Exam; + struct ExaEvt_Event Event; + + /***** Reset games *****/ + Gam_ResetGames (&Exams); + + /***** Get parameters *****/ + if ((Exam.ExaCod = Gam_GetParams (&Exams)) <= 0) + Lay_ShowErrorAndExit ("Code of exam is missing."); + if ((Event.EvtCod = Mch_GetParamEvtCod ()) <= 0) + Lay_ShowErrorAndExit ("Code of event is missing."); + Gam_GetDataOfGameByCod (&Exam); + ExaEvt_GetDataOfEventByCod (&Event); + + /***** Exam begin *****/ + Gam_ShowOnlyOneGameBegin (&Exams,&Exam, + false, // Do not list exam questions + false); // Do not put form to start new event + + /***** List events results in event *****/ + ExaRes_ShowResultsBegin (&Exams, + Str_BuildStringStr (Txt_Results_of_match_X,Event.Title), + false); // Do not list games to select + Str_FreeString (); + ExaRes_ListAllEvtResultsInEvt (&Exams,Event.EvtCod); + ExaRes_ShowResultsEnd (); + + /***** Exam end *****/ + Gam_ShowOnlyOneGameEnd (); + } + +static void ExaRes_ListAllEvtResultsInEvt (struct Exa_Exams *Exams,long EvtCod) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned long NumUsrs; + unsigned long NumUsr; + + /***** Table head *****/ + ExaRes_ShowHeaderEvtResults (Usr_OTHER); + + /***** Get all users who have answered any event question in this exam *****/ + NumUsrs = DB_QuerySELECT (&mysql_res,"can not get users in event", + "SELECT users.UsrCod FROM" + " (SELECT exa_results.UsrCod AS UsrCod" // row[0] + " FROM exa_results,exa_events,exa_exams" + " WHERE exa_results.EvtCod=%ld" + " AND exa_results.EvtCod=exa_events.EvtCod" + " AND exa_events.ExaCod=exa_exams.ExaCod" + " AND exa_exams.CrsCod=%ld)" // Extra check + " AS users,usr_data" + " WHERE users.UsrCod=usr_data.UsrCod" + " ORDER BY usr_data.Surname1," + "usr_data.Surname2," + "usr_data.FirstName", + EvtCod, + Gbl.Hierarchy.Crs.CrsCod); + if (NumUsrs) + { + /***** List events results for each user *****/ + for (NumUsr = 0; + NumUsr < NumUsrs; + NumUsr++) + { + row = mysql_fetch_row (mysql_res); + + /* Get event code (row[0]) */ + if ((Gbl.Usrs.Other.UsrDat.UsrCod = Str_ConvertStrCodToLongCod (row[0])) > 0) + if (Usr_ChkUsrCodAndGetAllUsrDataFromUsrCod (&Gbl.Usrs.Other.UsrDat,Usr_DONT_GET_PREFS)) + if (Usr_CheckIfICanViewMch (&Gbl.Usrs.Other.UsrDat)) + { + /***** Show events results *****/ + Gbl.Usrs.Other.UsrDat.Accepted = Usr_CheckIfUsrHasAcceptedInCurrentCrs (&Gbl.Usrs.Other.UsrDat); + ExaRes_ShowEvtResults (Exams,Usr_OTHER,EvtCod,-1L,NULL); + } + } + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/************************ Show results (begin / end) *************************/ +/*****************************************************************************/ + +static void ExaRes_ShowResultsBegin (struct Exa_Exams *Exams, + const char *Title,bool ListGamesToSelect) + { + extern const char *Hlp_ASSESSMENT_Games_results; + + /***** Begin box *****/ + HTM_SECTION_Begin (ExaRes_RESULTS_BOX_ID); + Box_BoxBegin ("100%",Title, + NULL,NULL, + Hlp_ASSESSMENT_Games_results,Box_NOT_CLOSABLE); + + /***** List games to select *****/ + if (ListGamesToSelect) + ExaRes_ListExamsToSelect (Exams); + + /***** Begin event results table *****/ + HTM_SECTION_Begin (ExaRes_RESULTS_TABLE_ID); + HTM_TABLE_BeginWidePadding (2); + } + +static void ExaRes_ShowResultsEnd (void) + { + /***** End event results table *****/ + HTM_TABLE_End (); + HTM_SECTION_End (); + + /***** End box *****/ + Box_BoxEnd (); + HTM_SECTION_End (); + } + +/*****************************************************************************/ +/********** Write list of those attendance events that have students *********/ +/*****************************************************************************/ + +static void ExaRes_ListExamsToSelect (struct Exa_Exams *Exams) + { + extern const char *Hlp_ASSESSMENT_Games_results; + extern const char *The_ClassFormLinkInBoxBold[The_NUM_THEMES]; + extern const char *Txt_Games; + extern const char *Txt_Game; + extern const char *Txt_Update_results; + unsigned UniqueId; + unsigned NumExam; + struct Exa_Exam Exam; + + /***** Begin box *****/ + Box_BoxBegin (NULL,Txt_Games, + NULL,NULL, + Hlp_ASSESSMENT_Games_results,Box_CLOSABLE); + + /***** Begin form to update the results + depending on the games selected *****/ + Frm_StartFormAnchor (Gbl.Action.Act,ExaRes_RESULTS_TABLE_ID); + Grp_PutParamsCodGrps (); + Usr_PutHiddenParSelectedUsrsCods (&Gbl.Usrs.Selected); + + /***** Begin table *****/ + HTM_TABLE_BeginWidePadding (2); + + /***** Heading row *****/ + HTM_TR_Begin (NULL); + + HTM_TH (1,2,NULL,NULL); + HTM_TH (1,1,"LM",Txt_Game); + + HTM_TR_End (); + + /***** List the events *****/ + for (NumExam = 0, UniqueId = 1, Gbl.RowEvenOdd = 0; + NumExam < Exams->Num; + NumExam++, UniqueId++, Gbl.RowEvenOdd = 1 - Gbl.RowEvenOdd) + { + /* Get data of this exam */ + Exam.ExaCod = Exams->Lst[NumExam].ExaCod; + Gam_GetDataOfGameByCod (&Exam); + + /* Write a row for this event */ + HTM_TR_Begin (NULL); + + HTM_TD_Begin ("class=\"DAT CT COLOR%u\"",Gbl.RowEvenOdd); + HTM_INPUT_CHECKBOX ("ExaCod",HTM_DONT_SUBMIT_ON_CHANGE, + "id=\"Gam%u\" value=\"%ld\"%s", + NumExam,Exams->Lst[NumExam].ExaCod, + Exams->Lst[NumExam].Selected ? " checked=\"checked\"" : + ""); + HTM_TD_End (); + + HTM_TD_Begin ("class=\"DAT RT COLOR%u\"",Gbl.RowEvenOdd); + HTM_LABEL_Begin ("for=\"Gam%u\"",NumExam); + HTM_TxtF ("%u:",NumExam + 1); + HTM_LABEL_End (); + HTM_TD_End (); + + HTM_TD_Begin ("class=\"DAT LT COLOR%u\"",Gbl.RowEvenOdd); + HTM_Txt (Exam.Title); + HTM_TD_End (); + + HTM_TR_End (); + } + + /***** Put button to refresh *****/ + HTM_TR_Begin (NULL); + + HTM_TD_Begin ("colspan=\"3\" class=\"CM\""); + HTM_BUTTON_Animated_Begin (Txt_Update_results, + The_ClassFormLinkInBoxBold[Gbl.Prefs.Theme], + NULL); + Ico_PutCalculateIconWithText (Txt_Update_results); + HTM_BUTTON_End (); + HTM_TD_End (); + + HTM_TR_End (); + + /***** End table *****/ + HTM_TABLE_End (); + + /***** End form *****/ + Frm_EndForm (); + + /***** End box *****/ + Box_BoxEnd (); + } + +/*****************************************************************************/ +/********************* Show header of my events results *********************/ +/*****************************************************************************/ + +static void ExaRes_ShowHeaderEvtResults (Usr_MeOrOther_t MeOrOther) + { + extern const char *Txt_User[Usr_NUM_SEXS]; + extern const char *Txt_Match; + extern const char *Txt_START_END_TIME[Dat_NUM_START_END_TIME]; + extern const char *Txt_Questions; + extern const char *Txt_Non_blank_BR_questions; + extern const char *Txt_Score; + extern const char *Txt_Average_BR_score_BR_per_question_BR_from_0_to_1; + extern const char *Txt_Grade; + + HTM_TR_Begin (NULL); + + HTM_TH (1,2,"CT",Txt_User[MeOrOther == Usr_ME ? Gbl.Usrs.Me.UsrDat.Sex : + Usr_SEX_UNKNOWN]); + HTM_TH (1,1,"LT",Txt_START_END_TIME[Dat_START_TIME]); + HTM_TH (1,1,"LT",Txt_START_END_TIME[Dat_END_TIME ]); + HTM_TH (1,1,"LT",Txt_Match); + HTM_TH (1,1,"RT",Txt_Questions); + HTM_TH (1,1,"RT",Txt_Non_blank_BR_questions); + HTM_TH (1,1,"RT",Txt_Score); + HTM_TH (1,1,"RT",Txt_Average_BR_score_BR_per_question_BR_from_0_to_1); + HTM_TH (1,1,"RT",Txt_Grade); + HTM_TH_Empty (1); + + HTM_TR_End (); + } + +/*****************************************************************************/ +/******* Build string with list of selected games separated by commas ********/ +/******* from list of selected games ********/ +/*****************************************************************************/ + +static void ExaRes_BuildExamsSelectedCommas (struct Exa_Exams *Exams, + char **ExamsSelectedCommas) + { + size_t MaxLength; + unsigned NumExam; + char LongStr[Cns_MAX_DECIMAL_DIGITS_LONG + 1]; + + /***** Allocate memory for subquery of games selected *****/ + MaxLength = (size_t) Exams->NumSelected * (Cns_MAX_DECIMAL_DIGITS_LONG + 1); + if ((*ExamsSelectedCommas = (char *) malloc (MaxLength + 1)) == NULL) + Lay_NotEnoughMemoryExit (); + + /***** Build subquery with list of selected games *****/ + (*ExamsSelectedCommas)[0] = '\0'; + for (NumExam = 0; + NumExam < Exams->Num; + NumExam++) + if (Exams->Lst[NumExam].Selected) + { + sprintf (LongStr,"%ld",Exams->Lst[NumExam].ExaCod); + if ((*ExamsSelectedCommas)[0]) + Str_Concat (*ExamsSelectedCommas,",",MaxLength); + Str_Concat (*ExamsSelectedCommas,LongStr,MaxLength); + } + } + +/*****************************************************************************/ +/********* Show the events results of a user in the current course **********/ +/*****************************************************************************/ + +static void ExaRes_ShowEvtResults (struct Exa_Exams *Exams, + Usr_MeOrOther_t MeOrOther, + long EvtCod, // <= 0 ==> any + long ExaCod, // <= 0 ==> any + const char *ExamsSelectedCommas) + { + extern const char *Txt_Match_result; + char *EvtSubQuery; + char *ExaSubQuery; + MYSQL_RES *mysql_res; + MYSQL_ROW row; + struct UsrData *UsrDat; + bool ICanViewResult; + bool ICanViewScore; + unsigned NumResults; + unsigned NumResult; + static unsigned UniqueId = 0; + char *Id; + struct ExaEvt_Event Event; + Dat_StartEndTime_t StartEndTime; + unsigned NumQstsInThisResult; + unsigned NumQstsNotBlankInThisResult; + unsigned NumTotalQsts = 0; + unsigned NumTotalQstsNotBlank = 0; + double ScoreInThisResult; + double TotalScoreOfAllResults = 0.0; + double MaxGrade; + double Grade; + double TotalGrade = 0.0; + unsigned Visibility; + time_t TimeUTC[Dat_NUM_START_END_TIME]; + + /***** Set user *****/ + UsrDat = (MeOrOther == Usr_ME) ? &Gbl.Usrs.Me.UsrDat : + &Gbl.Usrs.Other.UsrDat; + + /***** Build events subquery *****/ + if (EvtCod > 0) + { + if (asprintf (&EvtSubQuery," AND exa_results.EvtCod=%ld",EvtCod) < 0) + Lay_NotEnoughMemoryExit (); + } + else + { + if (asprintf (&EvtSubQuery,"%s","") < 0) + Lay_NotEnoughMemoryExit (); + } + + /***** Build games subquery *****/ + if (ExaCod > 0) + { + if (asprintf (&ExaSubQuery," AND exa_events.ExaCod=%ld",ExaCod) < 0) + Lay_NotEnoughMemoryExit (); + } + else if (ExamsSelectedCommas) + { + if (ExamsSelectedCommas[0]) + { + if (asprintf (&ExaSubQuery," AND exa_events.ExaCod IN (%s)", + ExamsSelectedCommas) < 0) + Lay_NotEnoughMemoryExit (); + } + else + { + if (asprintf (&ExaSubQuery,"%s","") < 0) + Lay_NotEnoughMemoryExit (); + } + } + else + { + if (asprintf (&ExaSubQuery,"%s","") < 0) + Lay_NotEnoughMemoryExit (); + } + + /***** Make database query *****/ + NumResults = + (unsigned) DB_QuerySELECT (&mysql_res,"can not get events results", + "SELECT exa_results.EvtCod," // row[0] + "UNIX_TIMESTAMP(exa_results.StartTime)," // row[1] + "UNIX_TIMESTAMP(exa_results.EndTime)," // row[2] + "exa_results.NumQsts," // row[3] + "exa_results.NumQstsNotBlank," // row[4] + "exa_results.Score," // row[5] + "exa_exams.MaxGrade," // row[6] + "exa_exams.Visibility" // row[7] + " FROM exa_results,exa_events,exa_exams" + " WHERE exa_results.UsrCod=%ld" + "%s" // Event subquery + " AND exa_results.EvtCod=exa_events.EvtCod" + "%s" // Exams subquery + " AND exa_events.ExaCod=exa_exams.ExaCod" + " AND exa_exams.CrsCod=%ld" // Extra check + " ORDER BY exa_events.Title", + UsrDat->UsrCod, + EvtSubQuery, + ExaSubQuery, + Gbl.Hierarchy.Crs.CrsCod); + free (ExaSubQuery); + free (EvtSubQuery); + + /***** Show user's data *****/ + HTM_TR_Begin (NULL); + Usr_ShowTableCellWithUsrData (UsrDat,NumResults); + + /***** Get and print events results *****/ + if (NumResults) + { + for (NumResult = 0; + NumResult < NumResults; + NumResult++) + { + row = mysql_fetch_row (mysql_res); + + /* Get event code (row[0]) */ + if ((Event.EvtCod = Str_ConvertStrCodToLongCod (row[0])) < 0) + Lay_ShowErrorAndExit ("Wrong code of event."); + ExaEvt_GetDataOfEventByCod (&Event); + + /* Get visibility (row[7]) */ + Visibility = TstVis_GetVisibilityFromStr (row[7]); + + /* Show event result? */ + ICanViewResult = ExaRes_CheckIfICanSeeEventResult (&Event,UsrDat->UsrCod); + ICanViewScore = ExaRes_CheckIfICanViewScore (ICanViewResult,Visibility); + + if (NumResult) + HTM_TR_Begin (NULL); + + /* Write start/end times (row[1], row[2] hold UTC start/end times) */ + for (StartEndTime = (Dat_StartEndTime_t) 0; + StartEndTime <= (Dat_StartEndTime_t) (Dat_NUM_START_END_TIME - 1); + StartEndTime++) + { + TimeUTC[StartEndTime] = Dat_GetUNIXTimeFromStr (row[1 + StartEndTime]); + UniqueId++; + if (asprintf (&Id,"exa_res_time_%u_%u",(unsigned) StartEndTime,UniqueId) < 0) + Lay_NotEnoughMemoryExit (); + HTM_TD_Begin ("id =\"%s\" class=\"DAT LT COLOR%u\"", + Id,Gbl.RowEvenOdd); + Dat_WriteLocalDateHMSFromUTC (Id,TimeUTC[StartEndTime], + Gbl.Prefs.DateFormat,Dat_SEPARATOR_BREAK, + true,true,false,0x7); + HTM_TD_End (); + free (Id); + } + + /* Write event title */ + HTM_TD_Begin ("class=\"DAT LT COLOR%u\"",Gbl.RowEvenOdd); + HTM_Txt (Event.Title); + HTM_TD_End (); + + if (ICanViewScore) + { + /* Get number of questions (row[3]) */ + if (sscanf (row[3],"%u",&NumQstsInThisResult) != 1) + NumQstsInThisResult = 0; + NumTotalQsts += NumQstsInThisResult; + + /* Get number of questions not blank (row[4]) */ + if (sscanf (row[4],"%u",&NumQstsNotBlankInThisResult) != 1) + NumQstsNotBlankInThisResult = 0; + NumTotalQstsNotBlank += NumQstsNotBlankInThisResult; + + Str_SetDecimalPointToUS (); // To get the decimal point as a dot + + /* Get score (row[5]) */ + if (sscanf (row[5],"%lf",&ScoreInThisResult) != 1) + ScoreInThisResult = 0.0; + TotalScoreOfAllResults += ScoreInThisResult; + + /* Get maximum grade (row[6]) */ + if (sscanf (row[6],"%lf",&MaxGrade) != 1) + MaxGrade = 0.0; + + Str_SetDecimalPointToLocal (); // Return to local system + } + + /* Write number of questions */ + HTM_TD_Begin ("class=\"DAT RT COLOR%u\"",Gbl.RowEvenOdd); + if (ICanViewScore) + HTM_Unsigned (NumQstsInThisResult); + else + Ico_PutIconNotVisible (); + HTM_TD_End (); + + /* Write number of questions not blank */ + HTM_TD_Begin ("class=\"DAT RT COLOR%u\"",Gbl.RowEvenOdd); + if (ICanViewScore) + HTM_Unsigned (NumQstsNotBlankInThisResult); + else + Ico_PutIconNotVisible (); + HTM_TD_End (); + + /* Write score */ + HTM_TD_Begin ("class=\"DAT RT COLOR%u\"",Gbl.RowEvenOdd); + if (ICanViewScore) + HTM_Double2Decimals (ScoreInThisResult); + else + Ico_PutIconNotVisible (); + HTM_TD_End (); + + /* Write average score per question */ + HTM_TD_Begin ("class=\"DAT RT COLOR%u\"",Gbl.RowEvenOdd); + if (ICanViewScore) + HTM_Double2Decimals (NumQstsInThisResult ? ScoreInThisResult / + (double) NumQstsInThisResult : + 0.0); + else + Ico_PutIconNotVisible (); + HTM_TD_End (); + + /* Write grade over maximum grade */ + HTM_TD_Begin ("class=\"DAT RT COLOR%u\"",Gbl.RowEvenOdd); + if (ICanViewScore) + { + Grade = TstExa_ComputeGrade (NumQstsInThisResult,ScoreInThisResult,MaxGrade); + TstExa_ShowGrade (Grade,MaxGrade); + TotalGrade += Grade; + } + else + Ico_PutIconNotVisible (); + HTM_TD_End (); + + /* Link to show this result */ + HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd); + if (ICanViewResult) + { + Exams->ExaCod = Event.ExaCod; + Exams->EvtCod = Event.EvtCod; + switch (MeOrOther) + { + case Usr_ME: + Frm_StartForm (ActSeeOneMchResMe); + Mch_PutParamsEdit (Exams); + break; + case Usr_OTHER: + Frm_StartForm (ActSeeOneMchResOth); + Mch_PutParamsEdit (Exams); + Usr_PutParamOtherUsrCodEncrypted (Gbl.Usrs.Other.UsrDat.EncryptedUsrCod); + break; + } + Ico_PutIconLink ("tasks.svg",Txt_Match_result); + Frm_EndForm (); + } + else + Ico_PutIconNotVisible (); + HTM_TD_End (); + + HTM_TR_End (); + } + + /***** Write totals for this user *****/ + ExaRes_ShowEvtResultsSummaryRow (NumResults, + NumTotalQsts,NumTotalQstsNotBlank, + TotalScoreOfAllResults, + TotalGrade); + } + else + { + HTM_TD_ColouredEmpty (9); + HTM_TR_End (); + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + Gbl.RowEvenOdd = 1 - Gbl.RowEvenOdd; + } + +/*****************************************************************************/ +/************** Show row with summary of user's events results **************/ +/*****************************************************************************/ + +static void ExaRes_ShowEvtResultsSummaryRow (unsigned NumResults, + unsigned NumTotalQsts, + unsigned NumTotalQstsNotBlank, + double TotalScoreOfAllResults, + double TotalGrade) + { + extern const char *Txt_Matches; + + /***** Start row *****/ + HTM_TR_Begin (NULL); + + /***** Row title *****/ + HTM_TD_Begin ("colspan=\"3\" class=\"DAT_N_LINE_TOP RM COLOR%u\"",Gbl.RowEvenOdd); + HTM_TxtColonNBSP (Txt_Matches); + HTM_Unsigned (NumResults); + HTM_TD_End (); + + /***** Write total number of questions *****/ + HTM_TD_Begin ("class=\"DAT_N_LINE_TOP RM COLOR%u\"",Gbl.RowEvenOdd); + if (NumResults) + HTM_Unsigned (NumTotalQsts); + HTM_TD_End (); + + /***** Write total number of questions not blank *****/ + HTM_TD_Begin ("class=\"DAT_N_LINE_TOP RM COLOR%u\"",Gbl.RowEvenOdd); + if (NumResults) + HTM_Unsigned (NumTotalQstsNotBlank); + HTM_TD_End (); + + /***** Write total score *****/ + HTM_TD_Begin ("class=\"DAT_N_LINE_TOP RM COLOR%u\"",Gbl.RowEvenOdd); + HTM_Double2Decimals (TotalScoreOfAllResults); + HTM_TD_End (); + + /***** Write average score per question *****/ + HTM_TD_Begin ("class=\"DAT_N_LINE_TOP RM COLOR%u\"",Gbl.RowEvenOdd); + HTM_Double2Decimals (NumTotalQsts ? TotalScoreOfAllResults / + (double) NumTotalQsts : + 0.0); + HTM_TD_End (); + + /***** Write total grade *****/ + HTM_TD_Begin ("class=\"DAT_N_LINE_TOP RM COLOR%u\"",Gbl.RowEvenOdd); + HTM_Double2Decimals (TotalGrade); + HTM_TD_End (); + + /***** Last cell *****/ + HTM_TD_Begin ("class=\"DAT_N_LINE_TOP COLOR%u\"",Gbl.RowEvenOdd); + HTM_TD_End (); + + /***** End row *****/ + HTM_TR_End (); + } + +/*****************************************************************************/ +/******************* Show one event result of another user *******************/ +/*****************************************************************************/ + +void ExaRes_ShowOneExaResult (void) + { + extern const char *Hlp_ASSESSMENT_Games_results; + extern const char *Txt_The_user_does_not_exist; + extern const char *Txt_ROLES_SINGUL_Abc[Rol_NUM_ROLES][Usr_NUM_SEXS]; + extern const char *Txt_START_END_TIME[Dat_NUM_START_END_TIME]; + extern const char *Txt_Questions; + extern const char *Txt_non_blank_QUESTIONS; + extern const char *Txt_Score; + extern const char *Txt_Grade; + extern const char *Txt_Tags; + struct Exa_Exams Exams; + struct Exa_Exam Exam; + struct ExaEvt_Event Event; + Usr_MeOrOther_t MeOrOther; + struct UsrData *UsrDat; + Dat_StartEndTime_t StartEndTime; + char *Id; + struct TstExa_Exam Exam; + bool ShowPhoto; + char PhotoURL[PATH_MAX + 1]; + bool ICanViewResult; + bool ICanViewScore; + + /***** Reset games *****/ + Gam_ResetGames (&Exams); + + /***** Get and check parameters *****/ + Mch_GetAndCheckParameters (&Exams,&Exam,&Event); + + /***** Pointer to user's data *****/ + MeOrOther = (Gbl.Action.Act == ActSeeOneMchResMe) ? Usr_ME : + Usr_OTHER; + switch (MeOrOther) + { + case Usr_ME: + UsrDat = &Gbl.Usrs.Me.UsrDat; + break; + case Usr_OTHER: + default: + UsrDat = &Gbl.Usrs.Other.UsrDat; + Usr_GetParamOtherUsrCodEncrypted (UsrDat); + break; + } + + /***** Get event result data *****/ + TstExa_ResetExam (&Exam); + ExaRes_GetEventResultDataByEvtCod (Event.EvtCod,UsrDat->UsrCod,&Exam); + + /***** Check if I can view this event result *****/ + switch (Gbl.Usrs.Me.Role.Logged) + { + case Rol_STD: + ICanViewResult = ExaRes_CheckIfICanSeeEventResult (&Event,UsrDat->UsrCod); + if (ICanViewResult) + ICanViewScore = TstVis_IsVisibleTotalScore (Exam.Visibility); + else + ICanViewScore = false; + break; + case Rol_NET: + case Rol_TCH: + case Rol_DEG_ADM: + case Rol_CTR_ADM: + case Rol_INS_ADM: + case Rol_SYS_ADM: + ICanViewResult = + ICanViewScore = true; + break; + default: + ICanViewResult = + ICanViewScore = false; + break; + } + + if (ICanViewResult) // I am allowed to view this event result + { + /***** Get questions and user's answers of the event result from database *****/ + ExaRes_GetExamResultQuestionsFromDB (Event.EvtCod,UsrDat->UsrCod, + &Exam); + + /***** Begin box *****/ + Box_BoxBegin (NULL,Event.Title, + NULL,NULL, + Hlp_ASSESSMENT_Games_results,Box_NOT_CLOSABLE); + Lay_WriteHeaderClassPhoto (false,false, + Gbl.Hierarchy.Ins.InsCod, + Gbl.Hierarchy.Deg.DegCod, + Gbl.Hierarchy.Crs.CrsCod); + + /***** Begin table *****/ + HTM_TABLE_BeginWideMarginPadding (5); + + /***** Header row *****/ + /* Get data of the user who answer the event */ + if (!Usr_ChkUsrCodAndGetAllUsrDataFromUsrCod (UsrDat,Usr_DONT_GET_PREFS)) + Lay_ShowErrorAndExit (Txt_The_user_does_not_exist); + if (!Usr_CheckIfICanViewTst (UsrDat)) + Lay_NoPermissionExit (); + + /* User */ + HTM_TR_Begin (NULL); + + HTM_TD_Begin ("class=\"DAT_N RT\""); + HTM_TxtF ("%s:",Txt_ROLES_SINGUL_Abc[UsrDat->Roles.InCurrentCrs.Role][UsrDat->Sex]); + HTM_TD_End (); + + HTM_TD_Begin ("class=\"DAT LT\""); + ID_WriteUsrIDs (UsrDat,NULL); + HTM_TxtF (" %s",UsrDat->Surname1); + if (UsrDat->Surname2[0]) + HTM_TxtF (" %s",UsrDat->Surname2); + if (UsrDat->FirstName[0]) + HTM_TxtF (", %s",UsrDat->FirstName); + HTM_BR (); + ShowPhoto = Pho_ShowingUsrPhotoIsAllowed (UsrDat,PhotoURL); + Pho_ShowUsrPhoto (UsrDat,ShowPhoto ? PhotoURL : + NULL, + "PHOTO45x60",Pho_ZOOM,false); + HTM_TD_End (); + + HTM_TR_End (); + + /* Start/end time (for user in this event) */ + for (StartEndTime = (Dat_StartEndTime_t) 0; + StartEndTime <= (Dat_StartEndTime_t) (Dat_NUM_START_END_TIME - 1); + StartEndTime++) + { + HTM_TR_Begin (NULL); + + HTM_TD_Begin ("class=\"DAT_N RT\""); + HTM_TxtF ("%s:",Txt_START_END_TIME[StartEndTime]); + HTM_TD_End (); + + if (asprintf (&Id,"match_%u",(unsigned) StartEndTime) < 0) + Lay_NotEnoughMemoryExit (); + HTM_TD_Begin ("id=\"%s\" class=\"DAT LT\"",Id); + Dat_WriteLocalDateHMSFromUTC (Id,Exam.TimeUTC[StartEndTime], + Gbl.Prefs.DateFormat,Dat_SEPARATOR_COMMA, + true,true,true,0x7); + HTM_TD_End (); + free (Id); + + HTM_TR_End (); + } + + /* Number of questions */ + HTM_TR_Begin (NULL); + + HTM_TD_Begin ("class=\"DAT_N RT\""); + HTM_TxtF ("%s:",Txt_Questions); + HTM_TD_End (); + + HTM_TD_Begin ("class=\"DAT LT\""); + HTM_TxtF ("%u (%u %s)", + Exam.NumQsts, + Exam.NumQstsNotBlank,Txt_non_blank_QUESTIONS); + HTM_TD_End (); + + HTM_TR_End (); + + /* Score */ + HTM_TR_Begin (NULL); + + HTM_TD_Begin ("class=\"DAT_N RT\""); + HTM_TxtF ("%s:",Txt_Score); + HTM_TD_End (); + + HTM_TD_Begin ("class=\"DAT LT\""); + if (ICanViewScore) + HTM_Double2Decimals (Exam.Score); + else + Ico_PutIconNotVisible (); + HTM_TD_End (); + + HTM_TR_End (); + + /* Grade */ + HTM_TR_Begin (NULL); + + HTM_TD_Begin ("class=\"DAT_N RT\""); + HTM_TxtF ("%s:",Txt_Grade); + HTM_TD_End (); + + HTM_TD_Begin ("class=\"DAT LT\""); + if (ICanViewScore) + TstExa_ComputeAndShowGrade (Exam.NumQsts, + Exam.Score, + Exam.MaxGrade); + else + Ico_PutIconNotVisible (); + HTM_TD_End (); + + HTM_TR_End (); + + /* Tags present in this result */ + HTM_TR_Begin (NULL); + + HTM_TD_Begin ("class=\"DAT_N RT\""); + HTM_TxtF ("%s:",Txt_Tags); + HTM_TD_End (); + + HTM_TD_Begin ("class=\"DAT LT\""); + Gam_ShowTstTagsPresentInAGame (Event.ExaCod); + HTM_TD_End (); + + HTM_TR_End (); + + /***** Write answers and solutions *****/ + TstExa_ShowExamAnswers (UsrDat,&Exam,Exam.Visibility); + + /***** End table *****/ + HTM_TABLE_End (); + + /***** Write total mark of event result *****/ + if (ICanViewScore) + { + HTM_DIV_Begin ("class=\"DAT_N_BOLD CM\""); + HTM_TxtColonNBSP (Txt_Score); + HTM_Double2Decimals (Exam.Score); + HTM_BR (); + HTM_TxtColonNBSP (Txt_Grade); + TstExa_ComputeAndShowGrade (Exam.NumQsts,Exam.Score,Exam.MaxGrade); + HTM_DIV_End (); + } + + /***** End box *****/ + Box_BoxEnd (); + } + else // I am not allowed to view this event result + Lay_NoPermissionExit (); + } + +/*****************************************************************************/ +/************ Get the questions of a event result from database **************/ +/*****************************************************************************/ + +void ExaRes_GetExamResultQuestionsFromDB (long EvtCod,long UsrCod, + struct TstExa_Exam *Exam) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NumQst; + long LongNum; + unsigned QstInd; + struct Mch_UsrAnswer UsrAnswer; + + /***** Get questions and answers of a event result *****/ + Exam->NumQsts = (unsigned) + DB_QuerySELECT (&mysql_res,"can not get questions and answers" + " of a event result", + "SELECT gam_questions.QstCod," // row[0] + "gam_questions.QstInd," // row[1] + "mch_indexes.Indexes" // row[2] + " FROM exa_events,gam_questions,mch_indexes" + " WHERE exa_events.EvtCod=%ld" + " AND exa_events.ExaCod=gam_questions.ExaCod" + " AND exa_events.EvtCod=mch_indexes.EvtCod" + " AND gam_questions.QstInd=mch_indexes.QstInd" + " ORDER BY gam_questions.QstInd", + EvtCod); + for (NumQst = 0, Exam->NumQstsNotBlank = 0; + NumQst < Exam->NumQsts; + NumQst++) + { + row = mysql_fetch_row (mysql_res); + + /* Get question code (row[0]) */ + if ((Exam->Questions[NumQst].QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0) + Lay_ShowErrorAndExit ("Wrong code of question."); + + /* Get question index (row[1]) */ + if ((LongNum = Str_ConvertStrCodToLongCod (row[1])) < 0) + Lay_ShowErrorAndExit ("Wrong code of question."); + QstInd = (unsigned) LongNum; + + /* Get indexes for this question (row[2]) */ + Str_Copy (Exam->Questions[NumQst].StrIndexes,row[2], + TstExa_MAX_BYTES_INDEXES_ONE_QST); + + /* Get answers selected by user for this question */ + Mch_GetQstAnsFromDB (EvtCod,UsrCod,QstInd,&UsrAnswer); + if (UsrAnswer.AnsInd >= 0) // UsrAnswer.AnsInd >= 0 ==> answer selected + { + snprintf (Exam->Questions[NumQst].StrAnswers,TstExa_MAX_BYTES_ANSWERS_ONE_QST + 1, + "%d",UsrAnswer.AnsInd); + Exam->NumQstsNotBlank++; + } + else // UsrAnswer.AnsInd < 0 ==> no answer selected + Exam->Questions[NumQst].StrAnswers[0] = '\0'; // Empty answer + + /* Replace each comma by a separator of multiple parameters */ + /* In database commas are used as separators instead of special chars */ + Par_ReplaceCommaBySeparatorMultiple (Exam->Questions[NumQst].StrIndexes); + Par_ReplaceCommaBySeparatorMultiple (Exam->Questions[NumQst].StrAnswers); + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/************* Get data of a event result using its event code ***************/ +/*****************************************************************************/ + +static void ExaRes_GetEventResultDataByEvtCod (long EvtCod,long UsrCod, + struct TstExa_Exam *Exam) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + Dat_StartEndTime_t StartEndTime; + + /***** Make database query *****/ + if (DB_QuerySELECT (&mysql_res,"can not get data" + " of a event result of a user", + "SELECT UNIX_TIMESTAMP(exa_results.StartTime)," // row[1] + "UNIX_TIMESTAMP(exa_results.EndTime)," // row[2] + "exa_results.NumQsts," // row[3] + "exa_results.NumQstsNotBlank," // row[4] + "exa_results.Score" // row[5] + " FROM exa_results,exa_events,exa_exams" + " WHERE exa_results.EvtCod=%ld" + " AND exa_results.UsrCod=%ld" + " AND exa_results.EvtCod=exa_events.EvtCod" + " AND exa_events.ExaCod=exa_exams.ExaCod" + " AND exa_exams.CrsCod=%ld", // Extra check + EvtCod,UsrCod, + Gbl.Hierarchy.Crs.CrsCod) == 1) + { + row = mysql_fetch_row (mysql_res); + + /* Get start time (row[0] and row[1] hold UTC date-times) */ + for (StartEndTime = (Dat_StartEndTime_t) 0; + StartEndTime <= (Dat_StartEndTime_t) (Dat_NUM_START_END_TIME - 1); + StartEndTime++) + Exam->TimeUTC[StartEndTime] = Dat_GetUNIXTimeFromStr (row[StartEndTime]); + + /* Get number of questions (row[2]) */ + if (sscanf (row[2],"%u",&Exam->NumQsts) != 1) + Exam->NumQsts = 0; + + /* Get number of questions not blank (row[3]) */ + if (sscanf (row[3],"%u",&Exam->NumQstsNotBlank) != 1) + Exam->NumQstsNotBlank = 0; + + /* Get score (row[4]) */ + Str_SetDecimalPointToUS (); // To get the decimal point as a dot + if (sscanf (row[4],"%lf",&Exam->Score) != 1) + Exam->Score = 0.0; + Str_SetDecimalPointToLocal (); // Return to local system + } + else + { + Exam->NumQsts = 0; + Exam->NumQstsNotBlank = 0; + Exam->Score = 0.0; + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/********************** Get if I can see event result ************************/ +/*****************************************************************************/ + +static bool ExaRes_CheckIfICanSeeEventResult (struct ExaEvt_Event *Event,long UsrCod) + { + bool ItsMe; + + switch (Gbl.Usrs.Me.Role.Logged) + { + case Rol_STD: + ItsMe = Usr_ItsMe (UsrCod); + if (ItsMe && Event->Status.ShowUsrResults) + return Mch_CheckIfICanPlayThisMatchBasedOnGrps (Event); + return false; + case Rol_NET: + case Rol_TCH: + case Rol_DEG_ADM: + case Rol_CTR_ADM: + case Rol_INS_ADM: + case Rol_SYS_ADM: + return true; + default: + return false; + } + } + +/*****************************************************************************/ +/********************** Get if I can see event result ************************/ +/*****************************************************************************/ + +static bool ExaRes_CheckIfICanViewScore (bool ICanViewResult,unsigned Visibility) + { + switch (Gbl.Usrs.Me.Role.Logged) + { + case Rol_STD: + if (ICanViewResult) + return TstVis_IsVisibleTotalScore (Visibility); + return false; + break; + case Rol_NET: + case Rol_TCH: + case Rol_DEG_ADM: + case Rol_CTR_ADM: + case Rol_INS_ADM: + case Rol_SYS_ADM: + return true; + default: + return false; + } + } diff --git a/swad_exam_result.h b/swad_exam_result.h new file mode 100644 index 00000000..066b9be4 --- /dev/null +++ b/swad_exam_result.h @@ -0,0 +1,56 @@ +// swad_exam_result.h: exams results + +#ifndef _SWAD_EXA_RES +#define _SWAD_EXA_RES +/* + SWAD (Shared Workspace At a Distance in Spanish), + 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-2020 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_test_exam.h" + +/*****************************************************************************/ +/************************** Public types and constants ***********************/ +/*****************************************************************************/ + +#define ExaRes_RESULTS_BOX_ID "exares_box" +#define ExaRes_RESULTS_TABLE_ID "exares_table" + +/*****************************************************************************/ +/***************************** Public prototypes *****************************/ +/*****************************************************************************/ + +void ExaRes_ShowMyExaResultsInCrs (void); +void ExaRes_ShowMyExaResultsInExa (void); +void ExaRes_ShowMyExaResultsInEve (void); + +void ExaRes_ShowAllExaResultsInCrs (void); +void ExaRes_SelUsrsToViewExaResults (void); +void ExaRes_ShowAllExaResultsInExa (void); +void ExaRes_ShowAllExaResultsInEve (void); + +void ExaRes_ShowOneExaResult (void); +void ExaRes_GetExamResultQuestionsFromDB (long EvtCod,long UsrCod, + struct TstExa_Exam *Exam); + +#endif diff --git a/swad_game.h b/swad_game.h index bb8b6f1a..77af8523 100644 --- a/swad_game.h +++ b/swad_game.h @@ -119,8 +119,8 @@ long Gam_GetParams (struct Gam_Games *Games); void Gam_GetListGames (struct Gam_Games *Games,Gam_Order_t SelectedOrder); void Gam_GetListSelectedGamCods (struct Gam_Games *Games); -void Gam_GetDataOfGameByCod (struct Gam_Game *Gam); -void Gam_GetDataOfGameByFolder (struct Gam_Game *Gam); +void Gam_GetDataOfGameByCod (struct Gam_Game *Game); +void Gam_GetDataOfGameByFolder (struct Gam_Game *Game); void Gam_FreeListGames (struct Gam_Games *Games); void Gam_AskRemGame (void); diff --git a/swad_global.c b/swad_global.c index 873f4a4e..d9789870 100644 --- a/swad_global.c +++ b/swad_global.c @@ -40,7 +40,7 @@ #include "swad_config.h" #include "swad_constant.h" #include "swad_department.h" -#include "swad_exam.h" +#include "swad_exam_announcement.h" #include "swad_follow.h" #include "swad_global.h" #include "swad_icon.h" diff --git a/swad_layout.c b/swad_layout.c index bede1c3d..00cd7f32 100644 --- a/swad_layout.c +++ b/swad_layout.c @@ -38,7 +38,7 @@ #include "swad_config.h" #include "swad_connected.h" #include "swad_database.h" -#include "swad_exam.h" +#include "swad_exam_announcement.h" #include "swad_firewall.h" #include "swad_follow.h" #include "swad_form.h" @@ -492,7 +492,7 @@ static void Lay_WriteScripts (void) extern const char *Txt_DAYS_SMALL[7]; extern const char *Txt_Exam_of_X; struct Hld_Holidays Holidays; - struct Exa_ExamAnnouncements ExamAnns; + struct ExaAnn_ExamAnnouncements ExamAnns; unsigned DayOfWeek; /* 0, 1, 2, 3, 4, 5, 6 */ unsigned NumHld; unsigned NumExamAnnouncement; // Number of exam announcement @@ -539,10 +539,10 @@ static void Lay_WriteScripts (void) Hld_GetListHolidays (&Holidays); /***** Reset exam announcements context *****/ - Exa_ResetExamAnnouncements (&ExamAnns); + ExaAnn_ResetExamAnnouncements (&ExamAnns); /***** Create list of exam announcements *****/ - Exa_CreateListExamAnnouncements (&ExamAnns); + ExaAnn_CreateListExamAnnouncements (&ExamAnns); /***** Write script to initialize variables used to draw months *****/ HTM_SCRIPT_Begin (NULL,NULL); @@ -587,7 +587,7 @@ static void Lay_WriteScripts (void) HTM_SCRIPT_End (); /***** Free list of exam announcements *****/ - Exa_FreeListExamAnnouncements (&ExamAnns); + ExaAnn_FreeListExamAnnouncements (&ExamAnns); /***** Free list of holidays *****/ Hld_FreeListHolidays (&Holidays); diff --git a/swad_match.c b/swad_match.c index d6e758ae..e5121147 100644 --- a/swad_match.c +++ b/swad_match.c @@ -2496,9 +2496,6 @@ static void Mch_PutFormCountdown (struct Mch_Match *Match,long Seconds,const cha Frm_StartFormOnSubmit (ActUnk,OnSubmit); } - else - { - } /***** Put icon *****/ HTM_DIV_Begin ("class=\"MCH_SMALLBUTTON_CONT\""); diff --git a/swad_notification.c b/swad_notification.c index 4b28056b..0cfbbb7f 100644 --- a/swad_notification.c +++ b/swad_notification.c @@ -37,7 +37,7 @@ #include "swad_config.h" #include "swad_database.h" #include "swad_enrolment.h" -#include "swad_exam.h" +#include "swad_exam_announcement.h" #include "swad_figure.h" #include "swad_follow.h" #include "swad_form.h" @@ -859,7 +859,7 @@ void Ntf_GetNotifSummaryAndContent (char SummaryStr[Ntf_MAX_BYTES_SUMMARY + 1], Asg_GetNotifAssignment (SummaryStr,ContentStr,Cod,GetContent); break; case Ntf_EVENT_EXAM_ANNOUNCEMENT: - Exa_GetSummaryAndContentExamAnnouncement (SummaryStr,ContentStr,Cod,GetContent); + ExaAnn_GetSummaryAndContentExamAnnouncement (SummaryStr,ContentStr,Cod,GetContent); break; case Ntf_EVENT_MARKS_FILE: Mrk_GetNotifMyMarks (SummaryStr,ContentStr,Cod,UsrCod,GetContent); diff --git a/swad_timeline.c b/swad_timeline.c index 34110fad..35039c7d 100644 --- a/swad_timeline.c +++ b/swad_timeline.c @@ -36,7 +36,7 @@ #include "swad_box.h" #include "swad_constant.h" #include "swad_database.h" -#include "swad_exam.h" +#include "swad_exam_announcement.h" #include "swad_figure.h" #include "swad_follow.h" #include "swad_form.h" @@ -2137,7 +2137,7 @@ static void TL_PutFormGoToAction (const struct TL_Note *SocNot, Frm_StartFormUniqueAnchor (TL_DefaultActions[SocNot->NoteType], Anchor); // Locate on this specific exam Frm_FreeAnchorStr (Anchor); - Exa_PutHiddenParamExaCod (SocNot->Cod); + ExaAnn_PutHiddenParamExaCod (SocNot->Cod); if (SocNot->HieCod != Gbl.Hierarchy.Crs.CrsCod) // Not the current course Crs_PutParamCrsCod (SocNot->HieCod); // Go to another course break; @@ -2209,7 +2209,7 @@ static void TL_GetNoteSummary (const struct TL_Note *SocNot, Brw_GetSummaryAndContentOfFile (SummaryStr,NULL,SocNot->Cod,false); break; case TL_NOTE_EXAM_ANNOUNCEMENT: - Exa_GetSummaryAndContentExamAnnouncement (SummaryStr,NULL,SocNot->Cod,false); + ExaAnn_GetSummaryAndContentExamAnnouncement (SummaryStr,NULL,SocNot->Cod,false); break; case TL_NOTE_POST: // Not applicable