From c100ca9af48aaa264f6bab4925a74b8f694e6203 Mon Sep 17 00:00:00 2001 From: acanas Date: Fri, 22 May 2020 20:10:45 +0200 Subject: [PATCH] Version19.241 --- Makefile | 2 +- sql/cambios.sql | 13 +- sql/swad.sql | 13 ++ swad_action.c | 4 +- swad_action.h | 141 +++++++++--------- swad_changelog.h | 8 +- swad_database.c | 27 ++++ swad_exam_log.c | 99 +++++++++++++ swad_exam_log.h | 42 ++++++ swad_exam_print.c | 78 +++++----- swad_exam_print.h | 3 + swad_exam_result.c | 52 +++++-- swad_exam_set.c | 2 +- swad_log.c | 4 + swad_match_result.c | 3 + swad_test.c | 339 ++++++++++++++++++++++---------------------- swad_test.h | 2 +- swad_test_print.c | 177 ++++++++++++++--------- swad_test_print.h | 4 +- swad_text_action.c | 25 +++- 20 files changed, 683 insertions(+), 355 deletions(-) create mode 100644 swad_exam_log.c create mode 100644 swad_exam_log.h diff --git a/Makefile b/Makefile index ced82a0f..5ee496c4 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_exam_announcement.o \ + swad_enrolment.o swad_exam.o swad_exam_announcement.o swad_exam_log.o \ swad_exam_print.o swad_exam_result.o swad_exam_session.o \ swad_exam_set.o \ swad_figure.o swad_figure_cache.o swad_file.o swad_file_browser.o \ diff --git a/sql/cambios.sql b/sql/cambios.sql index 1dc7efd7..1ef6ecb8 100644 --- a/sql/cambios.sql +++ b/sql/cambios.sql @@ -13363,4 +13363,15 @@ SELECT institutions.InsCod,institutions.ShortName,institutions.FullName,centres. -------------------------------- \ No newline at end of file +------------------------------- + +SELECT * FROM +(SELECT PrnCod,Score AS S FROM exa_prints ORDER BY PrnCod) AS ep +LEFT JOIN +(SELECT PrnCod,SUM(Score) AS S FROM exa_print_questions GROUP BY PrnCod ORDER BY PrnCod) AS epq +ON ep.PrnCod=epq.PrnCod +WHERE ABS(ep.S-epq.S)<0.001; + + + + diff --git a/sql/swad.sql b/sql/swad.sql index a7ac0c5d..aa2ebdf6 100644 --- a/sql/swad.sql +++ b/sql/swad.sql @@ -484,6 +484,19 @@ CREATE TABLE IF NOT EXISTS exa_exams ( UNIQUE INDEX(ExaCod), INDEX(CrsCod)); -- +-- Table exa_log: stores the access log to exam prints +-- +CREATE TABLE IF NOT EXISTS exa_log ( + LogCod INT NOT NULL, + PrnCod INT NOT NULL, + ActCod INT NOT NULL, + ClickTime DATETIME NOT NULL, + IP CHAR(15) NOT NULL, + SessionId CHAR(43) NOT NULL, + UNIQUE INDEX(LogCod), + INDEX(PrnCod,ClickTime), + INDEX(ClickTime)); +-- -- Table exa_print_questions: stores the questions and answers in exam prints made by users -- CREATE TABLE IF NOT EXISTS exa_print_questions ( diff --git a/swad_action.c b/swad_action.c index c3636d04..ca69a662 100644 --- a/swad_action.c +++ b/swad_action.c @@ -727,11 +727,12 @@ const struct Act_Actions Act_Actions[Act_NUM_ACTIONS] = [ActSeeExaPrn ] = {1904,-1,TabUnk,ActSeeAllExa ,0x238,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,ExaPrn_ShowExamPrint ,NULL}, [ActAnsExaPrn ] = {1906,-1,TabUnk,ActSeeAllExa ,0x238,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_AJAX_NORMAL,NULL ,ExaPrn_ReceivePrintAnswer ,NULL}, + [ActEndExaPrn ] = {1908,-1,TabUnk,ActSeeAllExa ,0x238,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,ExaRes_ShowOneExaResult ,NULL}, [ActSeeMyExaResCrs ] = {1867,-1,TabUnk,ActSeeAllExa ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,ExaRes_ShowMyResultsInCrs ,NULL}, [ActSeeMyExaResExa ] = {1868,-1,TabUnk,ActSeeAllExa ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,ExaRes_ShowMyResultsInExa ,NULL}, [ActSeeMyExaResSes ] = {1869,-1,TabUnk,ActSeeAllExa ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,ExaRes_ShowMyResultsInSes ,NULL}, - [ActSeeOneExaResMe ] = {1870,-1,TabUnk,ActSeeAllExa ,0x238,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,ExaRes_ShowOneExaResult ,NULL}, + [ActSeeOneExaResMe ] = {1870,-1,TabUnk,ActSeeAllExa ,0x208,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,ExaRes_ShowOneExaResult ,NULL}, [ActReqSeeUsrExaRes ] = {1871,-1,TabUnk,ActSeeAllExa ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,ExaRes_SelUsrsToViewResults ,NULL}, [ActSeeUsrExaResCrs ] = {1872,-1,TabUnk,ActSeeAllExa ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,ExaRes_ShowAllResultsInCrs ,NULL}, @@ -3716,6 +3717,7 @@ Act_Action_t Act_FromActCodToAction[1 + Act_MAX_ACTION_COD] = // Do not reuse un -1, // #1905 (obsolete action) ActAnsExaPrn, // #1906 ActEdiTag, // #1907 + ActEndExaPrn, // #1908 }; /*****************************************************************************/ diff --git a/swad_action.h b/swad_action.h index aabfee0d..7d6e2cc4 100644 --- a/swad_action.h +++ b/swad_action.h @@ -64,7 +64,7 @@ typedef enum typedef signed int Act_Action_t; // Must be a signed type, because -1 is used to indicate obsolete action -#define Act_MAX_ACTION_COD 1907 +#define Act_MAX_ACTION_COD 1908 #define Act_MAX_OPTIONS_IN_MENU_PER_TAB 13 @@ -692,83 +692,84 @@ typedef signed int Act_Action_t; // Must be a signed type, because -1 is used to #define ActSeeExaPrn (ActChgCrsTT1stDay + 163) #define ActAnsExaPrn (ActChgCrsTT1stDay + 164) +#define ActEndExaPrn (ActChgCrsTT1stDay + 165) -#define ActSeeMyExaResCrs (ActChgCrsTT1stDay + 165) -#define ActSeeMyExaResExa (ActChgCrsTT1stDay + 166) -#define ActSeeMyExaResSes (ActChgCrsTT1stDay + 167) -#define ActSeeOneExaResMe (ActChgCrsTT1stDay + 168) -#define ActReqSeeUsrExaRes (ActChgCrsTT1stDay + 169) -#define ActSeeUsrExaResCrs (ActChgCrsTT1stDay + 170) -#define ActSeeUsrExaResExa (ActChgCrsTT1stDay + 171) -#define ActSeeUsrExaResSes (ActChgCrsTT1stDay + 172) -#define ActSeeOneExaResOth (ActChgCrsTT1stDay + 173) -#define ActChgVisExaRes (ActChgCrsTT1stDay + 174) +#define ActSeeMyExaResCrs (ActChgCrsTT1stDay + 166) +#define ActSeeMyExaResExa (ActChgCrsTT1stDay + 167) +#define ActSeeMyExaResSes (ActChgCrsTT1stDay + 168) +#define ActSeeOneExaResMe (ActChgCrsTT1stDay + 169) +#define ActReqSeeUsrExaRes (ActChgCrsTT1stDay + 170) +#define ActSeeUsrExaResCrs (ActChgCrsTT1stDay + 171) +#define ActSeeUsrExaResExa (ActChgCrsTT1stDay + 172) +#define ActSeeUsrExaResSes (ActChgCrsTT1stDay + 173) +#define ActSeeOneExaResOth (ActChgCrsTT1stDay + 174) +#define ActChgVisExaRes (ActChgCrsTT1stDay + 175) -#define ActSeeGam (ActChgCrsTT1stDay + 175) -#define ActReqRemMch (ActChgCrsTT1stDay + 176) -#define ActRemMch (ActChgCrsTT1stDay + 177) -#define ActReqNewMch (ActChgCrsTT1stDay + 178) -#define ActNewMch (ActChgCrsTT1stDay + 179) -#define ActResMch (ActChgCrsTT1stDay + 180) -#define ActBckMch (ActChgCrsTT1stDay + 181) -#define ActPlyPauMch (ActChgCrsTT1stDay + 182) -#define ActFwdMch (ActChgCrsTT1stDay + 183) -#define ActChgNumColMch (ActChgCrsTT1stDay + 184) -#define ActChgVisResMchQst (ActChgCrsTT1stDay + 185) -#define ActMchCntDwn (ActChgCrsTT1stDay + 186) -#define ActRefMchTch (ActChgCrsTT1stDay + 187) +#define ActSeeGam (ActChgCrsTT1stDay + 176) +#define ActReqRemMch (ActChgCrsTT1stDay + 177) +#define ActRemMch (ActChgCrsTT1stDay + 178) +#define ActReqNewMch (ActChgCrsTT1stDay + 179) +#define ActNewMch (ActChgCrsTT1stDay + 180) +#define ActResMch (ActChgCrsTT1stDay + 181) +#define ActBckMch (ActChgCrsTT1stDay + 182) +#define ActPlyPauMch (ActChgCrsTT1stDay + 183) +#define ActFwdMch (ActChgCrsTT1stDay + 184) +#define ActChgNumColMch (ActChgCrsTT1stDay + 185) +#define ActChgVisResMchQst (ActChgCrsTT1stDay + 186) +#define ActMchCntDwn (ActChgCrsTT1stDay + 187) +#define ActRefMchTch (ActChgCrsTT1stDay + 188) -#define ActJoiMch (ActChgCrsTT1stDay + 188) -#define ActSeeMchAnsQstStd (ActChgCrsTT1stDay + 189) -#define ActRemMchAnsQstStd (ActChgCrsTT1stDay + 190) -#define ActAnsMchQstStd (ActChgCrsTT1stDay + 191) -#define ActRefMchStd (ActChgCrsTT1stDay + 192) +#define ActJoiMch (ActChgCrsTT1stDay + 189) +#define ActSeeMchAnsQstStd (ActChgCrsTT1stDay + 190) +#define ActRemMchAnsQstStd (ActChgCrsTT1stDay + 191) +#define ActAnsMchQstStd (ActChgCrsTT1stDay + 192) +#define ActRefMchStd (ActChgCrsTT1stDay + 193) -#define ActSeeMyMchResCrs (ActChgCrsTT1stDay + 193) -#define ActSeeMyMchResGam (ActChgCrsTT1stDay + 194) -#define ActSeeMyMchResMch (ActChgCrsTT1stDay + 195) -#define ActSeeOneMchResMe (ActChgCrsTT1stDay + 196) +#define ActSeeMyMchResCrs (ActChgCrsTT1stDay + 194) +#define ActSeeMyMchResGam (ActChgCrsTT1stDay + 195) +#define ActSeeMyMchResMch (ActChgCrsTT1stDay + 196) +#define ActSeeOneMchResMe (ActChgCrsTT1stDay + 197) -#define ActReqSeeUsrMchRes (ActChgCrsTT1stDay + 197) -#define ActSeeUsrMchResCrs (ActChgCrsTT1stDay + 198) -#define ActSeeUsrMchResGam (ActChgCrsTT1stDay + 199) -#define ActSeeUsrMchResMch (ActChgCrsTT1stDay + 200) -#define ActSeeOneMchResOth (ActChgCrsTT1stDay + 201) +#define ActReqSeeUsrMchRes (ActChgCrsTT1stDay + 198) +#define ActSeeUsrMchResCrs (ActChgCrsTT1stDay + 199) +#define ActSeeUsrMchResGam (ActChgCrsTT1stDay + 200) +#define ActSeeUsrMchResMch (ActChgCrsTT1stDay + 201) +#define ActSeeOneMchResOth (ActChgCrsTT1stDay + 202) -#define ActChgVisResMchUsr (ActChgCrsTT1stDay + 202) +#define ActChgVisResMchUsr (ActChgCrsTT1stDay + 203) -#define ActFrmNewGam (ActChgCrsTT1stDay + 203) -#define ActEdiOneGam (ActChgCrsTT1stDay + 204) -#define ActNewGam (ActChgCrsTT1stDay + 205) -#define ActChgGam (ActChgCrsTT1stDay + 206) -#define ActReqRemGam (ActChgCrsTT1stDay + 207) -#define ActRemGam (ActChgCrsTT1stDay + 208) -#define ActHidGam (ActChgCrsTT1stDay + 209) -#define ActShoGam (ActChgCrsTT1stDay + 210) -#define ActAddOneGamQst (ActChgCrsTT1stDay + 211) -#define ActGamLstTstQst (ActChgCrsTT1stDay + 212) -#define ActAddTstQstToGam (ActChgCrsTT1stDay + 213) -#define ActReqRemGamQst (ActChgCrsTT1stDay + 214) -#define ActRemGamQst (ActChgCrsTT1stDay + 215) -#define ActUp_GamQst (ActChgCrsTT1stDay + 216) -#define ActDwnGamQst (ActChgCrsTT1stDay + 217) +#define ActFrmNewGam (ActChgCrsTT1stDay + 204) +#define ActEdiOneGam (ActChgCrsTT1stDay + 205) +#define ActNewGam (ActChgCrsTT1stDay + 206) +#define ActChgGam (ActChgCrsTT1stDay + 207) +#define ActReqRemGam (ActChgCrsTT1stDay + 208) +#define ActRemGam (ActChgCrsTT1stDay + 209) +#define ActHidGam (ActChgCrsTT1stDay + 210) +#define ActShoGam (ActChgCrsTT1stDay + 211) +#define ActAddOneGamQst (ActChgCrsTT1stDay + 212) +#define ActGamLstTstQst (ActChgCrsTT1stDay + 213) +#define ActAddTstQstToGam (ActChgCrsTT1stDay + 214) +#define ActReqRemGamQst (ActChgCrsTT1stDay + 215) +#define ActRemGamQst (ActChgCrsTT1stDay + 216) +#define ActUp_GamQst (ActChgCrsTT1stDay + 217) +#define ActDwnGamQst (ActChgCrsTT1stDay + 218) -#define ActSeeSvy (ActChgCrsTT1stDay + 218) -#define ActAnsSvy (ActChgCrsTT1stDay + 219) -#define ActFrmNewSvy (ActChgCrsTT1stDay + 220) -#define ActEdiOneSvy (ActChgCrsTT1stDay + 221) -#define ActNewSvy (ActChgCrsTT1stDay + 222) -#define ActChgSvy (ActChgCrsTT1stDay + 223) -#define ActReqRemSvy (ActChgCrsTT1stDay + 224) -#define ActRemSvy (ActChgCrsTT1stDay + 225) -#define ActReqRstSvy (ActChgCrsTT1stDay + 226) -#define ActRstSvy (ActChgCrsTT1stDay + 227) -#define ActHidSvy (ActChgCrsTT1stDay + 228) -#define ActShoSvy (ActChgCrsTT1stDay + 229) -#define ActEdiOneSvyQst (ActChgCrsTT1stDay + 230) -#define ActRcvSvyQst (ActChgCrsTT1stDay + 231) -#define ActReqRemSvyQst (ActChgCrsTT1stDay + 232) -#define ActRemSvyQst (ActChgCrsTT1stDay + 233) +#define ActSeeSvy (ActChgCrsTT1stDay + 219) +#define ActAnsSvy (ActChgCrsTT1stDay + 220) +#define ActFrmNewSvy (ActChgCrsTT1stDay + 221) +#define ActEdiOneSvy (ActChgCrsTT1stDay + 222) +#define ActNewSvy (ActChgCrsTT1stDay + 223) +#define ActChgSvy (ActChgCrsTT1stDay + 224) +#define ActReqRemSvy (ActChgCrsTT1stDay + 225) +#define ActRemSvy (ActChgCrsTT1stDay + 226) +#define ActReqRstSvy (ActChgCrsTT1stDay + 227) +#define ActRstSvy (ActChgCrsTT1stDay + 228) +#define ActHidSvy (ActChgCrsTT1stDay + 229) +#define ActShoSvy (ActChgCrsTT1stDay + 230) +#define ActEdiOneSvyQst (ActChgCrsTT1stDay + 231) +#define ActRcvSvyQst (ActChgCrsTT1stDay + 232) +#define ActReqRemSvyQst (ActChgCrsTT1stDay + 233) +#define ActRemSvyQst (ActChgCrsTT1stDay + 234) /*****************************************************************************/ /******************************** Files tab **********************************/ diff --git a/swad_changelog.h b/swad_changelog.h index 5b903384..21ad3bfc 100644 --- a/swad_changelog.h +++ b/swad_changelog.h @@ -557,10 +557,16 @@ enscript -2 --landscape --color --file-align=2 --highlight --line-numbers -o - * En OpenSWAD: ps2pdf source.ps destination.pdf */ -#define Log_PLATFORM_VERSION "SWAD 19.239.9 (2020-05-21)" +#define Log_PLATFORM_VERSION "SWAD 19.241 (2020-05-22)" #define CSS_FILE "swad19.238.2.css" #define JS_FILE "swad19.239.6.js" /* + Version 19.241: May 22, 2020 Log in exams. + Bug fixing and code refactoring in tests and exams. (301712 lines) + 1 change necessary in database: +CREATE TABLE IF NOT EXISTS exa_log (LogCod INT NOT NULL,PrnCod INT NOT NULL,ActCod INT NOT NULL,ClickTime DATETIME NOT NULL,IP CHAR(15) NOT NULL,SessionId CHAR(43) NOT NULL,UNIQUE INDEX(LogCod),INDEX(PrnCod,ClickTime),INDEX(ClickTime)); + + Version 19.240: May 21, 2020 Code refactoring in tests and exams. (301428 lines) Version 19.239.9: May 21, 2020 Fixed issue in exam sessions: exam prints in sessions of hidden exams are no accesible. (301441 lines) Version 19.239.8: May 21, 2020 Fixed issue in exam sessions: a student can not see hidden sessions. (301433 lines) Version 19.239.7: May 21, 2020 Fixed bug in permissions to see exam results. Reported by Eduardo Ros Vidal. (301412 lines) diff --git a/swad_database.c b/swad_database.c index 2ad9f2b2..d214d892 100644 --- a/swad_database.c +++ b/swad_database.c @@ -1044,6 +1044,33 @@ mysql> DESCRIBE exa_exams; "UNIQUE INDEX(ExaCod)," "INDEX(CrsCod))"); + /***** Table exa_log *****/ +/* +mysql> DESCRIBE exa_log; ++-----------+----------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++-----------+----------+------+-----+---------+-------+ +| LogCod | int(11) | NO | PRI | NULL | | +| PrnCod | int(11) | NO | MUL | NULL | | +| ActCod | int(11) | NO | | NULL | | +| ClickTime | datetime | NO | MUL | NULL | | +| IP | char(15) | NO | | NULL | | +| SessionId | char(43) | NO | | NULL | | ++-----------+----------+------+-----+---------+-------+ +6 rows in set (0.00 sec) +*/ +// TODO: Change NtfCod and LogCod from INT to BIGINT in database tables. + DB_CreateTable ("CREATE TABLE IF NOT EXISTS exa_log (" + "LogCod INT NOT NULL," + "PrnCod INT NOT NULL," + "ActCod INT NOT NULL," + "ClickTime DATETIME NOT NULL," + "IP CHAR(15) NOT NULL," // Cns_MAX_BYTES_IP + "SessionId CHAR(43) NOT NULL," // Cns_BYTES_SESSION_ID + "UNIQUE INDEX(LogCod)," + "INDEX(PrnCod,ClickTime)," + "INDEX(ClickTime))"); + /***** Table exa_print_questions *****/ /* mysql> DESCRIBE exa_print_questions; diff --git a/swad_exam_log.c b/swad_exam_log.c new file mode 100644 index 00000000..1d12b823 --- /dev/null +++ b/swad_exam_log.c @@ -0,0 +1,99 @@ +// swad_exam_log.c: exam access log + +/* + 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_box.h" +#include "swad_database.h" +// #include "swad_exam.h" +#include "swad_exam_print.h" +// #include "swad_exam_result.h" +// #include "swad_exam_session.h" +// #include "swad_exam_set.h" +// #include "swad_exam_type.h" +// #include "swad_form.h" +#include "swad_global.h" + +/*****************************************************************************/ +/************** External global variables from others modules ****************/ +/*****************************************************************************/ + +extern struct Globals Gbl; + +/*****************************************************************************/ +/***************************** Private constants *****************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/******************************* Private types *******************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/***************************** Private variables *****************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/***************************** Private prototypes ****************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/**************************** Log access in database *************************/ +/*****************************************************************************/ + +void ExaLog_LogAccess (long LogCod) + { + long PrnCod; + + if (Gbl.Action.Act == ActAnsExaPrn || // Answer question + Gbl.Action.Act == ActSeeExaPrn || // Create/resume print exam + Gbl.Action.Act == ActEndExaPrn) // End print exam + { + PrnCod = ExaPrn_GetCurrentPrnCod (); + + if (PrnCod > 0) // Only if exam print is accesible (visible, open...) + /***** Insert access into database *****/ + /* Log access in exam log. + Redundant data (also present in log table) are stored for speed */ + DB_QueryINSERT ("can not log exam access", + "INSERT INTO exa_log " + "(LogCod,PrnCod,ActCod,ClickTime,IP,SessionId)" + " VALUES " + "(%ld,%ld,%ld,NOW(),'%s','%s')", + LogCod, + PrnCod, + Act_GetActCod (Gbl.Action.Act), // Redundant, for speed + // NOW() Redundant, for speed + Gbl.IP, // Redundant, for speed + Gbl.Session.Id); + } + } diff --git a/swad_exam_log.h b/swad_exam_log.h new file mode 100644 index 00000000..ee8793a9 --- /dev/null +++ b/swad_exam_log.h @@ -0,0 +1,42 @@ +// swad_exam_log.h: exam access log + +#ifndef _SWAD_EXA_LOG +#define _SWAD_EXA_LOG +/* + 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 **********************************/ +/*****************************************************************************/ + + +/*****************************************************************************/ +/************************* Public types and constants ************************/ +/*****************************************************************************/ + + +/*****************************************************************************/ +/***************************** Public prototypes *****************************/ +/*****************************************************************************/ + +void ExaLog_LogAccess (long LogCod); + +#endif diff --git a/swad_exam_print.c b/swad_exam_print.c index 460557a6..3e67677f 100644 --- a/swad_exam_print.c +++ b/swad_exam_print.c @@ -61,6 +61,8 @@ extern struct Globals Gbl; /***************************** Private variables *****************************/ /*****************************************************************************/ +static long ExaPrn_CurrentPrnCod = -1L; + /*****************************************************************************/ /***************************** Private prototypes ****************************/ /*****************************************************************************/ @@ -107,7 +109,6 @@ static unsigned ExaPrn_GetAnswerFromForm (struct ExaPrn_Print *Print); static unsigned ExaPrn_GetParamQstInd (void); -static void ExaPrn_ComputeScoresAndStoreQuestionsOfPrint (struct ExaPrn_Print *Print); static void ExaPrn_ComputeScoreAndStoreQuestionOfPrint (struct ExaPrn_Print *Print, unsigned NumQst); @@ -141,6 +142,20 @@ static void ExaPrn_GetNumQstsNotBlank (struct ExaPrn_Print *Print); static void ExaPrn_ComputeTotalScoreOfPrint (struct ExaPrn_Print *Print); static void ExaPrn_UpdatePrintInDB (const struct ExaPrn_Print *Print); +/*****************************************************************************/ +/************* Set and get current exam print code (used in log) *************/ +/*****************************************************************************/ + +void ExaPrn_SetCurrentPrnCod (long PrnCod) + { + ExaPrn_CurrentPrnCod = PrnCod; + } + +long ExaPrn_GetCurrentPrnCod (void) + { + return ExaPrn_CurrentPrnCod; + } + /*****************************************************************************/ /**************************** Reset exam print *******************************/ /*****************************************************************************/ @@ -201,13 +216,13 @@ void ExaPrn_ShowExamPrint (void) ExaPrn_GetQuestionsForNewPrintFromDB (&Print,Exam.ExaCod); if (Print.NumQsts) - { /***** Create/update new exam print in database *****/ ExaPrn_CreatePrintInDB (&Print); - ExaPrn_ComputeScoresAndStoreQuestionsOfPrint (&Print); - } } + /***** Set current print code (to be used in log) *****/ + ExaPrn_SetCurrentPrnCod (Print.PrnCod); + /***** Show test exam to be answered *****/ ExaPrn_ShowExamPrintToFillIt (&Exams,&Exam,&Print); } @@ -417,6 +432,9 @@ static unsigned ExaPrn_GetSomeQstsFromSetToPrint (struct ExaPrn_Print *Print, If the user does not confirm the submission of their exam ==> ==> the exam may be half filled ==> the answers displayed will be those selected by the user. */ Print->PrintedQuestions[*NumQstInPrint].StrAnswers[0] = '\0'; + + /* Reset score of this question in print */ + Print->PrintedQuestions[*NumQstInPrint].Score = 0.0; } return NumQstsInSet; @@ -493,6 +511,8 @@ static void ExaPrn_GenerateChoiceIndexes (struct TstPrn_PrintedQuestion *Printed static void ExaPrn_CreatePrintInDB (struct ExaPrn_Print *Print) { + unsigned NumQst; + /***** Insert new exam print into table *****/ Print->PrnCod = DB_QueryINSERTandReturnCode ("can not create new exam print", @@ -503,6 +523,13 @@ static void ExaPrn_CreatePrintInDB (struct ExaPrn_Print *Print) Print->SesCod, Gbl.Usrs.Me.UsrDat.UsrCod, Print->NumQsts); + + /***** Store all questions (with blank answers) + of this exam print just generated in database *****/ + for (NumQst = 0; + NumQst < Print->NumQsts; + NumQst++) + ExaPrn_StoreOneQstOfPrintInDB (Print,NumQst); } /*****************************************************************************/ @@ -599,7 +626,7 @@ static void ExaPrn_ShowExamPrintToFillIt (struct Exa_Exams *Exams, HTM_DIV_End (); // Used for AJAX based refresh /***** Form to end/close this exam print *****/ - Frm_StartForm (ActSeeOneExaResMe); + Frm_StartForm (ActEndExaPrn); ExaSes_PutParamsEdit (Exams); Btn_PutCreateButton (Txt_I_have_finished); Frm_EndForm (); @@ -968,6 +995,9 @@ void ExaPrn_ReceivePrintAnswer (void) if (Print.PrnCod <= 0) Lay_WrongExamExit (); + /***** Set current print code (to be used in log) *****/ + ExaPrn_SetCurrentPrnCod (Print.PrnCod); + /***** Get questions and user's answers of exam print from database *****/ ExaPrn_GetPrintQuestionsFromDB (&Print); @@ -1023,33 +1053,6 @@ static unsigned ExaPrn_GetParamQstInd (void) return (unsigned) NumQst; } -/*****************************************************************************/ -/*********** Compute score of each question and store in database ************/ -/*****************************************************************************/ - -static void ExaPrn_ComputeScoresAndStoreQuestionsOfPrint (struct ExaPrn_Print *Print) - { - unsigned NumQst; - - /***** Initialize total score *****/ - Print->Score = 0.0; - Print->NumQstsNotBlank = 0; - - /***** Compute and store scores of all questions *****/ - for (NumQst = 0; - NumQst < Print->NumQsts; - NumQst++) - { - /* Compute question score and store in database */ - ExaPrn_ComputeScoreAndStoreQuestionOfPrint (Print,NumQst); - - /* Accumulate total score */ - Print->Score += Print->PrintedQuestions[NumQst].Score; - if (Print->PrintedQuestions[NumQst].AnswerIsNotBlank) - Print->NumQstsNotBlank++; - } - } - /*****************************************************************************/ /*********** Compute score of one question and store in database *************/ /*****************************************************************************/ @@ -1074,7 +1077,12 @@ static void ExaPrn_ComputeScoreAndStoreQuestionOfPrint (struct ExaPrn_Print *Pri ExaPrn_GetAnswerFromDB (Print,Print->PrintedQuestions[NumQst].QstCod, CurrentStrAnswersInDB); if (!strcmp (Print->PrintedQuestions[NumQst].StrAnswers,CurrentStrAnswersInDB)) - Print->PrintedQuestions[NumQst].StrAnswers[0] = '\0'; + { + /* The answer just clicked by user + is the same as the last one checked and stored in database */ + Print->PrintedQuestions[NumQst].StrAnswers[0] = '\0'; // Uncheck option + Print->PrintedQuestions[NumQst].Score = 0; // Clear question score + } } /***** Store test exam question in database *****/ @@ -1395,7 +1403,9 @@ static void ExaPrn_ComputeTotalScoreOfPrint (struct ExaPrn_Print *Print) /***** Compute total score of exam print *****/ if (DB_QuerySELECT (&mysql_res,"can not get score of exam print", - "SELECT SUM(Score) FROM exa_print_questions WHERE PrnCod=%ld", + "SELECT SUM(Score)" + " FROM exa_print_questions" + " WHERE PrnCod=%ld", Print->PrnCod)) { /***** Get sum of individual scores (row[0]) *****/ diff --git a/swad_exam_print.h b/swad_exam_print.h index f6770b53..c705eea2 100644 --- a/swad_exam_print.h +++ b/swad_exam_print.h @@ -53,6 +53,9 @@ struct ExaPrn_Print /***************************** Public prototypes *****************************/ /*****************************************************************************/ +void ExaPrn_SetCurrentPrnCod (long PrnCod); +long ExaPrn_GetCurrentPrnCod (void); + void ExaPrn_ResetPrint (struct ExaPrn_Print *Print); void ExaPrn_ShowExamPrint (void); diff --git a/swad_exam_result.c b/swad_exam_result.c index 4d166590..6d9b2bc7 100644 --- a/swad_exam_result.c +++ b/swad_exam_result.c @@ -1110,8 +1110,9 @@ void ExaRes_ShowOneExaResult (void) ExaSes_GetAndCheckParameters (&Exams,&Exam,&Session); /***** Pointer to user's data *****/ - MeOrOther = (Gbl.Action.Act == ActSeeOneExaResMe) ? Usr_ME : - Usr_OTHER; + MeOrOther = (Gbl.Action.Act == ActSeeOneExaResMe || + Gbl.Action.Act == ActEndExaPrn) ? Usr_ME : + Usr_OTHER; switch (MeOrOther) { case Usr_ME: @@ -1130,12 +1131,18 @@ void ExaRes_ShowOneExaResult (void) Print.UsrCod = UsrDat->UsrCod; ExaPrn_GetPrintDataBySesCodAndUsrCod (&Print); + /***** Set current print code (to be used in log) *****/ + ExaPrn_SetCurrentPrnCod (Print.PrnCod); + /***** Check if I can view this print result *****/ switch (Gbl.Usrs.Me.Role.Logged) { case Rol_STD: + // Depends on visibility of result for this session (eye icon) ICanViewResult = ExaRes_CheckIfICanSeePrintResult (&Session,UsrDat->UsrCod); + if (ICanViewResult) + // Depends on 5 visibility icons ICanViewScore = TstVis_IsVisibleTotalScore (Exam.Visibility); else ICanViewScore = false; @@ -1393,7 +1400,35 @@ static void ExaRes_WriteQstAndAnsExam (struct UsrData *UsrDat, unsigned Visibility) { extern const char *Txt_Score; - bool IsVisibleQstAndAnsTxt = TstVis_IsVisibleQstAndAnsTxt (Visibility); + bool IsVisible[TstVis_NUM_ITEMS_VISIBILITY]; + + /***** Check if I can view each part of the question *****/ + switch (Gbl.Usrs.Me.Role.Logged) + { + case Rol_STD: + IsVisible[TstVis_VISIBLE_QST_ANS_TXT ] = TstVis_IsVisibleQstAndAnsTxt (Visibility); + IsVisible[TstVis_VISIBLE_FEEDBACK_TXT ] = TstVis_IsVisibleFeedbackTxt (Visibility); + IsVisible[TstVis_VISIBLE_CORRECT_ANSWER] = TstVis_IsVisibleCorrectAns (Visibility); + IsVisible[TstVis_VISIBLE_EACH_QST_SCORE] = TstVis_IsVisibleEachQstScore (Visibility); + break; + case Rol_NET: + case Rol_TCH: + case Rol_DEG_ADM: + case Rol_CTR_ADM: + case Rol_INS_ADM: + case Rol_SYS_ADM: + IsVisible[TstVis_VISIBLE_QST_ANS_TXT ] = + IsVisible[TstVis_VISIBLE_FEEDBACK_TXT ] = + IsVisible[TstVis_VISIBLE_CORRECT_ANSWER] = + IsVisible[TstVis_VISIBLE_EACH_QST_SCORE] = true; + break; + default: + IsVisible[TstVis_VISIBLE_QST_ANS_TXT ] = + IsVisible[TstVis_VISIBLE_FEEDBACK_TXT ] = + IsVisible[TstVis_VISIBLE_CORRECT_ANSWER] = + IsVisible[TstVis_VISIBLE_EACH_QST_SCORE] = false; + break; + } /***** Begin row *****/ HTM_TR_Begin (NULL); @@ -1408,20 +1443,21 @@ static void ExaRes_WriteQstAndAnsExam (struct UsrData *UsrDat, HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd); /* Stem */ - Tst_WriteQstStem (Question->Stem,"TEST_EXA",IsVisibleQstAndAnsTxt); + Tst_WriteQstStem (Question->Stem,"TEST_EXA",IsVisible[TstVis_VISIBLE_QST_ANS_TXT]); /* Media */ - if (IsVisibleQstAndAnsTxt) + if (IsVisible[TstVis_VISIBLE_QST_ANS_TXT]) Med_ShowMedia (&Question->Media, "TEST_MED_SHOW_CONT", "TEST_MED_SHOW"); /* Answers */ ExaPrn_ComputeAnswerScore (&Print->PrintedQuestions[NumQst],Question); - TstPrn_WriteAnswersExam (UsrDat,&Print->PrintedQuestions[NumQst],Question,Visibility); + TstPrn_WriteAnswersExam (UsrDat,&Print->PrintedQuestions[NumQst],Question, + IsVisible); /* Write score retrieved from database */ - if (TstVis_IsVisibleEachQstScore (Visibility)) + if (IsVisible[TstVis_VISIBLE_EACH_QST_SCORE]) { HTM_DIV_Begin ("class=\"DAT_SMALL LM\""); HTM_TxtColonNBSP (Txt_Score); @@ -1436,7 +1472,7 @@ static void ExaRes_WriteQstAndAnsExam (struct UsrData *UsrDat, } /* Question feedback */ - if (TstVis_IsVisibleFeedbackTxt (Visibility)) + if (IsVisible[TstVis_VISIBLE_FEEDBACK_TXT]) Tst_WriteQstFeedback (Question->Feedback,"TEST_EXA_LIGHT"); HTM_TD_End (); diff --git a/swad_exam_set.c b/swad_exam_set.c index f4c179d8..1cb84e23 100644 --- a/swad_exam_set.c +++ b/swad_exam_set.c @@ -1566,7 +1566,7 @@ static void ExaSet_ListQuestionForEdition (const struct Tst_Question *Question, Tst_WriteQstFeedback (Question->Feedback,"TEST_EDI_LIGHT"); /* Show answers */ - Tst_WriteAnswersListing (Question); + Tst_WriteAnswersBank (Question); HTM_ARTICLE_End (); HTM_TD_End (); diff --git a/swad_log.c b/swad_log.c index d534dd27..0b2c8392 100644 --- a/swad_log.c +++ b/swad_log.c @@ -32,6 +32,7 @@ #include "swad_banner.h" #include "swad_config.h" #include "swad_database.h" +#include "swad_exam_log.h" #include "swad_global.h" #include "swad_HTML.h" #include "swad_log.h" @@ -123,6 +124,9 @@ void Log_LogAccess (const char *Comments) Gbl.TimeSendInMicroseconds, Gbl.IP); + /* Log access while answering exam prints */ + ExaLog_LogAccess (LogCod); + /* Log comments */ if (Comments) { diff --git a/swad_match_result.c b/swad_match_result.c index 95f4fe42..7026cd2b 100644 --- a/swad_match_result.c +++ b/swad_match_result.c @@ -1181,8 +1181,11 @@ void MchRes_ShowOneMchResult (void) switch (Gbl.Usrs.Me.Role.Logged) { case Rol_STD: + // Depends on visibility of result for this match (eye icon) ICanViewResult = MchRes_CheckIfICanSeeMatchResult (&Match,UsrDat->UsrCod); + if (ICanViewResult) + // Depends on 5 visibility icons ICanViewScore = TstVis_IsVisibleTotalScore (Game.Visibility); else ICanViewScore = false; diff --git a/swad_test.c b/swad_test.c index 186b8d66..3ed164fe 100644 --- a/swad_test.c +++ b/swad_test.c @@ -183,25 +183,32 @@ static void TstPrn_WriteAnswersToFill (const struct TstPrn_PrintedQuestion *Prin unsigned NumQst, const struct Tst_Question *Question); -static void Tst_WriteIntAnsListing (const struct Tst_Question *Question); -static void TstPrn_WriteIntAnsSeeing (const struct TstPrn_PrintedQuestion *PrintedQuestion, - unsigned NumQst); +//----------------------------------------------------------------------------- -static void Tst_WriteFloatAnsEdit (const struct Tst_Question *Question); -static void TstPrn_WriteFloatAnsSeeing (const struct TstPrn_PrintedQuestion *PrintedQuestion, - unsigned NumQst); +static void Tst_WriteIntAnsBank (const struct Tst_Question *Question); +static void Tst_WriteFltAnsBank (const struct Tst_Question *Question); +static void Tst_WriteTF_AnsBank (const struct Tst_Question *Question); +static void Tst_WriteChoAnsBank (const struct Tst_Question *Question); -static void Tst_WriteTFAnsListing (const struct Tst_Question *Question); -static void TstPrn_WriteTFAnsSeeing (const struct TstPrn_PrintedQuestion *PrintedQuestion, - unsigned NumQst); +//----------------------------------------------------------------------------- -static void Tst_WriteChoiceAnsListing (const struct Tst_Question *Question); -static void TstPrn_WriteChoiceAnsSeeing (const struct TstPrn_PrintedQuestion *PrintedQuestion, - unsigned NumQst, - const struct Tst_Question *Question); +static void TstPrn_WriteIntAnsToFill (const struct TstPrn_PrintedQuestion *PrintedQuestion, + unsigned NumQst, + __attribute__((unused)) const struct Tst_Question *Question); +static void TstPrn_WriteFltAnsToFill (const struct TstPrn_PrintedQuestion *PrintedQuestion, + unsigned NumQst, + __attribute__((unused)) const struct Tst_Question *Question); +static void TstPrn_WriteTF_AnsToFill (const struct TstPrn_PrintedQuestion *PrintedQuestion, + unsigned NumQst, + __attribute__((unused)) const struct Tst_Question *Question); +static void TstPrn_WriteChoAnsToFill (const struct TstPrn_PrintedQuestion *PrintedQuestion, + unsigned NumQst, + const struct Tst_Question *Question); +static void TstPrn_WriteTxtAnsToFill (const struct TstPrn_PrintedQuestion *PrintedQuestion, + unsigned NumQst, + __attribute__((unused)) const struct Tst_Question *Question); -static void TstPrn_WriteTextAnsSeeing (const struct TstPrn_PrintedQuestion *PrintedQuestion, - unsigned NumQst); +//----------------------------------------------------------------------------- static bool Tst_GetParamsTst (struct Tst_Test *Test, Tst_ActionToDoWithQuestions_t ActionToDoWithQuestions); @@ -932,7 +939,7 @@ void Tst_ListQuestionForEdition (const struct Tst_Question *Question, Tst_WriteQstFeedback (Question->Feedback,"TEST_EDI_LIGHT"); /* Show answers */ - Tst_WriteAnswersListing (Question); + Tst_WriteAnswersBank (Question); } else { @@ -2541,7 +2548,7 @@ static void Tst_WriteQuestionListing (struct Tst_Test *Test,unsigned NumQst) /* Feedback (row[4]) and answers */ Tst_WriteQstFeedback (Test->Question.Feedback,"TEST_EDI_LIGHT"); - Tst_WriteAnswersListing (&Test->Question); + Tst_WriteAnswersBank (&Test->Question); HTM_TD_End (); /* Number of times this question has been answered */ @@ -2820,7 +2827,7 @@ static void Tst_WriteQuestionRowForSelection (unsigned NumQst, Tst_WriteQstFeedback (Question->Feedback,"TEST_EDI_LIGHT"); /* Write answers */ - Tst_WriteAnswersListing (Question); + Tst_WriteAnswersBank (Question); HTM_TD_End (); /***** End table row *****/ @@ -2881,28 +2888,20 @@ void Tst_GetAnswersQst (struct Tst_Question *Question,MYSQL_RES **mysql_res, /**************** Get and write the answers of a test question ***************/ /*****************************************************************************/ -void Tst_WriteAnswersListing (const struct Tst_Question *Question) +void Tst_WriteAnswersBank (const struct Tst_Question *Question) { + void (*TstPrn_WriteAnsBank[Tst_NUM_ANS_TYPES]) (const struct Tst_Question *Question) = + { + [Tst_ANS_INT ] = Tst_WriteIntAnsBank, + [Tst_ANS_FLOAT ] = Tst_WriteFltAnsBank, + [Tst_ANS_TRUE_FALSE ] = Tst_WriteTF_AnsBank, + [Tst_ANS_UNIQUE_CHOICE ] = Tst_WriteChoAnsBank, + [Tst_ANS_MULTIPLE_CHOICE] = Tst_WriteChoAnsBank, + [Tst_ANS_TEXT ] = Tst_WriteChoAnsBank, + }; + /***** Write answers *****/ - switch (Question->Answer.Type) - { - case Tst_ANS_INT: - Tst_WriteIntAnsListing (Question); - break; - case Tst_ANS_FLOAT: - Tst_WriteFloatAnsEdit (Question); - break; - case Tst_ANS_TRUE_FALSE: - Tst_WriteTFAnsListing (Question); - break; - case Tst_ANS_UNIQUE_CHOICE: - case Tst_ANS_MULTIPLE_CHOICE: - case Tst_ANS_TEXT: - Tst_WriteChoiceAnsListing (Question); - break; - default: - break; - } + TstPrn_WriteAnsBank[Question->Answer.Type] (Question); } /*****************************************************************************/ @@ -2913,28 +2912,20 @@ static void TstPrn_WriteAnswersToFill (const struct TstPrn_PrintedQuestion *Prin unsigned NumQst, const struct Tst_Question *Question) { - /***** Write answer depending on type *****/ - switch (Question->Answer.Type) - { - case Tst_ANS_INT: - TstPrn_WriteIntAnsSeeing (PrintedQuestion,NumQst); - break; - case Tst_ANS_FLOAT: - TstPrn_WriteFloatAnsSeeing (PrintedQuestion,NumQst); - break; - case Tst_ANS_TRUE_FALSE: - TstPrn_WriteTFAnsSeeing (PrintedQuestion,NumQst); - break; - case Tst_ANS_UNIQUE_CHOICE: - case Tst_ANS_MULTIPLE_CHOICE: - TstPrn_WriteChoiceAnsSeeing (PrintedQuestion,NumQst,Question); - break; - case Tst_ANS_TEXT: - TstPrn_WriteTextAnsSeeing (PrintedQuestion,NumQst); - break; - default: - break; - } + void (*TstPrn_WriteAnsBank[Tst_NUM_ANS_TYPES]) (const struct TstPrn_PrintedQuestion *PrintedQuestion, + unsigned NumQst, + const struct Tst_Question *Question) = + { + [Tst_ANS_INT ] = TstPrn_WriteIntAnsToFill, + [Tst_ANS_FLOAT ] = TstPrn_WriteFltAnsToFill, + [Tst_ANS_TRUE_FALSE ] = TstPrn_WriteTF_AnsToFill, + [Tst_ANS_UNIQUE_CHOICE ] = TstPrn_WriteChoAnsToFill, + [Tst_ANS_MULTIPLE_CHOICE] = TstPrn_WriteChoAnsToFill, + [Tst_ANS_TEXT ] = TstPrn_WriteTxtAnsToFill, + }; + + /***** Write answers *****/ + TstPrn_WriteAnsBank[Question->Answer.Type] (PrintedQuestion,NumQst,Question); } /*****************************************************************************/ @@ -2955,36 +2946,18 @@ bool Tst_CheckIfQuestionIsValidForGame (long QstCod) /****************** Write integer answer when editing a test *****************/ /*****************************************************************************/ -static void Tst_WriteIntAnsListing (const struct Tst_Question *Question) +static void Tst_WriteIntAnsBank (const struct Tst_Question *Question) { HTM_SPAN_Begin ("class=\"TEST_EDI\""); HTM_TxtF ("(%ld)",Question->Answer.Integer); HTM_SPAN_End (); } -/*****************************************************************************/ -/****************** Write integer answer when seeing a test ******************/ -/*****************************************************************************/ - -static void TstPrn_WriteIntAnsSeeing (const struct TstPrn_PrintedQuestion *PrintedQuestion, - unsigned NumQst) - { - char StrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x" - - /***** Write input field for the answer *****/ - snprintf (StrAns,sizeof (StrAns), - "Ans%010u", - NumQst); - HTM_INPUT_TEXT (StrAns,11,PrintedQuestion->StrAnswers, - HTM_DONT_SUBMIT_ON_CHANGE, - "size=\"11\""); - } - /*****************************************************************************/ /****************** Write float answer when editing a test *******************/ /*****************************************************************************/ -static void Tst_WriteFloatAnsEdit (const struct Tst_Question *Question) +static void Tst_WriteFltAnsBank (const struct Tst_Question *Question) { HTM_SPAN_Begin ("class=\"TEST_EDI\""); HTM_Txt ("(["); @@ -2995,29 +2968,11 @@ static void Tst_WriteFloatAnsEdit (const struct Tst_Question *Question) HTM_SPAN_End (); } -/*****************************************************************************/ -/****************** Write float answer when seeing a test ********************/ -/*****************************************************************************/ - -static void TstPrn_WriteFloatAnsSeeing (const struct TstPrn_PrintedQuestion *PrintedQuestion, - unsigned NumQst) - { - char StrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x" - - /***** Write input field for the answer *****/ - snprintf (StrAns,sizeof (StrAns), - "Ans%010u", - NumQst); - HTM_INPUT_TEXT (StrAns,Tst_MAX_BYTES_FLOAT_ANSWER,PrintedQuestion->StrAnswers, - HTM_DONT_SUBMIT_ON_CHANGE, - "size=\"11\""); - } - /*****************************************************************************/ /*********** Write false / true answer when listing test questions ***********/ /*****************************************************************************/ -static void Tst_WriteTFAnsListing (const struct Tst_Question *Question) +static void Tst_WriteTF_AnsBank (const struct Tst_Question *Question) { /***** Write answer *****/ HTM_SPAN_Begin ("class=\"TEST_EDI\""); @@ -3027,54 +2982,11 @@ static void Tst_WriteTFAnsListing (const struct Tst_Question *Question) HTM_SPAN_End (); } -/*****************************************************************************/ -/************** Write false / true answer when seeing a test ****************/ -/*****************************************************************************/ - -static void TstPrn_WriteTFAnsSeeing (const struct TstPrn_PrintedQuestion *PrintedQuestion, - unsigned NumQst) - { - extern const char *Txt_TF_QST[2]; - - /***** Write selector for the answer *****/ - /* Initially user has not answered the question ==> initially all the answers will be blank. - If the user does not confirm the submission of their exam ==> - ==> the exam may be half filled ==> the answers displayed will be those selected by the user. */ - HTM_SELECT_Begin (HTM_DONT_SUBMIT_ON_CHANGE, - "name=\"Ans%010u\"",NumQst); - HTM_OPTION (HTM_Type_STRING,"" ,PrintedQuestion->StrAnswers[0] == '\0',false," "); - HTM_OPTION (HTM_Type_STRING,"T",PrintedQuestion->StrAnswers[0] == 'T' ,false,"%s",Txt_TF_QST[0]); - HTM_OPTION (HTM_Type_STRING,"F",PrintedQuestion->StrAnswers[0] == 'F' ,false,"%s",Txt_TF_QST[1]); - HTM_SELECT_End (); - } - -/*****************************************************************************/ -/************** Write false / true answer when seeing a test *****************/ -/*****************************************************************************/ - -void Tst_WriteAnsTF (char AnsTF) - { - extern const char *Txt_TF_QST[2]; - - switch (AnsTF) - { - case 'T': // true - HTM_Txt (Txt_TF_QST[0]); - break; - case 'F': // false - HTM_Txt (Txt_TF_QST[1]); - break; - default: // no answer - HTM_NBSP (); - break; - } - } - /*****************************************************************************/ /**** Write single or multiple choice answer when listing test questions *****/ /*****************************************************************************/ -static void Tst_WriteChoiceAnsListing (const struct Tst_Question *Question) +static void Tst_WriteChoAnsBank (const struct Tst_Question *Question) { extern const char *Txt_TST_Answer_given_by_the_teachers; unsigned NumOpt; @@ -3160,19 +3072,89 @@ static void Tst_WriteChoiceAnsListing (const struct Tst_Question *Question) HTM_TABLE_End (); } +/*****************************************************************************/ +/****************** Write integer answer when seeing a test ******************/ +/*****************************************************************************/ + +static void TstPrn_WriteIntAnsToFill (const struct TstPrn_PrintedQuestion *PrintedQuestion, + unsigned NumQst, + __attribute__((unused)) const struct Tst_Question *Question) + { + char StrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x" + + /***** Write input field for the answer *****/ + snprintf (StrAns,sizeof (StrAns), + "Ans%010u", + NumQst); + HTM_INPUT_TEXT (StrAns,11,PrintedQuestion->StrAnswers, + HTM_DONT_SUBMIT_ON_CHANGE, + "size=\"11\""); + } + +/*****************************************************************************/ +/****************** Write float answer when seeing a test ********************/ +/*****************************************************************************/ + +static void TstPrn_WriteFltAnsToFill (const struct TstPrn_PrintedQuestion *PrintedQuestion, + unsigned NumQst, + __attribute__((unused)) const struct Tst_Question *Question) + { + char StrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x" + + /***** Write input field for the answer *****/ + snprintf (StrAns,sizeof (StrAns), + "Ans%010u", + NumQst); + HTM_INPUT_TEXT (StrAns,Tst_MAX_BYTES_FLOAT_ANSWER,PrintedQuestion->StrAnswers, + HTM_DONT_SUBMIT_ON_CHANGE, + "size=\"11\""); + } + +/*****************************************************************************/ +/************** Write false / true answer when seeing a test ****************/ +/*****************************************************************************/ + +static void TstPrn_WriteTF_AnsToFill (const struct TstPrn_PrintedQuestion *PrintedQuestion, + unsigned NumQst, + __attribute__((unused)) const struct Tst_Question *Question) + { + extern const char *Txt_TF_QST[2]; + + /***** Write selector for the answer *****/ + /* Initially user has not answered the question ==> initially all the answers will be blank. + If the user does not confirm the submission of their exam ==> + ==> the exam may be half filled ==> the answers displayed will be those selected by the user. */ + HTM_SELECT_Begin (HTM_DONT_SUBMIT_ON_CHANGE, + "name=\"Ans%010u\"",NumQst); + HTM_OPTION (HTM_Type_STRING,"" ,PrintedQuestion->StrAnswers[0] == '\0',false," "); + HTM_OPTION (HTM_Type_STRING,"T",PrintedQuestion->StrAnswers[0] == 'T' ,false,"%s",Txt_TF_QST[0]); + HTM_OPTION (HTM_Type_STRING,"F",PrintedQuestion->StrAnswers[0] == 'F' ,false,"%s",Txt_TF_QST[1]); + HTM_SELECT_End (); + } + /*****************************************************************************/ /******** Write single or multiple choice answer when seeing a test **********/ /*****************************************************************************/ -static void TstPrn_WriteChoiceAnsSeeing (const struct TstPrn_PrintedQuestion *PrintedQuestion, - unsigned NumQst, - const struct Tst_Question *Question) +static void TstPrn_WriteChoAnsToFill (const struct TstPrn_PrintedQuestion *PrintedQuestion, + unsigned NumQst, + const struct Tst_Question *Question) { unsigned NumOpt; unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION]; // Indexes of all answers of this question bool UsrAnswers[Tst_MAX_OPTIONS_PER_QUESTION]; char StrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x" + /***** Change format of answers text *****/ + for (NumOpt = 0; + NumOpt < Question->Answer.NumOptions; + NumOpt++) + /* Convert answer text, that is in HTML, to rigorous HTML */ + if (Question->Answer.Options[NumOpt].Text[0]) + Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, + Question->Answer.Options[NumOpt].Text, + Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false); + /***** Get indexes for this question from string *****/ TstPrn_GetIndexesFromStr (PrintedQuestion->StrIndexes,Indexes); @@ -3241,6 +3223,47 @@ static void TstPrn_WriteChoiceAnsSeeing (const struct TstPrn_PrintedQuestion *Pr HTM_TABLE_End (); } +/*****************************************************************************/ +/******************** Write text answer when seeing a test *******************/ +/*****************************************************************************/ + +static void TstPrn_WriteTxtAnsToFill (const struct TstPrn_PrintedQuestion *PrintedQuestion, + unsigned NumQst, + __attribute__((unused)) const struct Tst_Question *Question) + { + char StrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x" + + /***** Write input field for the answer *****/ + snprintf (StrAns,sizeof (StrAns), + "Ans%010u", + NumQst); + HTM_INPUT_TEXT (StrAns,Tst_MAX_CHARS_ANSWERS_ONE_QST,PrintedQuestion->StrAnswers, + HTM_DONT_SUBMIT_ON_CHANGE, + "size=\"40\""); + } + +/*****************************************************************************/ +/************** Write false / true answer when seeing a test *****************/ +/*****************************************************************************/ + +void Tst_WriteAnsTF (char AnsTF) + { + extern const char *Txt_TF_QST[2]; + + switch (AnsTF) + { + case 'T': // true + HTM_Txt (Txt_TF_QST[0]); + break; + case 'F': // false + HTM_Txt (Txt_TF_QST[1]); + break; + default: // no answer + HTM_NBSP (); + break; + } + } + /*****************************************************************************/ /************************ Get choice answer from row *************************/ /*****************************************************************************/ @@ -3301,24 +3324,6 @@ void Tst_GetChoiceAns (struct Tst_Question *Question,MYSQL_RES *mysql_res) } } -/*****************************************************************************/ -/******************** Write text answer when seeing a test *******************/ -/*****************************************************************************/ - -static void TstPrn_WriteTextAnsSeeing (const struct TstPrn_PrintedQuestion *PrintedQuestion, - unsigned NumQst) - { - char StrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Ansxx...x" - - /***** Write input field for the answer *****/ - snprintf (StrAns,sizeof (StrAns), - "Ans%010u", - NumQst); - HTM_INPUT_TEXT (StrAns,Tst_MAX_CHARS_ANSWERS_ONE_QST,PrintedQuestion->StrAnswers, - HTM_DONT_SUBMIT_ON_CHANGE, - "size=\"40\""); - } - /*****************************************************************************/ /*************** Write parameter with the code of a question *****************/ /*****************************************************************************/ @@ -5604,8 +5609,8 @@ static void Tst_InsertAnswersIntoDB (struct Tst_Question *Question) void Tst_UpdateQstScoreInDB (struct TstPrn_PrintedQuestion *PrintedQuestion) { /***** Update number of clicks and score of the question *****/ - Str_SetDecimalPointToUS (); // To print the floating point as a dot - if (PrintedQuestion->AnswerIsNotBlank) + Str_SetDecimalPointToUS (); // To print the floating point as a dot + if (PrintedQuestion->StrAnswers[0]) // User's answer is not blank DB_QueryUPDATE ("can not update the score of a question", "UPDATE tst_questions" " SET NumHits=NumHits+1,NumHitsNotBlank=NumHitsNotBlank+1," @@ -5613,7 +5618,7 @@ void Tst_UpdateQstScoreInDB (struct TstPrn_PrintedQuestion *PrintedQuestion) " WHERE QstCod=%ld", PrintedQuestion->Score, PrintedQuestion->QstCod); - else // The answer is blank + else // User's answer is blank DB_QueryUPDATE ("can not update the score of a question", "UPDATE tst_questions" " SET NumHits=NumHits+1" diff --git a/swad_test.h b/swad_test.h index d2fb1b82..45494119 100644 --- a/swad_test.h +++ b/swad_test.h @@ -130,7 +130,7 @@ void Tst_WriteParamEditQst (const struct Tst_Test *Test); unsigned Tst_GetNumAnswersQst (long QstCod); void Tst_GetAnswersQst (struct Tst_Question *Question,MYSQL_RES **mysql_res, bool Shuffle); -void Tst_WriteAnswersListing (const struct Tst_Question *Question); +void Tst_WriteAnswersBank (const struct Tst_Question *Question); bool Tst_CheckIfQuestionIsValidForGame (long QstCod); void Tst_WriteAnsTF (char AnsTF); void Tst_GetChoiceAns (struct Tst_Question *Question,MYSQL_RES *mysql_res); diff --git a/swad_test_print.c b/swad_test_print.c index 9cdb8eb4..5373b445 100644 --- a/swad_test_print.c +++ b/swad_test_print.c @@ -105,23 +105,23 @@ static void TstPrn_GetCorrectTxtAnswerFromDB (struct Tst_Question *Question); static void TstPrn_WriteIntAnsPrint (struct UsrData *UsrDat, const struct TstPrn_PrintedQuestion *PrintedQuestion, const struct Tst_Question *Question, - unsigned Visibility); + bool IsVisible[TstVis_NUM_ITEMS_VISIBILITY]); static void TstPrn_WriteFltAnsPrint (struct UsrData *UsrDat, const struct TstPrn_PrintedQuestion *PrintedQuestion, const struct Tst_Question *Question, - unsigned Visibility); + bool IsVisible[TstVis_NUM_ITEMS_VISIBILITY]); static void TstPrn_WriteTF_AnsPrint (struct UsrData *UsrDat, const struct TstPrn_PrintedQuestion *PrintedQuestion, const struct Tst_Question *Question, - unsigned Visibility); + bool IsVisible[TstVis_NUM_ITEMS_VISIBILITY]); static void TstPrn_WriteChoAnsPrint (struct UsrData *UsrDat, const struct TstPrn_PrintedQuestion *PrintedQuestion, const struct Tst_Question *Question, - unsigned Visibility); + bool IsVisible[TstVis_NUM_ITEMS_VISIBILITY]); static void TstPrn_WriteTxtAnsPrint (struct UsrData *UsrDat, const struct TstPrn_PrintedQuestion *PrintedQuestion, const struct Tst_Question *Question, - unsigned Visibility); + bool IsVisible[TstVis_NUM_ITEMS_VISIBILITY]); //----------------------------------------------------------------------------- @@ -250,7 +250,7 @@ void TstPrn_ShowPrintAfterAssess (struct TstPrn_Print *Print) /***** Compute total score *****/ Print->Score += Print->PrintedQuestions[NumQst].Score; - if (Print->PrintedQuestions[NumQst].AnswerIsNotBlank) + if (Print->PrintedQuestions[NumQst].StrAnswers[0]) // User's answer is not blank Print->NumQstsNotBlank++; /***** Update the number of accesses and the score of this question *****/ @@ -280,7 +280,35 @@ static void TstPrn_WriteQstAndAnsExam (struct UsrData *UsrDat, extern const char *Txt_Question_removed; extern const char *Txt_Question_modified; bool QuestionUneditedAfterExam = false; - bool IsVisibleQstAndAnsTxt = TstVis_IsVisibleQstAndAnsTxt (Visibility); + bool IsVisible[TstVis_NUM_ITEMS_VISIBILITY]; + + /***** Check if I can view each part of the question *****/ + switch (Gbl.Usrs.Me.Role.Logged) + { + case Rol_STD: + IsVisible[TstVis_VISIBLE_QST_ANS_TXT ] = TstVis_IsVisibleQstAndAnsTxt (Visibility); + IsVisible[TstVis_VISIBLE_FEEDBACK_TXT ] = TstVis_IsVisibleFeedbackTxt (Visibility); + IsVisible[TstVis_VISIBLE_CORRECT_ANSWER] = TstVis_IsVisibleCorrectAns (Visibility); + IsVisible[TstVis_VISIBLE_EACH_QST_SCORE] = TstVis_IsVisibleEachQstScore (Visibility); + break; + case Rol_NET: + case Rol_TCH: + case Rol_DEG_ADM: + case Rol_CTR_ADM: + case Rol_INS_ADM: + case Rol_SYS_ADM: + IsVisible[TstVis_VISIBLE_QST_ANS_TXT ] = + IsVisible[TstVis_VISIBLE_FEEDBACK_TXT ] = + IsVisible[TstVis_VISIBLE_CORRECT_ANSWER] = + IsVisible[TstVis_VISIBLE_EACH_QST_SCORE] = true; + break; + default: + IsVisible[TstVis_VISIBLE_QST_ANS_TXT ] = + IsVisible[TstVis_VISIBLE_FEEDBACK_TXT ] = + IsVisible[TstVis_VISIBLE_CORRECT_ANSWER] = + IsVisible[TstVis_VISIBLE_EACH_QST_SCORE] = false; + break; + } /***** If this question has been edited later than test time ==> don't show question ****/ @@ -306,17 +334,18 @@ static void TstPrn_WriteQstAndAnsExam (struct UsrData *UsrDat, if (QuestionUneditedAfterExam) { /* Stem */ - Tst_WriteQstStem (Question->Stem,"TEST_EXA",IsVisibleQstAndAnsTxt); + Tst_WriteQstStem (Question->Stem,"TEST_EXA",IsVisible[TstVis_VISIBLE_QST_ANS_TXT]); /* Media */ - if (IsVisibleQstAndAnsTxt) + if (IsVisible[TstVis_VISIBLE_QST_ANS_TXT]) Med_ShowMedia (&Question->Media, "TEST_MED_SHOW_CONT", "TEST_MED_SHOW"); /* Answers */ TstPrn_ComputeAnswerScore (&Print->PrintedQuestions[NumQst],Question); - TstPrn_WriteAnswersExam (UsrDat,&Print->PrintedQuestions[NumQst],Question,Visibility); + TstPrn_WriteAnswersExam (UsrDat,&Print->PrintedQuestions[NumQst],Question, + IsVisible); } else Ale_ShowAlert (Ale_WARNING,Txt_Question_modified); @@ -325,7 +354,7 @@ static void TstPrn_WriteQstAndAnsExam (struct UsrData *UsrDat, Ale_ShowAlert (Ale_WARNING,Txt_Question_removed); /* Write score retrieved from database */ - if (TstVis_IsVisibleEachQstScore (Visibility)) + if (IsVisible[TstVis_VISIBLE_EACH_QST_SCORE]) { HTM_DIV_Begin ("class=\"DAT_SMALL LM\""); HTM_TxtColonNBSP (Txt_Score); @@ -341,7 +370,7 @@ static void TstPrn_WriteQstAndAnsExam (struct UsrData *UsrDat, /* Question feedback */ if (QuestionUneditedAfterExam) - if (TstVis_IsVisibleFeedbackTxt (Visibility)) + if (IsVisible[TstVis_VISIBLE_FEEDBACK_TXT]) Tst_WriteQstFeedback (Question->Feedback,"TEST_EXA_LIGHT"); HTM_TD_End (); @@ -382,7 +411,7 @@ void TstPrn_ComputeScoresAndStoreQuestionsOfPrint (struct TstPrn_Print *Print, /* Accumulate total score */ Print->Score += Print->PrintedQuestions[NumQst].Score; - if (Print->PrintedQuestions[NumQst].AnswerIsNotBlank) + if (Print->PrintedQuestions[NumQst].StrAnswers[0]) // User's answer is not blank Print->NumQstsNotBlank++; /* Update the number of hits and the score of this question in tests database */ @@ -632,22 +661,20 @@ void TstPrn_ComputeIntAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion, { long AnswerUsr; - PrintedQuestion->Score = 0.0; // Default score for blank or wrong answer - PrintedQuestion->AnswerIsNotBlank = (PrintedQuestion->StrAnswers[0] != '\0'); - if (PrintedQuestion->AnswerIsNotBlank) // If user has answered the answer + PrintedQuestion->Score = 0.0; // Default score for blank or wrong answer + if (PrintedQuestion->StrAnswers[0]) // If user has answered the answer if (sscanf (PrintedQuestion->StrAnswers,"%ld",&AnswerUsr) == 1) if (AnswerUsr == Question->Answer.Integer) // Correct answer PrintedQuestion->Score = 1.0; } void TstPrn_ComputeFltAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion, - const struct Tst_Question *Question) + const struct Tst_Question *Question) { double AnswerUsr; - PrintedQuestion->Score = 0.0; // Default score for blank or wrong answer - PrintedQuestion->AnswerIsNotBlank = (PrintedQuestion->StrAnswers[0] != '\0'); - if (PrintedQuestion->AnswerIsNotBlank) // If user has answered the answer + PrintedQuestion->Score = 0.0; // Default score for blank or wrong answer + if (PrintedQuestion->StrAnswers[0]) // If user has answered the answer { AnswerUsr = Str_GetDoubleFromStr (PrintedQuestion->StrAnswers); @@ -661,8 +688,7 @@ void TstPrn_ComputeFltAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion, void TstPrn_ComputeTF_AnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion, const struct Tst_Question *Question) { - PrintedQuestion->AnswerIsNotBlank = (PrintedQuestion->StrAnswers[0] != '\0'); - if (PrintedQuestion->AnswerIsNotBlank) // User has selected T or F + if (PrintedQuestion->StrAnswers[0]) // If user has selected T or F PrintedQuestion->Score = (PrintedQuestion->StrAnswers[0] == Question->Answer.TF) ? 1.0 : // Correct -1.0; // Wrong else @@ -705,8 +731,7 @@ void TstPrn_ComputeChoAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion, } /* The answer is blank? */ - PrintedQuestion->AnswerIsNotBlank = NumAnsGood != 0 || NumAnsBad != 0; - if (PrintedQuestion->AnswerIsNotBlank) + if (NumAnsGood || NumAnsBad) // If user has answered the answer { /* Compute the score */ if (Question->Answer.Type == Tst_ANS_UNIQUE_CHOICE) @@ -748,8 +773,7 @@ void TstPrn_ComputeTxtAnsScore (struct TstPrn_PrintedQuestion *PrintedQuestion, char TextAnsOK[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1]; PrintedQuestion->Score = 0.0; // Default score for blank or wrong answer - PrintedQuestion->AnswerIsNotBlank = (PrintedQuestion->StrAnswers[0] != '\0'); - if (PrintedQuestion->AnswerIsNotBlank) // If user has answered the answer + if (PrintedQuestion->StrAnswers[0]) // If user has answered the answer { /* Filter the user answer */ Str_Copy (TextAnsUsr,PrintedQuestion->StrAnswers, @@ -892,12 +916,12 @@ void TstPrn_ShowGrade (double Grade,double MaxGrade) void TstPrn_WriteAnswersExam (struct UsrData *UsrDat, const struct TstPrn_PrintedQuestion *PrintedQuestion, const struct Tst_Question *Question, - unsigned Visibility) + bool IsVisible[TstVis_NUM_ITEMS_VISIBILITY]) { void (*TstPrn_WriteAnsExam[Tst_NUM_ANS_TYPES]) (struct UsrData *UsrDat, const struct TstPrn_PrintedQuestion *PrintedQuestion, const struct Tst_Question *Question, - unsigned Visibility) = + bool IsVisible[TstVis_NUM_ITEMS_VISIBILITY]) = { [Tst_ANS_INT ] = TstPrn_WriteIntAnsPrint, [Tst_ANS_FLOAT ] = TstPrn_WriteFltAnsPrint, @@ -908,7 +932,8 @@ void TstPrn_WriteAnswersExam (struct UsrData *UsrDat, }; /***** Get correct answer and compute answer score depending on type *****/ - TstPrn_WriteAnsExam[Question->Answer.Type] (UsrDat,PrintedQuestion,Question,Visibility); + TstPrn_WriteAnsExam[Question->Answer.Type] (UsrDat,PrintedQuestion,Question, + IsVisible); } /*****************************************************************************/ @@ -918,7 +943,7 @@ void TstPrn_WriteAnswersExam (struct UsrData *UsrDat, static void TstPrn_WriteIntAnsPrint (struct UsrData *UsrDat, const struct TstPrn_PrintedQuestion *PrintedQuestion, const struct Tst_Question *Question, - unsigned Visibility) + bool IsVisible[TstVis_NUM_ITEMS_VISIBILITY]) { long IntAnswerUsr; @@ -939,7 +964,7 @@ static void TstPrn_WriteIntAnsPrint (struct UsrData *UsrDat, if (sscanf (PrintedQuestion->StrAnswers,"%ld",&IntAnswerUsr) == 1) { HTM_TD_Begin ("class=\"%s CM\"", - TstVis_IsVisibleCorrectAns (Visibility) ? + IsVisible[TstVis_VISIBLE_CORRECT_ANSWER] ? (IntAnswerUsr == Question->Answer.Integer ? "ANS_OK" : "ANS_BAD") : "ANS_0"); @@ -958,8 +983,8 @@ static void TstPrn_WriteIntAnsPrint (struct UsrData *UsrDat, /***** Write the correct answer *****/ HTM_TD_Begin ("class=\"ANS_0 CM\""); - if (TstVis_IsVisibleQstAndAnsTxt (Visibility) && - TstVis_IsVisibleCorrectAns (Visibility)) + if (IsVisible[TstVis_VISIBLE_QST_ANS_TXT] && + IsVisible[TstVis_VISIBLE_CORRECT_ANSWER]) HTM_Long (Question->Answer.Integer); else Ico_PutIconNotVisible (); @@ -977,7 +1002,7 @@ static void TstPrn_WriteIntAnsPrint (struct UsrData *UsrDat, static void TstPrn_WriteFltAnsPrint (struct UsrData *UsrDat, const struct TstPrn_PrintedQuestion *PrintedQuestion, const struct Tst_Question *Question, - unsigned Visibility) + bool IsVisible[TstVis_NUM_ITEMS_VISIBILITY]) { double FloatAnsUsr = 0.0; @@ -999,7 +1024,7 @@ static void TstPrn_WriteFltAnsPrint (struct UsrData *UsrDat, FloatAnsUsr = Str_GetDoubleFromStr (PrintedQuestion->StrAnswers); // A bad formatted floating point answer will interpreted as 0.0 HTM_TD_Begin ("class=\"%s CM\"", - TstVis_IsVisibleCorrectAns (Visibility) ? + IsVisible[TstVis_VISIBLE_CORRECT_ANSWER] ? ((FloatAnsUsr >= Question->Answer.FloatingPoint[0] && FloatAnsUsr <= Question->Answer.FloatingPoint[1]) ? "ANS_OK" : "ANS_BAD") : @@ -1012,8 +1037,8 @@ static void TstPrn_WriteFltAnsPrint (struct UsrData *UsrDat, /***** Write the correct answer *****/ HTM_TD_Begin ("class=\"ANS_0 CM\""); - if (TstVis_IsVisibleQstAndAnsTxt (Visibility) && - TstVis_IsVisibleCorrectAns (Visibility)) + if (IsVisible[TstVis_VISIBLE_QST_ANS_TXT] && + IsVisible[TstVis_VISIBLE_CORRECT_ANSWER]) { HTM_Txt ("["); HTM_Double (Question->Answer.FloatingPoint[0]); @@ -1037,7 +1062,7 @@ static void TstPrn_WriteFltAnsPrint (struct UsrData *UsrDat, static void TstPrn_WriteTF_AnsPrint (struct UsrData *UsrDat, const struct TstPrn_PrintedQuestion *PrintedQuestion, const struct Tst_Question *Question, - unsigned Visibility) + bool IsVisible[TstVis_NUM_ITEMS_VISIBILITY]) { char AnsTFUsr; @@ -1057,7 +1082,7 @@ static void TstPrn_WriteTF_AnsPrint (struct UsrData *UsrDat, /***** Write the user answer *****/ HTM_TD_Begin ("class=\"%s CM\"", - TstVis_IsVisibleCorrectAns (Visibility) ? + IsVisible[TstVis_VISIBLE_CORRECT_ANSWER] ? (AnsTFUsr == Question->Answer.TF ? "ANS_OK" : "ANS_BAD") : "ANS_0"); @@ -1066,8 +1091,8 @@ static void TstPrn_WriteTF_AnsPrint (struct UsrData *UsrDat, /***** Write the correct answer *****/ HTM_TD_Begin ("class=\"ANS_0 CM\""); - if (TstVis_IsVisibleQstAndAnsTxt (Visibility) && - TstVis_IsVisibleCorrectAns (Visibility)) + if (IsVisible[TstVis_VISIBLE_QST_ANS_TXT] && + IsVisible[TstVis_VISIBLE_CORRECT_ANSWER]) Tst_WriteAnsTF (Question->Answer.TF); else Ico_PutIconNotVisible (); @@ -1085,7 +1110,7 @@ static void TstPrn_WriteTF_AnsPrint (struct UsrData *UsrDat, static void TstPrn_WriteChoAnsPrint (struct UsrData *UsrDat, const struct TstPrn_PrintedQuestion *PrintedQuestion, const struct Tst_Question *Question, - unsigned Visibility) + bool IsVisible[TstVis_NUM_ITEMS_VISIBILITY]) { extern const char *Txt_TST_Answer_given_by_the_user; extern const char *Txt_TST_Answer_given_by_the_teachers; @@ -1098,6 +1123,26 @@ static void TstPrn_WriteChoAnsPrint (struct UsrData *UsrDat, char *Str; } Ans; + /***** Change format of answers text and feedback *****/ + for (NumOpt = 0; + NumOpt < Question->Answer.NumOptions; + NumOpt++) + { + /* Convert answer text, that is in HTML, to rigorous HTML */ + if (Question->Answer.Options[NumOpt].Text[0]) + Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, + Question->Answer.Options[NumOpt].Text, + Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false); + + /* Convert answer feedback, that is in HTML, to rigorous HTML */ + if (IsVisible[TstVis_VISIBLE_FEEDBACK_TXT]) + if (Question->Answer.Options[NumOpt].Feedback) + if (Question->Answer.Options[NumOpt].Feedback[0]) + Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, + Question->Answer.Options[NumOpt].Feedback, + Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false); + } + /***** Get indexes for this question from string *****/ TstPrn_GetIndexesFromStr (PrintedQuestion->StrIndexes,Indexes); @@ -1121,7 +1166,7 @@ static void TstPrn_WriteChoAnsPrint (struct UsrData *UsrDat, /* Draw icon depending on user's answer */ if (UsrAnswers[Indexes[NumOpt]] == true) // This answer has been selected by the user { - if (TstVis_IsVisibleCorrectAns (Visibility)) + if (IsVisible[TstVis_VISIBLE_CORRECT_ANSWER]) { if (Question->Answer.Options[Indexes[NumOpt]].Correct) { @@ -1149,7 +1194,7 @@ static void TstPrn_WriteChoAnsPrint (struct UsrData *UsrDat, HTM_TD_Empty (1); /* Draw icon that indicates whether the answer is correct */ - if (TstVis_IsVisibleCorrectAns (Visibility)) + if (IsVisible[TstVis_VISIBLE_CORRECT_ANSWER]) { if (Question->Answer.Options[Indexes[NumOpt]].Correct) { @@ -1177,7 +1222,7 @@ static void TstPrn_WriteChoAnsPrint (struct UsrData *UsrDat, HTM_TD_Begin ("class=\"LT\""); HTM_DIV_Begin ("class=\"ANS_TXT\""); - if (TstVis_IsVisibleQstAndAnsTxt (Visibility)) + if (IsVisible[TstVis_VISIBLE_QST_ANS_TXT]) { HTM_Txt (Question->Answer.Options[Indexes[NumOpt]].Text); Med_ShowMedia (&Question->Answer.Options[Indexes[NumOpt]].Media, @@ -1188,7 +1233,7 @@ static void TstPrn_WriteChoAnsPrint (struct UsrData *UsrDat, Ico_PutIconNotVisible (); HTM_DIV_End (); - if (TstVis_IsVisibleCorrectAns (Visibility)) + if (IsVisible[TstVis_VISIBLE_CORRECT_ANSWER]) if (Question->Answer.Options[Indexes[NumOpt]].Feedback) if (Question->Answer.Options[Indexes[NumOpt]].Feedback[0]) { @@ -1213,30 +1258,31 @@ static void TstPrn_WriteChoAnsPrint (struct UsrData *UsrDat, static void TstPrn_WriteTxtAnsPrint (struct UsrData *UsrDat, const struct TstPrn_PrintedQuestion *PrintedQuestion, const struct Tst_Question *Question, - unsigned Visibility) + bool IsVisible[TstVis_NUM_ITEMS_VISIBILITY]) { unsigned NumOpt; char TextAnsUsr[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1]; char TextAnsOK[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1]; bool Correct = false; - /***** Get text and correctness of answers for this question from database (one row per answer) *****/ + /***** Change format of answers text and feedback *****/ for (NumOpt = 0; NumOpt < Question->Answer.NumOptions; NumOpt++) { - /***** Convert answer text, that is in HTML, to rigorous HTML ******/ + /* Convert answer text, that is in HTML, to rigorous HTML */ if (Question->Answer.Options[NumOpt].Text[0]) Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, Question->Answer.Options[NumOpt].Text, Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false); - /***** Convert answer feedback, that is in HTML, to rigorous HTML ******/ - if (TstVis_IsVisibleFeedbackTxt (Visibility)) - if (Question->Answer.Options[NumOpt].Feedback[0]) - Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, - Question->Answer.Options[NumOpt].Feedback, - Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false); + /* Convert answer feedback, that is in HTML, to rigorous HTML */ + if (IsVisible[TstVis_VISIBLE_FEEDBACK_TXT]) + if (Question->Answer.Options[NumOpt].Feedback) + if (Question->Answer.Options[NumOpt].Feedback[0]) + Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, + Question->Answer.Options[NumOpt].Feedback, + Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false); } /***** Header with the title of each column *****/ @@ -1277,10 +1323,9 @@ static void TstPrn_WriteTxtAnsPrint (struct UsrData *UsrDat, } } HTM_TD_Begin ("class=\"%s CT\"", - TstVis_IsVisibleCorrectAns (Visibility) ? - (Correct ? "ANS_OK" : - "ANS_BAD") : - "ANS_0"); + IsVisible[TstVis_VISIBLE_CORRECT_ANSWER] ? (Correct ? "ANS_OK" : + "ANS_BAD") : + "ANS_0"); HTM_Txt (PrintedQuestion->StrAnswers); } else // If user has omitted the answer @@ -1288,8 +1333,8 @@ static void TstPrn_WriteTxtAnsPrint (struct UsrData *UsrDat, HTM_TD_End (); /***** Write the correct answers *****/ - if (TstVis_IsVisibleQstAndAnsTxt (Visibility) && - TstVis_IsVisibleCorrectAns (Visibility)) + if (IsVisible[TstVis_VISIBLE_QST_ANS_TXT] && + IsVisible[TstVis_VISIBLE_CORRECT_ANSWER]) { HTM_TD_Begin ("class=\"CT\""); HTM_TABLE_BeginPadding (2); @@ -1312,7 +1357,7 @@ static void TstPrn_WriteTxtAnsPrint (struct UsrData *UsrDat, HTM_Txt (Question->Answer.Options[NumOpt].Text); HTM_DIV_End (); - if (TstVis_IsVisibleFeedbackTxt (Visibility)) + if (IsVisible[TstVis_VISIBLE_FEEDBACK_TXT]) if (Question->Answer.Options[NumOpt].Feedback) if (Question->Answer.Options[NumOpt].Feedback[0]) { @@ -1735,7 +1780,7 @@ static void TstPrn_ShowUsrPrints (struct UsrData *UsrDat) if (ICanView.Exam) { Frm_StartForm (Gbl.Action.Act == ActSeeMyTstResCrs ? ActSeeOneTstResMe : - ActSeeOneTstResOth); + ActSeeOneTstResOth); TstPrn_PutParamPrnCod (Print.PrnCod); Ico_PutIconLink ("tasks.svg",Txt_View_test); Frm_EndForm (); @@ -2079,7 +2124,7 @@ void TstPrn_ShowOnePrint (void) /***** Write answers and solutions *****/ TstPrn_ShowPrintAnswers (&Gbl.Usrs.Other.UsrDat,&Print, - TstCfg_GetConfigVisibility ()); + TstCfg_GetConfigVisibility ()); /***** End table *****/ HTM_TABLE_End (); @@ -2138,8 +2183,8 @@ static void TstPrn_ShowTagsPresentInAPrint (long ResCod) /*****************************************************************************/ void TstPrn_ShowPrintAnswers (struct UsrData *UsrDat, - struct TstPrn_Print *Print, - unsigned Visibility) + struct TstPrn_Print *Print, + unsigned Visibility) { unsigned NumQst; struct Tst_Question Question; diff --git a/swad_test_print.h b/swad_test_print.h index 4fbb421f..c51a5953 100644 --- a/swad_test_print.h +++ b/swad_test_print.h @@ -29,6 +29,7 @@ #include "swad_test_config.h" #include "swad_test_type.h" +#include "swad_test_visibility.h" #include "swad_user.h" /*****************************************************************************/ @@ -46,7 +47,6 @@ struct TstPrn_PrintedQuestion char StrIndexes[Tst_MAX_BYTES_INDEXES_ONE_QST + 1]; // 0 1 2 3, 3 0 2 1, etc. char StrAnswers[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1]; // Answers selected by user double Score; // Question score - bool AnswerIsNotBlank; // Answer not blank? }; struct TstPrn_Print @@ -103,7 +103,7 @@ void TstPrn_ShowGrade (double Grade,double MaxGrade); void TstPrn_WriteAnswersExam (struct UsrData *UsrDat, const struct TstPrn_PrintedQuestion *PrintedQuestion, const struct Tst_Question *Question, - unsigned Visibility); + bool IsVisible[TstVis_NUM_ITEMS_VISIBILITY]); void TstPrn_SelUsrsToViewUsrsPrints (void); void TstPrn_SelDatesToSeeMyPrints (void); diff --git a/swad_text_action.c b/swad_text_action.c index 9b999eab..bf54c1e6 100644 --- a/swad_text_action.c +++ b/swad_text_action.c @@ -10904,9 +10904,9 @@ const char *Txt_Actions[Act_NUM_ACTIONS] = #elif L==2 // de "" // Need Übersetzung #elif L==3 // en - "See exam print" + "Start / resume exam" #elif L==4 // es - "Ver impresión de examen" + "Comenzar / reanudar examen" #elif L==5 // fr "" // Besoin de traduction #elif L==6 // gn @@ -10938,6 +10938,27 @@ const char *Txt_Actions[Act_NUM_ACTIONS] = "" // Potrzebujesz tlumaczenie #elif L==9 // pt "" // Precisa de tradução +#endif + , + [ActEndExaPrn] = +#if L==1 // ca + "" // Necessita traducció +#elif L==2 // de + "" // Need Übersetzung +#elif L==3 // en + "End exam" +#elif L==4 // es + "Finalizar examen" +#elif L==5 // fr + "" // Besoin de traduction +#elif L==6 // gn + "" // Okoteve traducción +#elif L==7 // it + "" // Bisogno di traduzione +#elif L==8 // pl + "" // Potrzebujesz tlumaczenie +#elif L==9 // pt + "" // Precisa de tradução #endif , [ActSeeMyExaResCrs] =