swad-core/swad_test.c

8337 lines
290 KiB
C
Raw Normal View History

2014-12-01 23:55:08 +01:00
// swad_test.c: self-assessment tests
/*
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.
2020-01-01 14:53:57 +01:00
Copyright (C) 1999-2020 Antonio Ca<EFBFBD>as Vargas
2014-12-01 23:55:08 +01:00
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*****************************************************************************/
/*********************************** Headers *********************************/
/*****************************************************************************/
2019-11-01 22:53:39 +01:00
#define _GNU_SOURCE // For asprintf
2014-12-01 23:55:08 +01:00
#include <limits.h> // For UINT_MAX
#include <linux/limits.h> // For PATH_MAX
#include <mysql/mysql.h> // To access MySQL databases
2015-10-16 02:24:29 +02:00
#include <stdbool.h> // For boolean type
2019-12-29 12:39:00 +01:00
#include <stddef.h> // For NULL
2019-12-30 22:32:06 +01:00
#include <stdio.h> // For asprintf
2014-12-01 23:55:08 +01:00
#include <stdlib.h> // For exit, system, malloc, free, etc
#include <string.h> // For string functions
#include <sys/stat.h> // For mkdir
#include <sys/types.h> // For mkdir
#include "swad_action.h"
2017-06-10 21:38:10 +02:00
#include "swad_box.h"
2014-12-01 23:55:08 +01:00
#include "swad_database.h"
2018-11-09 20:47:39 +01:00
#include "swad_form.h"
2014-12-01 23:55:08 +01:00
#include "swad_global.h"
2019-10-23 19:05:05 +02:00
#include "swad_HTML.h"
2014-12-01 23:55:08 +01:00
#include "swad_ID.h"
2018-12-08 16:43:13 +01:00
#include "swad_language.h"
2019-09-14 12:59:34 +02:00
#include "swad_match.h"
2019-03-02 21:49:11 +01:00
#include "swad_media.h"
2014-12-01 23:55:08 +01:00
#include "swad_parameter.h"
#include "swad_theme.h"
#include "swad_test.h"
2020-03-21 15:41:25 +01:00
#include "swad_test_config.h"
2014-12-01 23:55:08 +01:00
#include "swad_test_import.h"
2020-02-18 09:19:33 +01:00
#include "swad_test_visibility.h"
2014-12-01 23:55:08 +01:00
#include "swad_user.h"
#include "swad_xml.h"
/*****************************************************************************/
/***************************** Public constants ******************************/
/*****************************************************************************/
2017-03-08 03:48:23 +01:00
// strings are limited to Tst_MAX_BYTES_ANSWER_TYPE characters
2014-12-01 23:55:08 +01:00
const char *Tst_StrAnswerTypesXML[Tst_NUM_ANS_TYPES] =
{
2019-11-22 01:04:03 +01:00
[Tst_ANS_INT ] = "int",
[Tst_ANS_FLOAT ] = "float",
[Tst_ANS_TRUE_FALSE ] = "TF",
[Tst_ANS_UNIQUE_CHOICE ] = "uniqueChoice",
[Tst_ANS_MULTIPLE_CHOICE] = "multipleChoice",
[Tst_ANS_TEXT ] = "text",
2014-12-01 23:55:08 +01:00
};
/*****************************************************************************/
/**************************** Private constants ******************************/
/*****************************************************************************/
2017-01-28 15:58:46 +01:00
#define Tst_MAX_BYTES_TAGS_LIST (16 * 1024)
2014-12-01 23:55:08 +01:00
#define Tst_MAX_BYTES_FLOAT_ANSWER 30 // Maximum length of the strings that store an floating point answer
2019-12-15 20:02:34 +01:00
static const char *Tst_StrAnswerTypesDB[Tst_NUM_ANS_TYPES] =
2014-12-01 23:55:08 +01:00
{
2019-12-15 20:02:34 +01:00
[Tst_ANS_INT ] = "int",
[Tst_ANS_FLOAT ] = "float",
[Tst_ANS_TRUE_FALSE ] = "true_false",
[Tst_ANS_UNIQUE_CHOICE ] = "unique_choice",
[Tst_ANS_MULTIPLE_CHOICE] = "multiple_choice",
[Tst_ANS_TEXT ] = "text",
2014-12-01 23:55:08 +01:00
};
2016-04-08 16:37:59 +02:00
// Test images will be saved with:
// - maximum width of Tst_IMAGE_SAVED_MAX_HEIGHT
// - maximum height of Tst_IMAGE_SAVED_MAX_HEIGHT
2016-04-01 13:37:49 +02:00
// - maintaining the original aspect ratio (aspect ratio recommended: 3:2)
2016-04-08 16:37:59 +02:00
#define Tst_IMAGE_SAVED_MAX_WIDTH 768
2020-02-13 22:33:31 +01:00
#define Tst_IMAGE_SAVED_MAX_HEIGHT 768
#define Tst_IMAGE_SAVED_QUALITY 90 // 1 to 100
2016-04-01 13:37:49 +02:00
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2019-11-21 16:47:07 +01:00
/******************************* Private types *******************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
#define Tst_NUM_STATUS 2
typedef enum
{
Tst_STATUS_SHOWN_BUT_NOT_ASSESSED = 0,
Tst_STATUS_ASSESSED = 1,
Tst_STATUS_ERROR = 2,
} Tst_Status_t;
/*****************************************************************************/
/************** External global variables from others modules ****************/
/*****************************************************************************/
extern struct Globals Gbl;
/*****************************************************************************/
2019-11-21 16:47:07 +01:00
/************************* Private global variables **************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
/*****************************************************************************/
2019-11-21 16:47:07 +01:00
/***************************** Private prototypes ****************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-03-26 21:39:44 +01:00
static void Tst_TstConstructor (struct Tst_Test *Test);
static void Tst_TstDestructor (struct Tst_Test *Test);
2020-03-21 15:41:25 +01:00
static void Tst_ResetTags (struct Tst_Tags *Tags);
2020-03-26 21:39:44 +01:00
static void Tst_FreeTagsList (struct Tst_Tags *Tags);
2020-03-21 15:41:25 +01:00
2020-03-24 01:50:39 +01:00
static void Tst_ShowFormRequestTest (struct Tst_Test *Test);
2020-03-21 15:41:25 +01:00
2020-03-30 16:40:12 +02:00
static void Tst_PutCheckBoxAllowTeachers (bool AllowTeachers);
2020-03-23 21:40:17 +01:00
static void Tst_GetQuestionsAndAnswersFromForm (struct TsR_Result *Result);
2020-04-01 03:11:05 +02:00
static void Tst_ComputeAndStoreResultScore (struct TsR_Result *Result,
bool UpdateQstScore);
static void Tst_ComputeAnswerScore (struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question);
static void Tst_ComputeIntAnsScore (struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question);
static void Tst_GetCorrectIntAnswerFromDB (struct Tst_Question *Question);
static void Tst_ComputeFloatAnsScore (struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question);
static void Tst_GetCorrectFloatAnswerFromDB (struct Tst_Question *Question);
static void Tst_ComputeTFAnsScore (struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question);
static void Tst_GetCorrectTFAnswerFromDB (struct Tst_Question *Question);
static void Tst_GetCorrectChoiceAnswerFromDB (struct Tst_Question *Question);
static void Tst_GetAnswersFromStr (const char StrAnswersOneQst[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1],
bool UsrAnswers[Tst_MAX_OPTIONS_PER_QUESTION]);
static void Tst_ComputeScoreQst (struct TsR_Result *Result,
unsigned NumQst,
const struct Tst_Question *Question,
unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION], // Indexes of all answers of this question
bool UsrAnswers[Tst_MAX_OPTIONS_PER_QUESTION]);
static void Tst_ComputeTextAnsScore (struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question);
static void Tst_GetCorrectTextAnswerFromDB (struct Tst_Question *Question);
2014-12-01 23:55:08 +01:00
static bool Tst_CheckIfNextTstAllowed (void);
static void Tst_SetTstStatus (unsigned NumTst,Tst_Status_t TstStatus);
static Tst_Status_t Tst_GetTstStatus (unsigned NumTst);
static unsigned Tst_GetNumAccessesTst (void);
2020-04-01 03:11:05 +02:00
static void Tst_ShowTestQuestionsWhenSeeing (struct TsR_Result *Result);
static void Tst_ShowTestResultAfterAssess (struct TsR_Result *Result);
static void Tst_WriteQstAndAnsSeeing (struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question,
MYSQL_ROW row);
2020-03-23 17:14:41 +01:00
2020-03-17 14:47:58 +01:00
static void Tst_PutFormToEditQstMedia (const struct Media *Media,int NumMediaInForm,
2016-04-05 10:47:36 +02:00
bool OptionsDisabled);
2020-04-01 03:11:05 +02:00
static void Tst_UpdateQstScore (const struct TsR_Result *Result,unsigned NumQst);
2014-12-01 23:55:08 +01:00
static void Tst_UpdateMyNumAccessTst (unsigned NumAccessesTst);
2020-03-22 01:15:27 +01:00
static void Tst_UpdateLastAccTst (unsigned NumQsts);
2020-03-21 15:41:25 +01:00
2020-03-24 01:50:39 +01:00
static void Tst_ShowFormRequestEditTests (struct Tst_Test *Test);
2020-03-25 01:36:22 +01:00
static void Tst_ShowFormRequestSelectTestsForGame (struct Tst_Test *Test);
2016-11-07 10:35:36 +01:00
static bool Tst_CheckIfICanEditTests (void);
2020-03-26 21:39:44 +01:00
static void Tst_PutIconsTests (void *TestPtr);
2017-09-07 12:00:01 +02:00
static void Tst_PutButtonToAddQuestion (void);
2014-12-01 23:55:08 +01:00
static long Tst_GetParamTagCode (void);
static bool Tst_CheckIfCurrentCrsHasTestTags (void);
2020-03-24 01:50:39 +01:00
static unsigned Tst_GetAllTagsFromCurrentCrs (MYSQL_RES **mysql_res);
static unsigned Tst_GetEnabledTagsFromThisCrs (MYSQL_RES **mysql_res);
2020-03-21 15:41:25 +01:00
static void Tst_ShowFormSelTags (const struct Tst_Tags *Tags,
2020-03-24 01:50:39 +01:00
MYSQL_RES *mysql_res,
2019-12-09 12:56:21 +01:00
bool ShowOnlyEnabledTags);
2014-12-01 23:55:08 +01:00
static void Tst_ShowFormEditTags (void);
static void Tst_PutIconEnable (long TagCod,const char *TagTxt);
static void Tst_PutIconDisable (long TagCod,const char *TagTxt);
static void Tst_ShowFormConfigTst (void);
2020-03-21 15:41:25 +01:00
2016-12-26 15:17:30 +01:00
static void Tst_PutInputFieldNumQst (const char *Field,const char *Label,
unsigned Value);
2020-03-21 15:41:25 +01:00
2020-03-21 22:18:24 +01:00
static void Tst_ShowFormAnswerTypes (const struct Tst_AnswerTypes *AnswerTypes);
2020-03-24 01:50:39 +01:00
static void Tst_GetQuestions (struct Tst_Test *Test,MYSQL_RES **mysql_res);
2020-04-01 03:11:05 +02:00
static void Tst_GetQuestionsForNewTestFromDB (struct Tst_Test *Test,
struct TsR_Result *Result);
static void Tst_GetChoiceAnsSeeing (struct TsR_Result *Result,
unsigned NumQst,
bool Shuffle);
2020-03-26 21:39:44 +01:00
static void Tst_ListOneQstToEdit (struct Tst_Test *Test);
static void Tst_ListOneOrMoreQuestionsForEdition (struct Tst_Test *Test,
2017-09-01 00:52:19 +02:00
MYSQL_RES *mysql_res);
2020-03-24 01:50:39 +01:00
static void Tst_WriteHeadingRowQuestionsForEdition (const struct Tst_Test *Test);
2020-03-30 18:54:17 +02:00
static void Tst_WriteQuestionListing (struct Tst_Test *Test,unsigned NumQst);
2020-03-24 01:50:39 +01:00
static void Tst_ListOneOrMoreQuestionsForSelection (unsigned NumQsts,
2017-09-01 00:52:19 +02:00
MYSQL_RES *mysql_res);
2020-03-27 21:54:13 +01:00
static void Tst_WriteQuestionRowForSelection (unsigned NumQst,
struct Tst_Question *Question);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
static void Tst_WriteAnswersSeeing (struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question);
2020-03-30 18:54:17 +02:00
static void Tst_WriteAnswersResult (struct UsrData *UsrDat,
const struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question,
2020-04-01 03:11:05 +02:00
unsigned Visibility);
static void Tst_WriteIntAnsListing (const struct Tst_Question *Question,
MYSQL_RES *mysql_res);
static void Tst_WriteIntAnsSeeing (const struct TsR_Result *Result,
unsigned NumQst);
static void Tst_WriteIntAnsResult (struct UsrData *UsrDat,
const struct TsR_Result *Result,
unsigned NumQst,
const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility);
static void Tst_WriteFloatAnsEdit (const struct Tst_Question *Question,
MYSQL_RES *mysql_res);
static void Tst_WriteFloatAnsSeeing (const struct TsR_Result *Result,
unsigned NumQst);
static void Tst_WriteFloatAnsResult (struct UsrData *UsrDat,
const struct TsR_Result *Result,
unsigned NumQst,
const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility);
2020-03-30 18:54:17 +02:00
static void Tst_WriteTFAnsListing (const struct Tst_Question *Question,
MYSQL_RES *mysql_res);
2020-04-01 03:11:05 +02:00
static void Tst_WriteTFAnsSeeing (const struct TsR_Result *Result,
unsigned NumQst);
2020-03-30 18:54:17 +02:00
static void Tst_WriteTFAnsResult (struct UsrData *UsrDat,
const struct TsR_Result *Result,
unsigned NumQst,
const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
2020-04-01 03:11:05 +02:00
unsigned Visibility);
2020-03-30 18:54:17 +02:00
static void Tst_WriteChoiceAnsListing (struct Tst_Question *Question,
MYSQL_RES *mysql_res);
2020-04-01 03:11:05 +02:00
static void Tst_WriteChoiceAnsSeeing (const struct TsR_Result *Result,
unsigned NumQst,
2020-03-30 18:54:17 +02:00
struct Tst_Question *Question,
2020-04-01 03:11:05 +02:00
MYSQL_RES *mysql_res);
2020-03-30 18:54:17 +02:00
static void Tst_WriteChoiceAnsResult (struct UsrData *UsrDat,
2020-03-23 21:40:17 +01:00
const struct TsR_Result *Result,
2020-03-18 01:57:08 +01:00
unsigned NumQst,
2020-03-30 18:54:17 +02:00
struct Tst_Question *Question,
2020-03-18 01:57:08 +01:00
MYSQL_RES *mysql_res,
2020-04-01 03:11:05 +02:00
unsigned Visibility);
2020-03-25 01:36:22 +01:00
static void Tst_GetChoiceAns (struct Tst_Question *Question,MYSQL_RES *mysql_res);
2017-09-04 17:03:49 +02:00
2020-04-01 03:11:05 +02:00
static void Tst_WriteTextAnsSeeing (const struct TsR_Result *Result,
unsigned NumQst);
2020-03-30 18:54:17 +02:00
static void Tst_WriteTextAnsResult (struct UsrData *UsrDat,
const struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question,
MYSQL_RES *mysql_res,
2020-04-01 03:11:05 +02:00
unsigned Visibility);
2017-09-04 17:03:49 +02:00
2018-12-09 13:11:20 +01:00
static void Tst_WriteHeadUserCorrect (struct UsrData *UsrDat);
2014-12-01 23:55:08 +01:00
static void Tst_WriteScoreStart (unsigned ColSpan);
static void Tst_WriteScoreEnd (void);
static void Tst_WriteParamQstCod (unsigned NumQst,long QstCod);
2020-03-24 00:58:42 +01:00
static bool Tst_GetParamsTst (struct Tst_Test *Test,
2020-03-21 15:41:25 +01:00
Tst_ActionToDoWithQuestions_t ActionToDoWithQuestions);
2014-12-01 23:55:08 +01:00
static unsigned Tst_GetAndCheckParamNumTst (void);
2020-03-22 01:15:27 +01:00
static unsigned Tst_GetParamNumQsts (void);
2020-03-21 15:41:25 +01:00
static unsigned Tst_CountNumTagsInList (const struct Tst_Tags *Tags);
2020-03-21 22:18:24 +01:00
static int Tst_CountNumAnswerTypesInList (const struct Tst_AnswerTypes *AnswerTypes);
2020-03-21 15:41:25 +01:00
2020-03-27 14:56:54 +01:00
static void Tst_PutFormEditOneQst (struct Tst_Question *Question,
2020-03-17 00:35:11 +01:00
char Stem[Cns_MAX_BYTES_TEXT + 1],
2017-01-17 03:10:43 +01:00
char Feedback[Cns_MAX_BYTES_TEXT + 1]);
2016-12-26 15:17:30 +01:00
static void Tst_PutFloatInputField (const char *Label,const char *Field,
2020-03-19 20:57:54 +01:00
const struct Tst_Question *Question,
unsigned Index);
static void Tst_PutTFInputField (const struct Tst_Question *Question,
2020-03-18 01:57:08 +01:00
const char *Label,char Value);
2016-04-03 01:24:20 +02:00
2020-03-19 20:57:54 +01:00
static void Tst_FreeTextChoiceAnswers (struct Tst_Question *Question);
static void Tst_FreeTextChoiceAnswer (struct Tst_Question *Question,unsigned NumOpt);
2016-04-06 19:26:09 +02:00
2020-03-19 20:57:54 +01:00
static void Tst_ResetMediaOfQuestion (struct Tst_Question *Question);
static void Tst_FreeMediaOfQuestion (struct Tst_Question *Question);
2016-04-04 21:51:21 +02:00
2020-04-01 03:11:05 +02:00
static Tst_AnswerType_t Tst_GetQstAnswerType (long QstCod);
2020-03-25 01:36:22 +01:00
static void Tst_GetQstDataFromDB (struct Tst_Question *Question,
2020-03-17 00:35:11 +01:00
char Stem[Cns_MAX_BYTES_TEXT + 1],
2017-01-17 03:10:43 +01:00
char Feedback[Cns_MAX_BYTES_TEXT + 1]);
2020-03-17 00:35:11 +01:00
static long Tst_GetMedCodFromDB (long CrsCod,long QstCod,int NumOpt);
static void Tst_GetMediaFromDB (long CrsCod,long QstCod,int NumOpt,
struct Media *Media);
2016-04-03 01:24:20 +02:00
2014-12-01 23:55:08 +01:00
static Tst_AnswerType_t Tst_ConvertFromUnsignedStrToAnsTyp (const char *UnsignedStr);
2020-03-25 01:36:22 +01:00
static void Tst_GetQstFromForm (struct Tst_Question *Question,
2020-03-17 14:47:58 +01:00
char *Stem,char *Feedback);
2020-03-25 01:36:22 +01:00
static void Tst_MoveMediaToDefinitiveDirectories (struct Tst_Question *Question);
2016-04-04 21:51:21 +02:00
2014-12-01 23:55:08 +01:00
static long Tst_GetTagCodFromTagTxt (const char *TagTxt);
static long Tst_CreateNewTag (long CrsCod,const char *TagTxt);
static void Tst_EnableOrDisableTag (long TagCod,bool TagHidden);
2016-04-05 10:05:52 +02:00
2020-03-26 21:39:44 +01:00
static void Tst_PutParamsRemoveSelectedQsts (void *TestPtr);
2020-03-26 15:22:51 +01:00
static void Tst_PutIconToRemoveOneQst (void *QstCodPtr);
static void Tst_PutParamsRemoveOnlyThisQst (void *QstCodPtr);
2020-03-26 21:39:44 +01:00
static void Tst_PutParamsRemoveOneQstWhileEditing (void *TestPtr);
2020-03-17 00:35:11 +01:00
static void Tst_RemoveOneQstFromDB (long CrsCod,long QstCod);
2016-04-05 10:05:52 +02:00
2016-04-06 19:26:09 +02:00
static long Tst_GetQstCod (void);
2014-12-01 23:55:08 +01:00
2020-03-25 01:36:22 +01:00
static void Tst_InsertOrUpdateQstIntoDB (struct Tst_Question *Question);
static void Tst_InsertTagsIntoDB (const struct Tst_Question *Question);
static void Tst_InsertAnswersIntoDB (struct Tst_Question *Question);
2014-12-01 23:55:08 +01:00
2020-03-17 00:35:11 +01:00
static void Tst_RemAnsFromQst (long QstCod);
static void Tst_RemTagsFromQst (long QstCod);
static void Tst_RemoveUnusedTagsFromCrs (long CrsCod);
2016-04-04 21:51:21 +02:00
2019-03-17 01:38:10 +01:00
static void Tst_RemoveAllMedFilesFromStemOfAllQstsInCrs (long CrsCod);
2019-03-19 11:20:29 +01:00
static void Tst_RemoveMediaFromAllAnsOfQst (long CrsCod,long QstCod);
2019-03-17 01:38:10 +01:00
static void Tst_RemoveAllMedFilesFromAnsOfAllQstsInCrs (long CrsCod);
2016-04-04 21:51:21 +02:00
2019-04-03 20:57:04 +02:00
static unsigned Tst_GetNumTstQuestions (Hie_Level_t Scope,Tst_AnswerType_t AnsType,struct Tst_Stats *Stats);
static unsigned Tst_GetNumCoursesWithTstQuestions (Hie_Level_t Scope,Tst_AnswerType_t AnsType);
static unsigned Tst_GetNumCoursesWithPluggableTstQuestions (Hie_Level_t Scope,Tst_AnswerType_t AnsType);
2014-12-01 23:55:08 +01:00
2020-03-21 15:41:25 +01:00
/*****************************************************************************/
/********************* Request a self-assessment test ************************/
/*****************************************************************************/
void Tst_RequestTest (void)
{
2020-03-24 00:58:42 +01:00
struct Tst_Test Test;
2020-03-21 15:41:25 +01:00
2020-03-26 21:39:44 +01:00
/***** Create test *****/
Tst_TstConstructor (&Test);
2020-03-21 22:18:24 +01:00
2020-03-21 15:41:25 +01:00
/***** Show form to generate a self-assessment test *****/
2020-03-24 00:58:42 +01:00
Tst_ShowFormRequestTest (&Test);
2020-03-26 21:39:44 +01:00
/***** Destroy test *****/
Tst_TstDestructor (&Test);
2020-03-21 15:41:25 +01:00
}
/*****************************************************************************/
2020-03-26 21:39:44 +01:00
/***************************** Test constructor ******************************/
2020-03-21 15:41:25 +01:00
/*****************************************************************************/
2020-03-26 21:39:44 +01:00
static void Tst_TstConstructor (struct Tst_Test *Test)
2020-03-21 15:41:25 +01:00
{
2020-03-25 01:36:22 +01:00
/***** Reset tags *****/
Tst_ResetTags (&Test->Tags);
/***** Reset answer types *****/
Test->AnswerTypes.All = false;
Test->AnswerTypes.List[0] = '\0';
/***** Reset selected order *****/
Test->SelectedOrder = Tst_DEFAULT_ORDER;
2020-03-26 21:39:44 +01:00
/***** Question constructor *****/
Tst_QstConstructor (&Test->Question);
}
/*****************************************************************************/
/****************************** Test destructor ******************************/
/*****************************************************************************/
static void Tst_TstDestructor (struct Tst_Test *Test)
{
/***** Question destructor *****/
Tst_QstDestructor (&Test->Question);
/***** Free tag list *****/
Tst_FreeTagsList (&Test->Tags);
2020-03-21 15:41:25 +01:00
}
2020-03-21 22:18:24 +01:00
/*****************************************************************************/
2020-03-25 01:36:22 +01:00
/********************************* Reset tags ********************************/
2020-03-21 22:18:24 +01:00
/*****************************************************************************/
2020-03-25 01:36:22 +01:00
static void Tst_ResetTags (struct Tst_Tags *Tags)
2020-03-21 22:18:24 +01:00
{
2020-03-25 01:36:22 +01:00
Tags->Num = 0;
Tags->All = false;
Tags->List = NULL;
2020-03-21 22:18:24 +01:00
}
2020-03-26 21:39:44 +01:00
/*****************************************************************************/
/**************** Free memory allocated for the list of tags *****************/
/*****************************************************************************/
static void Tst_FreeTagsList (struct Tst_Tags *Tags)
{
if (Tags->List)
{
free (Tags->List);
Tst_ResetTags (Tags);
}
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
/*************** Show form to generate a self-assessment test ****************/
/*****************************************************************************/
2020-03-24 01:50:39 +01:00
static void Tst_ShowFormRequestTest (struct Tst_Test *Test)
2014-12-01 23:55:08 +01:00
{
2016-11-13 20:18:49 +01:00
extern const char *Hlp_ASSESSMENT_Tests;
2016-04-07 13:06:06 +02:00
extern const char *Txt_Take_a_test;
2014-12-01 23:55:08 +01:00
extern const char *Txt_No_of_questions;
2016-11-21 13:15:08 +01:00
extern const char *Txt_Generate_test;
2016-03-21 02:13:19 +01:00
extern const char *Txt_No_test_questions;
2014-12-01 23:55:08 +01:00
MYSQL_RES *mysql_res;
/***** Read test configuration from database *****/
2020-03-21 15:41:25 +01:00
TstCfg_GetConfigFromDB ();
2014-12-01 23:55:08 +01:00
2019-10-26 02:19:42 +02:00
/***** Begin box *****/
2020-03-26 02:54:30 +01:00
Box_BoxBegin (NULL,Txt_Take_a_test,
2020-03-27 14:56:54 +01:00
Tst_PutIconsTests,Test,
2017-06-12 15:03:29 +02:00
Hlp_ASSESSMENT_Tests,Box_NOT_CLOSABLE);
2016-03-20 23:38:22 +01:00
2014-12-01 23:55:08 +01:00
/***** Get tags *****/
2020-03-24 01:50:39 +01:00
if ((Test->Tags.Num = Tst_GetEnabledTagsFromThisCrs (&mysql_res)) != 0)
2014-12-01 23:55:08 +01:00
{
/***** Check if minimum date-time of next access to test is older than now *****/
if (Tst_CheckIfNextTstAllowed ())
{
2018-11-09 20:47:39 +01:00
Frm_StartForm (ActSeeTst);
2017-05-01 21:17:38 +02:00
2019-10-23 19:05:05 +02:00
HTM_TABLE_BeginPadding (2);
2014-12-01 23:55:08 +01:00
/***** Selection of tags *****/
2020-03-24 01:50:39 +01:00
Tst_ShowFormSelTags (&Test->Tags,mysql_res,true);
2014-12-01 23:55:08 +01:00
/***** Selection of types of answers *****/
2020-03-24 00:58:42 +01:00
Tst_ShowFormAnswerTypes (&Test->AnswerTypes);
2014-12-01 23:55:08 +01:00
/***** Number of questions to generate ****/
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
2019-10-07 21:15:14 +02:00
2019-12-27 21:10:39 +01:00
/* Label */
2019-12-27 15:45:19 +01:00
Frm_LabelColumn ("RT","NumQst",Txt_No_of_questions);
2019-10-07 21:15:14 +02:00
2019-12-27 21:10:39 +01:00
/* Data */
2019-12-26 22:29:04 +01:00
HTM_TD_Begin ("class=\"LT\"");
2019-11-27 01:01:27 +01:00
HTM_INPUT_LONG ("NumQst",
2020-03-21 15:41:25 +01:00
(long) TstCfg_GetConfigMin (),
(long) TstCfg_GetConfigMax (),
(long) TstCfg_GetConfigDef (),
TstCfg_GetConfigMin () == TstCfg_GetConfigMax (),
"id=\"NumQst\"");
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TR_End ();
2017-05-01 21:17:38 +02:00
2019-10-23 19:05:05 +02:00
HTM_TABLE_End ();
2014-12-01 23:55:08 +01:00
/***** Send button *****/
2017-06-11 19:02:40 +02:00
Btn_PutConfirmButton (Txt_Generate_test);
2018-11-09 20:47:39 +01:00
Frm_EndForm ();
2014-12-01 23:55:08 +01:00
}
}
else
{
2016-03-21 02:13:19 +01:00
/***** Warning message *****/
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_INFO,Txt_No_test_questions);
2016-03-21 02:13:19 +01:00
/***** Button to create a new question *****/
2016-11-07 10:35:36 +01:00
if (Tst_CheckIfICanEditTests ())
2016-05-30 18:26:29 +02:00
Tst_PutButtonToAddQuestion ();
2014-12-01 23:55:08 +01:00
}
2017-06-12 14:16:33 +02:00
/***** End box *****/
2019-10-25 22:48:34 +02:00
Box_BoxEnd ();
2016-03-20 23:38:22 +01:00
2014-12-01 23:55:08 +01:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
/********************** Generate self-assessment test ************************/
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
void Tst_ShowNewTest (void)
2014-12-01 23:55:08 +01:00
{
2016-11-13 20:18:49 +01:00
extern const char *Hlp_ASSESSMENT_Tests;
2014-12-01 23:55:08 +01:00
extern const char *Txt_No_questions_found_matching_your_search_criteria;
2016-03-21 01:28:16 +01:00
extern const char *Txt_Test;
2016-11-21 13:15:08 +01:00
extern const char *Txt_Done_assess_test;
2020-03-24 00:58:42 +01:00
struct Tst_Test Test;
2020-04-01 03:11:05 +02:00
struct TsR_Result Result;
2014-12-01 23:55:08 +01:00
unsigned NumAccessesTst;
2020-03-26 21:39:44 +01:00
/***** Create test *****/
Tst_TstConstructor (&Test);
2014-12-01 23:55:08 +01:00
/***** Read test configuration from database *****/
2020-03-21 15:41:25 +01:00
TstCfg_GetConfigFromDB ();
2014-12-01 23:55:08 +01:00
if (Tst_CheckIfNextTstAllowed ())
{
/***** Check that all parameters used to generate a test are valid *****/
2020-03-24 01:50:39 +01:00
if (Tst_GetParamsTst (&Test,Tst_SHOW_TEST_TO_ANSWER)) // Get parameters from form
2014-12-01 23:55:08 +01:00
{
/***** Get questions *****/
2020-04-01 03:11:05 +02:00
Tst_GetQuestionsForNewTestFromDB (&Test,&Result);
if (Result.NumQsts)
2014-12-01 23:55:08 +01:00
{
/***** Get and update number of hits *****/
NumAccessesTst = Tst_GetNumAccessesTst () + 1;
if (Gbl.Usrs.Me.IBelongToCurrentCrs)
Tst_UpdateMyNumAccessTst (NumAccessesTst);
2020-04-01 03:11:05 +02:00
/***** Create new test in database to store the result *****/
TsR_CreateTestResultInDB (&Result);
2019-10-26 02:19:42 +02:00
/***** Begin box *****/
2020-03-26 02:54:30 +01:00
Box_BoxBegin (NULL,Txt_Test,
NULL,NULL,
2017-06-12 15:03:29 +02:00
Hlp_ASSESSMENT_Tests,Box_NOT_CLOSABLE);
2016-03-21 01:28:16 +01:00
Lay_WriteHeaderClassPhoto (false,false,
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Ins.InsCod,
Gbl.Hierarchy.Deg.DegCod,
2019-04-04 10:45:15 +02:00
Gbl.Hierarchy.Crs.CrsCod);
2016-03-21 01:28:16 +01:00
2019-10-20 22:00:28 +02:00
/***** Begin form *****/
2018-11-09 20:47:39 +01:00
Frm_StartForm (ActAssTst);
2020-04-01 03:11:05 +02:00
Par_PutHiddenParamLong (NULL,"TstCod",Result.TstCod);
2019-11-03 13:19:32 +01:00
Par_PutHiddenParamUnsigned (NULL,"NumTst",NumAccessesTst);
2020-03-24 01:50:39 +01:00
Par_PutHiddenParamUnsigned (NULL,"NumQst",Test.NumQsts);
2014-12-01 23:55:08 +01:00
2016-03-21 01:28:16 +01:00
/***** List the questions *****/
2020-04-01 03:11:05 +02:00
Tst_ShowTestQuestionsWhenSeeing (&Result);
2014-12-01 23:55:08 +01:00
2016-11-21 13:15:08 +01:00
/***** Test result will be saved? *****/
2020-03-30 16:40:12 +02:00
Tst_PutCheckBoxAllowTeachers (true);
2014-12-01 23:55:08 +01:00
2016-03-21 01:28:16 +01:00
/***** End form *****/
2017-06-11 19:02:40 +02:00
Btn_PutConfirmButton (Txt_Done_assess_test);
2018-11-09 20:47:39 +01:00
Frm_EndForm ();
2014-12-01 23:55:08 +01:00
2017-06-12 14:16:33 +02:00
/***** End box *****/
2019-10-25 22:48:34 +02:00
Box_BoxEnd ();
2016-03-21 01:28:16 +01:00
2014-12-01 23:55:08 +01:00
/***** Set test status *****/
Tst_SetTstStatus (NumAccessesTst,Tst_STATUS_SHOWN_BUT_NOT_ASSESSED);
/***** Update date-time of my next allowed access to test *****/
2017-06-04 18:18:54 +02:00
if (Gbl.Usrs.Me.Role.Logged == Rol_STD)
2020-03-24 01:50:39 +01:00
Tst_UpdateLastAccTst (Test.NumQsts);
2014-12-01 23:55:08 +01:00
}
2020-03-30 16:40:12 +02:00
else // No questions found
{
Ale_ShowAlert (Ale_INFO,Txt_No_questions_found_matching_your_search_criteria);
Tst_ShowFormRequestTest (&Test); // Show the form again
}
2014-12-01 23:55:08 +01:00
}
else
2020-03-24 00:58:42 +01:00
Tst_ShowFormRequestTest (&Test); // Show the form again
2014-12-01 23:55:08 +01:00
}
2020-03-26 21:39:44 +01:00
/***** Destroy test *****/
Tst_TstDestructor (&Test);
2014-12-01 23:55:08 +01:00
}
2020-03-30 16:40:12 +02:00
/*****************************************************************************/
/************ Put checkbox to allow teachers to see test result **************/
/*****************************************************************************/
static void Tst_PutCheckBoxAllowTeachers (bool AllowTeachers)
{
extern const char *The_ClassFormInBox[The_NUM_THEMES];
extern const char *Txt_Allow_teachers_to_consult_this_test;
/***** Test result will be available for teachers? *****/
HTM_DIV_Begin ("class=\"CM\"");
HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
HTM_INPUT_CHECKBOX ("AllowTchs",HTM_DONT_SUBMIT_ON_CHANGE,
"value=\"Y\"%s",
AllowTeachers ? " checked=\"checked\"" : // Teachers can see test result
"");
HTM_TxtF ("&nbsp;%s",Txt_Allow_teachers_to_consult_this_test);
HTM_LABEL_End ();
HTM_DIV_End ();
}
2020-03-30 00:30:08 +02:00
/*****************************************************************************/
/********************* Request the assessment of a test **********************/
/*****************************************************************************/
void Tst_RequestAssessTest (void)
{
extern const char *Txt_The_test_X_has_already_been_assessed_previously;
extern const char *Txt_There_was_an_error_in_assessing_the_test_X;
unsigned NumTst;
// long TstCod = -1L; // Initialized to avoid warning
struct TsR_Result Result;
/***** Read test configuration from database *****/
TstCfg_GetConfigFromDB ();
/***** Get number of this test from form *****/
NumTst = Tst_GetAndCheckParamNumTst ();
/****** Get test status in database for this session-course-num.test *****/
switch (Tst_GetTstStatus (NumTst))
{
case Tst_STATUS_SHOWN_BUT_NOT_ASSESSED:
/***** Get parameters from the form *****/
/* Get questions and answers from form to assess a test */
Tst_GetQuestionsAndAnswersFromForm (&Result);
/***** Show question and button to send the test *****/
/* Start alert */
Ale_ShowAlertAndButton1 (Ale_INFO,"Por favor, revise el test antes de enviarlo");
/* Show test again */
/* End alert */
Ale_ShowAlertAndButton2 (ActAssTst,NULL,NULL,
NULL,NULL,
Btn_CONFIRM_BUTTON,"Enviar test");
break;
case Tst_STATUS_ASSESSED:
Ale_ShowAlert (Ale_WARNING,Txt_The_test_X_has_already_been_assessed_previously,
NumTst);
break;
case Tst_STATUS_ERROR:
Ale_ShowAlert (Ale_WARNING,Txt_There_was_an_error_in_assessing_the_test_X,
NumTst);
break;
}
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/******************************** Assess a test ******************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
void Tst_AssessTest (void)
2014-12-01 23:55:08 +01:00
{
2016-11-13 20:18:49 +01:00
extern const char *Hlp_ASSESSMENT_Tests;
2016-03-21 01:28:16 +01:00
extern const char *Txt_Test_result;
extern const char *Txt_Test_No_X_that_you_make_in_this_course;
2019-11-28 09:12:34 +01:00
extern const char *Txt_Score;
2019-11-28 09:45:32 +01:00
extern const char *Txt_Grade;
2014-12-01 23:55:08 +01:00
extern const char *Txt_The_test_X_has_already_been_assessed_previously;
extern const char *Txt_There_was_an_error_in_assessing_the_test_X;
unsigned NumTst;
2020-03-23 21:40:17 +01:00
struct TsR_Result Result;
2014-12-01 23:55:08 +01:00
/***** Read test configuration from database *****/
2020-03-21 15:41:25 +01:00
TstCfg_GetConfigFromDB ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Get basic parameters of the exam *****/
/* Get test result code from form */
if ((Result.TstCod = Par_GetParToLong ("TstCod")) <= 0)
Lay_ShowErrorAndExit ("Wrong test result.");
/* Get number of this test from form */
2014-12-01 23:55:08 +01:00
NumTst = Tst_GetAndCheckParamNumTst ();
/****** Get test status in database for this session-course-num.test *****/
switch (Tst_GetTstStatus (NumTst))
{
case Tst_STATUS_SHOWN_BUT_NOT_ASSESSED:
2020-03-30 00:30:08 +02:00
/***** Get parameters from the form *****/
/* Get questions and answers from form to assess a test */
2020-03-23 21:40:17 +01:00
Tst_GetQuestionsAndAnswersFromForm (&Result);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Update test result in database *****/
Tst_ComputeAndStoreResultScore (&Result,
Gbl.Usrs.Me.Role.Logged == Rol_STD); // Update question score?
2014-12-01 23:55:08 +01:00
2019-10-26 02:19:42 +02:00
/***** Begin box *****/
2020-03-26 02:54:30 +01:00
Box_BoxBegin (NULL,Txt_Test_result,
NULL,NULL,
2017-06-12 15:03:29 +02:00
Hlp_ASSESSMENT_Tests,Box_NOT_CLOSABLE);
2016-03-21 01:28:16 +01:00
Lay_WriteHeaderClassPhoto (false,false,
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Ins.InsCod,
Gbl.Hierarchy.Deg.DegCod,
2019-04-04 10:45:15 +02:00
Gbl.Hierarchy.Crs.CrsCod);
2016-03-21 01:28:16 +01:00
/***** Header *****/
if (Gbl.Usrs.Me.IBelongToCurrentCrs)
{
2019-10-24 00:04:40 +02:00
HTM_DIV_Begin ("class=\"TEST_SUBTITLE\"");
2019-11-11 00:15:44 +01:00
HTM_TxtF (Txt_Test_No_X_that_you_make_in_this_course,NumTst);
2019-10-23 20:07:56 +02:00
HTM_DIV_End ();
2016-03-21 01:28:16 +01:00
}
2014-12-01 23:55:08 +01:00
/***** Write answers and solutions *****/
2019-10-23 19:05:05 +02:00
HTM_TABLE_BeginWideMarginPadding (10);
2020-04-01 03:11:05 +02:00
Tst_ShowTestResultAfterAssess (&Result);
2019-10-23 19:05:05 +02:00
HTM_TABLE_End ();
2014-12-01 23:55:08 +01:00
2019-11-28 09:45:32 +01:00
/***** Write total score and grade *****/
2020-03-21 15:41:25 +01:00
if (TsV_IsVisibleTotalScore (TstCfg_GetConfigVisibility ()))
2019-11-28 09:12:34 +01:00
{
2019-11-28 09:45:32 +01:00
HTM_DIV_Begin ("class=\"DAT_N_BOLD CM\"");
2020-01-11 15:22:02 +01:00
HTM_TxtColonNBSP (Txt_Score);
2020-03-23 21:40:17 +01:00
HTM_Double2Decimals (Result.Score);
2019-11-28 09:45:32 +01:00
HTM_BR ();
2020-01-11 15:22:02 +01:00
HTM_TxtColonNBSP (Txt_Grade);
2020-03-23 21:40:17 +01:00
Tst_ComputeAndShowGrade (Result.NumQsts,
Result.Score,
TsR_SCORE_MAX);
2019-11-28 09:12:34 +01:00
HTM_DIV_End ();
}
2014-12-01 23:55:08 +01:00
2017-06-12 14:16:33 +02:00
/***** End box *****/
2019-10-25 22:48:34 +02:00
Box_BoxEnd ();
2014-12-01 23:55:08 +01:00
/***** Set test status *****/
Tst_SetTstStatus (NumTst,Tst_STATUS_ASSESSED);
break;
case Tst_STATUS_ASSESSED:
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_The_test_X_has_already_been_assessed_previously,
NumTst);
2014-12-01 23:55:08 +01:00
break;
case Tst_STATUS_ERROR:
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_There_was_an_error_in_assessing_the_test_X,
NumTst);
2014-12-01 23:55:08 +01:00
break;
}
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/*********** Get questions and answers from form to assess a test ************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-03-23 21:40:17 +01:00
static void Tst_GetQuestionsAndAnswersFromForm (struct TsR_Result *Result)
2014-12-01 23:55:08 +01:00
{
unsigned NumQst;
2019-11-08 01:10:32 +01:00
char StrQstIndOrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Qstxx...x", "Indxx...x" or "Ansxx...x"
2014-12-01 23:55:08 +01:00
2020-03-30 00:30:08 +02:00
/***** Get number of questions *****/
Result->NumQsts = Tst_GetParamNumQsts ();
2020-04-01 03:11:05 +02:00
/***** Loop for every question computing the score *****/
2014-12-01 23:55:08 +01:00
for (NumQst = 0;
2020-03-23 21:40:17 +01:00
NumQst < Result->NumQsts;
2014-12-01 23:55:08 +01:00
NumQst++)
{
2020-04-01 03:11:05 +02:00
/***** Get question codes and user's answers from form *****/
2014-12-01 23:55:08 +01:00
/* Get question code */
2018-10-18 02:02:32 +02:00
snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns),
2020-03-30 00:30:08 +02:00
"Qst%010u",
2018-10-18 02:02:32 +02:00
NumQst);
2020-04-01 03:11:05 +02:00
if ((Result->Questions[NumQst].QstCod = Par_GetParToLong (StrQstIndOrAns)) <= 0)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Code of question is missing.");
2020-04-01 03:11:05 +02:00
/* Get indexes for this question */ // TODO: Get indexes from tst_exam_questions in database instead of form
2018-10-18 02:02:32 +02:00
snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns),
2020-03-30 00:30:08 +02:00
"Ind%010u",
2018-10-18 02:02:32 +02:00
NumQst);
2020-04-01 03:11:05 +02:00
Par_GetParMultiToText (StrQstIndOrAns,Result->Questions[NumQst].StrIndexes,
2017-03-13 19:02:15 +01:00
Tst_MAX_BYTES_INDEXES_ONE_QST); /* If choice ==> "0", "1", "2",... */
2014-12-01 23:55:08 +01:00
/* Get answers selected by user for this question */
2018-10-18 02:02:32 +02:00
snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns),
2020-03-30 00:30:08 +02:00
"Ans%010u",
2018-10-18 02:02:32 +02:00
NumQst);
2020-04-01 03:11:05 +02:00
Par_GetParMultiToText (StrQstIndOrAns,Result->Questions[NumQst].StrAnswers,
2017-03-13 19:02:15 +01:00
Tst_MAX_BYTES_ANSWERS_ONE_QST); /* If answer type == T/F ==> " ", "T", "F"; if choice ==> "0", "2",... */
2014-12-01 23:55:08 +01:00
}
2020-03-30 00:30:08 +02:00
/***** Get if test result will be visible by teachers *****/
Result->AllowTeachers = Par_GetParToBool ("AllowTchs");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/*********** Compute score of each question and store in database ************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_ComputeAndStoreResultScore (struct TsR_Result *Result,
bool UpdateQstScore)
2019-11-28 09:12:34 +01:00
{
2020-04-01 03:11:05 +02:00
unsigned NumQst;
struct Tst_Question Question;
2019-11-28 09:45:32 +01:00
2020-04-01 03:11:05 +02:00
/***** Initialize total score *****/
Result->Score = 0.0;
Result->NumQstsNotBlank = 0;
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Compute and store scores of all questions *****/
for (NumQst = 0;
NumQst < Result->NumQsts;
NumQst++)
2019-11-28 01:41:13 +01:00
{
2020-04-01 03:11:05 +02:00
/* Compute question score */
Tst_QstConstructor (&Question);
Question.QstCod = Result->Questions[NumQst].QstCod;
Question.Answer.Type = Tst_GetQstAnswerType (Question.QstCod);
Tst_ComputeAnswerScore (Result,NumQst,&Question);
Tst_QstDestructor (&Question);
/* Store test result question in database */
TsR_StoreOneTestResultQstInDB (Result,
NumQst); // 0, 1, 2, 3...
/* Accumulate total score */
Result->Score += Result->Questions[NumQst].Score;
if (Result->Questions[NumQst].AnswerIsNotBlank)
Result->NumQstsNotBlank++;
/* Update the number of hits and the score of this question in tests database */
if (UpdateQstScore)
Tst_UpdateQstScore (Result,NumQst);
2019-11-28 01:41:13 +01:00
}
2020-04-01 03:11:05 +02:00
/***** Store test result in database *****/
TsR_UpdateScoreOfTestResultInDB (Result);
2019-11-28 09:12:34 +01:00
}
2019-11-28 09:45:32 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************* Write answers of a question when assessing a test *************/
2019-11-28 09:45:32 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_ComputeAnswerScore (struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question)
2019-11-28 09:12:34 +01:00
{
2020-04-01 03:11:05 +02:00
/***** Write answer depending on type *****/
switch (Question->Answer.Type)
{
case Tst_ANS_INT:
Tst_ComputeIntAnsScore (Result,NumQst,Question);
break;
case Tst_ANS_FLOAT:
Tst_ComputeFloatAnsScore (Result,NumQst,Question);
break;
case Tst_ANS_TRUE_FALSE:
Tst_ComputeTFAnsScore (Result,NumQst,Question);
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
Tst_ComputeChoiceAnsScore (Result,NumQst,Question);
break;
case Tst_ANS_TEXT:
Tst_ComputeTextAnsScore (Result,NumQst,Question);
break;
default:
break;
}
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/**************** Write integer answer when assessing a test *****************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_ComputeIntAnsScore (struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question)
{
long AnswerUsr;
/***** Get the numerical value of the correct answer *****/
Tst_GetCorrectIntAnswerFromDB (Question);
/***** Compute score *****/
Result->Questions[NumQst].Score = 0.0; // Default score for blank or wrong answer
Result->Questions[NumQst].AnswerIsNotBlank = (Result->Questions[NumQst].StrAnswers[0] != '\0');
if (Result->Questions[NumQst].AnswerIsNotBlank) // If user has answered the answer
if (sscanf (Result->Questions[NumQst].StrAnswers,"%ld",&AnswerUsr) == 1)
if (AnswerUsr == Question->Answer.Integer) // Correct answer
Result->Questions[NumQst].Score = 1.0;
}
static void Tst_GetCorrectIntAnswerFromDB (struct Tst_Question *Question)
2014-12-01 23:55:08 +01:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
2020-04-01 03:11:05 +02:00
/***** Query database *****/
Question->Answer.NumOptions =
(unsigned) DB_QuerySELECT (&mysql_res,"can not get answers of a question",
"SELECT Answer" // row[0]
" FROM tst_answers"
" WHERE QstCod=%ld",
Question->QstCod);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Check if number of rows is correct *****/
Tst_CheckIfNumberOfAnswersIsOne (Question);
/***** Get correct answer *****/
row = mysql_fetch_row (mysql_res);
if (sscanf (row[0],"%ld",&Question->Answer.Integer) != 1)
Lay_ShowErrorAndExit ("Wrong integer answer.");
2014-12-01 23:55:08 +01:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/***************** Write float answer when assessing a test ******************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_ComputeFloatAnsScore (struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
double AnswerUsr;
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Check if number of rows is correct *****/
if (Question->Answer.NumOptions != 2)
Lay_ShowErrorAndExit ("Wrong float range.");
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Get the numerical value of the minimum and maximum correct answers *****/
Tst_GetCorrectFloatAnswerFromDB (Question);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Compute score *****/
Result->Questions[NumQst].Score = 0.0; // Default score for blank or wrong answer
Result->Questions[NumQst].AnswerIsNotBlank = (Result->Questions[NumQst].StrAnswers[0] != '\0');
if (Result->Questions[NumQst].AnswerIsNotBlank) // If user has answered the answer
{
AnswerUsr = Str_GetDoubleFromStr (Result->Questions[NumQst].StrAnswers);
// A bad formatted floating point answer will interpreted as 0.0
Result->Questions[NumQst].Score = (AnswerUsr >= Question->Answer.FloatingPoint[0] &&
AnswerUsr <= Question->Answer.FloatingPoint[1]) ? 1.0 : // If correct (inside the interval)
0.0; // If wrong (outside the interval)
}
}
static void Tst_GetCorrectFloatAnswerFromDB (struct Tst_Question *Question)
2014-12-01 23:55:08 +01:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
2020-04-01 03:11:05 +02:00
unsigned NumOpt;
double Tmp;
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Query database *****/
Question->Answer.NumOptions =
(unsigned) DB_QuerySELECT (&mysql_res,"can not get answers of a question",
"SELECT Answer" // row[0]
" FROM tst_answers"
" WHERE QstCod=%ld",
Question->QstCod);
/***** Check if number of rows is correct *****/
if (Question->Answer.NumOptions != 2)
Lay_ShowErrorAndExit ("Wrong float range.");
/***** Get float range *****/
for (NumOpt = 0;
NumOpt < 2;
NumOpt++)
2014-12-01 23:55:08 +01:00
{
row = mysql_fetch_row (mysql_res);
2020-04-01 03:11:05 +02:00
Question->Answer.FloatingPoint[NumOpt] = Str_GetDoubleFromStr (row[0]);
}
if (Question->Answer.FloatingPoint[0] >
Question->Answer.FloatingPoint[1]) // The maximum and the minimum are swapped
{
/* Swap maximum and minimum */
Tmp = Question->Answer.FloatingPoint[0];
Question->Answer.FloatingPoint[0] = Question->Answer.FloatingPoint[1];
Question->Answer.FloatingPoint[1] = Tmp;
2014-12-01 23:55:08 +01:00
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************** Write false / true answer when assessing a test **************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_ComputeTFAnsScore (struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question)
{
/***** Get answer true or false *****/
Tst_GetCorrectTFAnswerFromDB (Question);
/***** Compute score *****/
Result->Questions[NumQst].AnswerIsNotBlank = (Result->Questions[NumQst].StrAnswers[0] != '\0');
if (Result->Questions[NumQst].AnswerIsNotBlank) // User has selected T or F
Result->Questions[NumQst].Score = (Result->Questions[NumQst].StrAnswers[0] == Question->Answer.TF) ? 1.0 : // Correct
-1.0; // Wrong
else
Result->Questions[NumQst].Score = 0.0;
}
static void Tst_GetCorrectTFAnswerFromDB (struct Tst_Question *Question)
2014-12-01 23:55:08 +01:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
2020-04-01 03:11:05 +02:00
/***** Query database *****/
Question->Answer.NumOptions =
(unsigned) DB_QuerySELECT (&mysql_res,"can not get answers of a question",
"SELECT Answer" // row[0]
" FROM tst_answers"
" WHERE QstCod=%ld",
Question->QstCod);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Check if number of rows is correct *****/
Tst_CheckIfNumberOfAnswersIsOne (Question);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Get answer *****/
row = mysql_fetch_row (mysql_res);
Question->Answer.TF = row[0][0];
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************ Compute score for single or multiple choice answer *************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
void Tst_ComputeChoiceAnsScore (struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION]; // Indexes of all answers of this question
bool UsrAnswers[Tst_MAX_OPTIONS_PER_QUESTION];
2020-03-30 16:40:12 +02:00
2020-04-01 03:11:05 +02:00
/***** Get correct options of test question from database *****/
Tst_GetCorrectChoiceAnswerFromDB (Question);
2020-03-30 16:40:12 +02:00
2020-04-01 03:11:05 +02:00
/***** Get indexes for this question from string *****/
Tst_GetIndexesFromStr (Result->Questions[NumQst].StrIndexes,Indexes);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Get the user's answers for this question from string *****/
Tst_GetAnswersFromStr (Result->Questions[NumQst].StrAnswers,UsrAnswers);
2020-03-27 19:50:19 +01:00
2020-04-01 03:11:05 +02:00
/***** Compute the total score of this question *****/
Tst_ComputeScoreQst (Result,NumQst,Question,Indexes,UsrAnswers);
2020-03-17 13:10:44 +01:00
}
2020-04-01 03:11:05 +02:00
static void Tst_GetCorrectChoiceAnswerFromDB (struct Tst_Question *Question)
2020-03-17 13:10:44 +01:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
2020-04-01 03:11:05 +02:00
unsigned NumOpt;
2020-03-17 13:10:44 +01:00
2020-04-01 03:11:05 +02:00
/***** Query database *****/
Question->Answer.NumOptions =
(unsigned) DB_QuerySELECT (&mysql_res,"can not get answers of a question",
"SELECT Correct" // row[0]
" FROM tst_answers"
" WHERE QstCod=%ld"
" ORDER BY AnsInd",
Question->QstCod);
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
2020-03-17 13:10:44 +01:00
{
2020-04-01 03:11:05 +02:00
/* Get next answer */
2020-03-17 13:10:44 +01:00
row = mysql_fetch_row (mysql_res);
2020-04-01 03:11:05 +02:00
/* Assign correctness (row[0]) of this answer (this option) */
Question->Answer.Options[NumOpt].Correct = (row[0][0] == 'Y');
2014-12-01 23:55:08 +01:00
}
2020-04-01 03:11:05 +02:00
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
2014-12-01 23:55:08 +01:00
}
2019-09-24 22:48:25 +02:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/********************* Get vector of indexes from string *********************/
2019-09-24 22:48:25 +02:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
void Tst_GetIndexesFromStr (const char StrIndexesOneQst[Tst_MAX_BYTES_INDEXES_ONE_QST + 1], // 0 1 2 3, 3 0 2 1, etc.
unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION])
2019-09-24 22:48:25 +02:00
{
2020-04-01 03:11:05 +02:00
unsigned NumOpt;
const char *Ptr;
char StrOneIndex[Cns_MAX_DECIMAL_DIGITS_UINT + 1];
2019-09-24 22:48:25 +02:00
2020-04-01 03:11:05 +02:00
/***** Get indexes from string *****/
for (NumOpt = 0, Ptr = StrIndexesOneQst;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION && *Ptr;
NumOpt++)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
Par_GetNextStrUntilSeparParamMult (&Ptr,StrOneIndex,Cns_MAX_DECIMAL_DIGITS_UINT);
if (sscanf (StrOneIndex,"%u",&(Indexes[NumOpt])) != 1)
Lay_ShowErrorAndExit ("Wrong index of answer.");
if (Indexes[NumOpt] >= Tst_MAX_OPTIONS_PER_QUESTION)
Lay_ShowErrorAndExit ("Wrong index of answer.");
2014-12-01 23:55:08 +01:00
}
2020-04-01 03:11:05 +02:00
/***** Initialize remaining to 0 *****/
for (;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
Indexes[NumOpt] = 0;
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/****************** Get vector of user's answers from string *****************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_GetAnswersFromStr (const char StrAnswersOneQst[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1],
bool UsrAnswers[Tst_MAX_OPTIONS_PER_QUESTION])
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
unsigned NumOpt;
const char *Ptr;
char StrOneAnswer[Cns_MAX_DECIMAL_DIGITS_UINT + 1];
unsigned AnsUsr;
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Initialize all answers to false *****/
for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
UsrAnswers[NumOpt] = false;
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Set selected answers to true *****/
for (NumOpt = 0, Ptr = StrAnswersOneQst;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION && *Ptr;
NumOpt++)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
Par_GetNextStrUntilSeparParamMult (&Ptr,StrOneAnswer,Cns_MAX_DECIMAL_DIGITS_UINT);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
if (sscanf (StrOneAnswer,"%u",&AnsUsr) != 1)
Lay_ShowErrorAndExit ("Bad user's answer.");
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
if (AnsUsr >= Tst_MAX_OPTIONS_PER_QUESTION)
Lay_ShowErrorAndExit ("Bad user's answer.");
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
UsrAnswers[AnsUsr] = true;
}
}
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/*****************************************************************************/
/*********************** Compute the score of a question *********************/
/*****************************************************************************/
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
static void Tst_ComputeScoreQst (struct TsR_Result *Result,
unsigned NumQst,
const struct Tst_Question *Question,
unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION], // Indexes of all answers of this question
bool UsrAnswers[Tst_MAX_OPTIONS_PER_QUESTION])
{
unsigned NumOpt;
unsigned NumOptTotInQst = 0;
unsigned NumOptCorrInQst = 0;
unsigned NumAnsGood = 0;
unsigned NumAnsBad = 0;
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
/***** Compute the total score of this question *****/
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
{
NumOptTotInQst++;
if (Question->Answer.Options[Indexes[NumOpt]].Correct)
NumOptCorrInQst++;
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
if (UsrAnswers[Indexes[NumOpt]]) // This answer has been selected by the user
{
if (Question->Answer.Options[Indexes[NumOpt]].Correct)
NumAnsGood++;
else
NumAnsBad++;
}
}
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* The answer is blank? */
Result->Questions[NumQst].AnswerIsNotBlank = NumAnsGood != 0 || NumAnsBad != 0;
if (Result->Questions[NumQst].AnswerIsNotBlank)
{
/* Compute the score */
if (Question->Answer.Type == Tst_ANS_UNIQUE_CHOICE)
{
if (NumOptTotInQst >= 2) // It should be 2 options at least
Result->Questions[NumQst].Score = (double) NumAnsGood -
(double) NumAnsBad / (double) (NumOptTotInQst - 1);
else // 0 or 1 options (impossible)
Result->Questions[NumQst].Score = (double) NumAnsGood;
}
else // AnswerType == Tst_ANS_MULTIPLE_CHOICE
{
if (NumOptCorrInQst) // There are correct options in the question
{
if (NumOptCorrInQst < NumOptTotInQst) // If there are correct options and wrong options (typical case)
Result->Questions[NumQst].Score = (double) NumAnsGood / (double) NumOptCorrInQst -
(double) NumAnsBad / (double) (NumOptTotInQst - NumOptCorrInQst);
else // Si todas the opciones son correctas (caso raro)
Result->Questions[NumQst].Score = (double) NumAnsGood / (double) NumOptCorrInQst;
}
else
{
if (NumOptTotInQst) // There are options but none is correct (extrange case)
Result->Questions[NumQst].Score = - (double) NumAnsBad / (double) NumOptTotInQst;
else // There are no options (impossible!)
Result->Questions[NumQst].Score = 0.0;
}
}
2014-12-01 23:55:08 +01:00
}
2020-04-01 03:11:05 +02:00
else // Answer is blank
Result->Questions[NumQst].Score = 0.0;
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/********************* Compute score for text answer *************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_ComputeTextAnsScore (struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
unsigned NumOpt;
char TextAnsUsr[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1];
char TextAnsOK[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1];
bool Correct = false;
2020-03-23 17:14:41 +01:00
2020-04-01 03:11:05 +02:00
/***** Get correct answers for this question from database *****/
Tst_GetCorrectTextAnswerFromDB (Question);
2020-03-23 17:14:41 +01:00
2020-04-01 03:11:05 +02:00
/***** Compute score *****/
Result->Questions[NumQst].Score = 0.0; // Default score for blank or wrong answer
Result->Questions[NumQst].AnswerIsNotBlank = (Result->Questions[NumQst].StrAnswers[0] != '\0');
if (Result->Questions[NumQst].AnswerIsNotBlank) // If user has answered the answer
{
/* Filter the user answer */
Str_Copy (TextAnsUsr,Result->Questions[NumQst].StrAnswers,
Tst_MAX_BYTES_ANSWERS_ONE_QST);
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
/* In order to compare student answer to stored answer,
the text answers are stored avoiding two or more consecurive spaces */
Str_ReplaceSeveralSpacesForOne (TextAnsUsr);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
Str_ConvertToComparable (TextAnsUsr);
2020-03-23 17:14:41 +01:00
2020-04-01 03:11:05 +02:00
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
{
/* Filter this correct answer */
Str_Copy (TextAnsOK,Question->Answer.Options[NumOpt].Text,
Tst_MAX_BYTES_ANSWERS_ONE_QST);
Str_ConvertToComparable (TextAnsOK);
2020-03-23 17:14:41 +01:00
2020-04-01 03:11:05 +02:00
/* Check is user answer is correct */
if (!strcoll (TextAnsUsr,TextAnsOK))
{
Correct = true;
break;
}
}
2020-03-23 17:14:41 +01:00
2020-04-01 03:11:05 +02:00
if (Correct)
Result->Questions[NumQst].Score = 1.0; // Correct answer
}
2020-03-23 17:14:41 +01:00
}
2020-04-01 03:11:05 +02:00
static void Tst_GetCorrectTextAnswerFromDB (struct Tst_Question *Question)
2020-03-23 17:14:41 +01:00
{
2020-04-01 03:11:05 +02:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumOpt;
double Tmp;
2020-03-23 17:14:41 +01:00
2020-04-01 03:11:05 +02:00
/***** Query database *****/
Question->Answer.NumOptions =
(unsigned) DB_QuerySELECT (&mysql_res,"can not get answers of a question",
"SELECT Answer" // row[0]
" FROM tst_answers"
" WHERE QstCod=%ld",
Question->QstCod);
2019-10-23 20:07:56 +02:00
2020-04-01 03:11:05 +02:00
/***** Get text and correctness of answers for this question from database (one row per answer) *****/
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
{
/***** Get next answer *****/
row = mysql_fetch_row (mysql_res);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Allocate memory for text in this choice answer *****/
if (!Tst_AllocateTextChoiceAnswer (Question,NumOpt))
/* Abort on error */
Ale_ShowAlertsAndExit ();
2020-03-23 17:14:41 +01:00
2020-04-01 03:11:05 +02:00
/***** Copy answer text (row[1]) and convert it, that is in HTML, to rigorous HTML ******/
Str_Copy (Question->Answer.Options[NumOpt].Text,row[1],
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Question->Answer.Options[NumOpt].Text,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false);
}
2019-03-18 15:42:22 +01:00
2020-04-01 03:11:05 +02:00
/***** Get float range *****/
for (NumOpt = 0;
NumOpt < 2;
NumOpt++)
2020-02-17 23:15:08 +01:00
{
2020-04-01 03:11:05 +02:00
row = mysql_fetch_row (mysql_res);
Question->Answer.FloatingPoint[NumOpt] = Str_GetDoubleFromStr (row[0]);
}
if (Question->Answer.FloatingPoint[0] >
Question->Answer.FloatingPoint[1]) // The maximum and the minimum are swapped
{
/* Swap maximum and minimum */
Tmp = Question->Answer.FloatingPoint[0];
Question->Answer.FloatingPoint[0] = Question->Answer.FloatingPoint[1];
Question->Answer.FloatingPoint[1] = Tmp;
2020-02-17 23:15:08 +01:00
}
2016-04-06 19:26:09 +02:00
2020-04-01 03:11:05 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
2020-03-23 17:14:41 +01:00
2020-04-01 03:11:05 +02:00
/*****************************************************************************/
/************ Compute and show total grade out of maximum grade **************/
/*****************************************************************************/
2016-04-06 01:10:04 +02:00
2020-04-01 03:11:05 +02:00
void Tst_ComputeAndShowGrade (unsigned NumQsts,double Score,double MaxGrade)
{
Tst_ShowGrade (Tst_ComputeGrade (NumQsts,Score,MaxGrade),MaxGrade);
2014-12-01 23:55:08 +01:00
}
2020-03-23 17:14:41 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/**************** Compute total grade out of maximum grade *******************/
2020-03-23 17:14:41 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
double Tst_ComputeGrade (unsigned NumQsts,double Score,double MaxGrade)
2020-03-23 17:14:41 +01:00
{
2020-04-01 03:11:05 +02:00
double MaxScore;
double Grade;
/***** Compute grade *****/
if (NumQsts)
{
MaxScore = (double) NumQsts;
Grade = Score * MaxGrade / MaxScore;
}
else
Grade = 0.0;
return Grade;
2020-03-23 17:14:41 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/****************** Show total grade out of maximum grade ********************/
2020-03-23 17:14:41 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
void Tst_ShowGrade (double Grade,double MaxGrade)
2020-03-23 17:14:41 +01:00
{
2020-04-01 03:11:05 +02:00
/***** Write grade over maximum grade *****/
HTM_Double2Decimals (Grade);
HTM_Txt ("/");
HTM_Double2Decimals (MaxGrade);
2020-03-23 17:14:41 +01:00
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************** Check minimum date-time of next access to test ***************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
// Return true if allowed date-time of next access to test is older than now
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
static bool Tst_CheckIfNextTstAllowed (void)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
extern const char *Hlp_ASSESSMENT_Tests;
extern const char *Txt_Test;
extern const char *Txt_You_can_not_take_a_new_test_until;
extern const char *Txt_Today;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
long NumSecondsFromNowToNextAccTst = -1L; // Access allowed when this number <= 0
time_t TimeNextTestUTC = (time_t) 0;
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Teachers and superusers are allowed to do all tests they want *****/
if (Gbl.Usrs.Me.Role.Logged == Rol_TCH ||
Gbl.Usrs.Me.Role.Logged == Rol_SYS_ADM)
return true;
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Get date of next allowed access to test from database *****/
if (DB_QuerySELECT (&mysql_res,"can not get last access to test",
"SELECT UNIX_TIMESTAMP(LastAccTst+INTERVAL (NumQstsLastTst*%lu) SECOND)-"
"UNIX_TIMESTAMP()," // row[0]
"UNIX_TIMESTAMP(LastAccTst+INTERVAL (NumQstsLastTst*%lu) SECOND)" // row[1]
" FROM crs_usr"
" WHERE CrsCod=%ld AND UsrCod=%ld",
TstCfg_GetConfigMinTimeNxtTstPerQst (),
TstCfg_GetConfigMinTimeNxtTstPerQst (),
Gbl.Hierarchy.Crs.CrsCod,Gbl.Usrs.Me.UsrDat.UsrCod) == 1)
2020-02-17 15:30:55 +01:00
{
2020-04-01 03:11:05 +02:00
/* Get seconds from now to next access to test */
row = mysql_fetch_row (mysql_res);
if (row[0])
if (sscanf (row[0],"%ld",&NumSecondsFromNowToNextAccTst) == 1)
/* Time UTC of next access allowed (row[1]) */
TimeNextTestUTC = Dat_GetUNIXTimeFromStr (row[1]);
}
else
Lay_ShowErrorAndExit ("Error when reading date of next allowed access to test.");
2020-03-14 16:49:04 +01:00
2020-04-01 03:11:05 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
2020-02-17 15:30:55 +01:00
2020-04-01 03:11:05 +02:00
/***** Check if access is allowed *****/
if (NumSecondsFromNowToNextAccTst > 0)
{
/***** Write warning *****/
Ale_ShowAlert (Ale_WARNING,"%s:<br /><span id=\"date_next_test\"></span>."
"<script type=\"text/javascript\">"
"writeLocalDateHMSFromUTC('date_next_test',%ld,"
"%u,',&nbsp;','%s',true,true,0x7);"
"</script>",
Txt_You_can_not_take_a_new_test_until,
(long) TimeNextTestUTC,
(unsigned) Gbl.Prefs.DateFormat,Txt_Today);
2020-02-17 15:30:55 +01:00
2020-04-01 03:11:05 +02:00
return false;
2020-02-17 15:30:55 +01:00
}
2020-04-01 03:11:05 +02:00
return true;
2014-12-01 23:55:08 +01:00
}
2016-03-30 01:28:58 +02:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/****************************** Update test status ***************************/
2016-03-30 01:28:58 +02:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_SetTstStatus (unsigned NumTst,Tst_Status_t TstStatus)
2016-03-30 01:28:58 +02:00
{
2020-04-01 03:11:05 +02:00
/***** Delete old status from expired sessions *****/
DB_QueryDELETE ("can not remove old status of tests",
"DELETE FROM tst_status"
" WHERE SessionId NOT IN"
" (SELECT SessionId FROM sessions)");
2016-04-03 14:42:22 +02:00
2020-04-01 03:11:05 +02:00
/***** Update database *****/
DB_QueryREPLACE ("can not update status of test",
"REPLACE INTO tst_status"
" (SessionId,CrsCod,NumTst,Status)"
" VALUES"
" ('%s',%ld,%u,%u)",
Gbl.Session.Id,Gbl.Hierarchy.Crs.CrsCod,
NumTst,(unsigned) TstStatus);
}
2016-03-30 01:28:58 +02:00
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/****************************** Update test status ***************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static Tst_Status_t Tst_GetTstStatus (unsigned NumTst)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned UnsignedNum;
Tst_Status_t TstStatus = Tst_STATUS_ERROR;
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Get status of test from database *****/
if (DB_QuerySELECT (&mysql_res,"can not get status of test",
"SELECT Status" // row[0]
" FROM tst_status"
" WHERE SessionId='%s'"
" AND CrsCod=%ld"
" AND NumTst=%u",
Gbl.Session.Id,Gbl.Hierarchy.Crs.CrsCod,NumTst) == 1)
{
/* Get number of hits */
row = mysql_fetch_row (mysql_res);
if (row[0])
if (sscanf (row[0],"%u",&UnsignedNum) == 1)
if (UnsignedNum < Tst_NUM_STATUS)
TstStatus = (Tst_Status_t) UnsignedNum;
}
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
return TstStatus;
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************************* Get number of hits to test ************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static unsigned Tst_GetNumAccessesTst (void)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned long NumRows;
unsigned NumAccessesTst = 0;
2020-03-21 15:41:25 +01:00
2020-04-01 03:11:05 +02:00
if (Gbl.Usrs.Me.IBelongToCurrentCrs)
{
/***** Get number of hits to test from database *****/
NumRows = DB_QuerySELECT (&mysql_res,"can not get number of hits to test",
"SELECT NumAccTst" // row[0]
" FROM crs_usr"
" WHERE CrsCod=%ld AND UsrCod=%ld",
Gbl.Hierarchy.Crs.CrsCod,
Gbl.Usrs.Me.UsrDat.UsrCod);
2020-03-21 15:41:25 +01:00
2020-04-01 03:11:05 +02:00
if (NumRows == 0)
NumAccessesTst = 0;
else if (NumRows == 1)
{
/* Get number of hits */
row = mysql_fetch_row (mysql_res);
if (row[0] == NULL)
NumAccessesTst = 0;
else if (sscanf (row[0],"%u",&NumAccessesTst) != 1)
NumAccessesTst = 0;
}
else
Lay_ShowErrorAndExit ("Error when getting number of hits to test.");
2020-03-21 22:18:24 +01:00
2020-04-01 03:11:05 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
2020-03-26 21:39:44 +01:00
2020-04-01 03:11:05 +02:00
return NumAccessesTst;
2020-03-21 15:41:25 +01:00
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/*** Write the test questions when showing a new test exam to be answered ****/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_ShowTestQuestionsWhenSeeing (struct TsR_Result *Result)
2014-12-01 23:55:08 +01:00
{
MYSQL_RES *mysql_res;
2020-04-01 03:11:05 +02:00
MYSQL_ROW row;
unsigned NumQst;
struct Tst_Question Question;
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Trivial check *****/
if (Result->NumQsts == 0)
return;
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Begin table *****/
HTM_TABLE_BeginWideMarginPadding (10);
2016-03-21 02:13:19 +01:00
2020-04-01 03:11:05 +02:00
/***** Write one row for each question *****/
for (NumQst = 0;
NumQst < Result->NumQsts;
NumQst++)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
Gbl.RowEvenOdd = NumQst % 2;
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Create test question */
Tst_QstConstructor (&Question);
Question.QstCod = Result->Questions[NumQst].QstCod;
2017-05-01 21:17:38 +02:00
2020-04-01 03:11:05 +02:00
/* Show question */
if (Tst_GetOneQuestionByCod (Question.QstCod,&mysql_res)) // Question exists
{
/* Get row of the result of the query */
row = mysql_fetch_row (mysql_res);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Write question and answers */
Tst_WriteQstAndAnsSeeing (Result,NumQst,&Question,row);
}
else
Lay_ShowErrorAndExit ("Wrong question.");
2016-03-21 01:28:16 +01:00
2020-04-01 03:11:05 +02:00
/* Destroy test question */
Tst_QstDestructor (&Question);
2014-12-01 23:55:08 +01:00
}
2020-04-01 03:11:05 +02:00
/***** End table *****/
HTM_TABLE_End ();
2014-12-01 23:55:08 +01:00
}
2017-07-16 13:54:11 +02:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************************** Show list of test tags ***************************/
2020-03-21 15:41:25 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
void Tst_ShowTagList (unsigned NumTags,MYSQL_RES *mysql_res)
2020-03-21 15:41:25 +01:00
{
2020-04-01 03:11:05 +02:00
extern const char *Txt_no_tags;
MYSQL_ROW row;
unsigned NumTag;
2020-03-21 15:41:25 +01:00
2020-04-01 03:11:05 +02:00
if (NumTags)
{
/***** Write the tags *****/
HTM_UL_Begin (NULL);
for (NumTag = 0;
NumTag < NumTags;
NumTag++)
{
row = mysql_fetch_row (mysql_res);
HTM_LI_Begin (NULL);
HTM_Txt (row[0]);
HTM_LI_End ();
}
HTM_UL_End ();
}
else
HTM_Txt (Txt_no_tags);
2020-03-21 15:41:25 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/******************* Show the result of assessing a test *********************/
2017-07-16 13:54:11 +02:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_ShowTestResultAfterAssess (struct TsR_Result *Result)
2017-07-16 13:54:11 +02:00
{
2020-04-01 03:11:05 +02:00
extern const char *Txt_Question_removed;
2017-07-16 13:54:11 +02:00
MYSQL_RES *mysql_res;
2020-04-01 03:11:05 +02:00
MYSQL_ROW row;
unsigned NumQst;
2017-07-16 13:54:11 +02:00
2020-04-01 03:11:05 +02:00
/***** Initialize score and number of questions not blank *****/
Result->NumQstsNotBlank = 0;
Result->Score = 0.0;
2017-07-16 13:54:11 +02:00
2020-04-01 03:11:05 +02:00
for (NumQst = 0;
NumQst < Result->NumQsts;
NumQst++)
2017-07-16 13:54:11 +02:00
{
2020-04-01 03:11:05 +02:00
Gbl.RowEvenOdd = NumQst % 2;
2017-07-16 13:54:11 +02:00
2020-04-01 03:11:05 +02:00
/***** Query database *****/
if (Tst_GetOneQuestionByCod (Result->Questions[NumQst].QstCod,&mysql_res)) // Question exists
{
/***** Write question and answers *****/
row = mysql_fetch_row (mysql_res);
Tst_WriteQstAndAnsTestResult (&Gbl.Usrs.Me.UsrDat,
Result,
NumQst,
row,
TstCfg_GetConfigVisibility ());
2017-07-16 13:54:11 +02:00
2020-04-01 03:11:05 +02:00
/***** Store test result question in database *****/
TsR_StoreOneTestResultQstInDB (Result,
NumQst); // 0, 1, 2, 3...
2017-07-16 13:54:11 +02:00
2020-04-01 03:11:05 +02:00
/***** Compute total score *****/
Result->Score += Result->Questions[NumQst].Score;
if (Result->Questions[NumQst].AnswerIsNotBlank)
Result->NumQstsNotBlank++;
2017-07-16 13:54:11 +02:00
2020-04-01 03:11:05 +02:00
/***** Update the number of accesses and the score of this question *****/
if (Gbl.Usrs.Me.Role.Logged == Rol_STD)
Tst_UpdateQstScore (Result,NumQst);
}
else
{
/***** Question does not exists *****/
HTM_TR_Begin (NULL);
2017-07-16 13:54:11 +02:00
2020-04-01 03:11:05 +02:00
HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd);
Tst_WriteNumQst (NumQst + 1);
HTM_TD_End ();
2017-07-16 13:54:11 +02:00
2020-04-01 03:11:05 +02:00
HTM_TD_Begin ("class=\"DAT_LIGHT LT COLOR%u\"",Gbl.RowEvenOdd);
HTM_Txt (Txt_Question_removed);
HTM_TD_End ();
2017-07-16 13:54:11 +02:00
2020-04-01 03:11:05 +02:00
HTM_TR_End ();
}
2017-07-16 13:54:11 +02:00
2020-04-01 03:11:05 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
2017-07-16 13:54:11 +02:00
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/********** Write a row of a test, with one question and its answer **********/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_WriteQstAndAnsSeeing (struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question,
MYSQL_ROW row)
2016-11-07 10:35:36 +01:00
{
2020-04-01 03:11:05 +02:00
/*
row[0] UNIX_TIMESTAMP(EditTime)
row[1] AnsType
row[2] Shuffle
row[3] Stem
row[4] Feedback
row[5] MedCod
row[6] NumHits
row[7] NumHitsNotBlank
row[8] Score
*/
2016-11-07 10:35:36 +01:00
2020-04-01 03:11:05 +02:00
/***** Begin row *****/
HTM_TR_Begin (NULL);
2016-11-07 10:35:36 +01:00
2020-04-01 03:11:05 +02:00
/***** Number of question and answer type (row[1]) *****/
HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd);
Tst_WriteNumQst (NumQst + 1);
Question->Answer.Type = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[1]);
Tst_WriteAnswerType (Question->Answer.Type);
HTM_TD_End ();
2016-05-05 12:37:45 +02:00
2020-04-01 03:11:05 +02:00
/***** Stem, media and answers *****/
HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd);
2020-03-17 00:35:11 +01:00
2020-04-01 03:11:05 +02:00
/* Write parameter with question code */
Tst_WriteParamQstCod (NumQst,Question->QstCod);
2016-11-07 10:35:36 +01:00
2020-04-01 03:11:05 +02:00
/* Stem (row[3]) */
Tst_WriteQstStem (row[3],"TEST_EXA",true);
2016-11-07 10:35:36 +01:00
2020-04-01 03:11:05 +02:00
/* Media (row[5]) */
Question->Media.MedCod = Str_ConvertStrCodToLongCod (row[5]);
Med_GetMediaDataByCod (&Question->Media);
Med_ShowMedia (&Question->Media,
"TEST_MED_SHOW_CONT",
"TEST_MED_SHOW");
2016-11-07 10:35:36 +01:00
2020-04-01 03:11:05 +02:00
/* Answers */
Tst_WriteAnswersSeeing (Result,NumQst,Question);
2019-12-06 18:42:11 +01:00
2020-04-01 03:11:05 +02:00
HTM_TD_End ();
/***** End row *****/
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
}
2016-03-21 02:13:19 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/********** Write a row of a test, with one question and its answer **********/
2016-03-21 02:13:19 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
void Tst_WriteQstAndAnsTestResult (struct UsrData *UsrDat,
struct TsR_Result *Result,
unsigned NumQst,
MYSQL_ROW row,
unsigned Visibility)
2016-03-21 02:13:19 +01:00
{
2020-04-01 03:11:05 +02:00
struct Tst_Question Question;
bool IsVisibleQstAndAnsTxt = TsV_IsVisibleQstAndAnsTxt (Visibility);
/*
row[0] UNIX_TIMESTAMP(EditTime)
row[1] AnsType
row[2] Shuffle
row[3] Stem
row[4] Feedback
row[5] MedCod
row[6] NumHits
row[7] NumHitsNotBlank
row[8] Score
*/
2016-03-21 02:13:19 +01:00
2020-04-01 03:11:05 +02:00
/***** Create test question *****/
Tst_QstConstructor (&Question);
Question.QstCod = Result->Questions[NumQst].QstCod;
2016-03-21 02:13:19 +01:00
2020-04-01 03:11:05 +02:00
/***** Begin row *****/
HTM_TR_Begin (NULL);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Number of question and answer type (row[1]) *****/
HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd);
Tst_WriteNumQst (NumQst + 1);
Question.Answer.Type = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[1]);
Tst_WriteAnswerType (Question.Answer.Type);
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Stem, media and answers *****/
HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Stem (row[3]) */
Tst_WriteQstStem (row[3],"TEST_EXA",IsVisibleQstAndAnsTxt);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Media (row[5]) */
if (IsVisibleQstAndAnsTxt)
{
Question.Media.MedCod = Str_ConvertStrCodToLongCod (row[5]);
Med_GetMediaDataByCod (&Question.Media);
Med_ShowMedia (&Question.Media,
"TEST_MED_SHOW_CONT",
"TEST_MED_SHOW");
}
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Answers */
Tst_ComputeAnswerScore (Result,NumQst,&Question);
Tst_WriteAnswersResult (UsrDat,
Result,NumQst,&Question,
Visibility);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Question feedback (row[4]) */
if (TsV_IsVisibleFeedbackTxt (Visibility))
Tst_WriteQstFeedback (row[4],"TEST_EXA_LIGHT");
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** End row *****/
HTM_TR_End ();
/***** Destroy test question *****/
Tst_QstDestructor (&Question);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/********************* Write the number of a test question *******************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
// Number of question should be 1, 2, 3...
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
void Tst_WriteNumQst (unsigned NumQst)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
HTM_DIV_Begin ("class=\"BIG_INDEX\"");
HTM_Unsigned (NumQst);
HTM_DIV_End ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************************** Write the type of answer *************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
void Tst_WriteAnswerType (Tst_AnswerType_t AnswerType)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
extern const char *Txt_TST_STR_ANSWER_TYPES[Tst_NUM_ANS_TYPES];
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
HTM_DIV_Begin ("class=\"DAT_SMALL\"");
HTM_Txt (Txt_TST_STR_ANSWER_TYPES[AnswerType]);
HTM_DIV_End ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/********************* Write the stem of a test question *********************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
void Tst_WriteQstStem (const char *Stem,const char *ClassStem,bool Visible)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
unsigned long StemLength;
char *StemRigorousHTML;
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** DIV begin *****/
HTM_DIV_Begin ("class=\"%s\"",ClassStem);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Write stem *****/
if (Visible)
2016-05-31 20:28:17 +02:00
{
2020-04-01 03:11:05 +02:00
/* Convert the stem, that is in HTML, to rigorous HTML */
StemLength = strlen (Stem) * Str_MAX_BYTES_PER_CHAR;
if ((StemRigorousHTML = (char *) malloc (StemLength + 1)) == NULL)
Lay_NotEnoughMemoryExit ();
Str_Copy (StemRigorousHTML,Stem,
StemLength);
2016-05-31 20:28:17 +02:00
2020-04-01 03:11:05 +02:00
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
StemRigorousHTML,StemLength,false);
2016-05-31 20:28:17 +02:00
2020-04-01 03:11:05 +02:00
/* Write stem text */
HTM_Txt (StemRigorousHTML);
2016-05-31 20:28:17 +02:00
2020-04-01 03:11:05 +02:00
/* Free memory allocated for the stem */
free (StemRigorousHTML);
2016-05-31 11:21:04 +02:00
}
2020-04-01 03:11:05 +02:00
else
Ico_PutIconNotVisible ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** DIV end *****/
HTM_DIV_End ();
2014-12-01 23:55:08 +01:00
}
2020-03-21 15:41:25 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************* Put form to upload a new image for a test question ************/
2020-03-21 15:41:25 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_PutFormToEditQstMedia (const struct Media *Media,int NumMediaInForm,
bool OptionsDisabled)
2020-03-21 15:41:25 +01:00
{
2020-04-01 03:11:05 +02:00
extern const char *The_ClassFormInBox[The_NUM_THEMES];
extern const char *Txt_No_image_video;
extern const char *Txt_Current_image_video;
extern const char *Txt_Change_image_video;
static unsigned UniqueId = 0;
struct ParamUploadMedia ParamUploadMedia;
2020-03-21 15:41:25 +01:00
2020-04-01 03:11:05 +02:00
if (Media->Name[0])
2020-03-21 15:41:25 +01:00
{
2020-04-01 03:11:05 +02:00
/***** Set names of parameters depending on number of image in form *****/
Med_SetParamNames (&ParamUploadMedia,NumMediaInForm);
2020-03-21 15:41:25 +01:00
2020-04-01 03:11:05 +02:00
/***** Start container *****/
HTM_DIV_Begin ("class=\"TEST_MED_EDIT_FORM\"");
2020-03-21 15:41:25 +01:00
2020-04-01 03:11:05 +02:00
/***** Choice 1: No media *****/
HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
HTM_INPUT_RADIO (ParamUploadMedia.Action,false,
"value=\"%u\"%s",
(unsigned) Med_ACTION_NO_MEDIA,
OptionsDisabled ? " disabled=\"disabled\"" : "");
HTM_Txt (Txt_No_image_video);
HTM_LABEL_End ();
HTM_BR ();
2020-03-21 15:41:25 +01:00
2020-04-01 03:11:05 +02:00
/***** Choice 2: Current media *****/
HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
HTM_INPUT_RADIO (ParamUploadMedia.Action,false,
"value=\"%u\"%s checked=\"checked\"",
(unsigned) Med_ACTION_KEEP_MEDIA,
OptionsDisabled ? " disabled=\"disabled\"" : "");
HTM_Txt (Txt_Current_image_video);
HTM_LABEL_End ();
Med_ShowMedia (Media,
"TEST_MED_EDIT_ONE_CONT",
"TEST_MED_EDIT_ONE");
2020-03-21 15:41:25 +01:00
2020-04-01 03:11:05 +02:00
/***** Choice 3: Change media *****/
UniqueId++;
HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
HTM_INPUT_RADIO (ParamUploadMedia.Action,false,
"id=\"chg_img_%u\" value=\"%u\"%s",
UniqueId,
(unsigned) Med_ACTION_NEW_MEDIA,
OptionsDisabled ? " disabled=\"disabled\"" : "");
HTM_TxtColonNBSP (Txt_Change_image_video);
HTM_LABEL_End ();
Med_PutMediaUploader (NumMediaInForm,"TEST_MED_INPUT");
/***** End container *****/
HTM_DIV_End ();
}
else // No current image
/***** Attached media *****/
Med_PutMediaUploader (NumMediaInForm,"TEST_MED_INPUT");
2020-03-21 15:41:25 +01:00
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/******************* Write the feedback of a test question *******************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
void Tst_WriteQstFeedback (const char *Feedback,const char *ClassFeedback)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
unsigned long FeedbackLength;
char *FeedbackRigorousHTML;
if (Feedback)
if (Feedback[0])
{
/***** Convert the feedback, that is in HTML, to rigorous HTML *****/
FeedbackLength = strlen (Feedback) * Str_MAX_BYTES_PER_CHAR;
if ((FeedbackRigorousHTML = (char *) malloc (FeedbackLength + 1)) == NULL)
Lay_NotEnoughMemoryExit ();
Str_Copy (FeedbackRigorousHTML,Feedback,
FeedbackLength);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
FeedbackRigorousHTML,FeedbackLength,false);
/***** Write the feedback *****/
HTM_DIV_Begin ("class=\"%s\"",ClassFeedback);
HTM_Txt (FeedbackRigorousHTML);
HTM_DIV_End ();
/***** Free memory allocated for the feedback *****/
free (FeedbackRigorousHTML);
}
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/*********************** Update the score of a question **********************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_UpdateQstScore (const struct TsR_Result *Result,unsigned NumQst)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
/***** Update number of clicks and score of the question *****/
Str_SetDecimalPointToUS (); // To print the floating point as a dot
if (Result->Questions[NumQst].AnswerIsNotBlank)
DB_QueryUPDATE ("can not update the score of a question",
"UPDATE tst_questions"
" SET NumHits=NumHits+1,NumHitsNotBlank=NumHitsNotBlank+1,"
"Score=Score+(%.15lg)"
" WHERE QstCod=%ld",
Result->Questions[NumQst].Score,
Result->Questions[NumQst].QstCod);
else // The answer is blank
DB_QueryUPDATE ("can not update the score of a question",
"UPDATE tst_questions"
" SET NumHits=NumHits+1"
" WHERE QstCod=%ld",
Result->Questions[NumQst].QstCod);
Str_SetDecimalPointToLocal (); // Return to local system
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/*********** Update my number of accesses to test in this course *************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_UpdateMyNumAccessTst (unsigned NumAccessesTst)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
/***** Update my number of accesses to test in this course *****/
DB_QueryUPDATE ("can not update the number of accesses to test",
"UPDATE crs_usr SET NumAccTst=%u"
" WHERE CrsCod=%ld AND UsrCod=%ld",
NumAccessesTst,
Gbl.Hierarchy.Crs.CrsCod,Gbl.Usrs.Me.UsrDat.UsrCod);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************ Update date-time of my next allowed access to test *************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_UpdateLastAccTst (unsigned NumQsts)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
/***** Update date-time and number of questions of this test *****/
DB_QueryUPDATE ("can not update time and number of questions of this test",
"UPDATE crs_usr SET LastAccTst=NOW(),NumQstsLastTst=%u"
" WHERE CrsCod=%ld AND UsrCod=%ld",
NumQsts,
Gbl.Hierarchy.Crs.CrsCod,
Gbl.Usrs.Me.UsrDat.UsrCod);
}
2019-10-10 10:41:00 +02:00
2020-04-01 03:11:05 +02:00
/*****************************************************************************/
/*********************** Request the edition of tests ************************/
/*****************************************************************************/
2019-10-10 10:41:00 +02:00
2020-04-01 03:11:05 +02:00
void Tst_RequestEditTests (void)
{
struct Tst_Test Test;
2019-11-04 20:41:35 +01:00
2020-04-01 03:11:05 +02:00
/***** Create test *****/
Tst_TstConstructor (&Test);
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
/***** Show form to generate a self-assessment test *****/
Tst_ShowFormRequestEditTests (&Test);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Destroy test *****/
Tst_TstDestructor (&Test);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/******* Select tags and dates for edition of the self-assessment test *******/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_ShowFormRequestEditTests (struct Tst_Test *Test)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
extern const char *Hlp_ASSESSMENT_Tests_editing_questions;
2016-03-21 02:13:19 +01:00
extern const char *Txt_No_test_questions;
2020-04-01 03:11:05 +02:00
extern const char *Txt_List_edit_questions;
extern const char *Txt_Show_questions;
2014-12-01 23:55:08 +01:00
MYSQL_RES *mysql_res;
2020-04-01 03:11:05 +02:00
static const Dat_SetHMS SetHMS[Dat_NUM_START_END_TIME] =
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
Dat_HMS_DO_NOT_SET,
Dat_HMS_DO_NOT_SET
};
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Contextual menu *****/
Mnu_ContextMenuBegin ();
TsI_PutFormToImportQuestions (); // Import questions from XML file
Mnu_ContextMenuEnd ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Begin box *****/
Box_BoxBegin (NULL,Txt_List_edit_questions,
Tst_PutIconsTests,Test,
Hlp_ASSESSMENT_Tests_editing_questions,Box_NOT_CLOSABLE);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Get tags already present in the table of questions *****/
if ((Test->Tags.Num = Tst_GetAllTagsFromCurrentCrs (&mysql_res)))
{
Frm_StartForm (ActLstTstQst);
Par_PutHiddenParamUnsigned (NULL,"Order",(unsigned) Tst_DEFAULT_ORDER);
2019-10-10 10:41:00 +02:00
2020-04-01 03:11:05 +02:00
HTM_TABLE_BeginPadding (2);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Selection of tags *****/
Tst_ShowFormSelTags (&Test->Tags,mysql_res,false);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Selection of types of answers *****/
Tst_ShowFormAnswerTypes (&Test->AnswerTypes);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Starting and ending dates in the search *****/
Dat_PutFormStartEndClientLocalDateTimesWithYesterdayToday (SetHMS);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
HTM_TABLE_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Send button *****/
Btn_PutConfirmButton (Txt_Show_questions);
Frm_EndForm ();
}
else // No test questions
{
/***** Warning message *****/
Ale_ShowAlert (Ale_INFO,Txt_No_test_questions);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Button to create a new question *****/
Tst_PutButtonToAddQuestion ();
}
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** End box *****/
Box_BoxEnd ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/******************* Select test questions for a game ************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
void Tst_RequestSelectTestsForGame (void)
2014-12-01 23:55:08 +01:00
{
2020-03-26 21:39:44 +01:00
struct Tst_Test Test;
2014-12-01 23:55:08 +01:00
2020-03-26 21:39:44 +01:00
/***** Create test *****/
Tst_TstConstructor (&Test);
2020-04-01 03:11:05 +02:00
/***** Show form to select test for game *****/
Tst_ShowFormRequestSelectTestsForGame (&Test); // No tags selected
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Destroy test *****/
Tst_TstDestructor (&Test);
}
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
/*****************************************************************************/
/************** Show form to select test questions for a game ****************/
/*****************************************************************************/
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
static void Tst_ShowFormRequestSelectTestsForGame (struct Tst_Test *Test)
{
extern const char *Hlp_ASSESSMENT_Games_questions;
extern const char *Txt_No_test_questions;
extern const char *Txt_Select_questions;
extern const char *Txt_Show_questions;
MYSQL_RES *mysql_res;
static const Dat_SetHMS SetHMS[Dat_NUM_START_END_TIME] =
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
Dat_HMS_DO_NOT_SET,
Dat_HMS_DO_NOT_SET
};
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
/***** Begin box *****/
Box_BoxBegin (NULL,Txt_Select_questions,
NULL,NULL,
Hlp_ASSESSMENT_Games_questions,Box_NOT_CLOSABLE);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Get tags already present in the table of questions *****/
if ((Test->Tags.Num = Tst_GetAllTagsFromCurrentCrs (&mysql_res)))
{
Frm_StartForm (ActGamLstTstQst);
Gam_PutParams (&Gbl);
2020-02-17 09:31:32 +01:00
2020-04-01 03:11:05 +02:00
HTM_TABLE_BeginPadding (2);
2020-02-17 09:31:32 +01:00
2020-04-01 03:11:05 +02:00
/***** Selection of tags *****/
Tst_ShowFormSelTags (&Test->Tags,mysql_res,false);
2020-02-17 09:31:32 +01:00
2020-04-01 03:11:05 +02:00
/***** Starting and ending dates in the search *****/
Dat_PutFormStartEndClientLocalDateTimesWithYesterdayToday (SetHMS);
2020-02-17 09:31:32 +01:00
2020-04-01 03:11:05 +02:00
HTM_TABLE_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Send button *****/
Btn_PutConfirmButton (Txt_Show_questions);
Frm_EndForm ();
}
else // No test questions
{
/***** Warning message *****/
Ale_ShowAlert (Ale_INFO,Txt_No_test_questions);
2015-04-11 23:46:21 +02:00
2020-04-01 03:11:05 +02:00
/***** Button to create a new question *****/
Tst_PutButtonToAddQuestion ();
}
2016-05-05 12:37:45 +02:00
2017-06-12 14:16:33 +02:00
/***** End box *****/
2019-10-25 22:48:34 +02:00
Box_BoxEnd ();
2020-03-26 21:39:44 +01:00
2020-04-01 03:11:05 +02:00
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************************* Check if I can edit tests *************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static bool Tst_CheckIfICanEditTests (void)
2016-12-26 15:17:30 +01:00
{
2020-04-01 03:11:05 +02:00
return (bool) (Gbl.Usrs.Me.Role.Logged == Rol_TCH ||
Gbl.Usrs.Me.Role.Logged == Rol_SYS_ADM);
}
2019-11-04 01:29:46 +01:00
2020-04-01 03:11:05 +02:00
/*****************************************************************************/
/********************* Put contextual icons in tests *************************/
/*****************************************************************************/
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
static void Tst_PutIconsTests (void *TestPtr)
{
extern const char *Txt_New_question;
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
if (TestPtr)
{
if (Tst_CheckIfICanEditTests ())
{
switch (Gbl.Action.Act)
{
case ActLstTstQst: // List selected test questions for edition
case ActReqRemSevTstQst: // Request removal of selected questions
case ActReqRemOneTstQst: // Request removal of a question
case ActRemOneTstQst: // Remove a question
case ActChgShfTstQst: // Change shuffle of a question
/***** Put form to remove selected test questions *****/
Ico_PutContextualIconToRemove (ActReqRemSevTstQst,
Tst_PutParamsRemoveSelectedQsts,TestPtr);
break;
default:
break;
}
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
if (Gbl.Action.Act != ActEdiTstQst)
/***** Put form to edit existing test questions *****/
Ico_PutContextualIconToEdit (ActEdiTstQst,NULL,
NULL,NULL);
2016-12-26 15:17:30 +01:00
2020-04-01 03:11:05 +02:00
if (Gbl.Action.Act != ActEdiOneTstQst)
/***** Put form to create a new test question *****/
Ico_PutContextualIconToAdd (ActEdiOneTstQst,NULL,
NULL,NULL,
Txt_New_question);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Put form to go to test configuration *****/
Ico_PutContextualIconToConfigure (ActCfgTst,
NULL,NULL);
}
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Put icon to view tests results *****/
switch (Gbl.Usrs.Me.Role.Logged)
{
case Rol_STD:
Ico_PutContextualIconToShowResults (ActReqSeeMyTstRes,NULL,
NULL,NULL);
break;
case Rol_NET:
case Rol_TCH:
case Rol_SYS_ADM:
Ico_PutContextualIconToShowResults (ActReqSeeUsrTstRes,NULL,
NULL,NULL);
break;
default:
break;
}
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
/***** Put icon to show a figure *****/
Gbl.Figures.FigureType = Fig_TESTS;
Fig_PutIconToShowFigure ();
}
}
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/*****************************************************************************/
/**************** Put button to create a new test question *******************/
/*****************************************************************************/
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
static void Tst_PutButtonToAddQuestion (void)
{
extern const char *Txt_New_question;
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
Frm_StartForm (ActEdiOneTstQst);
Btn_PutConfirmButton (Txt_New_question);
Frm_EndForm ();
}
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
/*****************************************************************************/
/***************************** Form to rename tags ***************************/
/*****************************************************************************/
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
void Tst_ShowFormConfig (void)
{
extern const char *Txt_Please_specify_if_you_allow_access_to_test_questions_from_mobile_applications;
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
/***** If current course has tests and pluggable is unknown... *****/
if (Tst_CheckIfCourseHaveTestsAndPluggableIsUnknown ())
Ale_ShowAlert (Ale_WARNING,Txt_Please_specify_if_you_allow_access_to_test_questions_from_mobile_applications);
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
/***** Form to configure test *****/
Tst_ShowFormConfigTst ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Form to edit tags *****/
Tst_ShowFormEditTags ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/******************************* Enable a test tag ***************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
void Tst_EnableTag (void)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
long TagCod = Tst_GetParamTagCode ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Change tag status to enabled *****/
Tst_EnableOrDisableTag (TagCod,false);
2020-03-26 21:39:44 +01:00
2020-04-01 03:11:05 +02:00
/***** Show again the form to configure test *****/
Tst_ShowFormConfig ();
}
2020-03-17 00:35:11 +01:00
2020-04-01 03:11:05 +02:00
/*****************************************************************************/
/****************************** Disable a test tag ***************************/
/*****************************************************************************/
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
void Tst_DisableTag (void)
{
long TagCod = Tst_GetParamTagCode ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Change tag status to disabled *****/
Tst_EnableOrDisableTag (TagCod,true);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Show again the form to configure test *****/
Tst_ShowFormConfig ();
2014-12-01 23:55:08 +01:00
}
2017-07-16 13:54:11 +02:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************************* Get parameter with tag code ***********************/
2017-07-16 13:54:11 +02:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static long Tst_GetParamTagCode (void)
2017-07-16 13:54:11 +02:00
{
2020-04-01 03:11:05 +02:00
long TagCod;
2017-07-16 13:54:11 +02:00
2020-04-01 03:11:05 +02:00
/***** Get tag code *****/
if ((TagCod = Par_GetParToLong ("TagCod")) <= 0)
Lay_ShowErrorAndExit ("Code of tag is missing.");
2017-07-16 13:54:11 +02:00
2020-04-01 03:11:05 +02:00
return TagCod;
2017-07-16 13:54:11 +02:00
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************************ Rename a tag of test questions *********************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
void Tst_RenameTag (void)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
extern const char *Txt_The_tag_X_has_been_renamed_as_Y;
extern const char *Txt_The_tag_X_has_not_changed;
char OldTagTxt[Tst_MAX_BYTES_TAG + 1];
char NewTagTxt[Tst_MAX_BYTES_TAG + 1];
long ExistingTagCod;
long OldTagCod;
bool ComplexRenaming;
2018-10-29 22:22:02 +01:00
2020-04-01 03:11:05 +02:00
/***** Get old and new tags from the form *****/
Par_GetParToText ("OldTagTxt",OldTagTxt,Tst_MAX_BYTES_TAG);
Par_GetParToText ("NewTagTxt",NewTagTxt,Tst_MAX_BYTES_TAG);
/***** Check that the new tag is not empty *****/
if (NewTagTxt[0]) // New tag not empty
{
/***** Check if the old tag is equal to the new one *****/
if (!strcmp (OldTagTxt,NewTagTxt)) // The old and the new tag
// are exactly the same (case sensitively).
// This happens when user press INTRO
// without changing anything in the form.
Ale_ShowAlert (Ale_INFO,Txt_The_tag_X_has_not_changed,
NewTagTxt);
else // The old and the new tag
// are not exactly the same (case sensitively).
{
/***** Check if renaming is complex or easy *****/
ComplexRenaming = false;
if (strcasecmp (OldTagTxt,NewTagTxt)) // The old and the new tag
// are not the same (case insensitively)
/* Check if the new tag text is equal to any of the tags
already present in the database */
if ((ExistingTagCod = Tst_GetTagCodFromTagTxt (NewTagTxt)) > 0)
// The new tag was already in database
ComplexRenaming = true;
if (ComplexRenaming) // Renaming is not easy
{
/***** Complex update made to not repeat tags:
- If the new tag existed for a question ==>
delete old tag from tst_question_tags;
the new tag will remain
- If the new tag did not exist for a question ==>
change old tag to new tag in tst_question_tags *****/
/* Get tag code of the old tag */
if ((OldTagCod = Tst_GetTagCodFromTagTxt (OldTagTxt)) < 0)
Lay_ShowErrorAndExit ("Tag does not exists.");
/* Create a temporary table with all the question codes
that had the new tag as one of their tags */
DB_Query ("can not remove temporary table",
"DROP TEMPORARY TABLE IF EXISTS tst_question_tags_tmp");
DB_Query ("can not create temporary table",
"CREATE TEMPORARY TABLE tst_question_tags_tmp"
" ENGINE=MEMORY"
" SELECT QstCod FROM tst_question_tags"
" WHERE TagCod=%ld",
ExistingTagCod);
/* Remove old tag in questions where it would be repeated */
// New tag existed for a question ==> delete old tag
DB_QueryDELETE ("can not remove a tag from some questions",
"DELETE FROM tst_question_tags"
" WHERE TagCod=%ld"
" AND QstCod IN"
" (SELECT QstCod FROM tst_question_tags_tmp)",
OldTagCod);
/* Change old tag to new tag in questions where it would not be repeated */
// New tag did not exist for a question ==> change old tag to new tag
DB_QueryUPDATE ("can not update a tag in some questions",
"UPDATE tst_question_tags"
" SET TagCod=%ld"
" WHERE TagCod=%ld"
" AND QstCod NOT IN"
" (SELECT QstCod FROM tst_question_tags_tmp)",
ExistingTagCod,
OldTagCod);
/* Drop temporary table, no longer necessary */
DB_Query ("can not remove temporary table",
"DROP TEMPORARY TABLE IF EXISTS tst_question_tags_tmp");
/***** Delete old tag from tst_tags
because it is not longer used *****/
DB_QueryDELETE ("can not remove old tag",
"DELETE FROM tst_tags WHERE TagCod=%ld",
OldTagCod);
}
else // Renaming is easy
{
/***** Simple update replacing each instance of the old tag by the new tag *****/
DB_QueryUPDATE ("can not update tag",
"UPDATE tst_tags SET TagTxt='%s',ChangeTime=NOW()"
" WHERE tst_tags.CrsCod=%ld"
" AND tst_tags.TagTxt='%s'",
NewTagTxt,Gbl.Hierarchy.Crs.CrsCod,OldTagTxt);
}
/***** Write message to show the change made *****/
Ale_ShowAlert (Ale_SUCCESS,Txt_The_tag_X_has_been_renamed_as_Y,
OldTagTxt,NewTagTxt);
}
}
else // New tag empty
Ale_ShowAlertYouCanNotLeaveFieldEmpty ();
/***** Show again the form to configure test *****/
Tst_ShowFormConfig ();
}
/*****************************************************************************/
/*************** Get configuration of test for current course ****************/
/*****************************************************************************/
// Returns true if course has test tags and pluggable is unknown
// Return false if course has no test tags or pluggable is known
bool Tst_CheckIfCourseHaveTestsAndPluggableIsUnknown (void)
{
extern const char *TstCfg_PluggableDB[TstCfg_NUM_OPTIONS_PLUGGABLE];
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned long NumRows;
TstCfg_Pluggable_t Pluggable;
/***** Get pluggability of tests for current course from database *****/
NumRows = DB_QuerySELECT (&mysql_res,"can not get configuration of test",
"SELECT Pluggable" // row[0]
" FROM tst_config"
" WHERE CrsCod=%ld",
Gbl.Hierarchy.Crs.CrsCod);
if (NumRows == 0)
TstCfg_SetConfigPluggable (TstCfg_PLUGGABLE_UNKNOWN);
else // NumRows == 1
{
/***** Get whether test are visible via plugins or not *****/
row = mysql_fetch_row (mysql_res);
TstCfg_SetConfigPluggable (TstCfg_PLUGGABLE_UNKNOWN);
for (Pluggable = TstCfg_PLUGGABLE_NO;
Pluggable <= TstCfg_PLUGGABLE_YES;
Pluggable++)
if (!strcmp (row[0],TstCfg_PluggableDB[Pluggable]))
{
TstCfg_SetConfigPluggable (Pluggable);
break;
}
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
/***** Get if current course has tests from database *****/
if (TstCfg_GetConfigPluggable () == TstCfg_PLUGGABLE_UNKNOWN)
return Tst_CheckIfCurrentCrsHasTestTags (); // Return true if course has tests
return false; // Pluggable is not unknown
}
/*****************************************************************************/
/******************* Check if current course has test tags *******************/
/*****************************************************************************/
// Return the number of rows of the result
static bool Tst_CheckIfCurrentCrsHasTestTags (void)
{
/***** Get available tags from database *****/
return (DB_QueryCOUNT ("can not check if course has tags",
"SELECT COUNT(*) FROM tst_tags"
" WHERE CrsCod=%ld",
Gbl.Hierarchy.Crs.CrsCod) != 0);
}
/*****************************************************************************/
/********* Get all (enabled or disabled) test tags for this course ***********/
/*****************************************************************************/
// Return the number of rows of the result
static unsigned Tst_GetAllTagsFromCurrentCrs (MYSQL_RES **mysql_res)
{
/***** Get available tags from database *****/
return (unsigned) DB_QuerySELECT (mysql_res,"can not get available tags",
"SELECT TagCod," // row[0]
"TagTxt," // row[1]
"TagHidden" // row[2]
" FROM tst_tags"
" WHERE CrsCod=%ld"
" ORDER BY TagTxt",
Gbl.Hierarchy.Crs.CrsCod);
}
/*****************************************************************************/
/********************** Get enabled test tags for this course ****************/
/*****************************************************************************/
// Return the number of rows of the result
static unsigned Tst_GetEnabledTagsFromThisCrs (MYSQL_RES **mysql_res)
{
/***** Get available not hidden tags from database *****/
return (unsigned) DB_QuerySELECT (mysql_res,"can not get available enabled tags",
"SELECT TagCod," // row[0]
"TagTxt" // row[1]
" FROM tst_tags"
" WHERE CrsCod=%ld AND TagHidden='N'"
" ORDER BY TagTxt",
Gbl.Hierarchy.Crs.CrsCod);
}
/*****************************************************************************/
/********************* Show a form to select test tags ***********************/
/*****************************************************************************/
static void Tst_ShowFormSelTags (const struct Tst_Tags *Tags,
MYSQL_RES *mysql_res,
bool ShowOnlyEnabledTags)
{
extern const char *The_ClassFormInBox[The_NUM_THEMES];
extern const char *Txt_Tags;
extern const char *Txt_All_tags;
extern const char *Txt_Tag_not_allowed;
extern const char *Txt_Tag_allowed;
unsigned NumTag;
MYSQL_ROW row;
bool TagHidden = false;
bool Checked;
const char *Ptr;
char TagText[Tst_MAX_BYTES_TAG + 1];
/*
row[0] TagCod
row[1] TagTxt
row[2] TagHidden
*/
HTM_TR_Begin (NULL);
/***** Label *****/
HTM_TD_Begin ("class=\"RT %s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
HTM_TxtF ("%s:",Txt_Tags);
HTM_TD_End ();
/***** Select all tags *****/
HTM_TD_Begin ("class=\"LT\"");
HTM_TABLE_BeginPadding (2);
HTM_TR_Begin (NULL);
if (!ShowOnlyEnabledTags)
HTM_TD_Empty (1);
HTM_TD_Begin ("class=\"LM\"");
HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
HTM_INPUT_CHECKBOX ("AllTags",HTM_DONT_SUBMIT_ON_CHANGE,
"value=\"Y\"%s onclick=\"togglecheckChildren(this,'ChkTag');\"",
Tags->All ? " checked=\"checked\"" :
"");
HTM_TxtF ("&nbsp;%s",Txt_All_tags);
HTM_LABEL_End ();
HTM_TD_End ();
HTM_TR_End ();
/***** Select tags one by one *****/
for (NumTag = 1;
NumTag <= Tags->Num;
NumTag++)
{
row = mysql_fetch_row (mysql_res);
HTM_TR_Begin (NULL);
if (!ShowOnlyEnabledTags)
{
TagHidden = (row[2][0] == 'Y');
HTM_TD_Begin ("class=\"LM\"");
Ico_PutIconOff (TagHidden ? "eye-slash-red.svg" :
"eye-green.svg",
TagHidden ? Txt_Tag_not_allowed :
Txt_Tag_allowed);
HTM_TD_End ();
}
Checked = false;
if (Tags->List)
{
Ptr = Tags->List;
while (*Ptr)
{
Par_GetNextStrUntilSeparParamMult (&Ptr,TagText,Tst_MAX_BYTES_TAG);
if (!strcmp (row[1],TagText))
{
Checked = true;
break;
}
}
}
HTM_TD_Begin ("class=\"LM\"");
HTM_LABEL_Begin ("class=\"DAT\"");
HTM_INPUT_CHECKBOX ("ChkTag",HTM_DONT_SUBMIT_ON_CHANGE,
"value=\"%s\"%s onclick=\"checkParent(this,'AllTags');\"",
row[1],
Checked ? " checked=\"checked\"" :
"");
HTM_TxtF ("&nbsp;%s",row[1]);
HTM_LABEL_End ();
HTM_TD_End ();
HTM_TR_End ();
}
HTM_TABLE_End ();
HTM_TD_End ();
HTM_TR_End ();
}
/*****************************************************************************/
/************* Show a form to enable/disable and rename test tags ************/
/*****************************************************************************/
static void Tst_ShowFormEditTags (void)
{
extern const char *Hlp_ASSESSMENT_Tests_writing_a_question;
extern const char *Txt_No_test_questions;
extern const char *Txt_Tags;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumTags;
unsigned NumTag;
long TagCod;
/***** Get current tags in current course *****/
if ((NumTags = Tst_GetAllTagsFromCurrentCrs (&mysql_res)))
{
/***** Begin box and table *****/
Box_BoxTableBegin (NULL,Txt_Tags,
NULL,NULL,
Hlp_ASSESSMENT_Tests_writing_a_question,Box_NOT_CLOSABLE,2);
/***** Show tags *****/
for (NumTag = 0;
NumTag < NumTags;
NumTag++)
{
row = mysql_fetch_row (mysql_res);
/*
row[0] TagCod
row[1] TagTxt
row[2] TagHidden
*/
if ((TagCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
Lay_ShowErrorAndExit ("Wrong code of tag.");
HTM_TR_Begin (NULL);
/* Form to enable / disable this tag */
if (row[2][0] == 'Y') // Tag disabled
Tst_PutIconEnable (TagCod,row[1]);
else
Tst_PutIconDisable (TagCod,row[1]);
/* Form to rename this tag */
HTM_TD_Begin ("class=\"LM\"");
Frm_StartForm (ActRenTag);
Par_PutHiddenParamString (NULL,"OldTagTxt",row[1]);
HTM_INPUT_TEXT ("NewTagTxt",Tst_MAX_CHARS_TAG,row[1],true,
"size=\"36\" required=\"required\"");
Frm_EndForm ();
HTM_TD_End ();
HTM_TR_End ();
}
/***** End table and box *****/
Box_BoxTableEnd ();
}
else
Ale_ShowAlert (Ale_INFO,Txt_No_test_questions);
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
/******************* Put a link and an icon to enable a tag ******************/
/*****************************************************************************/
static void Tst_PutIconEnable (long TagCod,const char *TagTxt)
{
extern const char *Txt_Tag_X_not_allowed_Click_to_allow_it;
HTM_TD_Begin ("class=\"BM\"");
Frm_StartForm (ActEnableTag);
Par_PutHiddenParamLong (NULL,"TagCod",TagCod);
Ico_PutIconLink ("eye-slash-red.svg",
Str_BuildStringStr (Txt_Tag_X_not_allowed_Click_to_allow_it,
TagTxt));
Str_FreeString ();
Frm_EndForm ();
HTM_TD_End ();
}
/*****************************************************************************/
/****************** Put a link and an icon to disable a tag ******************/
/*****************************************************************************/
static void Tst_PutIconDisable (long TagCod,const char *TagTxt)
{
extern const char *Txt_Tag_X_allowed_Click_to_disable_it;
HTM_TD_Begin ("class=\"BM\"");
Frm_StartForm (ActDisableTag);
Par_PutHiddenParamLong (NULL,"TagCod",TagCod);
Ico_PutIconLink ("eye-green.svg",
Str_BuildStringStr (Txt_Tag_X_allowed_Click_to_disable_it,
TagTxt));
Str_FreeString ();
Frm_EndForm ();
HTM_TD_End ();
}
/*****************************************************************************/
/********************* Show a form to to configure test **********************/
/*****************************************************************************/
static void Tst_ShowFormConfigTst (void)
{
extern const char *Hlp_ASSESSMENT_Tests;
extern const char *The_ClassFormInBox[The_NUM_THEMES];
extern const char *Txt_Configure_tests;
extern const char *Txt_Plugins;
extern const char *Txt_TST_PLUGGABLE[TstCfg_NUM_OPTIONS_PLUGGABLE];
extern const char *Txt_No_of_questions;
extern const char *Txt_minimum;
extern const char *Txt_default;
extern const char *Txt_maximum;
extern const char *Txt_Minimum_time_seconds_per_question_between_two_tests;
extern const char *Txt_Result_visibility;
extern const char *Txt_Save_changes;
struct Tst_Test Test;
TstCfg_Pluggable_t Pluggable;
char StrMinTimeNxtTstPerQst[Cns_MAX_DECIMAL_DIGITS_ULONG + 1];
/***** Create test *****/
Tst_TstConstructor (&Test);
/***** Read test configuration from database *****/
TstCfg_GetConfigFromDB ();
/***** Begin box *****/
Box_BoxBegin (NULL,Txt_Configure_tests,
Tst_PutIconsTests,&Test,
Hlp_ASSESSMENT_Tests,Box_NOT_CLOSABLE);
/***** Begin form *****/
Frm_StartForm (ActRcvCfgTst);
/***** Tests are visible from plugins? *****/
HTM_TABLE_BeginCenterPadding (2);
HTM_TR_Begin (NULL);
HTM_TD_Begin ("class=\"%s RT\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
HTM_TxtF ("%s:",Txt_Plugins);
HTM_TD_End ();
HTM_TD_Begin ("class=\"LB\"");
for (Pluggable = TstCfg_PLUGGABLE_NO;
Pluggable <= TstCfg_PLUGGABLE_YES;
Pluggable++)
{
HTM_LABEL_Begin ("class=\"DAT\"");
HTM_INPUT_RADIO ("Pluggable",false,
"value=\"%u\"%s",
(unsigned) Pluggable,
Pluggable == TstCfg_GetConfigPluggable () ? " checked=\"checked\"" :
"");
HTM_Txt (Txt_TST_PLUGGABLE[Pluggable]);
HTM_LABEL_End ();
HTM_BR ();
}
HTM_TD_End ();
HTM_TR_End ();
/***** Number of questions *****/
HTM_TR_Begin (NULL);
HTM_TD_Begin ("class=\"%s RT\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
HTM_TxtF ("%s:",Txt_No_of_questions);
HTM_TD_End ();
HTM_TD_Begin ("class=\"LB\"");
HTM_TABLE_BeginPadding (2);
Tst_PutInputFieldNumQst ("NumQstMin",Txt_minimum,
TstCfg_GetConfigMin ()); // Minimum number of questions
Tst_PutInputFieldNumQst ("NumQstDef",Txt_default,
TstCfg_GetConfigDef ()); // Default number of questions
Tst_PutInputFieldNumQst ("NumQstMax",Txt_maximum,
TstCfg_GetConfigMax ()); // Maximum number of questions
HTM_TABLE_End ();
HTM_TD_End ();
HTM_TR_End ();
/***** Minimum time between consecutive tests, per question *****/
HTM_TR_Begin (NULL);
/* Label */
Frm_LabelColumn ("RT","MinTimeNxtTstPerQst",
Txt_Minimum_time_seconds_per_question_between_two_tests);
/* Data */
HTM_TD_Begin ("class=\"LB\"");
snprintf (StrMinTimeNxtTstPerQst,sizeof (StrMinTimeNxtTstPerQst),
"%lu",
TstCfg_GetConfigMinTimeNxtTstPerQst ());
HTM_INPUT_TEXT ("MinTimeNxtTstPerQst",Cns_MAX_DECIMAL_DIGITS_ULONG,StrMinTimeNxtTstPerQst,false,
"id=\"MinTimeNxtTstPerQst\" size=\"7\" 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\"");
TsV_PutVisibilityCheckboxes (TstCfg_GetConfigVisibility ());
HTM_TD_End ();
HTM_TR_End ();
HTM_TABLE_End ();
/***** Send button *****/
Btn_PutConfirmButton (Txt_Save_changes);
/***** End form *****/
Frm_EndForm ();
/***** End box *****/
Box_BoxEnd ();
/***** Destroy test *****/
Tst_TstDestructor (&Test);
}
/*****************************************************************************/
/*************** Get configuration of test for current course ****************/
/*****************************************************************************/
static void Tst_PutInputFieldNumQst (const char *Field,const char *Label,
unsigned Value)
{
char StrValue[Cns_MAX_DECIMAL_DIGITS_UINT + 1];
HTM_TR_Begin (NULL);
HTM_TD_Begin ("class=\"RM\"");
HTM_LABEL_Begin ("for=\"%s\" class=\"DAT\"",Field);
HTM_Txt (Label);
HTM_LABEL_End ();
HTM_TD_End ();
HTM_TD_Begin ("class=\"LM\"");
snprintf (StrValue,sizeof (StrValue),
"%u",
Value);
HTM_INPUT_TEXT (Field,Cns_MAX_DECIMAL_DIGITS_UINT,StrValue,false,
"id=\"%s\" size=\"3\" required=\"required\"",Field);
HTM_TD_End ();
HTM_TR_End ();
}
/*****************************************************************************/
/***************** Show form for select the types of answers *****************/
/*****************************************************************************/
static void Tst_ShowFormAnswerTypes (const struct Tst_AnswerTypes *AnswerTypes)
{
extern const char *The_ClassFormInBox[The_NUM_THEMES];
extern const char *Txt_Types_of_answers;
extern const char *Txt_All_types_of_answers;
extern const char *Txt_TST_STR_ANSWER_TYPES[Tst_NUM_ANS_TYPES];
Tst_AnswerType_t AnsType;
bool Checked;
char UnsignedStr[Cns_MAX_DECIMAL_DIGITS_UINT + 1];
const char *Ptr;
HTM_TR_Begin (NULL);
/***** Label *****/
HTM_TD_Begin ("class=\"RT %s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
HTM_TxtF ("%s:",Txt_Types_of_answers);
HTM_TD_End ();
/***** Select all types of answers *****/
HTM_TD_Begin ("class=\"LT\"");
HTM_TABLE_BeginPadding (2);
HTM_TR_Begin (NULL);
HTM_TD_Begin ("class=\"LM\"");
HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
HTM_INPUT_CHECKBOX ("AllAnsTypes",HTM_DONT_SUBMIT_ON_CHANGE,
"value=\"Y\"%s onclick=\"togglecheckChildren(this,'AnswerType');\"",
AnswerTypes->All ? " checked=\"checked\"" :
"");
HTM_TxtF ("&nbsp;%s",Txt_All_types_of_answers);
HTM_LABEL_End ();
HTM_TD_End ();
HTM_TR_End ();
/***** Type of answer *****/
for (AnsType = (Tst_AnswerType_t) 0;
AnsType <= (Tst_AnswerType_t) (Tst_NUM_ANS_TYPES - 1);
AnsType++)
{
HTM_TR_Begin (NULL);
Checked = false;
Ptr = AnswerTypes->List;
while (*Ptr)
{
Par_GetNextStrUntilSeparParamMult (&Ptr,UnsignedStr,Cns_MAX_DECIMAL_DIGITS_UINT);
if (Tst_ConvertFromUnsignedStrToAnsTyp (UnsignedStr) == AnsType)
{
Checked = true;
break;
}
}
HTM_TD_Begin ("class=\"LM\"");
HTM_LABEL_Begin ("class=\"DAT\"");
HTM_INPUT_CHECKBOX ("AnswerType",HTM_DONT_SUBMIT_ON_CHANGE,
"value=\"%u\"%s onclick=\"checkParent(this,'AllAnsTypes');\"",
(unsigned) AnsType,
Checked ? " checked=\"checked\"" :
"");
HTM_TxtF ("&nbsp;%s",Txt_TST_STR_ANSWER_TYPES[AnsType]);
HTM_LABEL_End ();
HTM_TD_End ();
HTM_TR_End ();
}
HTM_TABLE_End ();
HTM_TD_End ();
HTM_TR_End ();
}
/*****************************************************************************/
/***************** List several test questions for edition *******************/
/*****************************************************************************/
void Tst_ListQuestionsToEdit (void)
{
struct Tst_Test Test;
MYSQL_RES *mysql_res;
/***** Create test *****/
Tst_TstConstructor (&Test);
/***** Get parameters, query the database and list the questions *****/
if (Tst_GetParamsTst (&Test,Tst_EDIT_TEST)) // Get parameters from the form
{
/***** Get question codes from database *****/
Tst_GetQuestions (&Test,&mysql_res); // Query database
if (Test.NumQsts)
{
/* Contextual menu */
Mnu_ContextMenuBegin ();
if (TsI_GetCreateXMLParamFromForm ())
TsI_CreateXML (Test.NumQsts,mysql_res); // Create XML file with exported questions...
// ...and put a link to download it
else
TsI_PutFormToExportQuestions (&Test); // Export questions
Mnu_ContextMenuEnd ();
/* Show the table with the questions */
Tst_ListOneOrMoreQuestionsForEdition (&Test,mysql_res);
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
else
/* Show the form again */
Tst_ShowFormRequestEditTests (&Test);
/***** Destroy test *****/
Tst_TstDestructor (&Test);
}
/*****************************************************************************/
/**************** List several test questions for selection ******************/
/*****************************************************************************/
void Tst_ListQuestionsToSelect (void)
{
struct Tst_Test Test;
MYSQL_RES *mysql_res;
/***** Create test *****/
Tst_TstConstructor (&Test);
/***** Get parameters, query the database and list the questions *****/
if (Tst_GetParamsTst (&Test,Tst_SELECT_QUESTIONS_FOR_GAME)) // Get parameters from the form
{
Tst_GetQuestions (&Test,&mysql_res); // Query database
if (Test.NumQsts)
/* Show the table with the questions */
Tst_ListOneOrMoreQuestionsForSelection (Test.NumQsts,mysql_res);
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
else
/* Show the form again */
Tst_ShowFormRequestSelectTestsForGame (&Test);
/***** Destroy test *****/
Tst_TstDestructor (&Test);
}
/*****************************************************************************/
/********** Get from the database several test questions for listing *********/
/*****************************************************************************/
#define Tst_MAX_BYTES_QUERY_TEST (16 * 1024 - 1)
static void Tst_GetQuestions (struct Tst_Test *Test,MYSQL_RES **mysql_res)
{
extern const char *Txt_No_questions_found_matching_your_search_criteria;
char *Query = NULL;
long LengthQuery;
unsigned NumItemInList;
const char *Ptr;
char TagText[Tst_MAX_BYTES_TAG + 1];
char LongStr[Cns_MAX_DECIMAL_DIGITS_LONG + 1];
char UnsignedStr[Cns_MAX_DECIMAL_DIGITS_UINT + 1];
Tst_AnswerType_t AnsType;
char CrsCodStr[Cns_MAX_DECIMAL_DIGITS_LONG + 1];
/***** Allocate space for query *****/
if ((Query = (char *) malloc (Tst_MAX_BYTES_QUERY_TEST + 1)) == NULL)
Lay_NotEnoughMemoryExit ();
/***** Select questions *****/
2014-12-01 23:55:08 +01:00
/* Start query */
2018-10-30 02:37:09 +01:00
snprintf (Query,Tst_MAX_BYTES_QUERY_TEST + 1,
2020-03-17 00:35:11 +01:00
"SELECT tst_questions.QstCod" // row[0]
2018-10-29 22:22:02 +01:00
" FROM tst_questions");
2020-03-24 00:58:42 +01:00
if (!Test->Tags.All)
2018-10-30 02:37:09 +01:00
Str_Concat (Query,",tst_question_tags,tst_tags",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2014-12-01 23:55:08 +01:00
2018-10-30 02:37:09 +01:00
Str_Concat (Query," WHERE tst_questions.CrsCod='",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2018-10-18 02:02:32 +02:00
snprintf (CrsCodStr,sizeof (CrsCodStr),
"%ld",
2019-04-04 10:45:15 +02:00
Gbl.Hierarchy.Crs.CrsCod);
2018-10-30 02:37:09 +01:00
Str_Concat (Query,CrsCodStr,
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2018-10-30 02:37:09 +01:00
Str_Concat (Query,"' AND tst_questions.EditTime>=FROM_UNIXTIME('",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2018-10-18 02:02:32 +02:00
snprintf (LongStr,sizeof (LongStr),
"%ld",
2020-02-14 10:02:58 +01:00
(long) Gbl.DateRange.TimeUTC[Dat_START_TIME]);
2018-10-30 02:37:09 +01:00
Str_Concat (Query,LongStr,
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2018-10-30 02:37:09 +01:00
Str_Concat (Query,"') AND tst_questions.EditTime<=FROM_UNIXTIME('",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2018-10-18 02:02:32 +02:00
snprintf (LongStr,sizeof (LongStr),
"%ld",
2020-02-14 10:02:58 +01:00
(long) Gbl.DateRange.TimeUTC[Dat_END_TIME]);
2018-10-30 02:37:09 +01:00
Str_Concat (Query,LongStr,
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2018-10-30 02:37:09 +01:00
Str_Concat (Query,"')",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2014-12-01 23:55:08 +01:00
/* Add the tags selected */
2020-03-24 00:58:42 +01:00
if (!Test->Tags.All)
2014-12-01 23:55:08 +01:00
{
2018-10-30 02:37:09 +01:00
Str_Concat (Query," AND tst_questions.QstCod=tst_question_tags.QstCod"
2017-01-16 01:51:01 +01:00
" AND tst_question_tags.TagCod=tst_tags.TagCod"
2017-01-17 03:33:05 +01:00
" AND tst_tags.CrsCod='",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2018-10-30 02:37:09 +01:00
Str_Concat (Query,CrsCodStr,
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2018-10-30 02:37:09 +01:00
Str_Concat (Query,"'",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2018-10-30 02:37:09 +01:00
LengthQuery = strlen (Query);
2014-12-01 23:55:08 +01:00
NumItemInList = 0;
2020-03-24 00:58:42 +01:00
Ptr = Test->Tags.List;
2014-12-01 23:55:08 +01:00
while (*Ptr)
{
Par_GetNextStrUntilSeparParamMult (&Ptr,TagText,Tst_MAX_BYTES_TAG);
LengthQuery = LengthQuery + 35 + strlen (TagText) + 1;
2017-03-08 03:48:23 +01:00
if (LengthQuery > Tst_MAX_BYTES_QUERY_TEST - 256)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Query size exceed.");
2018-10-30 02:37:09 +01:00
Str_Concat (Query,
2017-01-16 01:51:01 +01:00
NumItemInList ? " OR tst_tags.TagTxt='" :
" AND (tst_tags.TagTxt='",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2018-10-30 02:37:09 +01:00
Str_Concat (Query,TagText,
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2018-10-30 02:37:09 +01:00
Str_Concat (Query,"'",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2014-12-01 23:55:08 +01:00
NumItemInList++;
}
2018-10-30 02:37:09 +01:00
Str_Concat (Query,")",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2014-12-01 23:55:08 +01:00
}
/* Add the types of answer selected */
2020-03-24 00:58:42 +01:00
if (!Test->AnswerTypes.All)
2014-12-01 23:55:08 +01:00
{
2018-10-30 02:37:09 +01:00
LengthQuery = strlen (Query);
2014-12-01 23:55:08 +01:00
NumItemInList = 0;
2020-03-24 00:58:42 +01:00
Ptr = Test->AnswerTypes.List;
2014-12-01 23:55:08 +01:00
while (*Ptr)
{
Par_GetNextStrUntilSeparParamMult (&Ptr,UnsignedStr,Tst_MAX_BYTES_TAG);
AnsType = Tst_ConvertFromUnsignedStrToAnsTyp (UnsignedStr);
LengthQuery = LengthQuery + 35 + strlen (Tst_StrAnswerTypesDB[AnsType]) + 1;
2017-03-08 03:48:23 +01:00
if (LengthQuery > Tst_MAX_BYTES_QUERY_TEST - 256)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Query size exceed.");
2018-10-30 02:37:09 +01:00
Str_Concat (Query,
2017-01-16 01:51:01 +01:00
NumItemInList ? " OR tst_questions.AnsType='" :
" AND (tst_questions.AnsType='",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2018-10-30 02:37:09 +01:00
Str_Concat (Query,Tst_StrAnswerTypesDB[AnsType],
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2018-10-30 02:37:09 +01:00
Str_Concat (Query,"'",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2014-12-01 23:55:08 +01:00
NumItemInList++;
}
2018-10-30 02:37:09 +01:00
Str_Concat (Query,")",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2014-12-01 23:55:08 +01:00
}
/* End the query */
2018-10-30 02:37:09 +01:00
Str_Concat (Query," GROUP BY tst_questions.QstCod",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2014-12-01 23:55:08 +01:00
2020-03-24 00:58:42 +01:00
switch (Test->SelectedOrder)
2014-12-01 23:55:08 +01:00
{
case Tst_ORDER_STEM:
2018-10-30 02:37:09 +01:00
Str_Concat (Query," ORDER BY tst_questions.Stem",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2014-12-01 23:55:08 +01:00
break;
case Tst_ORDER_NUM_HITS:
2018-10-30 02:37:09 +01:00
Str_Concat (Query," ORDER BY tst_questions.NumHits DESC,"
2018-10-29 22:22:02 +01:00
"tst_questions.Stem",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2014-12-01 23:55:08 +01:00
break;
case Tst_ORDER_AVERAGE_SCORE:
2018-10-30 02:37:09 +01:00
Str_Concat (Query," ORDER BY tst_questions.Score/tst_questions.NumHits DESC,"
2018-10-29 22:22:02 +01:00
"tst_questions.NumHits DESC,"
"tst_questions.Stem",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2014-12-01 23:55:08 +01:00
break;
case Tst_ORDER_NUM_HITS_NOT_BLANK:
2018-10-30 02:37:09 +01:00
Str_Concat (Query," ORDER BY tst_questions.NumHitsNotBlank DESC,"
2018-10-29 22:22:02 +01:00
"tst_questions.Stem",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2014-12-01 23:55:08 +01:00
break;
case Tst_ORDER_AVERAGE_SCORE_NOT_BLANK:
2018-10-30 02:37:09 +01:00
Str_Concat (Query," ORDER BY tst_questions.Score/tst_questions.NumHitsNotBlank DESC,"
2018-10-29 22:22:02 +01:00
"tst_questions.NumHitsNotBlank DESC,"
"tst_questions.Stem",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2014-12-01 23:55:08 +01:00
break;
}
/* Make the query */
2020-03-24 01:50:39 +01:00
Test->NumQsts = (unsigned) DB_QuerySELECT (mysql_res,"can not get questions",
"%s",
Query);
if (Test->NumQsts == 0)
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_INFO,Txt_No_questions_found_matching_your_search_criteria);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************** Get questions for a new test from the database ***************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_GetQuestionsForNewTestFromDB (struct Tst_Test *Test,
struct TsR_Result *Result)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
2018-10-30 03:29:40 +01:00
char *Query = NULL;
2014-12-01 23:55:08 +01:00
long LengthQuery;
unsigned NumItemInList;
const char *Ptr;
2017-01-28 15:58:46 +01:00
char TagText[Tst_MAX_BYTES_TAG + 1];
2019-11-08 01:10:32 +01:00
char UnsignedStr[Cns_MAX_DECIMAL_DIGITS_UINT + 1];
2014-12-01 23:55:08 +01:00
Tst_AnswerType_t AnsType;
2020-04-01 03:11:05 +02:00
bool Shuffle;
2019-11-08 01:10:32 +01:00
char StrNumQsts[Cns_MAX_DECIMAL_DIGITS_UINT + 1];
2020-04-01 03:11:05 +02:00
unsigned NumQst;
/***** Trivial check: number of questions *****/
if (Test->NumQsts == 0 ||
Test->NumQsts > TstCfg_MAX_QUESTIONS_PER_TEST)
Lay_ShowErrorAndExit ("Wrong number of questions.");
2014-12-01 23:55:08 +01:00
2018-10-29 22:22:02 +01:00
/***** Allocate space for query *****/
2018-10-30 02:37:09 +01:00
if ((Query = (char *) malloc (Tst_MAX_BYTES_QUERY_TEST + 1)) == NULL)
2018-10-29 22:22:02 +01:00
Lay_NotEnoughMemoryExit ();
2014-12-01 23:55:08 +01:00
/***** Select questions without hidden tags *****/
/* Start query */
// Reject questions with any tag hidden
// Select only questions with tags
2015-06-16 20:12:38 +02:00
// DISTINCTROW is necessary to not repeat questions
2018-10-30 02:37:09 +01:00
snprintf (Query,Tst_MAX_BYTES_QUERY_TEST + 1,
2020-04-01 03:11:05 +02:00
"SELECT DISTINCTROW tst_questions.QstCod," // row[0]
"tst_questions.AnsType," // row[1]
"tst_questions.Shuffle" // row[2]
2018-10-29 22:22:02 +01:00
" FROM tst_questions,tst_question_tags,tst_tags"
" WHERE tst_questions.CrsCod=%ld"
" AND tst_questions.QstCod NOT IN"
" (SELECT tst_question_tags.QstCod"
" FROM tst_tags,tst_question_tags"
" WHERE tst_tags.CrsCod=%ld AND tst_tags.TagHidden='Y'"
" AND tst_tags.TagCod=tst_question_tags.TagCod)"
" AND tst_questions.QstCod=tst_question_tags.QstCod"
" AND tst_question_tags.TagCod=tst_tags.TagCod"
" AND tst_tags.CrsCod=%ld",
2019-04-04 10:45:15 +02:00
Gbl.Hierarchy.Crs.CrsCod,
Gbl.Hierarchy.Crs.CrsCod,
Gbl.Hierarchy.Crs.CrsCod);
2014-12-01 23:55:08 +01:00
2020-03-24 00:58:42 +01:00
if (!Test->Tags.All) // User has not selected all the tags
2014-12-01 23:55:08 +01:00
{
/* Add selected tags */
2018-10-30 02:37:09 +01:00
LengthQuery = strlen (Query);
2014-12-01 23:55:08 +01:00
NumItemInList = 0;
2020-03-24 00:58:42 +01:00
Ptr = Test->Tags.List;
2014-12-01 23:55:08 +01:00
while (*Ptr)
{
Par_GetNextStrUntilSeparParamMult (&Ptr,TagText,Tst_MAX_BYTES_TAG);
LengthQuery = LengthQuery + 35 + strlen (TagText) + 1;
2017-03-08 03:48:23 +01:00
if (LengthQuery > Tst_MAX_BYTES_QUERY_TEST - 128)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Query size exceed.");
2018-10-30 02:37:09 +01:00
Str_Concat (Query,
2017-01-16 01:51:01 +01:00
NumItemInList ? " OR tst_tags.TagTxt='" :
" AND (tst_tags.TagTxt='",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2018-10-30 02:37:09 +01:00
Str_Concat (Query,TagText,
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2018-10-30 02:37:09 +01:00
Str_Concat (Query,"'",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2014-12-01 23:55:08 +01:00
NumItemInList++;
}
2018-10-30 02:37:09 +01:00
Str_Concat (Query,")",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2014-12-01 23:55:08 +01:00
}
/* Add answer types selected */
2020-03-24 00:58:42 +01:00
if (!Test->AnswerTypes.All)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
LengthQuery = strlen (Query);
NumItemInList = 0;
Ptr = Test->AnswerTypes.List;
while (*Ptr)
{
Par_GetNextStrUntilSeparParamMult (&Ptr,UnsignedStr,Tst_MAX_BYTES_TAG);
AnsType = Tst_ConvertFromUnsignedStrToAnsTyp (UnsignedStr);
LengthQuery = LengthQuery + 35 + strlen (Tst_StrAnswerTypesDB[AnsType]) + 1;
if (LengthQuery > Tst_MAX_BYTES_QUERY_TEST - 128)
Lay_ShowErrorAndExit ("Query size exceed.");
Str_Concat (Query,
NumItemInList ? " OR tst_questions.AnsType='" :
" AND (tst_questions.AnsType='",
Tst_MAX_BYTES_QUERY_TEST);
Str_Concat (Query,Tst_StrAnswerTypesDB[AnsType],
Tst_MAX_BYTES_QUERY_TEST);
Str_Concat (Query,"'",
Tst_MAX_BYTES_QUERY_TEST);
NumItemInList++;
}
Str_Concat (Query,")",
Tst_MAX_BYTES_QUERY_TEST);
}
/* End query */
Str_Concat (Query," ORDER BY RAND(NOW()) LIMIT ",
Tst_MAX_BYTES_QUERY_TEST);
snprintf (StrNumQsts,sizeof (StrNumQsts),
"%u",
Test->NumQsts);
Str_Concat (Query,StrNumQsts,
Tst_MAX_BYTES_QUERY_TEST);
/*
if (Gbl.Usrs.Me.Roles.LoggedRole == Rol_SYS_ADM)
Lay_ShowAlert (Lay_INFO,Query);
*/
/* Make the query */
Result->NumQsts =
Test->NumQsts = (unsigned) DB_QuerySELECT (&mysql_res,"can not get questions",
"%s",
Query);
/***** Get questions and answers from database and store them in result *****/
for (NumQst = 0;
NumQst < Result->NumQsts;
NumQst++)
{
/* Get question row */
row = mysql_fetch_row (mysql_res);
/*
QstCod row[0]
AnsType row[1]
Shuffle row[2]
*/
/* Get question code (row[0]) */
if ((Result->Questions[NumQst].QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
Lay_ShowErrorAndExit ("Wrong code of question.");
/* Get answer type (row[1]) */
AnsType = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[1]);
/* Get shuffle (row[2]) */
Shuffle = (row[2][0] == 'Y');
/* Set indexes of answers */
switch (AnsType)
{
case Tst_ANS_INT:
case Tst_ANS_FLOAT:
case Tst_ANS_TRUE_FALSE:
case Tst_ANS_TEXT:
Result->Questions[NumQst].StrIndexes[0] = '\0';
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
/* If answer type is unique or multiple option,
get indexes of answers depending on shuffle */
Tst_GetChoiceAnsSeeing (Result,NumQst,Shuffle);
break;
default:
break;
}
/* Reset user's answers.
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. */
Result->Questions[NumQst].StrAnswers[0] = '\0';
}
/***** Get if test result will be visible by teachers *****/
Result->AllowTeachers = Par_GetParToBool ("AllowTchs");
}
/*****************************************************************************/
/********* Get single or multiple choice answer when seeing a test ***********/
/*****************************************************************************/
static void Tst_GetChoiceAnsSeeing (struct TsR_Result *Result,
unsigned NumQst,
bool Shuffle)
{
struct Tst_Question Question;
unsigned NumOpt;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned Index;
bool ErrorInIndex;
char StrInd[Cns_MAX_DECIMAL_DIGITS_UINT + 1];
/***** Create test question *****/
Tst_QstConstructor (&Question);
Question.QstCod = Result->Questions[NumQst].QstCod;
/***** Get answers of question from database *****/
Tst_GetAnswersQst (&Question,&mysql_res,Shuffle);
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
for (NumOpt = 0;
NumOpt < Question.Answer.NumOptions;
NumOpt++)
{
/***** Get next answer *****/
row = mysql_fetch_row (mysql_res);
/***** Assign index (row[0]).
Index is 0,1,2,3... if no shuffle
or 1,3,0,2... (example) if shuffle *****/
ErrorInIndex = false;
if (sscanf (row[0],"%u",&Index) == 1)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
if (Index >= Tst_MAX_OPTIONS_PER_QUESTION)
ErrorInIndex = true;
2014-12-01 23:55:08 +01:00
}
2020-04-01 03:11:05 +02:00
else
ErrorInIndex = true;
if (ErrorInIndex)
Lay_ShowErrorAndExit ("Wrong index of answer.");
snprintf (StrInd,sizeof (StrInd),NumOpt == 0 ? "%u" :
",%u",Index);
Str_Concat (Result->Questions[NumQst].StrIndexes,StrInd,
Tst_MAX_BYTES_INDEXES_ONE_QST);
2014-12-01 23:55:08 +01:00
}
2020-04-01 03:11:05 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
/***** Destroy test question *****/
Tst_QstDestructor (&Question);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/*********************** List a test question for edition ********************/
/*****************************************************************************/
2020-03-26 21:39:44 +01:00
static void Tst_ListOneQstToEdit (struct Tst_Test *Test)
2014-12-01 23:55:08 +01:00
{
2020-03-17 00:35:11 +01:00
extern const char *Hlp_ASSESSMENT_Tests;
extern const char *Txt_Questions;
2014-12-01 23:55:08 +01:00
2020-03-24 01:50:39 +01:00
/***** List only one question *****/
Test->NumQsts = 1;
2020-03-17 00:35:11 +01:00
/***** Begin box *****/
2020-03-26 02:54:30 +01:00
Box_BoxBegin (NULL,Txt_Questions,
2020-03-27 14:56:54 +01:00
Tst_PutIconsTests,Test,
2020-03-17 00:35:11 +01:00
Hlp_ASSESSMENT_Tests,Box_NOT_CLOSABLE);
2014-12-01 23:55:08 +01:00
2020-03-17 00:35:11 +01:00
/***** Write the heading *****/
HTM_TABLE_BeginWideMarginPadding (2);
2020-03-24 01:50:39 +01:00
Tst_WriteHeadingRowQuestionsForEdition (Test);
2020-03-17 00:35:11 +01:00
/***** Write question row *****/
2020-03-30 18:54:17 +02:00
Tst_WriteQuestionListing (Test,0);
2020-03-17 00:35:11 +01:00
/***** End table *****/
HTM_TABLE_End ();
/***** Button to add a new question *****/
Tst_PutButtonToAddQuestion ();
/***** End box *****/
Box_BoxEnd ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/*********************** Get data of one test question ***********************/
/*****************************************************************************/
// Return true on success, false on error
2017-09-01 14:36:25 +02:00
bool Tst_GetOneQuestionByCod (long QstCod,MYSQL_RES **mysql_res)
2014-12-01 23:55:08 +01:00
{
/***** Get data of a question from database *****/
2018-11-02 01:38:44 +01:00
return (DB_QuerySELECT (mysql_res,"can not get data of a question",
2020-03-17 13:10:44 +01:00
"SELECT UNIX_TIMESTAMP(EditTime)," // row[0]
"AnsType," // row[1]
"Shuffle," // row[2]
"Stem," // row[3]
"Feedback," // row[4]
"MedCod," // row[5]
"NumHits," // row[6]
"NumHitsNotBlank," // row[7]
"Score" // row[8]
2018-11-02 01:38:44 +01:00
" FROM tst_questions"
" WHERE QstCod=%ld",
QstCod) == 1);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/****************** List for edition one or more test questions **************/
/*****************************************************************************/
2020-03-26 21:39:44 +01:00
static void Tst_ListOneOrMoreQuestionsForEdition (struct Tst_Test *Test,
2017-09-01 00:52:19 +02:00
MYSQL_RES *mysql_res)
2014-12-01 23:55:08 +01:00
{
2016-11-13 20:18:49 +01:00
extern const char *Hlp_ASSESSMENT_Tests;
2015-04-12 18:01:06 +02:00
extern const char *Txt_Questions;
2020-03-24 01:50:39 +01:00
unsigned NumQst;
2014-12-01 23:55:08 +01:00
MYSQL_ROW row;
2019-10-26 02:19:42 +02:00
/***** Begin box *****/
2020-03-26 02:54:30 +01:00
Box_BoxBegin (NULL,Txt_Questions,
2020-03-27 14:56:54 +01:00
Tst_PutIconsTests,Test,
2017-07-16 20:50:01 +02:00
Hlp_ASSESSMENT_Tests,Box_NOT_CLOSABLE);
2014-12-01 23:55:08 +01:00
/***** Write the heading *****/
2019-10-23 19:05:05 +02:00
HTM_TABLE_BeginWideMarginPadding (2);
2020-03-24 01:50:39 +01:00
Tst_WriteHeadingRowQuestionsForEdition (Test);
2020-03-17 00:35:11 +01:00
/***** Write rows *****/
2020-03-24 01:50:39 +01:00
for (NumQst = 0;
NumQst < Test->NumQsts;
NumQst++)
2020-03-17 00:35:11 +01:00
{
2020-03-24 01:50:39 +01:00
Gbl.RowEvenOdd = NumQst % 2;
2020-03-17 00:35:11 +01:00
2020-03-25 01:36:22 +01:00
/***** Create test question *****/
2020-03-26 21:39:44 +01:00
Tst_QstConstructor (&Test->Question);
2020-03-25 01:36:22 +01:00
2020-03-17 00:35:11 +01:00
/***** Get question code (row[0]) *****/
row = mysql_fetch_row (mysql_res);
2020-03-26 21:39:44 +01:00
if ((Test->Question.QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
2020-03-17 00:35:11 +01:00
Lay_ShowErrorAndExit ("Wrong code of question.");
/***** Write question row *****/
2020-03-30 18:54:17 +02:00
Tst_WriteQuestionListing (Test,NumQst);
2020-03-25 01:36:22 +01:00
/***** Destroy test question *****/
2020-03-26 21:39:44 +01:00
Tst_QstDestructor (&Test->Question);
2020-03-17 00:35:11 +01:00
}
/***** End table *****/
HTM_TABLE_End ();
/***** Button to add a new question *****/
Tst_PutButtonToAddQuestion ();
/***** End box *****/
Box_BoxEnd ();
}
/*****************************************************************************/
/*********** Write heading row in listing of questions for edition ***********/
/*****************************************************************************/
2020-03-24 01:50:39 +01:00
static void Tst_WriteHeadingRowQuestionsForEdition (const struct Tst_Test *Test)
2020-03-17 00:35:11 +01:00
{
extern const char *Txt_No_INDEX;
extern const char *Txt_Code;
extern const char *Txt_Date;
extern const char *Txt_Tags;
extern const char *Txt_Shuffle;
extern const char *Txt_TST_STR_ORDER_FULL[Tst_NUM_TYPES_ORDER_QST];
extern const char *Txt_TST_STR_ORDER_SHORT[Tst_NUM_TYPES_ORDER_QST];
Tst_QuestionsOrder_t Order;
/***** Begin row *****/
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
2019-10-12 00:07:52 +02:00
2020-03-17 00:35:11 +01:00
/***** First columns *****/
2019-10-23 19:05:05 +02:00
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_Date);
HTM_TH (1,1,"CT",Txt_Tags);
HTM_TH (1,1,"CT",Txt_Shuffle);
2014-12-01 23:55:08 +01:00
2020-03-17 00:35:11 +01:00
/***** Columns which data can be ordered *****/
2014-12-01 23:55:08 +01:00
/* Stem and answers of question */
/* Number of times that the question has been answered */
/* Average score */
2019-12-15 20:02:34 +01:00
for (Order = (Tst_QuestionsOrder_t) 0;
Order <= (Tst_QuestionsOrder_t) (Tst_NUM_TYPES_ORDER_QST - 1);
2014-12-01 23:55:08 +01:00
Order++)
{
2019-10-23 19:05:05 +02:00
HTM_TH_Begin (1,1,"LT");
2019-10-13 17:25:00 +02:00
2020-03-24 01:50:39 +01:00
if (Test->NumQsts > 1)
2014-12-01 23:55:08 +01:00
{
2018-11-09 20:47:39 +01:00
Frm_StartForm (ActLstTstQst);
2019-02-11 22:15:18 +01:00
Dat_WriteParamsIniEndDates ();
2020-03-26 21:39:44 +01:00
Tst_WriteParamEditQst (Test);
2019-11-03 13:19:32 +01:00
Par_PutHiddenParamUnsigned (NULL,"Order",(unsigned) Order);
2019-11-20 10:17:42 +01:00
HTM_BUTTON_SUBMIT_Begin (Txt_TST_STR_ORDER_FULL[Order],"BT_LINK TIT_TBL",NULL);
2020-03-24 00:58:42 +01:00
if (Order == Test->SelectedOrder)
2019-11-10 16:41:47 +01:00
HTM_U_Begin ();
2014-12-01 23:55:08 +01:00
}
2019-11-10 12:36:37 +01:00
HTM_Txt (Txt_TST_STR_ORDER_SHORT[Order]);
2020-03-24 01:50:39 +01:00
if (Test->NumQsts > 1)
2014-12-01 23:55:08 +01:00
{
2020-03-24 00:58:42 +01:00
if (Order == Test->SelectedOrder)
2019-11-10 16:41:47 +01:00
HTM_U_End ();
2019-11-18 20:12:10 +01:00
HTM_BUTTON_End ();
2018-11-09 20:47:39 +01:00
Frm_EndForm ();
2014-12-01 23:55:08 +01:00
}
2019-10-13 17:25:00 +02:00
2019-10-23 19:05:05 +02:00
HTM_TH_End ();
2014-12-01 23:55:08 +01:00
}
2020-03-17 00:35:11 +01:00
/***** End row *****/
2019-10-23 19:05:05 +02:00
HTM_TR_End ();
2020-03-17 00:35:11 +01:00
}
2014-12-01 23:55:08 +01:00
2020-03-17 00:35:11 +01:00
/*****************************************************************************/
/********** Write question row in listing of questions for edition ***********/
/*****************************************************************************/
2014-12-01 23:55:08 +01:00
2020-03-30 18:54:17 +02:00
static void Tst_WriteQuestionListing (struct Tst_Test *Test,unsigned NumQst)
2020-03-17 00:35:11 +01:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
static unsigned UniqueId = 0;
char *Id;
time_t TimeUTC;
unsigned long NumHitsThisQst;
unsigned long NumHitsNotBlankThisQst;
2020-04-01 03:11:05 +02:00
double Score;
2020-03-17 00:35:11 +01:00
2020-03-25 01:36:22 +01:00
/***** Get and show question data *****/
2020-03-26 21:39:44 +01:00
if (Tst_GetOneQuestionByCod (Test->Question.QstCod,&mysql_res))
2020-03-17 00:35:11 +01:00
{
/***** Get row from database *****/
2014-12-01 23:55:08 +01:00
row = mysql_fetch_row (mysql_res);
2016-03-29 22:24:03 +02:00
/*
2020-03-17 13:10:44 +01:00
row[0] UNIX_TIMESTAMP(EditTime)
row[1] AnsType
row[2] Shuffle
row[3] Stem
row[4] Feedback
row[5] MedCod
row[6] NumHits
row[7] NumHitsNotBlank
row[8] Score
2016-03-29 22:24:03 +02:00
*/
2020-03-17 00:35:11 +01:00
/***** Begin table row *****/
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
2019-10-10 10:41:00 +02:00
/***** Icons *****/
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"BT%u\"",Gbl.RowEvenOdd);
2017-07-16 20:50:01 +02:00
/* Write icon to remove the question */
2020-03-17 00:35:11 +01:00
Frm_StartForm (ActReqRemOneTstQst);
2020-03-27 14:56:54 +01:00
Tst_PutParamQstCod (&Test->Question.QstCod);
2020-03-24 01:50:39 +01:00
if (Test->NumQsts == 1)
2020-03-17 00:35:11 +01:00
Par_PutHiddenParamChar ("OnlyThisQst",'Y'); // If there are only one row, don't list again after removing
2019-02-11 22:15:18 +01:00
Dat_WriteParamsIniEndDates ();
2020-03-26 21:39:44 +01:00
Tst_WriteParamEditQst (Test);
2017-06-11 19:13:28 +02:00
Ico_PutIconRemove ();
2018-11-09 20:47:39 +01:00
Frm_EndForm ();
2014-12-01 23:55:08 +01:00
/* Write icon to edit the question */
2020-03-26 02:54:30 +01:00
Ico_PutContextualIconToEdit (ActEdiOneTstQst,NULL,
2020-03-27 14:56:54 +01:00
Tst_PutParamQstCod,&Test->Question.QstCod);
2017-07-16 20:50:01 +02:00
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2020-03-23 17:14:41 +01:00
/* Number of question and answer type (row[1]) */
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd);
2020-03-24 01:50:39 +01:00
Tst_WriteNumQst (NumQst + 1);
2020-03-26 21:39:44 +01:00
Test->Question.Answer.Type = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[1]);
Tst_WriteAnswerType (Test->Question.Answer.Type);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2020-03-23 17:14:41 +01:00
/* Question code */
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"DAT_SMALL CT COLOR%u\"",Gbl.RowEvenOdd);
2020-03-26 21:39:44 +01:00
HTM_TxtF ("%ld&nbsp;",Test->Question.QstCod);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2020-03-23 17:14:41 +01:00
/* Date (row[0] has the UTC date-time) */
2020-03-17 13:10:44 +01:00
TimeUTC = Dat_GetUNIXTimeFromStr (row[0]);
2020-03-17 00:35:11 +01:00
if (asprintf (&Id,"tst_date_%u",++UniqueId) < 0)
2019-11-01 22:53:39 +01:00
Lay_NotEnoughMemoryExit ();
HTM_TD_Begin ("id=\"%s\" class=\"DAT_SMALL CT COLOR%u\"",
2020-03-17 00:35:11 +01:00
Id,Gbl.RowEvenOdd);
2019-11-01 23:35:55 +01:00
Dat_WriteLocalDateHMSFromUTC (Id,TimeUTC,
2019-11-02 12:10:58 +01:00
Gbl.Prefs.DateFormat,Dat_SEPARATOR_BREAK,
2019-11-02 11:45:41 +01:00
true,true,false,0x7);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2019-11-06 19:45:20 +01:00
free (Id);
2014-12-01 23:55:08 +01:00
2020-03-23 17:14:41 +01:00
/* Question tags */
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd);
2020-03-26 21:39:44 +01:00
Tst_GetAndWriteTagsQst (Test->Question.QstCod);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2020-03-23 17:14:41 +01:00
/* Shuffle (row[2]) */
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"DAT_SMALL CT COLOR%u\"",Gbl.RowEvenOdd);
2020-03-26 21:39:44 +01:00
if (Test->Question.Answer.Type == Tst_ANS_UNIQUE_CHOICE ||
Test->Question.Answer.Type == Tst_ANS_MULTIPLE_CHOICE)
2020-03-17 00:35:11 +01:00
{
Frm_StartForm (ActChgShfTstQst);
2020-03-27 14:56:54 +01:00
Tst_PutParamQstCod (&Test->Question.QstCod);
2020-03-17 00:35:11 +01:00
Dat_WriteParamsIniEndDates ();
2020-03-26 21:39:44 +01:00
Tst_WriteParamEditQst (Test);
2020-03-24 01:50:39 +01:00
if (Test->NumQsts == 1)
2016-04-05 02:59:34 +02:00
Par_PutHiddenParamChar ("OnlyThisQst",'Y'); // If editing only one question, don't edit others
2020-03-24 00:58:42 +01:00
Par_PutHiddenParamUnsigned (NULL,"Order",(unsigned) Test->SelectedOrder);
2020-03-17 00:35:11 +01:00
HTM_INPUT_CHECKBOX ("Shuffle",HTM_SUBMIT_ON_CHANGE,
"value=\"Y\"%s",
2020-03-17 13:10:44 +01:00
row[2][0] == 'Y' ? " checked=\"checked\"" :
2020-03-17 00:35:11 +01:00
"");
Frm_EndForm ();
}
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2020-03-23 17:14:41 +01:00
/* Stem (row[3]) */
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd);
2020-03-17 13:10:44 +01:00
Tst_WriteQstStem (row[3],"TEST_EDI",
2020-03-17 00:35:11 +01:00
true); // Visible
2019-03-18 15:42:22 +01:00
2020-03-17 13:10:44 +01:00
/***** Get and show media (row[5]) *****/
2020-03-26 21:39:44 +01:00
Test->Question.Media.MedCod = Str_ConvertStrCodToLongCod (row[5]);
Med_GetMediaDataByCod (&Test->Question.Media);
Med_ShowMedia (&Test->Question.Media,
2020-03-17 00:35:11 +01:00
"TEST_MED_EDIT_LIST_CONT",
"TEST_MED_EDIT_LIST");
2019-03-18 15:42:22 +01:00
2020-03-23 17:14:41 +01:00
/* Feedback (row[4]) and answers */
2020-03-17 13:10:44 +01:00
Tst_WriteQstFeedback (row[4],"TEST_EDI_LIGHT");
2020-03-30 18:54:17 +02:00
Tst_WriteAnswersListing (&Test->Question);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2016-03-29 22:24:03 +02:00
/* Get number of hits
2020-03-17 00:35:11 +01:00
(number of times that the question has been answered,
2020-03-17 13:10:44 +01:00
including blank answers) (row[6]) */
if (sscanf (row[6],"%lu",&NumHitsThisQst) != 1)
2020-03-17 00:35:11 +01:00
Lay_ShowErrorAndExit ("Wrong number of hits to a question.");
2014-12-01 23:55:08 +01:00
2016-03-29 22:24:03 +02:00
/* Get number of hits not blank
2020-03-17 00:35:11 +01:00
(number of times that the question has been answered
2020-03-17 13:10:44 +01:00
with a not blank answer) (row[7]) */
if (sscanf (row[7],"%lu",&NumHitsNotBlankThisQst) != 1)
2020-03-17 00:35:11 +01:00
Lay_ShowErrorAndExit ("Wrong number of hits not blank to a question.");
2014-12-01 23:55:08 +01:00
2020-03-17 13:10:44 +01:00
/* Get the acumulated score of the question (row[8]) */
2016-06-04 14:21:01 +02:00
Str_SetDecimalPointToUS (); // To get the decimal point as a dot
2020-04-01 03:11:05 +02:00
if (sscanf (row[8],"%lf",&Score) != 1)
2020-03-17 00:35:11 +01:00
Lay_ShowErrorAndExit ("Wrong score of a question.");
2016-06-04 14:21:01 +02:00
Str_SetDecimalPointToLocal (); // Return to local system
2014-12-01 23:55:08 +01:00
2020-03-23 17:14:41 +01:00
/* Number of times this question has been answered */
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"DAT_SMALL CT COLOR%u\"",Gbl.RowEvenOdd);
2019-11-10 16:47:05 +01:00
HTM_UnsignedLong (NumHitsThisQst);
2020-04-01 03:11:05 +02:00
HTM_TD_End ();
2019-03-18 15:42:22 +01:00
2020-04-01 03:11:05 +02:00
/* Average score */
HTM_TD_Begin ("class=\"DAT_SMALL CT COLOR%u\"",Gbl.RowEvenOdd);
if (NumHitsThisQst)
HTM_Double2Decimals (Score / (double) NumHitsThisQst);
else
HTM_Txt ("N.A.");
HTM_TD_End ();
2019-03-18 15:42:22 +01:00
2020-04-01 03:11:05 +02:00
/* Number of times this question has been answered (not blank) */
HTM_TD_Begin ("class=\"DAT_SMALL CT COLOR%u\"",Gbl.RowEvenOdd);
HTM_UnsignedLong (NumHitsNotBlankThisQst);
HTM_TD_End ();
/* Average score (not blank) */
HTM_TD_Begin ("class=\"DAT_SMALL CT COLOR%u\"",Gbl.RowEvenOdd);
if (NumHitsNotBlankThisQst)
HTM_Double2Decimals (Score / (double) NumHitsNotBlankThisQst);
else
HTM_Txt ("N.A.");
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2020-03-17 00:35:11 +01:00
/***** End table row *****/
2019-10-23 19:05:05 +02:00
HTM_TR_End ();
2017-07-16 20:50:01 +02:00
}
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
2020-03-22 19:34:53 +01:00
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/*************** List for selection one or more test questions ***************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_ListOneOrMoreQuestionsForSelection (unsigned NumQsts,
MYSQL_RES *mysql_res)
2019-09-23 19:17:12 +02:00
{
2020-04-01 03:11:05 +02:00
extern const char *Hlp_ASSESSMENT_Games_questions;
extern const char *Txt_Questions;
extern const char *Txt_No_INDEX;
extern const char *Txt_Code;
extern const char *Txt_Date;
extern const char *Txt_Tags;
extern const char *Txt_Type;
extern const char *Txt_Shuffle;
extern const char *Txt_Question;
extern const char *Txt_Add_questions;
unsigned NumQst;
struct Tst_Question Question;
2019-09-23 19:17:12 +02:00
MYSQL_ROW row;
2020-04-01 03:11:05 +02:00
/***** Begin box *****/
Box_BoxBegin (NULL,Txt_Questions,
NULL,NULL,
Hlp_ASSESSMENT_Games_questions,Box_NOT_CLOSABLE);
2019-09-23 19:17:12 +02:00
2020-04-01 03:11:05 +02:00
/***** Begin form *****/
Frm_StartForm (ActAddTstQstToGam);
Gam_PutParams (&Gbl);
2019-09-23 19:17:12 +02:00
2020-04-01 03:11:05 +02:00
/***** Write the heading *****/
HTM_TABLE_BeginWideMarginPadding (2);
HTM_TR_Begin (NULL);
2019-09-23 19:17:12 +02:00
2020-04-01 03:11:05 +02:00
HTM_TH_Empty (1);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
HTM_TH (1,1,"CT",Txt_No_INDEX);
HTM_TH (1,1,"CT",Txt_Code);
HTM_TH (1,1,"CT",Txt_Date);
HTM_TH (1,1,"LT",Txt_Tags);
HTM_TH (1,1,"CT",Txt_Type);
HTM_TH (1,1,"CT",Txt_Shuffle);
HTM_TH (1,1,"CT",Txt_Question);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
HTM_TR_End ();
2020-03-18 01:57:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Write rows *****/
for (NumQst = 0;
NumQst < NumQsts;
NumQst++)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
Gbl.RowEvenOdd = NumQst % 2;
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Create test question */
Tst_QstConstructor (&Question);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Get question code (row[0]) */
row = mysql_fetch_row (mysql_res);
if ((Question.QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
Lay_ShowErrorAndExit ("Wrong code of question.");
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Write question row */
Tst_WriteQuestionRowForSelection (NumQst,&Question);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Destroy test question */
Tst_QstDestructor (&Question);
2014-12-01 23:55:08 +01:00
}
2020-03-18 01:57:08 +01:00
2020-04-01 03:11:05 +02:00
/***** End table *****/
HTM_TABLE_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Button to add questions *****/
Btn_PutConfirmButton (Txt_Add_questions);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** End form *****/
Frm_EndForm ();
2019-05-22 09:36:18 +02:00
2020-04-01 03:11:05 +02:00
/***** End box *****/
Box_BoxEnd ();
2017-09-04 17:03:49 +02:00
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/********************** Write question row for selection *********************/
2020-03-30 18:54:17 +02:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_WriteQuestionRowForSelection (unsigned NumQst,
struct Tst_Question *Question)
2020-03-30 18:54:17 +02:00
{
2020-04-01 03:11:05 +02:00
extern const char *Txt_TST_STR_ANSWER_TYPES[Tst_NUM_ANS_TYPES];
MYSQL_RES *mysql_res;
2020-03-30 18:54:17 +02:00
MYSQL_ROW row;
2020-04-01 03:11:05 +02:00
static unsigned UniqueId = 0;
char *Id;
time_t TimeUTC;
2020-03-30 18:54:17 +02:00
2020-04-01 03:11:05 +02:00
/***** Get and show questvoidion data *****/
if (Tst_GetOneQuestionByCod (Question->QstCod,&mysql_res))
{
/***** Get row of the result of the query *****/
row = mysql_fetch_row (mysql_res);
/*
row[0] UNIX_TIMESTAMP(EditTime)
row[1] AnsType
row[2] Shuffle
row[3] Stem
row[4] Feedback
row[5] MedCod
row[6] NumHits
row[7] NumHitsNotBlank
row[8] Score
*/
2020-03-30 18:54:17 +02:00
2020-04-01 03:11:05 +02:00
/***** Begin table row *****/
HTM_TR_Begin (NULL);
2020-03-30 18:54:17 +02:00
2020-04-01 03:11:05 +02:00
/***** Icons *****/
HTM_TD_Begin ("class=\"BT%u\"",Gbl.RowEvenOdd);
2020-03-30 18:54:17 +02:00
2020-04-01 03:11:05 +02:00
/* Write checkbox to select the question */
HTM_INPUT_CHECKBOX ("QstCods",HTM_DONT_SUBMIT_ON_CHANGE,
"value=\"%ld\"",
Question->QstCod);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Write number of question */
HTM_TD_Begin ("class=\"DAT_SMALL CT COLOR%u\"",Gbl.RowEvenOdd);
HTM_TxtF ("%u&nbsp;",NumQst + 1);
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Write question code */
HTM_TD_Begin ("class=\"DAT_SMALL CT COLOR%u\"",Gbl.RowEvenOdd);
HTM_TxtF ("%ld&nbsp;",Question->QstCod);
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Write the date (row[0] has the UTC date-time) */
TimeUTC = Dat_GetUNIXTimeFromStr (row[0]);
if (asprintf (&Id,"tst_date_%u",++UniqueId) < 0)
Lay_NotEnoughMemoryExit ();
HTM_TD_Begin ("id=\"%s\" class=\"DAT_SMALL CT COLOR%u\">",
Id,Gbl.RowEvenOdd);
Dat_WriteLocalDateHMSFromUTC (Id,TimeUTC,
Gbl.Prefs.DateFormat,Dat_SEPARATOR_BREAK,
true,true,false,0x7);
HTM_TD_End ();
free (Id);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Write the question tags */
HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd);
Tst_GetAndWriteTagsQst (Question->QstCod);
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Write the question type (row[1]) */
Question->Answer.Type = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[1]);
HTM_TD_Begin ("class=\"DAT_SMALL CT COLOR%u\"",Gbl.RowEvenOdd);
HTM_TxtF ("%s&nbsp;",Txt_TST_STR_ANSWER_TYPES[Question->Answer.Type]);
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Write if shuffle is enabled (row[2]) */
HTM_TD_Begin ("class=\"DAT_SMALL CT COLOR%u\"",Gbl.RowEvenOdd);
HTM_INPUT_CHECKBOX ("Shuffle",HTM_DONT_SUBMIT_ON_CHANGE,
"value=\"Y\"%s disabled=\"disabled\"",
row[2][0] == 'Y' ? " checked=\"checked\"" :
"");
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Write stem (row[3]) */
HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd);
Tst_WriteQstStem (row[3],"TEST_EDI",
true); // Visible
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Get and show media (row[5]) *****/
Question->Media.MedCod = Str_ConvertStrCodToLongCod (row[5]);
Med_GetMediaDataByCod (&Question->Media);
Med_ShowMedia (&Question->Media,
"TEST_MED_EDIT_LIST_CONT",
"TEST_MED_EDIT_LIST");
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Write feedback (row[4]) */
Tst_WriteQstFeedback (row[4],"TEST_EDI_LIGHT");
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Write answers */
Tst_WriteAnswersListing (Question);
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** End table row *****/
HTM_TR_End ();
}
}
2019-10-10 10:41:00 +02:00
2020-04-01 03:11:05 +02:00
/*****************************************************************************/
/*********** Write hidden parameters for edition of test questions ***********/
/*****************************************************************************/
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
void Tst_WriteParamEditQst (const struct Tst_Test *Test)
{
Par_PutHiddenParamChar ("AllTags",Test->Tags.All ? 'Y' :
'N');
Par_PutHiddenParamString (NULL,"ChkTag",Test->Tags.List ? Test->Tags.List :
"");
Par_PutHiddenParamChar ("AllAnsTypes",Test->AnswerTypes.All ? 'Y' :
'N');
Par_PutHiddenParamString (NULL,"AnswerType",Test->AnswerTypes.List);
}
2019-10-10 10:41:00 +02:00
2020-04-01 03:11:05 +02:00
/*****************************************************************************/
/*************** Get answers of a test question from database ****************/
/*****************************************************************************/
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
unsigned Tst_GetNumAnswersQst (long QstCod)
{
return (unsigned) DB_QueryCOUNT ("can not get number of answers of a question",
"SELECT COUNT(*)"
" FROM tst_answers"
" WHERE QstCod=%ld",
QstCod);
}
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
void Tst_GetAnswersQst (struct Tst_Question *Question,MYSQL_RES **mysql_res,
bool Shuffle)
{
/***** Get answers of a question from database *****/
Question->Answer.NumOptions = (unsigned)
DB_QuerySELECT (mysql_res,"can not get answers of a question",
"SELECT AnsInd," // row[0]
"Answer," // row[1]
"Feedback," // row[2]
"MedCod," // row[3]
"Correct" // row[4]
" FROM tst_answers"
" WHERE QstCod=%ld"
" ORDER BY %s",
Question->QstCod,
Shuffle ? "RAND(NOW())" :
"AnsInd");
if (!Question->Answer.NumOptions)
Ale_ShowAlert (Ale_ERROR,"Error when getting answers of a question.");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/**************** Get and write the answers of a test question ***************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
void Tst_WriteAnswersListing (struct Tst_Question *Question)
2020-03-30 18:54:17 +02:00
{
2020-04-01 03:11:05 +02:00
MYSQL_RES *mysql_res;
/***** Get answers *****/
Tst_GetAnswersQst (Question,&mysql_res,
false); // Don't shuffle
2020-03-30 18:54:17 +02:00
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
2020-04-01 03:11:05 +02:00
/***** Write answers *****/
switch (Question->Answer.Type)
2020-03-30 18:54:17 +02:00
{
2020-04-01 03:11:05 +02:00
case Tst_ANS_INT:
Tst_WriteIntAnsListing (Question,mysql_res);
break;
case Tst_ANS_FLOAT:
Tst_WriteFloatAnsEdit (Question,mysql_res);
break;
case Tst_ANS_TRUE_FALSE:
Tst_WriteTFAnsListing (Question,mysql_res);
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
case Tst_ANS_TEXT:
Tst_WriteChoiceAnsListing (Question,mysql_res);
break;
default:
break;
}
2020-03-30 18:54:17 +02:00
2020-04-01 03:11:05 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
2020-03-30 18:54:17 +02:00
2020-04-01 03:11:05 +02:00
/*****************************************************************************/
/************** Write answers of a question when seeing a test ***************/
/*****************************************************************************/
2020-03-30 18:54:17 +02:00
2020-04-01 03:11:05 +02:00
static void Tst_WriteAnswersSeeing (struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question)
{
MYSQL_RES *mysql_res;
2020-03-30 18:54:17 +02:00
2020-04-01 03:11:05 +02:00
/***** Write answer depending on type *****/
switch (Question->Answer.Type)
{
case Tst_ANS_INT:
Tst_WriteIntAnsSeeing (Result,NumQst);
break;
case Tst_ANS_FLOAT:
Tst_WriteFloatAnsSeeing (Result,NumQst);
break;
case Tst_ANS_TRUE_FALSE:
Tst_WriteTFAnsSeeing (Result,NumQst);
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
/* Get answer of a question from database */
Tst_GetAnswersQst (Question,&mysql_res,
false); // Don't shuffle
2020-03-30 18:54:17 +02:00
2020-04-01 03:11:05 +02:00
/* Write answer */
Tst_WriteChoiceAnsSeeing (Result,NumQst,Question,mysql_res);
2020-03-30 18:54:17 +02:00
2020-04-01 03:11:05 +02:00
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
break;
case Tst_ANS_TEXT:
Tst_WriteTextAnsSeeing (Result,NumQst);
break;
default:
break;
2020-03-30 18:54:17 +02:00
}
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************* Write answers of a question when assessing a test *************/
2020-03-30 18:54:17 +02:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_WriteAnswersResult (struct UsrData *UsrDat,
const struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question,
unsigned Visibility)
2014-12-01 23:55:08 +01:00
{
MYSQL_RES *mysql_res;
2020-04-01 03:11:05 +02:00
/***** Get answer of a question from database *****/
Tst_GetAnswersQst (Question,&mysql_res,
false); // Don't shuffle
2016-04-06 14:41:47 +02:00
/*
2019-03-03 12:27:29 +01:00
row[0] AnsInd
row[1] Answer
row[2] Feedback
2019-03-18 15:42:22 +01:00
row[3] MedCod
row[4] Correct
2016-04-06 14:41:47 +02:00
*/
2016-04-06 19:26:09 +02:00
2020-04-01 03:11:05 +02:00
/***** Write answer depending on type *****/
switch (Question->Answer.Type)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
case Tst_ANS_INT:
Tst_WriteIntAnsResult (UsrDat,Result,
NumQst,Question,mysql_res,
Visibility);
break;
case Tst_ANS_FLOAT:
Tst_WriteFloatAnsResult (UsrDat,Result,
NumQst,Question,mysql_res,
Visibility);
break;
case Tst_ANS_TRUE_FALSE:
Tst_WriteTFAnsResult (UsrDat,Result,
NumQst,Question,mysql_res,
Visibility);
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
Tst_WriteChoiceAnsResult (UsrDat,Result,
NumQst,Question,mysql_res,
Visibility);
break;
case Tst_ANS_TEXT:
Tst_WriteTextAnsResult (UsrDat,Result,
NumQst,Question,mysql_res,
Visibility);
break;
default:
break;
}
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
2019-10-07 21:15:14 +02:00
2020-04-01 03:11:05 +02:00
/*****************************************************************************/
/***************** Check if a question is valid for a game *******************/
/*****************************************************************************/
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
bool Tst_CheckIfQuestionIsValidForGame (long QstCod)
{
/***** Check if a question is valid for a game from database *****/
return DB_QueryCOUNT ("can not check type of a question",
"SELECT COUNT(*)"
" FROM tst_questions"
" WHERE QstCod=%ld AND AnsType='%s'",
QstCod,Tst_StrAnswerTypesDB[Tst_ANS_UNIQUE_CHOICE]) != 0;
}
2019-10-10 10:41:00 +02:00
2020-04-01 03:11:05 +02:00
/*****************************************************************************/
/****************** Write integer answer when editing a test *****************/
/*****************************************************************************/
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
static void Tst_WriteIntAnsListing (const struct Tst_Question *Question,
MYSQL_RES *mysql_res)
{
MYSQL_ROW row;
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
Tst_CheckIfNumberOfAnswersIsOne (Question);
row = mysql_fetch_row (mysql_res);
HTM_SPAN_Begin ("class=\"TEST_EDI\"");
HTM_TxtF ("(%ld)",Tst_GetIntAnsFromStr (row[1]));
HTM_SPAN_End ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/****************** Write integer answer when seeing a test ******************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_WriteIntAnsSeeing (const struct TsR_Result *Result,
unsigned NumQst)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
char StrQstIndOrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Qstxx...x", "Indxx...x" or "Ansxx...x"
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Write input field for the answer *****/
snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns),
"Ans%010u",
NumQst);
HTM_INPUT_TEXT (StrQstIndOrAns,11,Result->Questions[NumQst].StrAnswers,false,
"size=\"11\"");
}
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/*****************************************************************************/
/**************** Write integer answer when assessing a test *****************/
/*****************************************************************************/
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
static void Tst_WriteIntAnsResult (struct UsrData *UsrDat,
const struct TsR_Result *Result,
unsigned NumQst,
const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility)
{
MYSQL_ROW row;
long IntAnswerUsr;
long IntAnswerCorr;
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
/***** Check if number of rows is correct *****/
Tst_CheckIfNumberOfAnswersIsOne (Question);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Get the numerical value of the correct answer *****/
row = mysql_fetch_row (mysql_res);
if (sscanf (row[1],"%ld",&IntAnswerCorr) != 1)
Lay_ShowErrorAndExit ("Wrong integer answer.");
2019-09-23 01:48:28 +02:00
2020-04-01 03:11:05 +02:00
/***** Header with the title of each column *****/
2019-10-23 19:05:05 +02:00
HTM_TABLE_BeginPadding (2);
HTM_TR_Begin (NULL);
2018-12-09 13:11:20 +01:00
Tst_WriteHeadUserCorrect (UsrDat);
2019-10-23 19:05:05 +02:00
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
HTM_TR_Begin (NULL);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Write the user answer *****/
if (Result->Questions[NumQst].StrAnswers[0]) // If user has answered the question
{
if (sscanf (Result->Questions[NumQst].StrAnswers,"%ld",&IntAnswerUsr) == 1)
2019-10-07 21:15:14 +02:00
{
2020-04-01 03:11:05 +02:00
HTM_TD_Begin ("class=\"%s CM\"",
TsV_IsVisibleCorrectAns (Visibility) ?
(IntAnswerUsr == IntAnswerCorr ? "ANS_OK" :
"ANS_BAD") :
"ANS_0");
HTM_Long (IntAnswerUsr);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2019-10-07 21:15:14 +02:00
}
2020-02-17 23:15:08 +01:00
else
2020-04-01 03:11:05 +02:00
{
HTM_TD_Begin ("class=\"ANS_0 CM\"");
HTM_Txt ("?");
HTM_TD_End ();
}
}
else // If user has omitted the answer
HTM_TD_Empty (1);
2019-10-24 00:04:40 +02:00
2020-04-01 03:11:05 +02:00
/***** Write the correct answer *****/
HTM_TD_Begin ("class=\"ANS_0 CM\"");
if (TsV_IsVisibleQstAndAnsTxt (Visibility) &&
TsV_IsVisibleCorrectAns (Visibility))
HTM_Long (IntAnswerCorr);
else
Ico_PutIconNotVisible ();
HTM_TD_End ();
2019-10-10 10:41:00 +02:00
2020-04-01 03:11:05 +02:00
HTM_TR_End ();
2019-09-23 01:48:28 +02:00
2020-02-17 12:27:28 +01:00
/***** Write the score of this question *****/
2020-02-18 09:19:33 +01:00
if (TsV_IsVisibleEachQstScore (Visibility))
2019-09-23 01:48:28 +02:00
{
2020-04-01 03:11:05 +02:00
Tst_WriteScoreStart (2);
if (!Result->Questions[NumQst].StrAnswers[0]) // If user has omitted the answer
{
2019-11-07 10:24:00 +01:00
HTM_SPAN_Begin ("class=\"ANS_0\"");
2020-04-01 03:11:05 +02:00
HTM_Double2Decimals (0.0);
}
else if (IntAnswerUsr == IntAnswerCorr) // If correct
{
2019-11-07 10:24:00 +01:00
HTM_SPAN_Begin ("class=\"ANS_OK\"");
2020-04-01 03:11:05 +02:00
HTM_Double2Decimals (1.0);
}
else // If wrong
{
2019-11-07 10:24:00 +01:00
HTM_SPAN_Begin ("class=\"ANS_BAD\"");
2020-04-01 03:11:05 +02:00
HTM_Double2Decimals (0.0);
}
2019-11-07 10:24:00 +01:00
HTM_SPAN_End ();
2019-09-23 01:48:28 +02:00
Tst_WriteScoreEnd ();
}
2019-10-23 19:05:05 +02:00
HTM_TABLE_End ();
2019-09-23 01:48:28 +02:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/****************** Write float answer when editing a test *******************/
/*****************************************************************************/
static void Tst_WriteFloatAnsEdit (const struct Tst_Question *Question,
MYSQL_RES *mysql_res)
{
MYSQL_ROW row;
unsigned i;
double FloatNum[2];
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
if (Question->Answer.NumOptions != 2)
Lay_ShowErrorAndExit ("Wrong float range.");
for (i = 0;
i < 2;
i++)
{
row = mysql_fetch_row (mysql_res);
FloatNum[i] = Str_GetDoubleFromStr (row[1]);
}
HTM_SPAN_Begin ("class=\"TEST_EDI\"");
HTM_Txt ("([");
HTM_Double (FloatNum[0]);
HTM_Txt ("; ");
HTM_Double (FloatNum[1]);
HTM_Txt ("])");
HTM_SPAN_End ();
}
/*****************************************************************************/
/****************** Write float answer when seeing a test ********************/
/*****************************************************************************/
static void Tst_WriteFloatAnsSeeing (const struct TsR_Result *Result,
unsigned NumQst)
{
char StrQstIndOrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Qstxx...x", "Indxx...x" or "Ansxx...x"
/***** Write input field for the answer *****/
snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns),
"Ans%010u",
NumQst);
HTM_INPUT_TEXT (StrQstIndOrAns,Tst_MAX_BYTES_FLOAT_ANSWER,Result->Questions[NumQst].StrAnswers,false,
"size=\"11\"");
}
/*****************************************************************************/
/***************** Write float answer when assessing a test ******************/
2019-09-23 01:48:28 +02:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_WriteFloatAnsResult (struct UsrData *UsrDat,
const struct TsR_Result *Result,
unsigned NumQst,
const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility)
2019-09-23 01:48:28 +02:00
{
MYSQL_ROW row;
2020-04-01 03:11:05 +02:00
unsigned i;
double FloatAnsUsr = 0.0,Tmp;
double FloatAnsCorr[2];
2019-09-23 01:48:28 +02:00
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
2020-04-01 03:11:05 +02:00
/***** Check if number of rows is correct *****/
if (Question->Answer.NumOptions != 2)
Lay_ShowErrorAndExit ("Wrong float range.");
/***** Get the numerical value of the minimum and maximum correct answers *****/
for (i = 0;
i < 2;
i++)
2019-09-23 01:48:28 +02:00
{
row = mysql_fetch_row (mysql_res);
2020-04-01 03:11:05 +02:00
FloatAnsCorr[i] = Str_GetDoubleFromStr (row[1]);
}
if (FloatAnsCorr[0] > FloatAnsCorr[1]) // The maximum and the minimum are swapped
{
/* Swap maximum and minimum */
Tmp = FloatAnsCorr[0];
FloatAnsCorr[0] = FloatAnsCorr[1];
FloatAnsCorr[1] = Tmp;
}
2019-09-23 01:48:28 +02:00
2020-04-01 03:11:05 +02:00
/***** Header with the title of each column *****/
HTM_TABLE_BeginPadding (2);
HTM_TR_Begin (NULL);
Tst_WriteHeadUserCorrect (UsrDat);
HTM_TR_End ();
2019-09-23 01:48:28 +02:00
2020-04-01 03:11:05 +02:00
HTM_TR_Begin (NULL);
2019-09-23 01:48:28 +02:00
2020-04-01 03:11:05 +02:00
/***** Write the user answer *****/
if (Result->Questions[NumQst].StrAnswers[0]) // If user has answered the question
{
FloatAnsUsr = Str_GetDoubleFromStr (Result->Questions[NumQst].StrAnswers);
// A bad formatted floating point answer will interpreted as 0.0
HTM_TD_Begin ("class=\"%s CM\"",
TsV_IsVisibleCorrectAns (Visibility) ?
((FloatAnsUsr >= FloatAnsCorr[0] &&
FloatAnsUsr <= FloatAnsCorr[1]) ? "ANS_OK" :
"ANS_BAD") :
"ANS_0");
HTM_Double (FloatAnsUsr);
2019-09-23 01:48:28 +02:00
}
2020-04-01 03:11:05 +02:00
else // If user has omitted the answer
HTM_TD_Begin (NULL);
HTM_TD_End ();
2019-09-23 19:17:12 +02:00
2020-04-01 03:11:05 +02:00
/***** Write the correct answer *****/
HTM_TD_Begin ("class=\"ANS_0 CM\"");
if (TsV_IsVisibleQstAndAnsTxt (Visibility) &&
TsV_IsVisibleCorrectAns (Visibility))
2019-09-23 19:17:12 +02:00
{
2020-04-01 03:11:05 +02:00
HTM_Txt ("[");
HTM_Double (FloatAnsCorr[0]);
HTM_Txt ("; ");
HTM_Double (FloatAnsCorr[1]);
HTM_Txt ("]");
}
else
Ico_PutIconNotVisible ();
HTM_TD_End ();
2019-09-24 09:24:36 +02:00
2020-04-01 03:11:05 +02:00
HTM_TR_End ();
2019-09-24 09:24:36 +02:00
2020-04-01 03:11:05 +02:00
/***** Write the score of this question *****/
if (TsV_IsVisibleEachQstScore (Visibility))
{
Tst_WriteScoreStart (2);
if (!Result->Questions[NumQst].StrAnswers[0]) // If user has omitted the answer
{
HTM_SPAN_Begin ("class=\"ANS_0\"");
HTM_Double2Decimals (0.0);
}
else if (FloatAnsUsr >= FloatAnsCorr[0] &&
FloatAnsUsr <= FloatAnsCorr[1]) // If correct (inside the interval)
{
HTM_SPAN_Begin ("class=\"ANS_OK\"");
HTM_Double2Decimals (1.0);
}
else // If wrong (outside the interval)
{
HTM_SPAN_Begin ("class=\"ANS_BAD\"");
HTM_Double2Decimals (0.0);
}
HTM_SPAN_End ();
Tst_WriteScoreEnd ();
2019-09-23 19:17:12 +02:00
}
2019-09-24 09:24:36 +02:00
2020-04-01 03:11:05 +02:00
HTM_TABLE_End ();
2019-09-23 19:17:12 +02:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/*********** Write false / true answer when listing test questions ***********/
2019-09-23 19:17:12 +02:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_WriteTFAnsListing (const struct Tst_Question *Question,
MYSQL_RES *mysql_res)
2019-09-23 19:17:12 +02:00
{
2020-04-01 03:11:05 +02:00
MYSQL_ROW row;
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
2019-09-24 15:00:46 +02:00
2020-04-01 03:11:05 +02:00
Tst_CheckIfNumberOfAnswersIsOne (Question);
2019-09-24 15:00:46 +02:00
2020-04-01 03:11:05 +02:00
/***** Get answer true or false *****/
row = mysql_fetch_row (mysql_res);
2019-09-24 15:00:46 +02:00
2020-04-01 03:11:05 +02:00
/***** Write answer *****/
HTM_SPAN_Begin ("class=\"TEST_EDI\"");
HTM_Txt ("(");
Tst_WriteAnsTF (row[1][0]);
HTM_Txt (")");
HTM_SPAN_End ();
2019-09-23 19:17:12 +02:00
}
2019-09-23 01:48:28 +02:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************** Write false / true answer when seeing a test ****************/
2019-09-23 01:48:28 +02:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_WriteTFAnsSeeing (const struct TsR_Result *Result,
unsigned NumQst)
2019-09-23 01:48:28 +02:00
{
2020-04-01 03:11:05 +02:00
extern const char *Txt_TF_QST[2];
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** 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 (false,
"name=\"Ans%010u\"",NumQst);
HTM_OPTION (HTM_Type_STRING,"" ,true ,Result->Questions[NumQst].StrAnswers[0] == '\0',"&nbsp;");
HTM_OPTION (HTM_Type_STRING,"T",false,Result->Questions[NumQst].StrAnswers[0] == 'T' ,"%s",Txt_TF_QST[0]);
HTM_OPTION (HTM_Type_STRING,"F",false,Result->Questions[NumQst].StrAnswers[0] == 'F' ,"%s",Txt_TF_QST[1]);
HTM_SELECT_End ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************** Write false / true answer when seeing a test *****************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
void Tst_WriteAnsTF (char AnsTF)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
extern const char *Txt_TF_QST[2];
2019-11-04 01:29:46 +01:00
2020-04-01 03:11:05 +02:00
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;
}
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************** Write false / true answer when assessing a test **************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_WriteTFAnsResult (struct UsrData *UsrDat,
const struct TsR_Result *Result,
unsigned NumQst,
const struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility)
2014-12-01 23:55:08 +01:00
{
MYSQL_ROW row;
2020-04-01 03:11:05 +02:00
char AnsTF;
2016-04-06 14:41:47 +02:00
/*
2019-03-03 12:27:29 +01:00
row[0] AnsInd
row[1] Answer
row[2] Feedback
2019-03-18 15:42:22 +01:00
row[3] MedCod
row[4] Correct
2016-04-06 14:41:47 +02:00
*/
2020-04-01 03:11:05 +02:00
/***** Check if number of rows is correct *****/
Tst_CheckIfNumberOfAnswersIsOne (Question);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Get answer true or false *****/
row = mysql_fetch_row (mysql_res);
AnsTF = Result->Questions[NumQst].StrAnswers[0];
2014-12-01 23:55:08 +01:00
/***** Header with the title of each column *****/
2019-10-23 19:05:05 +02:00
HTM_TABLE_BeginPadding (2);
HTM_TR_Begin (NULL);
2018-12-09 13:11:20 +01:00
Tst_WriteHeadUserCorrect (UsrDat);
2019-10-23 19:05:05 +02:00
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
2019-10-10 10:41:00 +02:00
/***** Write the user answer *****/
2020-04-01 03:11:05 +02:00
HTM_TD_Begin ("class=\"%s CM\"",
TsV_IsVisibleCorrectAns (Visibility) ?
(AnsTF == row[1][0] ? "ANS_OK" :
"ANS_BAD") :
"ANS_0");
Tst_WriteAnsTF (AnsTF);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Write the correct answer *****/
HTM_TD_Begin ("class=\"ANS_0 CM\"");
2020-02-18 09:19:33 +01:00
if (TsV_IsVisibleQstAndAnsTxt (Visibility) &&
TsV_IsVisibleCorrectAns (Visibility))
2020-04-01 03:11:05 +02:00
Tst_WriteAnsTF (row[1][0]);
2014-12-01 23:55:08 +01:00
else
2020-02-19 00:45:26 +01:00
Ico_PutIconNotVisible ();
2020-04-01 03:11:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
2020-02-17 12:27:28 +01:00
/***** Write the score of this question *****/
2020-02-18 09:19:33 +01:00
if (TsV_IsVisibleEachQstScore (Visibility))
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
Tst_WriteScoreStart (2);
if (AnsTF == '\0') // If user has omitted the answer
2019-11-07 10:24:00 +01:00
{
HTM_SPAN_Begin ("class=\"ANS_0\"");
2019-12-14 13:35:35 +01:00
HTM_Double2Decimals (0.0);
2019-11-07 10:24:00 +01:00
}
2020-04-01 03:11:05 +02:00
else if (AnsTF == row[1][0]) // If correct
2019-11-07 10:24:00 +01:00
{
HTM_SPAN_Begin ("class=\"ANS_OK\"");
2019-12-14 13:35:35 +01:00
HTM_Double2Decimals (1.0);
2019-11-07 10:24:00 +01:00
}
2020-04-01 03:11:05 +02:00
else // If wrong
2019-11-07 10:24:00 +01:00
{
HTM_SPAN_Begin ("class=\"ANS_BAD\"");
2020-04-01 03:11:05 +02:00
HTM_Double2Decimals (-1.0);
2019-11-07 10:24:00 +01:00
}
HTM_SPAN_End ();
2014-12-01 23:55:08 +01:00
Tst_WriteScoreEnd ();
}
2019-10-23 19:05:05 +02:00
HTM_TABLE_End ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/**** Write single or multiple choice answer when listing test questions *****/
/*****************************************************************************/
static void Tst_WriteChoiceAnsListing (struct Tst_Question *Question,
MYSQL_RES *mysql_res)
{
extern const char *Txt_TST_Answer_given_by_the_teachers;
MYSQL_ROW row;
unsigned NumOpt;
char *AnswerTxt;
char *Feedback;
size_t LengthAnswerTxt;
size_t LengthFeedback;
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
HTM_TABLE_BeginPadding (2);
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
{
row = mysql_fetch_row (mysql_res);
/* Convert the answer (row[1]), that is in HTML, to rigorous HTML */
LengthAnswerTxt = strlen (row[1]) * Str_MAX_BYTES_PER_CHAR;
if ((AnswerTxt = (char *) malloc (LengthAnswerTxt + 1)) == NULL)
Lay_NotEnoughMemoryExit ();
Str_Copy (AnswerTxt,row[1],
LengthAnswerTxt);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
AnswerTxt,LengthAnswerTxt,false);
/* Convert the feedback (row[2]), that is in HTML, to rigorous HTML */
LengthFeedback = 0;
Feedback = NULL;
if (row[2])
if (row[2][0])
{
LengthFeedback = strlen (row[2]) * Str_MAX_BYTES_PER_CHAR;
if ((Feedback = (char *) malloc (LengthFeedback + 1)) == NULL)
Lay_NotEnoughMemoryExit ();
Str_Copy (Feedback,row[2],
LengthFeedback);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Feedback,LengthFeedback,false);
}
/* Get media (row[3]) */
Question->Answer.Options[NumOpt].Media.MedCod = Str_ConvertStrCodToLongCod (row[3]);
Med_GetMediaDataByCod (&Question->Answer.Options[NumOpt].Media);
HTM_TR_Begin (NULL);
/* Put an icon that indicates whether the answer
is correct or wrong (row[4]) */
HTM_TD_Begin ("class=\"BT%u\"",Gbl.RowEvenOdd);
if (row[4][0] == 'Y')
Ico_PutIcon ("check.svg",Txt_TST_Answer_given_by_the_teachers,"CONTEXT_ICO_16x16");
HTM_TD_End ();
/* Write the number of option */
HTM_TD_Begin ("class=\"DAT_SMALL LT\"");
HTM_TxtF ("%c)&nbsp;",'a' + (char) NumOpt);
HTM_TD_End ();
HTM_TD_Begin ("class=\"LT\"");
/* Write the text of the answer and the media */
HTM_DIV_Begin ("class=\"TEST_EDI\"");
HTM_Txt (AnswerTxt);
Med_ShowMedia (&Question->Answer.Options[NumOpt].Media,
"TEST_MED_EDIT_LIST_CONT",
"TEST_MED_EDIT_LIST");
HTM_DIV_End ();
/* Write the text of the feedback */
HTM_DIV_Begin ("class=\"TEST_EDI_LIGHT\"");
if (LengthFeedback)
HTM_Txt (Feedback);
HTM_DIV_End ();
HTM_TD_End ();
HTM_TR_End ();
/* Free memory allocated for the answer and the feedback */
free (AnswerTxt);
if (LengthFeedback)
free (Feedback);
}
HTM_TABLE_End ();
}
/*****************************************************************************/
/******** Write single or multiple choice answer when seeing a test **********/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_WriteChoiceAnsSeeing (const struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question,
MYSQL_RES *mysql_res)
2020-03-30 18:54:17 +02:00
{
2020-04-01 03:11:05 +02:00
unsigned NumOpt;
unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION]; // Indexes of all answers of this question
bool UsrAnswers[Tst_MAX_OPTIONS_PER_QUESTION];
char StrQstIndOrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Qstxx...x", "Indxx...x" or "Ansxx...x"
2020-03-30 18:54:17 +02:00
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
2020-04-01 03:11:05 +02:00
/***** Get text and correctness of answers for this question
from database (one row per answer) *****/
Tst_GetChoiceAns (Question,mysql_res);
2020-03-30 18:54:17 +02:00
2020-04-01 03:11:05 +02:00
/***** Get indexes for this question from string *****/
Tst_GetIndexesFromStr (Result->Questions[NumQst].StrIndexes,Indexes);
2020-03-30 18:54:17 +02:00
2020-04-01 03:11:05 +02:00
/***** Get the user's answers for this question from string *****/
Tst_GetAnswersFromStr (Result->Questions[NumQst].StrAnswers,UsrAnswers);
2020-03-30 18:54:17 +02:00
2020-04-01 03:11:05 +02:00
/***** Begin table *****/
HTM_TABLE_BeginPadding (2);
2019-11-04 01:29:46 +01:00
2020-04-01 03:11:05 +02:00
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
{
/***** Allocate memory for text in this choice answer *****/
if (!Tst_AllocateTextChoiceAnswer (Question,NumOpt))
/* Abort on error */
Ale_ShowAlertsAndExit ();
/***** Indexes are 0,1,2,3... if no shuffle
or 3,1,0,2... (example) if shuffle *****/
HTM_TR_Begin (NULL);
/***** Write selectors and letter of this option *****/
/* 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_TD_Begin ("class=\"LT\"");
snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns), // TODO: Remove indexes from this form because they will be got from tst_exam_questions in database
"Ind%010u",
NumQst);
Par_PutHiddenParamUnsigned (NULL,StrQstIndOrAns,Indexes[NumOpt]);
snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns),
"Ans%010u",
NumQst);
if (Question->Answer.Type == Tst_ANS_UNIQUE_CHOICE)
HTM_INPUT_RADIO (StrQstIndOrAns,false,
"id=\"Ans%010u_%u\" value=\"%u\"%s"
" onclick=\"selectUnselectRadio(this,this.form.Ans%010u,%u);\"",
NumQst,NumOpt,
Indexes[NumOpt],
UsrAnswers[Indexes[NumOpt]] ? " checked=\"checked\"" :
"",
NumQst,Question->Answer.NumOptions);
else // Answer.Type == Tst_ANS_MULTIPLE_CHOICE
HTM_INPUT_CHECKBOX (StrQstIndOrAns,HTM_DONT_SUBMIT_ON_CHANGE,
"id=\"Ans%010u_%u\" value=\"%u\"%s",
NumQst,NumOpt,
Indexes[NumOpt],
UsrAnswers[Indexes[NumOpt]] ? " checked=\"checked\"" :
"");
HTM_TD_End ();
HTM_TD_Begin ("class=\"LT\"");
HTM_LABEL_Begin ("for=\"Ans%010u_%u\" class=\"ANS_TXT\"",NumQst,NumOpt);
HTM_TxtF ("%c)&nbsp;",'a' + (char) NumOpt);
HTM_LABEL_End ();
HTM_TD_End ();
/***** Write the option text *****/
HTM_TD_Begin ("class=\"LT\"");
HTM_LABEL_Begin ("for=\"Ans%010u_%u\" class=\"ANS_TXT\"",NumQst,NumOpt);
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");
HTM_TD_End ();
HTM_TR_End ();
}
/***** End table *****/
HTM_TABLE_End ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/******* Write single or multiple choice answer when assessing a test ********/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_WriteChoiceAnsResult (struct UsrData *UsrDat,
const struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
extern const char *Txt_TST_Answer_given_by_the_user;
extern const char *Txt_TST_Answer_given_by_the_teachers;
unsigned NumOpt;
unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION]; // Indexes of all answers of this question
bool UsrAnswers[Tst_MAX_OPTIONS_PER_QUESTION];
struct
{
char *Class;
char *Str;
} Ans;
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Get text and correctness of answers for this question
from database (one row per answer) *****/
Tst_GetChoiceAns (Question,mysql_res);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Get indexes for this question from string *****/
Tst_GetIndexesFromStr (Result->Questions[NumQst].StrIndexes,Indexes);
/***** Get the user's answers for this question from string *****/
Tst_GetAnswersFromStr (Result->Questions[NumQst].StrAnswers,UsrAnswers);
/***** Begin table *****/
2019-10-23 19:05:05 +02:00
HTM_TABLE_BeginPadding (2);
HTM_TR_Begin (NULL);
2018-12-09 13:11:20 +01:00
Tst_WriteHeadUserCorrect (UsrDat);
2020-04-01 03:11:05 +02:00
HTM_TD_Empty (2);
2019-10-23 19:05:05 +02:00
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Write answers (one row per answer) *****/
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
HTM_TR_Begin (NULL);
/* Draw icon depending on user's answer */
if (UsrAnswers[Indexes[NumOpt]] == true) // This answer has been selected by the user
{
if (TsV_IsVisibleCorrectAns (Visibility))
{
if (Question->Answer.Options[Indexes[NumOpt]].Correct)
{
Ans.Class = "ANS_OK";
Ans.Str = "&check;";
}
else
{
Ans.Class = "ANS_BAD";
Ans.Str = "&cross;";
}
}
else
{
Ans.Class = "ANS_0";
Ans.Str = "&bull;";
}
HTM_TD_Begin ("class=\"%s CT\" title=\"%s\"",
Ans.Class,Txt_TST_Answer_given_by_the_user);
HTM_Txt (Ans.Str);
HTM_TD_End ();
}
else // This answer has NOT been selected by the user
HTM_TD_Empty (1);
/* Draw icon that indicates whether the answer is correct */
if (TsV_IsVisibleCorrectAns (Visibility))
{
if (Question->Answer.Options[Indexes[NumOpt]].Correct)
{
HTM_TD_Begin ("class=\"ANS_0 CT\" title=\"%s\"",
Txt_TST_Answer_given_by_the_teachers);
HTM_Txt ("&bull;");
HTM_TD_End ();
}
else
HTM_TD_Empty (1);
}
else
2019-10-10 15:03:47 +02:00
{
2020-04-01 03:11:05 +02:00
HTM_TD_Begin ("class=\"ANS_0 CT\"");
Ico_PutIconNotVisible ();
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2019-10-10 15:03:47 +02:00
}
2020-04-01 03:11:05 +02:00
/* Answer letter (a, b, c,...) */
HTM_TD_Begin ("class=\"ANS_TXT LT\"");
HTM_TxtF ("%c)&nbsp;",'a' + (char) NumOpt);
HTM_TD_End ();
/* Answer text and feedback */
HTM_TD_Begin ("class=\"LT\"");
HTM_DIV_Begin ("class=\"ANS_TXT\"");
if (TsV_IsVisibleQstAndAnsTxt (Visibility))
{
HTM_Txt (Question->Answer.Options[Indexes[NumOpt]].Text);
Med_ShowMedia (&Question->Answer.Options[Indexes[NumOpt]].Media,
"TEST_MED_SHOW_CONT",
"TEST_MED_SHOW");
}
2014-12-01 23:55:08 +01:00
else
2020-04-01 03:11:05 +02:00
Ico_PutIconNotVisible ();
HTM_DIV_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
if (TsV_IsVisibleCorrectAns (Visibility))
if (Question->Answer.Options[Indexes[NumOpt]].Feedback)
if (Question->Answer.Options[Indexes[NumOpt]].Feedback[0])
{
HTM_DIV_Begin ("class=\"TEST_EXA_LIGHT\"");
HTM_Txt (Question->Answer.Options[Indexes[NumOpt]].Feedback);
HTM_DIV_End ();
}
2019-10-10 10:41:00 +02:00
2020-04-01 03:11:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
}
2020-02-17 12:27:28 +01:00
/***** Write the score of this question *****/
2020-02-18 09:19:33 +01:00
if (TsV_IsVisibleEachQstScore (Visibility))
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
Tst_WriteScoreStart (4);
if (Result->Questions[NumQst].Score == 0.0)
HTM_SPAN_Begin ("class=\"ANS_0\"");
else if (Result->Questions[NumQst].Score > 0.0)
2019-11-07 10:24:00 +01:00
HTM_SPAN_Begin ("class=\"ANS_OK\"");
2020-04-01 03:11:05 +02:00
else
2019-11-07 10:24:00 +01:00
HTM_SPAN_Begin ("class=\"ANS_BAD\"");
2020-04-01 03:11:05 +02:00
HTM_Double2Decimals (Result->Questions[NumQst].Score);
2019-11-07 10:24:00 +01:00
HTM_SPAN_End ();
2014-12-01 23:55:08 +01:00
Tst_WriteScoreEnd ();
}
2020-04-01 03:11:05 +02:00
/***** End table *****/
2019-10-23 19:05:05 +02:00
HTM_TABLE_End ();
2014-12-01 23:55:08 +01:00
}
2020-03-30 18:54:17 +02:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/************************ Get choice answer from row *************************/
2020-03-30 18:54:17 +02:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_GetChoiceAns (struct Tst_Question *Question,MYSQL_RES *mysql_res)
2020-03-30 18:54:17 +02:00
{
2020-04-01 03:11:05 +02:00
unsigned NumOpt;
2020-03-30 18:54:17 +02:00
MYSQL_ROW row;
2020-04-01 03:11:05 +02:00
/***** Get text and correctness of answers for this question
from database (one row per answer) *****/
2020-03-30 18:54:17 +02:00
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
2020-04-01 03:11:05 +02:00
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
2020-03-30 18:54:17 +02:00
{
2020-04-01 03:11:05 +02:00
/***** Get next answer *****/
2020-03-30 18:54:17 +02:00
row = mysql_fetch_row (mysql_res);
2020-04-01 03:11:05 +02:00
/***** Allocate memory for text in this choice option *****/
if (!Tst_AllocateTextChoiceAnswer (Question,NumOpt))
/* Abort on error */
Ale_ShowAlertsAndExit ();
/***** Copy answer text (row[1]) and convert it,
that is in HTML, to rigorous HTML ******/
Str_Copy (Question->Answer.Options[NumOpt].Text,row[1],
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Question->Answer.Options[NumOpt].Text,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false);
/***** Copy answer feedback (row[2]) and convert it,
that is in HTML, to rigorous HTML ******/
if (TsV_IsVisibleFeedbackTxt (TstCfg_GetConfigVisibility ()))
if (row[2])
if (row[2][0])
{
Str_Copy (Question->Answer.Options[NumOpt].Feedback,row[2],
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Question->Answer.Options[NumOpt].Feedback,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false);
}
/***** Get media (row[3]) *****/
Question->Answer.Options[NumOpt].Media.MedCod = Str_ConvertStrCodToLongCod (row[3]);
Med_GetMediaDataByCod (&Question->Answer.Options[NumOpt].Media);
/***** Assign correctness (row[4]) of this answer (this option) *****/
Question->Answer.Options[NumOpt].Correct = (row[4][0] == 'Y');
2020-03-30 18:54:17 +02:00
}
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/******************** Write text answer when seeing a test *******************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_WriteTextAnsSeeing (const struct TsR_Result *Result,
unsigned NumQst)
2014-12-01 23:55:08 +01:00
{
2020-03-30 00:30:08 +02:00
char StrQstIndOrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Qstxx...x", "Indxx...x" or "Ansxx...x"
2019-11-04 01:29:46 +01:00
2014-12-01 23:55:08 +01:00
/***** Write input field for the answer *****/
2020-03-30 00:30:08 +02:00
snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns),
"Ans%010u",
2019-11-04 01:29:46 +01:00
NumQst);
2020-04-01 03:11:05 +02:00
HTM_INPUT_TEXT (StrQstIndOrAns,Tst_MAX_BYTES_ANSWERS_ONE_QST,Result->Questions[NumQst].StrAnswers,false,
"size=\"40\"");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
/***************** Write text answer when assessing a test *******************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-04-01 03:11:05 +02:00
static void Tst_WriteTextAnsResult (struct UsrData *UsrDat,
const struct TsR_Result *Result,
unsigned NumQst,
struct Tst_Question *Question,
MYSQL_RES *mysql_res,
unsigned Visibility)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
unsigned NumOpt;
2014-12-01 23:55:08 +01:00
MYSQL_ROW row;
2020-04-01 03:11:05 +02:00
char TextAnsUsr[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1];
char TextAnsOK[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1];
bool Correct = false;
2016-04-06 14:41:47 +02:00
/*
2019-03-02 21:49:11 +01:00
row[0] AnsInd
row[1] Answer
row[2] Feedback
2019-03-18 15:42:22 +01:00
row[3] MedCod
row[4] Correct
2016-04-06 14:41:47 +02:00
*/
2020-04-01 03:11:05 +02:00
/***** Get text and correctness of answers for this question from database (one row per answer) *****/
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
/***** Get next answer *****/
2014-12-01 23:55:08 +01:00
row = mysql_fetch_row (mysql_res);
2020-04-01 03:11:05 +02:00
/***** Allocate memory for text in this choice answer *****/
if (!Tst_AllocateTextChoiceAnswer (Question,NumOpt))
/* Abort on error */
Ale_ShowAlertsAndExit ();
/***** Copy answer text (row[1]) and convert it, that is in HTML, to rigorous HTML ******/
Str_Copy (Question->Answer.Options[NumOpt].Text,row[1],
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Question->Answer.Options[NumOpt].Text,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false);
/***** Copy answer feedback (row[2]) and convert it, that is in HTML, to rigorous HTML ******/
if (TsV_IsVisibleFeedbackTxt (Visibility))
if (row[2])
if (row[2][0])
{
Str_Copy (Question->Answer.Options[NumOpt].Feedback,row[2],
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Question->Answer.Options[NumOpt].Feedback,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false);
}
/***** Assign correctness (row[4]) of this answer (this option) *****/
Question->Answer.Options[NumOpt].Correct = (row[4][0] == 'Y');
2014-12-01 23:55:08 +01:00
}
/***** Header with the title of each column *****/
2019-10-23 19:05:05 +02:00
HTM_TABLE_BeginPadding (2);
HTM_TR_Begin (NULL);
2018-12-09 13:11:20 +01:00
Tst_WriteHeadUserCorrect (UsrDat);
2019-10-23 19:05:05 +02:00
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
2019-10-10 10:41:00 +02:00
/***** Write the user answer *****/
2020-04-01 03:11:05 +02:00
if (Result->Questions[NumQst].StrAnswers[0]) // If user has answered the question
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
/* Filter the user answer */
Str_Copy (TextAnsUsr,Result->Questions[NumQst].StrAnswers,
Tst_MAX_BYTES_ANSWERS_ONE_QST);
/* In order to compare student answer to stored answer,
the text answers are stored avoiding two or more consecurive spaces */
Str_ReplaceSeveralSpacesForOne (TextAnsUsr);
Str_ConvertToComparable (TextAnsUsr);
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
2019-10-10 11:08:53 +02:00
{
2020-04-01 03:11:05 +02:00
/* Filter this correct answer */
Str_Copy (TextAnsOK,Question->Answer.Options[NumOpt].Text,
Tst_MAX_BYTES_ANSWERS_ONE_QST);
Str_ConvertToComparable (TextAnsOK);
/* Check is user answer is correct */
if (!strcoll (TextAnsUsr,TextAnsOK))
{
Correct = true;
break;
}
2019-10-10 11:08:53 +02:00
}
2020-04-01 03:11:05 +02:00
HTM_TD_Begin ("class=\"%s CT\"",
TsV_IsVisibleCorrectAns (Visibility) ?
(Correct ? "ANS_OK" :
"ANS_BAD") :
"ANS_0");
HTM_Txt (Result->Questions[NumQst].StrAnswers);
2014-12-01 23:55:08 +01:00
}
2020-04-01 03:11:05 +02:00
else // If user has omitted the answer
2019-10-23 19:05:05 +02:00
HTM_TD_Begin (NULL);
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/***** Write the correct answers *****/
2020-02-18 09:19:33 +01:00
if (TsV_IsVisibleQstAndAnsTxt (Visibility) &&
TsV_IsVisibleCorrectAns (Visibility))
2020-01-11 15:22:02 +01:00
{
2020-04-01 03:11:05 +02:00
HTM_TD_Begin ("class=\"CT\"");
HTM_TABLE_BeginPadding (2);
2019-10-10 10:41:00 +02:00
2020-04-01 03:11:05 +02:00
for (NumOpt = 0;
NumOpt < Question->Answer.NumOptions;
NumOpt++)
{
HTM_TR_Begin (NULL);
2014-12-01 23:55:08 +01:00
2020-04-01 03:11:05 +02:00
/* Answer letter (a, b, c,...) */
HTM_TD_Begin ("class=\"ANS_0 LT\"");
HTM_TxtF ("%c)&nbsp;",'a' + (char) NumOpt);
HTM_TD_End ();
/* Answer text and feedback */
HTM_TD_Begin ("class=\"LT\"");
HTM_DIV_Begin ("class=\"ANS_0\"");
HTM_Txt (Question->Answer.Options[NumOpt].Text);
HTM_DIV_End ();
if (TsV_IsVisibleFeedbackTxt (Visibility))
if (Question->Answer.Options[NumOpt].Feedback)
if (Question->Answer.Options[NumOpt].Feedback[0])
{
HTM_DIV_Begin ("class=\"TEST_EXA_LIGHT\"");
HTM_Txt (Question->Answer.Options[NumOpt].Feedback);
HTM_DIV_End ();
}
HTM_TD_End ();
HTM_TR_End ();
}
HTM_TABLE_End ();
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
}
else
{
2020-04-01 03:11:05 +02:00
HTM_TD_Begin ("class=\"ANS_0 CT\"");
Ico_PutIconNotVisible ();
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
}
2020-04-01 03:11:05 +02:00
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
2020-02-17 12:27:28 +01:00
/***** Write the score of this question *****/
2020-02-18 09:19:33 +01:00
if (TsV_IsVisibleEachQstScore (Visibility))
2014-12-01 23:55:08 +01:00
{
2020-04-01 03:11:05 +02:00
Tst_WriteScoreStart (4);
if (!Result->Questions[NumQst].StrAnswers[0]) // If user has omitted the answer
2019-11-07 10:24:00 +01:00
{
HTM_SPAN_Begin ("class=\"ANS_0\"");
2019-12-14 13:35:35 +01:00
HTM_Double2Decimals (0.0);
2019-11-07 10:24:00 +01:00
}
2020-04-01 03:11:05 +02:00
else if (Correct) // If correct
2019-11-07 10:24:00 +01:00
{
HTM_SPAN_Begin ("class=\"ANS_OK\"");
2019-12-14 13:35:35 +01:00
HTM_Double2Decimals (1.0);
2019-11-07 10:24:00 +01:00
}
2020-04-01 03:11:05 +02:00
else // If wrong
2019-11-07 10:24:00 +01:00
{
HTM_SPAN_Begin ("class=\"ANS_BAD\"");
2019-12-14 13:35:35 +01:00
HTM_Double2Decimals (0.0);
2019-11-07 10:24:00 +01:00
}
HTM_SPAN_End ();
2014-12-01 23:55:08 +01:00
Tst_WriteScoreEnd ();
}
2019-10-23 19:05:05 +02:00
HTM_TABLE_End ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/********* Write head with two columns: ********/
/********* one for the user's answer and other for the correct answer ********/
/*****************************************************************************/
2018-12-09 13:11:20 +01:00
static void Tst_WriteHeadUserCorrect (struct UsrData *UsrDat)
2014-12-01 23:55:08 +01:00
{
2016-06-15 14:42:28 +02:00
extern const char *Txt_User[Usr_NUM_SEXS];
2018-12-09 13:11:20 +01:00
extern const char *Txt_ROLES_PLURAL_Abc[Rol_NUM_ROLES][Usr_NUM_SEXS];
2014-12-01 23:55:08 +01:00
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"DAT_SMALL CM\"");
2019-11-10 12:36:37 +01:00
HTM_Txt (Txt_User[UsrDat->Sex]);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"DAT_SMALL CM\"");
2019-11-10 12:36:37 +01:00
HTM_Txt (Txt_ROLES_PLURAL_Abc[Rol_TCH][Usr_SEX_UNKNOWN]);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/*********** Write the start ans the end of the score of an answer ***********/
/*****************************************************************************/
static void Tst_WriteScoreStart (unsigned ColSpan)
{
extern const char *Txt_Score;
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
HTM_TD_Begin ("colspan=\"%u\" class=\"DAT_SMALL LM\"",ColSpan);
2020-01-11 15:22:02 +01:00
HTM_TxtColonNBSP (Txt_Score);
2014-12-01 23:55:08 +01:00
}
static void Tst_WriteScoreEnd (void)
{
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/*************** Write parameter with the code of a question *****************/
/*****************************************************************************/
static void Tst_WriteParamQstCod (unsigned NumQst,long QstCod)
{
2020-03-30 00:30:08 +02:00
char StrQstIndOrAns[3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1]; // "Qstxx...x", "Indxx...x" or "Ansxx...x"
2014-12-01 23:55:08 +01:00
2020-03-30 00:30:08 +02:00
snprintf (StrQstIndOrAns,sizeof (StrQstIndOrAns),
"Qst%010u",
2018-10-18 02:02:32 +02:00
NumQst);
2020-03-30 00:30:08 +02:00
Par_PutHiddenParamLong (NULL,StrQstIndOrAns,QstCod);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/********************* Check if number of answers is one *********************/
/*****************************************************************************/
2020-03-19 20:57:54 +01:00
void Tst_CheckIfNumberOfAnswersIsOne (const struct Tst_Question *Question)
2014-12-01 23:55:08 +01:00
{
2020-03-19 20:57:54 +01:00
if (Question->Answer.NumOptions != 1)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Wrong answer.");
}
/*****************************************************************************/
/************************* Get tags of a test question ***********************/
/*****************************************************************************/
unsigned long Tst_GetTagsQst (long QstCod,MYSQL_RES **mysql_res)
{
/***** Get the tags of a question from database *****/
2018-11-02 01:38:44 +01:00
return DB_QuerySELECT (mysql_res,"can not get the tags of a question",
"SELECT tst_tags.TagTxt FROM tst_question_tags,tst_tags"
" WHERE tst_question_tags.QstCod=%ld"
" AND tst_question_tags.TagCod=tst_tags.TagCod"
" AND tst_tags.CrsCod=%ld"
" ORDER BY tst_question_tags.TagInd",
2019-04-04 10:45:15 +02:00
QstCod,Gbl.Hierarchy.Crs.CrsCod);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/******************** Get and write tags of a test question ******************/
/*****************************************************************************/
2017-09-06 15:03:43 +02:00
void Tst_GetAndWriteTagsQst (long QstCod)
2014-12-01 23:55:08 +01:00
{
extern const char *Txt_no_tags;
2019-11-07 14:34:03 +01:00
unsigned long NumRow;
unsigned long NumRows;
2014-12-01 23:55:08 +01:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
if ((NumRows = Tst_GetTagsQst (QstCod,&mysql_res))) // Result: TagTxt
{
/***** Write the tags *****/
2019-10-26 12:25:27 +02:00
HTM_UL_Begin ("class=\"TEST_TAG_LIST DAT_SMALL\"");
2014-12-01 23:55:08 +01:00
for (NumRow = 0;
NumRow < NumRows;
NumRow++)
{
row = mysql_fetch_row (mysql_res);
2019-10-26 22:49:13 +02:00
HTM_LI_Begin (NULL);
2019-11-10 12:36:37 +01:00
HTM_Txt (row[0]);
2019-10-26 22:49:13 +02:00
HTM_LI_End ();
2014-12-01 23:55:08 +01:00
}
2019-10-26 02:19:42 +02:00
HTM_UL_End ();
2014-12-01 23:55:08 +01:00
}
else
2019-11-07 10:24:00 +01:00
{
HTM_SPAN_Begin ("class=\"DAT_SMALL\"");
2019-11-11 00:15:44 +01:00
HTM_TxtF ("(%s)",Txt_no_tags);
2019-11-07 10:24:00 +01:00
HTM_SPAN_End ();
}
2014-12-01 23:55:08 +01:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
/************ Get parameters for the selection of test questions *************/
/*****************************************************************************/
2016-04-05 02:59:34 +02:00
// Return true (OK) if all parameters are found, or false (error) if any necessary parameter is not found
2014-12-01 23:55:08 +01:00
2020-03-24 00:58:42 +01:00
static bool Tst_GetParamsTst (struct Tst_Test *Test,
2020-03-21 15:41:25 +01:00
Tst_ActionToDoWithQuestions_t ActionToDoWithQuestions)
2014-12-01 23:55:08 +01:00
{
extern const char *Txt_You_must_select_one_ore_more_tags;
extern const char *Txt_You_must_select_one_ore_more_types_of_answer;
extern const char *Txt_The_number_of_questions_must_be_in_the_interval_X;
bool Error = false;
2019-11-08 01:10:32 +01:00
char UnsignedStr[Cns_MAX_DECIMAL_DIGITS_UINT + 1];
2014-12-01 23:55:08 +01:00
unsigned UnsignedNum;
/***** Tags *****/
/* Get parameter that indicates whether all tags are selected */
2020-03-24 00:58:42 +01:00
Test->Tags.All = Par_GetParToBool ("AllTags");
2014-12-01 23:55:08 +01:00
/* Get the tags */
2020-03-24 00:58:42 +01:00
if ((Test->Tags.List = (char *) malloc (Tst_MAX_BYTES_TAGS_LIST + 1)) == NULL)
2018-10-18 20:06:54 +02:00
Lay_NotEnoughMemoryExit ();
2020-03-24 00:58:42 +01:00
Par_GetParMultiToText ("ChkTag",Test->Tags.List,Tst_MAX_BYTES_TAGS_LIST);
2014-12-01 23:55:08 +01:00
/* Check number of tags selected */
2020-03-24 00:58:42 +01:00
if (Tst_CountNumTagsInList (&Test->Tags) == 0) // If no tags selected...
2020-03-21 15:41:25 +01:00
{ // ...write alert
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_You_must_select_one_ore_more_tags);
2014-12-01 23:55:08 +01:00
Error = true;
}
/***** Types of answer *****/
2017-07-16 13:54:11 +02:00
switch (ActionToDoWithQuestions)
{
2017-09-04 17:03:49 +02:00
case Tst_SHOW_TEST_TO_ANSWER:
case Tst_EDIT_TEST:
2017-07-16 13:54:11 +02:00
/* Get parameter that indicates if all types of answer are selected */
2020-03-24 00:58:42 +01:00
Test->AnswerTypes.All = Par_GetParToBool ("AllAnsTypes");
2017-07-16 13:54:11 +02:00
/* Get types of answer */
2020-03-24 00:58:42 +01:00
Par_GetParMultiToText ("AnswerType",Test->AnswerTypes.List,Tst_MAX_BYTES_LIST_ANSWER_TYPES);
2017-07-16 13:54:11 +02:00
/* Check number of types of answer */
2020-03-24 00:58:42 +01:00
if (Tst_CountNumAnswerTypesInList (&Test->AnswerTypes) == 0) // If no types of answer selected...
2020-03-21 22:18:24 +01:00
{ // ...write warning alert
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_You_must_select_one_ore_more_types_of_answer);
2017-07-16 13:54:11 +02:00
Error = true;
}
break;
case Tst_SELECT_QUESTIONS_FOR_GAME:
/* The unique allowed type of answer in a game is unique choice */
2020-03-24 00:58:42 +01:00
Test->AnswerTypes.All = false;
snprintf (Test->AnswerTypes.List,sizeof (Test->AnswerTypes.List),
2018-10-18 02:02:32 +02:00
"%u",
(unsigned) Tst_ANS_UNIQUE_CHOICE);
2017-07-16 13:54:11 +02:00
break;
2017-09-01 14:36:25 +02:00
default:
break;
2014-12-01 23:55:08 +01:00
}
/***** Get other parameters, depending on action *****/
2017-07-16 13:54:11 +02:00
switch (ActionToDoWithQuestions)
2014-12-01 23:55:08 +01:00
{
2017-09-04 17:03:49 +02:00
case Tst_SHOW_TEST_TO_ANSWER:
2020-03-24 01:50:39 +01:00
Test->NumQsts = Tst_GetParamNumQsts ();
if (Test->NumQsts < TstCfg_GetConfigMin () ||
Test->NumQsts > TstCfg_GetConfigMax ())
2017-07-16 13:54:11 +02:00
{
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_The_number_of_questions_must_be_in_the_interval_X,
2020-03-21 15:41:25 +01:00
TstCfg_GetConfigMin (),TstCfg_GetConfigMax ());
2017-07-16 13:54:11 +02:00
Error = true;
}
break;
2017-09-04 17:03:49 +02:00
case Tst_EDIT_TEST:
2017-07-16 13:54:11 +02:00
/* Get starting and ending dates */
Dat_GetIniEndDatesFromForm ();
/* Get ordering criteria */
2019-11-08 01:10:32 +01:00
Par_GetParMultiToText ("Order",UnsignedStr,Cns_MAX_DECIMAL_DIGITS_UINT);
2017-07-16 13:54:11 +02:00
if (sscanf (UnsignedStr,"%u",&UnsignedNum) == 1)
2020-03-24 00:58:42 +01:00
Test->SelectedOrder = (Tst_QuestionsOrder_t)
((UnsignedNum < Tst_NUM_TYPES_ORDER_QST) ? UnsignedNum :
0);
2017-07-16 13:54:11 +02:00
else
2020-03-24 00:58:42 +01:00
Test->SelectedOrder = (Tst_QuestionsOrder_t) 0;
2017-07-16 13:54:11 +02:00
break;
case Tst_SELECT_QUESTIONS_FOR_GAME:
/* Get starting and ending dates */
Dat_GetIniEndDatesFromForm ();
2014-12-01 23:55:08 +01:00
2017-07-16 13:54:11 +02:00
/* Order question by stem */
2020-03-24 00:58:42 +01:00
Test->SelectedOrder = Tst_ORDER_STEM;
2017-07-16 13:54:11 +02:00
break;
2017-09-01 14:36:25 +02:00
default:
break;
2014-12-01 23:55:08 +01:00
}
2016-04-05 02:59:34 +02:00
return !Error;
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/******************** Get parameter with the number of test ******************/
/*****************************************************************************/
static unsigned Tst_GetAndCheckParamNumTst (void)
{
2017-01-29 21:41:08 +01:00
return (unsigned) Par_GetParToUnsignedLong ("NumTst",
1,
UINT_MAX,
1);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/***** Get parameter with the number of questions to generate in an test *****/
/*****************************************************************************/
2020-03-22 01:15:27 +01:00
static unsigned Tst_GetParamNumQsts (void)
2014-12-01 23:55:08 +01:00
{
2020-03-22 01:15:27 +01:00
return (unsigned) Par_GetParToUnsignedLong ("NumQst",
(unsigned long) TstCfg_GetConfigMin (),
(unsigned long) TstCfg_GetConfigMax (),
(unsigned long) TstCfg_GetConfigDef ());
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/***************** Count number of tags in the list of tags ******************/
/*****************************************************************************/
2020-03-21 15:41:25 +01:00
static unsigned Tst_CountNumTagsInList (const struct Tst_Tags *Tags)
2014-12-01 23:55:08 +01:00
{
const char *Ptr;
2020-03-21 15:41:25 +01:00
unsigned NumTags = 0;
2017-01-28 15:58:46 +01:00
char TagText[Tst_MAX_BYTES_TAG + 1];
2014-12-01 23:55:08 +01:00
2020-03-21 15:41:25 +01:00
/***** Go over the list of tags counting the number of tags *****/
Ptr = Tags->List;
while (*Ptr)
2014-12-01 23:55:08 +01:00
{
2020-03-21 15:41:25 +01:00
Par_GetNextStrUntilSeparParamMult (&Ptr,TagText,Tst_MAX_BYTES_TAG);
NumTags++;
2014-12-01 23:55:08 +01:00
}
2020-03-21 15:41:25 +01:00
2014-12-01 23:55:08 +01:00
return NumTags;
}
/*****************************************************************************/
/**** Count the number of types of answers in the list of types of answers ***/
/*****************************************************************************/
2020-03-21 22:18:24 +01:00
static int Tst_CountNumAnswerTypesInList (const struct Tst_AnswerTypes *AnswerTypes)
2014-12-01 23:55:08 +01:00
{
const char *Ptr;
int NumAnsTypes = 0;
2019-11-08 01:10:32 +01:00
char UnsignedStr[Cns_MAX_DECIMAL_DIGITS_UINT + 1];
2014-12-01 23:55:08 +01:00
2020-03-21 22:18:24 +01:00
/***** Go over the list of answer types counting the number of types of answer *****/
Ptr = AnswerTypes->List;
2014-12-01 23:55:08 +01:00
while (*Ptr)
{
2019-11-08 01:10:32 +01:00
Par_GetNextStrUntilSeparParamMult (&Ptr,UnsignedStr,Cns_MAX_DECIMAL_DIGITS_UINT);
2014-12-01 23:55:08 +01:00
Tst_ConvertFromUnsignedStrToAnsTyp (UnsignedStr);
NumAnsTypes++;
}
return NumAnsTypes;
}
/*****************************************************************************/
/******************** Show form to edit one test question ********************/
/*****************************************************************************/
void Tst_ShowFormEditOneQst (void)
{
2020-03-17 14:47:58 +01:00
struct Tst_Question Question;
2017-01-17 03:10:43 +01:00
char Stem[Cns_MAX_BYTES_TEXT + 1];
char Feedback[Cns_MAX_BYTES_TEXT + 1];
2014-12-01 23:55:08 +01:00
2016-04-06 19:26:09 +02:00
/***** Create test question *****/
2020-03-19 20:57:54 +01:00
Tst_QstConstructor (&Question);
2020-03-18 01:57:08 +01:00
/***** Get question data *****/
2020-03-25 01:36:22 +01:00
Question.QstCod = Tst_GetQstCod ();
2014-12-01 23:55:08 +01:00
Stem[0] = Feedback[0] = '\0';
2020-03-25 01:36:22 +01:00
if (Question.QstCod > 0) // If question already exists in the database
Tst_GetQstDataFromDB (&Question,Stem,Feedback);
2016-04-06 19:26:09 +02:00
/***** Put form to edit question *****/
2020-03-25 01:36:22 +01:00
Tst_PutFormEditOneQst (&Question,Stem,Feedback);
2016-04-06 19:26:09 +02:00
/***** Destroy test question *****/
2020-03-19 20:57:54 +01:00
Tst_QstDestructor (&Question);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/******************** Show form to edit one test question ********************/
/*****************************************************************************/
// This function may be called from three places:
2016-11-27 14:34:36 +01:00
// 1. By clicking "New question" icon
// 2. By clicking "Edit" icon in a listing of existing questions
2014-12-01 23:55:08 +01:00
// 3. From the action associated to reception of a question, on error in the parameters received from the form
2020-03-27 14:56:54 +01:00
static void Tst_PutFormEditOneQst (struct Tst_Question *Question,
2020-03-17 00:35:11 +01:00
char Stem[Cns_MAX_BYTES_TEXT + 1],
2017-01-17 03:10:43 +01:00
char Feedback[Cns_MAX_BYTES_TEXT + 1])
2014-12-01 23:55:08 +01:00
{
2019-03-27 14:36:57 +01:00
extern const char *Hlp_ASSESSMENT_Tests_writing_a_question;
2019-02-22 21:47:50 +01:00
extern const char *The_ClassFormInBox[The_NUM_THEMES];
2014-12-01 23:55:08 +01:00
extern const char *Txt_Question_code_X;
extern const char *Txt_New_question;
extern const char *Txt_Tags;
extern const char *Txt_new_tag;
2019-09-12 23:53:00 +02:00
extern const char *Txt_Wording;
2014-12-01 23:55:08 +01:00
extern const char *Txt_Feedback;
2016-04-05 17:33:33 +02:00
extern const char *Txt_optional;
2014-12-01 23:55:08 +01:00
extern const char *Txt_Type;
extern const char *Txt_TST_STR_ANSWER_TYPES[Tst_NUM_ANS_TYPES];
extern const char *Txt_Answers;
extern const char *Txt_Integer_number;
extern const char *Txt_Real_number_between_A_and_B_1;
extern const char *Txt_Real_number_between_A_and_B_2;
extern const char *Txt_TF_QST[2];
extern const char *Txt_Shuffle;
2016-04-07 14:40:50 +02:00
extern const char *Txt_Expand;
extern const char *Txt_Contract;
2019-02-18 18:27:45 +01:00
extern const char *Txt_Save_changes;
2015-04-11 20:18:30 +02:00
extern const char *Txt_Create_question;
2014-12-01 23:55:08 +01:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
2020-03-24 01:50:39 +01:00
unsigned NumTags;
unsigned IndTag;
unsigned NumTag;
2014-12-01 23:55:08 +01:00
unsigned NumOpt;
Tst_AnswerType_t AnsType;
2019-11-07 00:34:20 +01:00
bool IsThisTag;
bool TagFound;
2014-12-01 23:55:08 +01:00
bool OptionsDisabled;
2016-04-07 10:23:28 +02:00
bool AnswerHasContent;
2016-04-07 17:14:35 +02:00
bool DisplayRightColumn;
2019-11-08 01:10:32 +01:00
char StrTagTxt[6 + Cns_MAX_DECIMAL_DIGITS_UINT + 1];
char StrInteger[Cns_MAX_DECIMAL_DIGITS_UINT + 1];
2019-12-30 22:32:06 +01:00
char *Title;
2014-12-01 23:55:08 +01:00
2019-10-26 02:19:42 +02:00
/***** Begin box *****/
2020-03-25 01:36:22 +01:00
if (Question->QstCod > 0) // The question already has assigned a code
2015-04-11 13:05:44 +02:00
{
2020-03-25 01:36:22 +01:00
Box_BoxBegin (NULL,Str_BuildStringLong (Txt_Question_code_X,Question->QstCod),
2020-03-27 14:56:54 +01:00
Tst_PutIconToRemoveOneQst,&Question->QstCod,
2019-03-27 14:36:57 +01:00
Hlp_ASSESSMENT_Tests_writing_a_question,Box_NOT_CLOSABLE);
2019-12-30 21:47:07 +01:00
Str_FreeString ();
2015-04-11 13:05:44 +02:00
}
else
2020-03-26 02:54:30 +01:00
Box_BoxBegin (NULL,Txt_New_question,
NULL,NULL,
2019-03-27 14:36:57 +01:00
Hlp_ASSESSMENT_Tests_writing_a_question,Box_NOT_CLOSABLE);
2016-04-05 10:05:52 +02:00
2019-10-20 22:00:28 +02:00
/***** Begin form *****/
2018-11-09 20:47:39 +01:00
Frm_StartForm (ActRcvTstQst);
2020-03-27 14:56:54 +01:00
Tst_PutParamQstCod (&Question->QstCod);
2016-04-05 10:05:52 +02:00
2019-10-20 22:00:28 +02:00
/***** Begin table *****/
2019-10-23 19:05:05 +02:00
HTM_TABLE_BeginPadding (2); // Table for this question
2015-04-11 13:05:44 +02:00
/***** Help for text editor *****/
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
HTM_TD_Begin ("colspan=\"2\"");
2015-04-11 13:05:44 +02:00
Lay_HelpPlainEditor ();
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
/***** Get tags already existing for questions in current course *****/
2020-03-24 01:50:39 +01:00
NumTags = Tst_GetAllTagsFromCurrentCrs (&mysql_res);
2014-12-01 23:55:08 +01:00
/***** Write the tags *****/
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"RT %s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
2019-11-11 10:59:24 +01:00
HTM_TxtF ("%s:",Txt_Tags);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"LT\"");
HTM_TABLE_BeginPadding (2); // Table for tags
2017-05-01 21:17:38 +02:00
2020-03-24 01:50:39 +01:00
for (IndTag = 0;
IndTag < Tst_MAX_TAGS_PER_QUESTION;
IndTag++)
2014-12-01 23:55:08 +01:00
{
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
2014-12-01 23:55:08 +01:00
/***** Write the tags already existing in a selector *****/
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"LM\"");
2019-11-05 15:47:35 +01:00
HTM_SELECT_Begin (false,
2020-03-24 01:50:39 +01:00
"id=\"SelTag%u\" name=\"SelTag%u\""
2019-11-05 15:47:35 +01:00
" class=\"TAG_SEL\" onchange=\"changeTxtTag('%u')\"",
2020-03-24 01:50:39 +01:00
IndTag,IndTag,IndTag);
2019-11-07 00:34:20 +01:00
HTM_OPTION (HTM_Type_STRING,"",false,false,"&nbsp;");
2017-04-25 14:48:47 +02:00
mysql_data_seek (mysql_res,0);
2019-11-07 00:34:20 +01:00
TagFound = false;
2020-03-24 01:50:39 +01:00
for (NumTag = 1;
NumTag <= NumTags;
NumTag++)
2014-12-01 23:55:08 +01:00
{
row = mysql_fetch_row (mysql_res);
2019-03-18 15:42:22 +01:00
/*
row[0] TagCod
row[1] TagTxt
row[2] TagHidden
*/
2019-11-07 00:34:20 +01:00
IsThisTag = false;
2020-03-25 01:36:22 +01:00
if (!strcasecmp (Question->Tags.Txt[IndTag],row[1]))
2014-12-01 23:55:08 +01:00
{
2019-11-11 00:15:44 +01:00
HTM_Txt (" selected=\"selected\"");
2019-11-07 00:34:20 +01:00
IsThisTag = true;
TagFound = true;
2014-12-01 23:55:08 +01:00
}
2019-11-07 00:34:20 +01:00
HTM_OPTION (HTM_Type_STRING,row[1],
IsThisTag,false,
"%s",row[1]);
2014-12-01 23:55:08 +01:00
}
/* If it's a new tag received from the form */
2020-03-25 01:36:22 +01:00
if (!TagFound && Question->Tags.Txt[IndTag][0])
HTM_OPTION (HTM_Type_STRING,Question->Tags.Txt[IndTag],
2019-11-07 00:34:20 +01:00
true,false,
2020-03-25 01:36:22 +01:00
"%s",Question->Tags.Txt[IndTag]);
2019-11-07 00:34:20 +01:00
HTM_OPTION (HTM_Type_STRING,"",
false,false,
"[%s]",Txt_new_tag);
2019-11-05 08:46:38 +01:00
HTM_SELECT_End ();
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
/***** Input of a new tag *****/
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"RM\"");
2019-11-04 01:29:46 +01:00
snprintf (StrTagTxt,sizeof (StrTagTxt),
"TagTxt%u",
2020-03-24 01:50:39 +01:00
IndTag);
2020-03-25 01:36:22 +01:00
HTM_INPUT_TEXT (StrTagTxt,Tst_MAX_CHARS_TAG,Question->Tags.Txt[IndTag],false,
2019-11-13 09:49:52 +01:00
"id=\"%s\" class=\"TAG_TXT\" onchange=\"changeSelTag('%u')\"",
2020-03-24 01:50:39 +01:00
StrTagTxt,IndTag);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
2019-10-23 19:05:05 +02:00
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
}
2017-05-01 21:17:38 +02:00
2019-10-23 19:05:05 +02:00
HTM_TABLE_End (); // Table for tags
HTM_TD_End ();
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
2016-04-04 10:11:05 +02:00
/***** Stem and image *****/
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
2019-10-07 21:15:14 +02:00
2019-12-27 21:10:39 +01:00
/* Label */
2019-12-27 15:45:19 +01:00
Frm_LabelColumn ("RT","Stem",Txt_Wording);
2019-10-07 21:15:14 +02:00
2019-12-27 21:10:39 +01:00
/* Data */
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"LT\"");
2019-10-31 17:42:05 +01:00
HTM_TEXTAREA_Begin ("id=\"Stem\" name=\"Stem\" class=\"STEM_TEXTAREA\""
" rows=\"5\" required=\"required\"");
2019-11-10 12:36:37 +01:00
HTM_Txt (Stem);
2019-10-31 17:42:05 +01:00
HTM_TEXTAREA_End ();
2019-11-09 21:08:20 +01:00
HTM_BR ();
2020-03-17 14:47:58 +01:00
Tst_PutFormToEditQstMedia (&Question->Media,-1,
2016-04-14 21:33:24 +02:00
false);
2016-03-29 10:24:14 +02:00
2014-12-01 23:55:08 +01:00
/***** Feedback *****/
2019-11-02 23:40:52 +01:00
HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
2019-11-11 10:59:24 +01:00
HTM_TxtF ("%s&nbsp;(%s):",Txt_Feedback,Txt_optional);
2019-11-09 21:08:20 +01:00
HTM_BR ();
2019-10-31 17:42:05 +01:00
HTM_TEXTAREA_Begin ("name=\"Feedback\" class=\"STEM_TEXTAREA\" rows=\"2\"");
2014-12-01 23:55:08 +01:00
if (Feedback)
if (Feedback[0])
2019-11-10 12:36:37 +01:00
HTM_Txt (Feedback);
2019-10-31 17:42:05 +01:00
HTM_TEXTAREA_End ();
2019-11-02 12:59:31 +01:00
HTM_LABEL_End ();
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
/***** Type of answer *****/
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"RT %s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
2019-11-11 10:59:24 +01:00
HTM_TxtF ("%s:",Txt_Type);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"%s LT\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
2019-12-15 20:02:34 +01:00
for (AnsType = (Tst_AnswerType_t) 0;
AnsType <= (Tst_AnswerType_t) (Tst_NUM_ANS_TYPES - 1);
2014-12-01 23:55:08 +01:00
AnsType++)
{
2019-11-03 10:41:31 +01:00
HTM_LABEL_Begin (NULL);
2019-11-04 18:17:39 +01:00
HTM_INPUT_RADIO ("AnswerType",false,
"value=\"%u\"%s onclick=\"enableDisableAns(this.form);\"",
(unsigned) AnsType,
2020-03-19 20:57:54 +01:00
AnsType == Question->Answer.Type ? " checked=\"checked\"" :
"");
2019-11-11 10:59:24 +01:00
HTM_TxtF ("%s&nbsp;",Txt_TST_STR_ANSWER_TYPES[AnsType]);
2019-11-02 12:59:31 +01:00
HTM_LABEL_End ();
2019-11-09 21:08:20 +01:00
HTM_BR ();
2014-12-01 23:55:08 +01:00
}
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
/***** Answers *****/
/* Integer answer */
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"RT %s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
2019-11-11 10:59:24 +01:00
HTM_TxtF ("%s:",Txt_Answers);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"LT\"");
2019-11-02 23:40:52 +01:00
HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
2020-01-11 15:22:02 +01:00
HTM_TxtColonNBSP (Txt_Integer_number);
2019-11-04 01:29:46 +01:00
snprintf (StrInteger,sizeof (StrInteger),
"%ld",
2020-03-19 20:57:54 +01:00
Question->Answer.Integer);
2019-11-08 01:10:32 +01:00
HTM_INPUT_TEXT ("AnsInt",Cns_MAX_DECIMAL_DIGITS_LONG,StrInteger,false,
2019-11-04 01:29:46 +01:00
"size=\"11\" required=\"required\"%s",
2020-03-19 20:57:54 +01:00
Question->Answer.Type == Tst_ANS_INT ? "" :
" disabled=\"disabled\"");
2019-11-02 12:59:31 +01:00
HTM_LABEL_End ();
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
/* Floating point answer */
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
HTM_TD_Empty (1);
HTM_TD_Begin ("class=\"LT\"");
2016-12-26 15:17:30 +01:00
Tst_PutFloatInputField (Txt_Real_number_between_A_and_B_1,"AnsFloatMin",
2020-03-19 20:57:54 +01:00
Question,0);
2016-12-26 15:17:30 +01:00
Tst_PutFloatInputField (Txt_Real_number_between_A_and_B_2,"AnsFloatMax",
2020-03-19 20:57:54 +01:00
Question,1);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
/* T/F answer */
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
HTM_TD_Empty (1);
HTM_TD_Begin ("class=\"LT\"");
2020-03-19 20:57:54 +01:00
Tst_PutTFInputField (Question,Txt_TF_QST[0],'T');
Tst_PutTFInputField (Question,Txt_TF_QST[1],'F');
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
/* Questions can be shuffled? */
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TD_Empty (1);
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"LT\"");
2019-11-02 23:40:52 +01:00
HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
2020-03-12 13:53:37 +01:00
HTM_INPUT_CHECKBOX ("Shuffle",HTM_DONT_SUBMIT_ON_CHANGE,
2019-11-04 20:41:35 +01:00
"value=\"Y\"%s%s",
2020-03-17 14:47:58 +01:00
Question->Shuffle ? " checked=\"checked\"" :
"",
2020-03-19 20:57:54 +01:00
Question->Answer.Type != Tst_ANS_UNIQUE_CHOICE &&
Question->Answer.Type != Tst_ANS_MULTIPLE_CHOICE ? " disabled=\"disabled\"" :
"");
2019-11-10 12:36:37 +01:00
HTM_Txt (Txt_Shuffle);
2019-11-02 12:59:31 +01:00
HTM_LABEL_End ();
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
/* Simple or multiple choice answers */
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
HTM_TD_Empty (1);
HTM_TD_Begin ("class=\"LT\"");
HTM_TABLE_BeginPadding (2); // Table with choice answers
2017-05-01 21:17:38 +02:00
2020-03-19 20:57:54 +01:00
OptionsDisabled = Question->Answer.Type != Tst_ANS_UNIQUE_CHOICE &&
Question->Answer.Type != Tst_ANS_MULTIPLE_CHOICE &&
Question->Answer.Type != Tst_ANS_TEXT;
2014-12-01 23:55:08 +01:00
for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
{
2016-04-05 14:32:02 +02:00
Gbl.RowEvenOdd = NumOpt % 2;
2016-04-07 14:40:50 +02:00
AnswerHasContent = false;
2020-03-19 20:57:54 +01:00
if (Question->Answer.Options[NumOpt].Text)
if (Question->Answer.Options[NumOpt].Text[0] || // Text
Question->Answer.Options[NumOpt].Media.Type != Med_TYPE_NONE) // or media
2016-04-07 14:40:50 +02:00
AnswerHasContent = true;
2016-11-19 19:39:17 +01:00
DisplayRightColumn = NumOpt < 2 || // Display at least the two first options
AnswerHasContent;
2016-04-07 14:40:50 +02:00
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
2019-10-07 21:15:14 +02:00
/***** Left column: selectors *****/
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"TEST_EDI_ANS_LEFT_COL COLOR%u\"",Gbl.RowEvenOdd);
2016-11-19 19:39:17 +01:00
/* Radio selector for unique choice answers */
2019-11-04 18:17:39 +01:00
HTM_INPUT_RADIO ("AnsUni",false,
"value=\"%u\"%s%s%s onclick=\"enableDisableAns(this.form);\"",
NumOpt,
2020-03-19 20:57:54 +01:00
Question->Answer.Options[NumOpt].Correct ? " checked=\"checked\"" :
"",
2020-03-18 18:49:45 +01:00
NumOpt < 2 ? " required=\"required\"" : // First or second options required
"",
2020-03-19 20:57:54 +01:00
Question->Answer.Type == Tst_ANS_UNIQUE_CHOICE ? "" :
" disabled=\"disabled\"");
2016-11-19 19:39:17 +01:00
/* Checkbox for multiple choice answers */
2020-03-12 13:53:37 +01:00
HTM_INPUT_CHECKBOX ("AnsMulti",HTM_DONT_SUBMIT_ON_CHANGE,
2019-11-04 20:41:35 +01:00
"value=\"%u\"%s%s",
NumOpt,
2020-03-19 20:57:54 +01:00
Question->Answer.Options[NumOpt].Correct ? " checked=\"checked\"" :
"",
Question->Answer.Type == Tst_ANS_MULTIPLE_CHOICE ? "" :
" disabled=\"disabled\"");
2016-11-19 19:39:17 +01:00
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2016-04-07 17:14:35 +02:00
/***** Center column: letter of the answer and expand / contract icon *****/
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"%s TEST_EDI_ANS_CENTER_COL COLOR%u\"",
2019-10-10 23:14:13 +02:00
The_ClassFormInBox[Gbl.Prefs.Theme],Gbl.RowEvenOdd);
2019-11-11 00:15:44 +01:00
HTM_TxtF ("%c)",'a' + (char) NumOpt);
2014-12-01 23:55:08 +01:00
2016-04-07 17:14:35 +02:00
/* Icon to expand (show the answer) */
2019-10-28 20:38:29 +01:00
HTM_A_Begin ("href=\"\" id=\"exp_%u\"%s"
" onclick=\"toggleAnswer('%u');return false;\"",
NumOpt,
DisplayRightColumn ? " style=\"display:none;\"" : // Answer does have content ==> Hide icon
"",
NumOpt);
2019-12-30 22:32:06 +01:00
if (asprintf (&Title,"%s %c)",Txt_Expand,'a' + (char) NumOpt) < 0)
Lay_NotEnoughMemoryExit ();
Ico_PutIcon ("caret-right.svg",Title,"ICO16x16");
free (Title);
2019-10-28 13:56:04 +01:00
HTM_A_End ();
2016-04-07 17:14:35 +02:00
/* Icon to contract (hide the answer) */
2019-10-28 20:38:29 +01:00
HTM_A_Begin ("href=\"\" id=\"con_%u\"%s"
" onclick=\"toggleAnswer(%u);return false;\"",
NumOpt,
DisplayRightColumn ? "" :
" style=\"display:none;\"", // Answer does not have content ==> Hide icon
NumOpt);
2019-12-30 22:32:06 +01:00
if (asprintf (&Title,"%s %c)",Txt_Contract,'a' + (char) NumOpt) < 0)
Lay_NotEnoughMemoryExit ();
Ico_PutIcon ("caret-down.svg",Title,"ICO16x16");
free (Title);
2019-10-28 13:56:04 +01:00
HTM_A_End ();
2016-04-07 10:23:28 +02:00
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2016-04-07 14:40:50 +02:00
/***** Right column: content of the answer *****/
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"TEST_EDI_ANS_RIGHT_COL COLOR%u\"",Gbl.RowEvenOdd);
2019-10-28 20:38:29 +01:00
HTM_DIV_Begin ("id=\"ans_%u\"%s",
NumOpt,
DisplayRightColumn ? "" :
" style=\"display:none;\""); // Answer does not have content ==> Hide column
2016-04-07 10:23:28 +02:00
/* Answer text */
2019-10-31 17:42:05 +01:00
HTM_TEXTAREA_Begin ("name=\"AnsStr%u\" class=\"ANSWER_TEXTAREA\" rows=\"5\"%s",
NumOpt,OptionsDisabled ? " disabled=\"disabled\"" :
"");
2016-04-07 10:23:28 +02:00
if (AnswerHasContent)
2020-03-19 20:57:54 +01:00
HTM_Txt (Question->Answer.Options[NumOpt].Text);
2019-10-31 17:42:05 +01:00
HTM_TEXTAREA_End ();
2016-04-05 14:32:02 +02:00
2019-03-02 21:49:11 +01:00
/* Media */
2020-03-19 20:57:54 +01:00
Tst_PutFormToEditQstMedia (&Question->Answer.Options[NumOpt].Media,
2016-04-14 21:33:24 +02:00
(int) NumOpt,
2016-04-05 21:44:06 +02:00
OptionsDisabled);
2014-12-01 23:55:08 +01:00
/* Feedback */
2019-11-02 23:40:52 +01:00
HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
2019-11-11 10:59:24 +01:00
HTM_TxtF ("%s&nbsp;(%s):",Txt_Feedback,Txt_optional);
2019-11-09 21:08:20 +01:00
HTM_BR ();
2019-10-31 17:42:05 +01:00
HTM_TEXTAREA_Begin ("name=\"FbStr%u\" class=\"ANSWER_TEXTAREA\" rows=\"2\"%s",
NumOpt,OptionsDisabled ? " disabled=\"disabled\"" :
"");
2020-03-19 20:57:54 +01:00
if (Question->Answer.Options[NumOpt].Feedback)
if (Question->Answer.Options[NumOpt].Feedback[0])
HTM_Txt (Question->Answer.Options[NumOpt].Feedback);
2019-10-31 17:42:05 +01:00
HTM_TEXTAREA_End ();
2019-11-02 12:59:31 +01:00
HTM_LABEL_End ();
2016-04-07 10:23:28 +02:00
/* End of right column */
2019-10-23 20:07:56 +02:00
HTM_DIV_End ();
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
}
2019-10-23 19:05:05 +02:00
HTM_TABLE_End (); // Table with choice answers
HTM_TD_End ();
HTM_TR_End ();
2015-04-11 13:05:44 +02:00
2016-04-05 10:05:52 +02:00
/***** End table *****/
2019-10-23 19:05:05 +02:00
HTM_TABLE_End (); // Table for this question
2016-04-05 10:05:52 +02:00
/***** Send button *****/
2020-03-25 01:36:22 +01:00
if (Question->QstCod > 0) // The question already has assigned a code
2019-02-18 18:27:45 +01:00
Btn_PutConfirmButton (Txt_Save_changes);
2015-04-11 20:18:30 +02:00
else
2017-06-11 19:02:40 +02:00
Btn_PutCreateButton (Txt_Create_question);
2015-04-11 13:05:44 +02:00
/***** End form *****/
2018-11-09 20:47:39 +01:00
Frm_EndForm ();
2014-12-01 23:55:08 +01:00
2017-06-12 14:16:33 +02:00
/***** End box *****/
2019-10-25 22:48:34 +02:00
Box_BoxEnd ();
2016-12-26 15:17:30 +01:00
}
/*****************************************************************************/
/********************* Put input field for floating answer *******************/
/*****************************************************************************/
static void Tst_PutFloatInputField (const char *Label,const char *Field,
2020-03-19 20:57:54 +01:00
const struct Tst_Question *Question,
unsigned Index)
2016-12-26 15:17:30 +01:00
{
2019-02-22 21:47:50 +01:00
extern const char *The_ClassFormInBox[The_NUM_THEMES];
2019-11-11 10:59:24 +01:00
char StrDouble[32];
2016-12-26 15:17:30 +01:00
2019-11-02 23:40:52 +01:00
HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
2019-11-11 10:59:24 +01:00
HTM_TxtF ("%s&nbsp;",Label);
snprintf (StrDouble,sizeof (StrDouble),
2020-01-11 15:22:02 +01:00
"%.15lg",
2020-03-19 20:57:54 +01:00
Question->Answer.FloatingPoint[Index]);
2019-11-11 10:59:24 +01:00
HTM_INPUT_TEXT (Field,Tst_MAX_BYTES_FLOAT_ANSWER,StrDouble,false,
2019-11-04 01:29:46 +01:00
"size=\"11\" required=\"required\"%s",
2020-03-19 20:57:54 +01:00
Question->Answer.Type == Tst_ANS_FLOAT ? "" :
" disabled=\"disabled\"");
2019-11-02 12:59:31 +01:00
HTM_LABEL_End ();
2016-12-26 16:30:46 +01:00
}
/*****************************************************************************/
/*********************** Put input field for T/F answer **********************/
/*****************************************************************************/
2020-03-19 20:57:54 +01:00
static void Tst_PutTFInputField (const struct Tst_Question *Question,
2020-03-18 01:57:08 +01:00
const char *Label,char Value)
2016-12-26 16:30:46 +01:00
{
2019-02-22 21:47:50 +01:00
extern const char *The_ClassFormInBox[The_NUM_THEMES];
2016-12-26 16:30:46 +01:00
2019-11-02 23:40:52 +01:00
HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
2019-11-04 18:17:39 +01:00
HTM_INPUT_RADIO ("AnsTF",false,
"value=\"%c\"%s%s required=\"required\"",
Value,
2020-03-19 20:57:54 +01:00
Question->Answer.TF == Value ? " checked=\"checked\"" :
"",
Question->Answer.Type == Tst_ANS_TRUE_FALSE ? "" :
" disabled=\"disabled\"");
2019-11-10 12:36:37 +01:00
HTM_Txt (Label);
2019-11-02 12:59:31 +01:00
HTM_LABEL_End ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/********************* Initialize a new question to zero *********************/
/*****************************************************************************/
2020-03-19 20:57:54 +01:00
void Tst_QstConstructor (struct Tst_Question *Question)
2014-12-01 23:55:08 +01:00
{
unsigned NumOpt;
2020-03-25 01:36:22 +01:00
Tst_ResetTags (&Question->Tags);
2020-03-17 14:47:58 +01:00
Question->Stem.Text = NULL;
Question->Stem.Length = 0;
Question->Feedback.Text = NULL;
Question->Feedback.Length = 0;
Question->Shuffle = false;
2020-03-19 20:57:54 +01:00
Question->Answer.Type = Tst_ANS_UNIQUE_CHOICE;
Question->Answer.NumOptions = 0;
Question->Answer.TF = ' ';
2016-04-08 23:30:43 +02:00
/***** Initialize image attached to stem *****/
2020-03-17 14:47:58 +01:00
Med_MediaConstructor (&Question->Media);
2016-04-08 23:30:43 +02:00
2014-12-01 23:55:08 +01:00
for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
{
2020-03-19 20:57:54 +01:00
Question->Answer.Options[NumOpt].Correct = false;
Question->Answer.Options[NumOpt].Text = NULL;
Question->Answer.Options[NumOpt].Feedback = NULL;
2016-04-08 23:30:43 +02:00
/***** Initialize image attached to option *****/
2020-03-19 20:57:54 +01:00
Med_MediaConstructor (&Question->Answer.Options[NumOpt].Media);
2014-12-01 23:55:08 +01:00
}
2020-03-19 20:57:54 +01:00
Question->Answer.Integer = 0;
Question->Answer.FloatingPoint[0] =
Question->Answer.FloatingPoint[1] = 0.0;
2016-04-04 21:51:21 +02:00
}
2016-04-06 19:26:09 +02:00
/*****************************************************************************/
/***************** Free memory allocated for test question *******************/
/*****************************************************************************/
2020-03-19 20:57:54 +01:00
void Tst_QstDestructor (struct Tst_Question *Question)
2016-04-06 19:26:09 +02:00
{
2020-03-19 20:57:54 +01:00
Tst_FreeTextChoiceAnswers (Question);
Tst_FreeMediaOfQuestion (Question);
2016-04-06 19:26:09 +02:00
}
/*****************************************************************************/
/******************* Allocate memory for a choice answer *********************/
/*****************************************************************************/
2020-03-25 01:36:22 +01:00
// Return false on error
2016-04-06 19:26:09 +02:00
2020-03-25 01:36:22 +01:00
bool Tst_AllocateTextChoiceAnswer (struct Tst_Question *Question,unsigned NumOpt)
2016-04-06 19:26:09 +02:00
{
2020-03-26 21:39:44 +01:00
// Tst_FreeTagsList (&Question->Tags); // TODO: Necessary?
2020-03-25 01:36:22 +01:00
2020-03-19 20:57:54 +01:00
Tst_FreeTextChoiceAnswer (Question,NumOpt);
2016-04-06 19:26:09 +02:00
2020-03-19 20:57:54 +01:00
if ((Question->Answer.Options[NumOpt].Text =
2018-10-08 12:37:29 +02:00
(char *) malloc (Tst_MAX_BYTES_ANSWER_OR_FEEDBACK + 1)) == NULL)
2016-04-06 19:26:09 +02:00
{
2019-03-09 20:12:44 +01:00
Ale_CreateAlert (Ale_ERROR,NULL,
"Not enough memory to store answer.");
2020-03-25 01:36:22 +01:00
return false;
2016-04-06 19:26:09 +02:00
}
2020-03-19 20:57:54 +01:00
if ((Question->Answer.Options[NumOpt].Feedback =
2018-10-08 12:37:29 +02:00
(char *) malloc (Tst_MAX_BYTES_ANSWER_OR_FEEDBACK + 1)) == NULL)
2016-04-06 19:26:09 +02:00
{
2019-03-09 20:12:44 +01:00
Ale_CreateAlert (Ale_ERROR,NULL,
"Not enough memory to store feedback.");
2020-03-25 01:36:22 +01:00
return false;
2016-04-06 19:26:09 +02:00
}
2020-03-19 20:57:54 +01:00
Question->Answer.Options[NumOpt].Text[0] =
Question->Answer.Options[NumOpt].Feedback[0] = '\0';
2020-03-25 01:36:22 +01:00
return true;
2016-04-06 19:26:09 +02:00
}
/*****************************************************************************/
/******************** Free memory of all choice answers **********************/
/*****************************************************************************/
2020-03-19 20:57:54 +01:00
static void Tst_FreeTextChoiceAnswers (struct Tst_Question *Question)
2016-04-06 19:26:09 +02:00
{
unsigned NumOpt;
for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
2020-03-19 20:57:54 +01:00
Tst_FreeTextChoiceAnswer (Question,NumOpt);
2016-04-06 19:26:09 +02:00
}
/*****************************************************************************/
/********************** Free memory of a choice answer ***********************/
/*****************************************************************************/
2020-03-19 20:57:54 +01:00
static void Tst_FreeTextChoiceAnswer (struct Tst_Question *Question,unsigned NumOpt)
2016-04-06 19:26:09 +02:00
{
2020-03-19 20:57:54 +01:00
if (Question->Answer.Options[NumOpt].Text)
2016-04-06 19:26:09 +02:00
{
2020-03-19 20:57:54 +01:00
free (Question->Answer.Options[NumOpt].Text);
Question->Answer.Options[NumOpt].Text = NULL;
2016-04-06 19:26:09 +02:00
}
2020-03-19 20:57:54 +01:00
if (Question->Answer.Options[NumOpt].Feedback)
2016-04-06 19:26:09 +02:00
{
2020-03-19 20:57:54 +01:00
free (Question->Answer.Options[NumOpt].Feedback);
Question->Answer.Options[NumOpt].Feedback = NULL;
2016-04-06 19:26:09 +02:00
}
}
2016-04-04 21:51:21 +02:00
/*****************************************************************************/
/***************** Initialize images of a question to zero *******************/
/*****************************************************************************/
2016-04-09 02:04:45 +02:00
2020-03-19 20:57:54 +01:00
static void Tst_ResetMediaOfQuestion (struct Tst_Question *Question)
2016-04-04 21:51:21 +02:00
{
unsigned NumOpt;
2019-03-17 14:47:58 +01:00
/***** Reset media for stem *****/
2020-03-17 14:47:58 +01:00
Med_ResetMedia (&Question->Media);
2016-04-08 23:30:43 +02:00
2019-03-17 14:47:58 +01:00
/***** Reset media for every answer option *****/
2016-04-04 21:51:21 +02:00
for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
2020-03-19 20:57:54 +01:00
Med_ResetMedia (&Question->Answer.Options[NumOpt].Media);
2014-12-01 23:55:08 +01:00
}
2016-04-09 02:04:45 +02:00
2016-04-06 01:10:04 +02:00
/*****************************************************************************/
/*********************** Free images of a question ***************************/
/*****************************************************************************/
2020-03-19 20:57:54 +01:00
static void Tst_FreeMediaOfQuestion (struct Tst_Question *Question)
2016-04-06 01:10:04 +02:00
{
unsigned NumOpt;
2020-03-17 14:47:58 +01:00
Med_MediaDestructor (&Question->Media);
2016-04-06 01:10:04 +02:00
for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
2020-03-19 20:57:54 +01:00
Med_MediaDestructor (&Question->Answer.Options[NumOpt].Media);
2016-04-06 01:10:04 +02:00
}
2020-04-01 03:11:05 +02:00
/*****************************************************************************/
/*************** Get answer type of a question from database *****************/
/*****************************************************************************/
static Tst_AnswerType_t Tst_GetQstAnswerType (long QstCod)
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
Tst_AnswerType_t AnswerType;
/***** Get type of answer from database *****/
if (!DB_QuerySELECT (&mysql_res,"can not get a question",
"SELECT AnsType" // row[0]
" FROM tst_questions"
" WHERE QstCod=%ld"
" AND CrsCod=%ld", // Extra check
QstCod,Gbl.Hierarchy.Crs.CrsCod))
Lay_ShowErrorAndExit ("Question does not exist.");
/* Get type of answer */
row = mysql_fetch_row (mysql_res);
AnswerType = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[0]);
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
return AnswerType;
}
2016-04-03 01:24:20 +02:00
/*****************************************************************************/
/****************** Get data of a question from database *********************/
/*****************************************************************************/
2020-03-25 01:36:22 +01:00
static void Tst_GetQstDataFromDB (struct Tst_Question *Question,
2020-03-17 00:35:11 +01:00
char Stem[Cns_MAX_BYTES_TEXT + 1],
2017-01-17 03:10:43 +01:00
char Feedback[Cns_MAX_BYTES_TEXT + 1])
2016-04-03 01:24:20 +02:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned long NumRows;
unsigned long NumRow;
unsigned NumOpt;
2020-04-01 03:11:05 +02:00
/***** Get question data from database *****/
if (!DB_QuerySELECT (&mysql_res,"can not get a question",
"SELECT AnsType," // row[0]
"Shuffle," // row[1]
"Stem," // row[2]
"Feedback," // row[3]
"MedCod" // row[4]
" FROM tst_questions"
" WHERE QstCod=%ld"
" AND CrsCod=%ld", // Extra check
Question->QstCod,
Gbl.Hierarchy.Crs.CrsCod))
Lay_ShowErrorAndExit ("Question does not exist.");
2016-04-03 01:24:20 +02:00
row = mysql_fetch_row (mysql_res);
2019-03-02 21:49:11 +01:00
2016-04-03 01:24:20 +02:00
/* Get the type of answer */
2020-03-19 20:57:54 +01:00
Question->Answer.Type = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[0]);
2016-04-03 01:24:20 +02:00
/* Get shuffle (row[1]) */
2020-03-17 14:47:58 +01:00
Question->Shuffle = (row[1][0] == 'Y');
2016-04-03 01:24:20 +02:00
/* Get the stem of the question from the database (row[2]) */
2017-01-17 03:10:43 +01:00
Str_Copy (Stem,row[2],
Cns_MAX_BYTES_TEXT);
2016-04-03 01:24:20 +02:00
2016-04-06 01:10:04 +02:00
/* Get the feedback of the question from the database (row[3]) */
2016-04-03 01:24:20 +02:00
Feedback[0] = '\0';
2016-04-06 01:10:04 +02:00
if (row[3])
if (row[3][0])
2017-01-17 03:10:43 +01:00
Str_Copy (Feedback,row[3],
Cns_MAX_BYTES_TEXT);
2016-04-03 01:24:20 +02:00
2019-03-18 15:42:22 +01:00
/* Get media (row[4]) */
2020-03-17 14:47:58 +01:00
Question->Media.MedCod = Str_ConvertStrCodToLongCod (row[4]);
Med_GetMediaDataByCod (&Question->Media);
2016-04-06 01:10:04 +02:00
2016-04-03 01:24:20 +02:00
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
/***** Get the tags from the database *****/
2020-03-25 01:36:22 +01:00
NumRows = Tst_GetTagsQst (Question->QstCod,&mysql_res);
2016-04-03 01:24:20 +02:00
for (NumRow = 0;
NumRow < NumRows;
NumRow++)
{
row = mysql_fetch_row (mysql_res);
2020-03-25 01:36:22 +01:00
Str_Copy (Question->Tags.Txt[NumRow],row[0],
2017-01-17 03:10:43 +01:00
Tst_MAX_BYTES_TAG);
2016-04-03 01:24:20 +02:00
}
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
/***** Get the answers from the database *****/
2020-03-25 01:36:22 +01:00
Tst_GetAnswersQst (Question,&mysql_res,
false); // Don't shuffle
2016-04-06 14:41:47 +02:00
/*
2019-03-02 21:49:11 +01:00
row[0] AnsInd
row[1] Answer
row[2] Feedback
2019-03-18 15:42:22 +01:00
row[3] MedCod
row[4] Correct
2016-04-06 14:41:47 +02:00
*/
2016-04-03 01:24:20 +02:00
for (NumOpt = 0;
2020-03-19 20:57:54 +01:00
NumOpt < Question->Answer.NumOptions;
2016-04-03 01:24:20 +02:00
NumOpt++)
{
row = mysql_fetch_row (mysql_res);
2020-03-19 20:57:54 +01:00
switch (Question->Answer.Type)
2016-04-03 01:24:20 +02:00
{
case Tst_ANS_INT:
2020-03-19 20:57:54 +01:00
if (Question->Answer.NumOptions != 1)
2016-04-03 01:24:20 +02:00
Lay_ShowErrorAndExit ("Wrong answer.");
2020-03-19 20:57:54 +01:00
Question->Answer.Integer = Tst_GetIntAnsFromStr (row[1]);
2016-04-03 01:24:20 +02:00
break;
case Tst_ANS_FLOAT:
2020-03-19 20:57:54 +01:00
if (Question->Answer.NumOptions != 2)
2016-04-03 01:24:20 +02:00
Lay_ShowErrorAndExit ("Wrong answer.");
2020-03-19 20:57:54 +01:00
Question->Answer.FloatingPoint[NumOpt] = Str_GetDoubleFromStr (row[1]);
2016-04-03 01:24:20 +02:00
break;
case Tst_ANS_TRUE_FALSE:
2020-03-19 20:57:54 +01:00
if (Question->Answer.NumOptions != 1)
2016-04-03 01:24:20 +02:00
Lay_ShowErrorAndExit ("Wrong answer.");
2020-03-19 20:57:54 +01:00
Question->Answer.TF = row[1][0];
2016-04-03 01:24:20 +02:00
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
case Tst_ANS_TEXT:
2020-03-19 20:57:54 +01:00
if (Question->Answer.NumOptions > Tst_MAX_OPTIONS_PER_QUESTION)
2016-04-03 01:24:20 +02:00
Lay_ShowErrorAndExit ("Wrong answer.");
2020-03-19 20:57:54 +01:00
if (!Tst_AllocateTextChoiceAnswer (Question,NumOpt))
2019-03-09 20:12:44 +01:00
/* Abort on error */
Ale_ShowAlertsAndExit ();
2016-04-03 01:24:20 +02:00
2020-03-19 20:57:54 +01:00
Str_Copy (Question->Answer.Options[NumOpt].Text,row[1],
2017-01-15 18:02:52 +01:00
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2016-04-03 01:24:20 +02:00
2016-04-06 14:41:47 +02:00
// Feedback (row[2]) is initialized to empty string
if (row[2])
if (row[2][0])
2020-03-19 20:57:54 +01:00
Str_Copy (Question->Answer.Options[NumOpt].Feedback,row[2],
2017-01-15 18:02:52 +01:00
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2016-04-03 01:24:20 +02:00
2019-03-18 15:42:22 +01:00
/* Get media (row[3]) */
2020-03-19 20:57:54 +01:00
Question->Answer.Options[NumOpt].Media.MedCod = Str_ConvertStrCodToLongCod (row[3]);
Med_GetMediaDataByCod (&Question->Answer.Options[NumOpt].Media);
2016-04-04 21:51:21 +02:00
2019-03-18 15:42:22 +01:00
/* Get if this option is correct (row[4]) */
2020-03-19 20:57:54 +01:00
Question->Answer.Options[NumOpt].Correct = (row[4][0] == 'Y');
2016-04-03 01:24:20 +02:00
break;
default:
break;
}
}
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
2019-03-19 11:20:29 +01:00
/******* Get media code associated with a test question from database ********/
2016-04-03 01:24:20 +02:00
/*****************************************************************************/
2019-03-19 01:41:27 +01:00
// NumOpt < 0 ==> media associated to stem
// NumOpt >= 0 ==> media associated to answer
2016-04-03 01:24:20 +02:00
2020-03-17 00:35:11 +01:00
static long Tst_GetMedCodFromDB (long CrsCod,long QstCod,int NumOpt)
2016-04-03 01:24:20 +02:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
2019-03-21 20:04:01 +01:00
unsigned long NumRows;
long MedCod = -1L;
2016-04-03 01:24:20 +02:00
2020-03-17 00:35:11 +01:00
if (QstCod > 0) // Existing question
2019-03-21 20:04:01 +01:00
{
2019-04-01 00:45:50 +02:00
/***** Query depending on NumOpt *****/
if (NumOpt < 0)
// Get media associated to stem
NumRows = DB_QuerySELECT (&mysql_res,"can not get media",
"SELECT MedCod" // row[0]
" FROM tst_questions"
" WHERE QstCod=%ld AND CrsCod=%ld",
2020-03-17 00:35:11 +01:00
QstCod,CrsCod);
2019-04-01 00:45:50 +02:00
else
// Get media associated to answer
NumRows = DB_QuerySELECT (&mysql_res,"can not get media",
"SELECT MedCod" // row[0]
" FROM tst_answers"
" WHERE QstCod=%ld AND AnsInd=%u",
2020-03-17 00:35:11 +01:00
QstCod,(unsigned) NumOpt);
2019-04-01 00:45:50 +02:00
if (NumRows)
2019-03-21 20:04:01 +01:00
{
2019-04-01 00:45:50 +02:00
if (NumRows == 1)
{
/***** Get media code (row[0]) *****/
row = mysql_fetch_row (mysql_res);
MedCod = Str_ConvertStrCodToLongCod (row[0]);
}
else // NumRows > 1
Lay_ShowErrorAndExit ("Duplicated media in database.");
2019-03-21 20:04:01 +01:00
}
2016-04-03 01:24:20 +02:00
2019-04-01 00:45:50 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
2019-03-19 11:20:29 +01:00
return MedCod;
}
/*****************************************************************************/
/***** Get possible media associated with a test question from database ******/
/*****************************************************************************/
// NumOpt < 0 ==> media associated to stem
// NumOpt >= 0 ==> media associated to an answer option
2020-03-17 00:35:11 +01:00
static void Tst_GetMediaFromDB (long CrsCod,long QstCod,int NumOpt,
struct Media *Media)
2019-03-19 11:20:29 +01:00
{
/***** Get media *****/
2020-03-17 00:35:11 +01:00
Media->MedCod = Tst_GetMedCodFromDB (CrsCod,QstCod,NumOpt);
2019-03-19 11:20:29 +01:00
Med_GetMediaDataByCod (Media);
2016-04-03 01:24:20 +02:00
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
/** Convert a string with the type of answer in database to type of answer ***/
/*****************************************************************************/
Tst_AnswerType_t Tst_ConvertFromStrAnsTypDBToAnsTyp (const char *StrAnsTypeBD)
{
Tst_AnswerType_t AnsType;
if (StrAnsTypeBD != NULL)
2019-12-15 20:02:34 +01:00
for (AnsType = (Tst_AnswerType_t) 0;
AnsType <= (Tst_AnswerType_t) (Tst_NUM_ANS_TYPES - 1);
2014-12-01 23:55:08 +01:00
AnsType++)
if (!strcmp (StrAnsTypeBD,Tst_StrAnswerTypesDB[AnsType]))
return AnsType;
2020-03-17 13:10:44 +01:00
Lay_ShowErrorAndExit ("Wrong type of answer. 1");
2014-12-01 23:55:08 +01:00
return (Tst_AnswerType_t) 0; // Not reached
}
/*****************************************************************************/
/************ Convert a string with an unsigned to answer type ***************/
/*****************************************************************************/
static Tst_AnswerType_t Tst_ConvertFromUnsignedStrToAnsTyp (const char *UnsignedStr)
{
unsigned AnsType;
if (sscanf (UnsignedStr,"%u",&AnsType) != 1)
2020-03-17 13:10:44 +01:00
Lay_ShowErrorAndExit ("Wrong type of answer. 2");
2014-12-01 23:55:08 +01:00
if (AnsType >= Tst_NUM_ANS_TYPES)
2020-03-17 13:10:44 +01:00
Lay_ShowErrorAndExit ("Wrong type of answer. 3");
2014-12-01 23:55:08 +01:00
return (Tst_AnswerType_t) AnsType;
}
/*****************************************************************************/
/*************** Receive a question of the self-assessment test **************/
/*****************************************************************************/
void Tst_ReceiveQst (void)
{
2020-03-24 00:58:42 +01:00
struct Tst_Test Test;
2017-01-28 15:58:46 +01:00
char Stem[Cns_MAX_BYTES_TEXT + 1];
char Feedback[Cns_MAX_BYTES_TEXT + 1];
2014-12-01 23:55:08 +01:00
2020-03-26 21:39:44 +01:00
/***** Create test *****/
Tst_TstConstructor (&Test);
2014-12-01 23:55:08 +01:00
/***** Get parameters of the question from form *****/
2016-04-06 19:26:09 +02:00
Stem[0] = Feedback[0] = '\0';
2020-03-26 21:39:44 +01:00
Tst_GetQstFromForm (&Test.Question,Stem,Feedback);
2014-12-01 23:55:08 +01:00
/***** Make sure that tags, text and answer are not empty *****/
2020-03-26 21:39:44 +01:00
if (Tst_CheckIfQstFormatIsCorrectAndCountNumOptions (&Test.Question))
2014-12-01 23:55:08 +01:00
{
2016-04-04 21:51:21 +02:00
/***** Move images to definitive directories *****/
2020-03-26 21:39:44 +01:00
Tst_MoveMediaToDefinitiveDirectories (&Test.Question);
2016-04-04 01:02:41 +02:00
/***** Insert or update question, tags and answer in the database *****/
2020-03-26 21:39:44 +01:00
Tst_InsertOrUpdateQstTagsAnsIntoDB (&Test.Question);
2014-12-01 23:55:08 +01:00
/***** Show the question just inserted in the database *****/
2020-03-25 01:36:22 +01:00
snprintf (Test.AnswerTypes.List,sizeof (Test.AnswerTypes.List),
"%u",
2020-03-26 21:39:44 +01:00
(unsigned) Test.Question.Answer.Type);
Tst_ListOneQstToEdit (&Test);
2014-12-01 23:55:08 +01:00
}
else // Question is wrong
2016-04-04 03:00:12 +02:00
{
2016-04-04 21:51:21 +02:00
/***** Whether images has been received or not, reset images *****/
2020-03-26 21:39:44 +01:00
Tst_ResetMediaOfQuestion (&Test.Question);
2016-04-04 03:00:12 +02:00
2016-04-03 01:24:20 +02:00
/***** Put form to edit question again *****/
2020-03-26 21:39:44 +01:00
Tst_PutFormEditOneQst (&Test.Question,Stem,Feedback);
2016-04-04 03:00:12 +02:00
}
2014-12-01 23:55:08 +01:00
2020-03-26 21:39:44 +01:00
/***** Destroy test *****/
Tst_TstDestructor (&Test);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/**************** Get parameters of a test question from form ****************/
/*****************************************************************************/
2020-03-25 01:36:22 +01:00
static void Tst_GetQstFromForm (struct Tst_Question *Question,
2020-03-17 14:47:58 +01:00
char *Stem,char *Feedback)
2014-12-01 23:55:08 +01:00
{
unsigned NumTag;
unsigned NumTagRead;
unsigned NumOpt;
2019-11-08 01:10:32 +01:00
char UnsignedStr[Cns_MAX_DECIMAL_DIGITS_UINT + 1];
char TagStr[6 + Cns_MAX_DECIMAL_DIGITS_UINT + 1];
char AnsStr[6 + Cns_MAX_DECIMAL_DIGITS_UINT + 1];
char FbStr[5 + Cns_MAX_DECIMAL_DIGITS_UINT + 1];
2017-03-13 19:02:15 +01:00
char StrMultiAns[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1];
2017-02-24 19:04:52 +01:00
char TF[1 + 1]; // (T)rue or (F)alse
2014-12-01 23:55:08 +01:00
const char *Ptr;
unsigned NumCorrectAns;
/***** Get question code *****/
2020-03-25 01:36:22 +01:00
Question->QstCod = Tst_GetQstCod ();
2014-12-01 23:55:08 +01:00
/***** Get answer type *****/
2020-03-19 20:57:54 +01:00
Question->Answer.Type = (Tst_AnswerType_t)
Par_GetParToUnsignedLong ("AnswerType",
0,
Tst_NUM_ANS_TYPES - 1,
(unsigned long) Tst_ANS_ALL);
if (Question->Answer.Type == Tst_ANS_ALL)
2020-03-17 13:10:44 +01:00
Lay_ShowErrorAndExit ("Wrong type of answer. 4");
2014-12-01 23:55:08 +01:00
/***** Get question tags *****/
for (NumTag = 0;
NumTag < Tst_MAX_TAGS_PER_QUESTION;
NumTag++)
{
2018-10-18 02:02:32 +02:00
snprintf (TagStr,sizeof (TagStr),
"TagTxt%u",
NumTag);
2020-03-25 01:36:22 +01:00
Par_GetParToText (TagStr,Question->Tags.Txt[NumTag],Tst_MAX_BYTES_TAG);
2016-03-30 01:28:58 +02:00
2020-03-25 01:36:22 +01:00
if (Question->Tags.Txt[NumTag][0])
2014-12-01 23:55:08 +01:00
{
Str_ChangeFormat (Str_FROM_FORM,Str_TO_TEXT,
2020-03-25 01:36:22 +01:00
Question->Tags.Txt[NumTag],Tst_MAX_BYTES_TAG,true);
2014-12-01 23:55:08 +01:00
/* Check if not repeated */
for (NumTagRead = 0;
NumTagRead < NumTag;
NumTagRead++)
2020-03-25 01:36:22 +01:00
if (!strcmp (Question->Tags.Txt[NumTagRead],Question->Tags.Txt[NumTag]))
2014-12-01 23:55:08 +01:00
{
2020-03-25 01:36:22 +01:00
Question->Tags.Txt[NumTag][0] = '\0';
2014-12-01 23:55:08 +01:00
break;
}
}
}
/***** Get question stem *****/
Par_GetParToHTML ("Stem",Stem,Cns_MAX_BYTES_TEXT);
2016-04-06 01:10:04 +02:00
/***** Get question feedback *****/
Par_GetParToHTML ("Feedback",Feedback,Cns_MAX_BYTES_TEXT);
2019-03-19 01:41:27 +01:00
/***** Get media associated to the stem (action, file and title) *****/
2020-03-17 14:47:58 +01:00
Question->Media.Width = Tst_IMAGE_SAVED_MAX_WIDTH;
Question->Media.Height = Tst_IMAGE_SAVED_MAX_HEIGHT;
Question->Media.Quality = Tst_IMAGE_SAVED_QUALITY;
2020-03-25 01:36:22 +01:00
Med_GetMediaFromForm (Gbl.Hierarchy.Crs.CrsCod,Question->QstCod,
2020-03-17 00:35:11 +01:00
-1, // < 0 ==> the image associated to the stem
2020-03-17 14:47:58 +01:00
&Question->Media,
2020-03-17 00:35:11 +01:00
Tst_GetMediaFromDB,
2019-03-17 14:47:58 +01:00
NULL);
Ale_ShowAlerts (NULL);
2016-04-04 12:13:37 +02:00
2014-12-01 23:55:08 +01:00
/***** Get answers *****/
2020-03-17 14:47:58 +01:00
Question->Shuffle = false;
2020-03-19 20:57:54 +01:00
switch (Question->Answer.Type)
2014-12-01 23:55:08 +01:00
{
case Tst_ANS_INT:
2020-03-19 20:57:54 +01:00
if (!Tst_AllocateTextChoiceAnswer (Question,0))
2019-03-09 20:12:44 +01:00
/* Abort on error */
Ale_ShowAlertsAndExit ();
2014-12-01 23:55:08 +01:00
2020-03-19 20:57:54 +01:00
Par_GetParToText ("AnsInt",Question->Answer.Options[0].Text,
2019-11-08 01:10:32 +01:00
Cns_MAX_DECIMAL_DIGITS_LONG);
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_FLOAT:
2020-03-19 20:57:54 +01:00
if (!Tst_AllocateTextChoiceAnswer (Question,0))
2019-03-09 20:12:44 +01:00
/* Abort on error */
Ale_ShowAlertsAndExit ();
2020-03-19 20:57:54 +01:00
Par_GetParToText ("AnsFloatMin",Question->Answer.Options[0].Text,
2017-01-29 21:41:08 +01:00
Tst_MAX_BYTES_FLOAT_ANSWER);
2014-12-01 23:55:08 +01:00
2020-03-19 20:57:54 +01:00
if (!Tst_AllocateTextChoiceAnswer (Question,1))
2019-03-09 20:12:44 +01:00
/* Abort on error */
Ale_ShowAlertsAndExit ();
2020-03-19 20:57:54 +01:00
Par_GetParToText ("AnsFloatMax",Question->Answer.Options[1].Text,
2017-01-29 21:41:08 +01:00
Tst_MAX_BYTES_FLOAT_ANSWER);
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_TRUE_FALSE:
2017-02-24 19:04:52 +01:00
Par_GetParToText ("AnsTF",TF,1);
2020-03-19 20:57:54 +01:00
Question->Answer.TF = TF[0];
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
/* Get shuffle */
2020-03-17 14:47:58 +01:00
Question->Shuffle = Par_GetParToBool ("Shuffle");
2018-10-04 21:57:25 +02:00
/* falls through */
/* no break */
2014-12-01 23:55:08 +01:00
case Tst_ANS_TEXT:
/* Get the texts of the answers */
for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
{
2020-03-19 20:57:54 +01:00
if (!Tst_AllocateTextChoiceAnswer (Question,NumOpt))
2019-03-09 20:12:44 +01:00
/* Abort on error */
Ale_ShowAlertsAndExit ();
2014-12-01 23:55:08 +01:00
/* Get answer */
2018-10-18 02:02:32 +02:00
snprintf (AnsStr,sizeof (AnsStr),
"AnsStr%u",
NumOpt);
2020-03-19 20:57:54 +01:00
Par_GetParToHTML (AnsStr,Question->Answer.Options[NumOpt].Text,
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2020-03-19 20:57:54 +01:00
if (Question->Answer.Type == Tst_ANS_TEXT)
2016-11-27 14:34:36 +01:00
/* In order to compare student answer to stored answer,
the text answers are stored avoiding two or more consecurive spaces */
2020-03-19 20:57:54 +01:00
Str_ReplaceSeveralSpacesForOne (Question->Answer.Options[NumOpt].Text);
2014-12-01 23:55:08 +01:00
/* Get feedback */
2018-10-18 02:02:32 +02:00
snprintf (FbStr,sizeof (FbStr),
"FbStr%u",
NumOpt);
2020-03-19 20:57:54 +01:00
Par_GetParToHTML (FbStr,Question->Answer.Options[NumOpt].Feedback,
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2016-04-04 14:48:12 +02:00
2019-03-19 01:41:27 +01:00
/* Get media associated to the answer (action, file and title) */
2020-03-19 20:57:54 +01:00
if (Question->Answer.Type == Tst_ANS_UNIQUE_CHOICE ||
Question->Answer.Type == Tst_ANS_MULTIPLE_CHOICE)
2016-04-04 14:48:12 +02:00
{
2020-03-19 20:57:54 +01:00
Question->Answer.Options[NumOpt].Media.Width = Tst_IMAGE_SAVED_MAX_WIDTH;
Question->Answer.Options[NumOpt].Media.Height = Tst_IMAGE_SAVED_MAX_HEIGHT;
Question->Answer.Options[NumOpt].Media.Quality = Tst_IMAGE_SAVED_QUALITY;
2020-03-25 01:36:22 +01:00
Med_GetMediaFromForm (Gbl.Hierarchy.Crs.CrsCod,Question->QstCod,
2020-03-17 00:35:11 +01:00
(int) NumOpt, // >= 0 ==> the image associated to an answer
2020-03-19 20:57:54 +01:00
&Question->Answer.Options[NumOpt].Media,
2019-03-17 14:47:58 +01:00
Tst_GetMediaFromDB,
NULL);
Ale_ShowAlerts (NULL);
2016-04-04 14:48:12 +02:00
}
2014-12-01 23:55:08 +01:00
}
/* Get the numbers of correct answers */
2020-03-19 20:57:54 +01:00
if (Question->Answer.Type == Tst_ANS_UNIQUE_CHOICE)
2014-12-01 23:55:08 +01:00
{
2017-01-29 21:41:08 +01:00
NumCorrectAns = (unsigned) Par_GetParToUnsignedLong ("AnsUni",
0,
Tst_MAX_OPTIONS_PER_QUESTION - 1,
0);
2020-03-19 20:57:54 +01:00
Question->Answer.Options[NumCorrectAns].Correct = true;
2014-12-01 23:55:08 +01:00
}
2020-03-19 20:57:54 +01:00
else if (Question->Answer.Type == Tst_ANS_MULTIPLE_CHOICE)
2014-12-01 23:55:08 +01:00
{
2017-03-13 19:02:15 +01:00
Par_GetParMultiToText ("AnsMulti",StrMultiAns,Tst_MAX_BYTES_ANSWERS_ONE_QST);
2014-12-01 23:55:08 +01:00
Ptr = StrMultiAns;
while (*Ptr)
{
2019-11-08 01:10:32 +01:00
Par_GetNextStrUntilSeparParamMult (&Ptr,UnsignedStr,Cns_MAX_DECIMAL_DIGITS_UINT);
2014-12-01 23:55:08 +01:00
if (sscanf (UnsignedStr,"%u",&NumCorrectAns) != 1)
Lay_ShowErrorAndExit ("Wrong selected answer.");
if (NumCorrectAns >= Tst_MAX_OPTIONS_PER_QUESTION)
Lay_ShowErrorAndExit ("Wrong selected answer.");
2020-03-19 20:57:54 +01:00
Question->Answer.Options[NumCorrectAns].Correct = true;
2014-12-01 23:55:08 +01:00
}
}
else // Tst_ANS_TEXT
for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
2020-03-19 20:57:54 +01:00
if (Question->Answer.Options[NumOpt].Text[0])
Question->Answer.Options[NumOpt].Correct = true; // All the answers are correct
2014-12-01 23:55:08 +01:00
break;
default:
break;
}
2020-03-21 15:41:25 +01:00
/***** Adjust variables related to this test question *****/
2020-03-25 01:36:22 +01:00
for (NumTag = 0, Question->Tags.Num = 0;
2014-12-01 23:55:08 +01:00
NumTag < Tst_MAX_TAGS_PER_QUESTION;
NumTag++)
2020-03-25 01:36:22 +01:00
if (Question->Tags.Txt[NumTag][0])
Question->Tags.Num++;
2020-03-17 14:47:58 +01:00
Question->Stem.Text = Stem;
Question->Stem.Length = strlen (Question->Stem.Text);
Question->Feedback.Text = Feedback;
Question->Feedback.Length = strlen (Question->Feedback.Text);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/*********************** Check if a question is correct **********************/
/*****************************************************************************/
// Returns false if question format is wrong
2020-03-19 20:57:54 +01:00
// Counts Question->Answer.NumOptions
// Computes Question->Answer.Integer and Question->Answer.FloatingPoint[0..1]
2014-12-01 23:55:08 +01:00
2020-03-25 01:36:22 +01:00
bool Tst_CheckIfQstFormatIsCorrectAndCountNumOptions (struct Tst_Question *Question)
2014-12-01 23:55:08 +01:00
{
extern const char *Txt_You_must_type_at_least_one_tag_for_the_question;
extern const char *Txt_You_must_type_the_stem_of_the_question;
extern const char *Txt_You_must_select_a_T_F_answer;
extern const char *Txt_You_can_not_leave_empty_intermediate_answers;
extern const char *Txt_You_must_type_at_least_the_first_two_answers;
extern const char *Txt_You_must_mark_an_answer_as_correct;
extern const char *Txt_You_must_type_at_least_the_first_answer;
extern const char *Txt_You_must_enter_an_integer_value_as_the_correct_answer;
extern const char *Txt_You_must_enter_the_range_of_floating_point_values_allowed_as_answer;
extern const char *Txt_The_lower_limit_of_correct_answers_must_be_less_than_or_equal_to_the_upper_limit;
unsigned NumOpt;
unsigned NumLastOpt;
bool ThereIsEndOfAnswers;
unsigned i;
/***** This function also counts the number of options. Initialize this number to 0. *****/
2020-03-19 20:57:54 +01:00
Question->Answer.NumOptions = 0;
2014-12-01 23:55:08 +01:00
/***** A question must have at least one tag *****/
2020-03-25 01:36:22 +01:00
if (!Question->Tags.Num) // There are no tags with text
2014-12-01 23:55:08 +01:00
{
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_You_must_type_at_least_one_tag_for_the_question);
2014-12-01 23:55:08 +01:00
return false;
}
/***** A question must have a stem*****/
2020-03-17 14:47:58 +01:00
if (!Question->Stem.Length)
2014-12-01 23:55:08 +01:00
{
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_You_must_type_the_stem_of_the_question);
2014-12-01 23:55:08 +01:00
return false;
}
/***** Check answer *****/
2020-03-19 20:57:54 +01:00
switch (Question->Answer.Type)
2014-12-01 23:55:08 +01:00
{
case Tst_ANS_INT:
2019-03-17 14:47:58 +01:00
/* First option should be filled */
2020-03-19 20:57:54 +01:00
if (!Question->Answer.Options[0].Text)
2014-12-01 23:55:08 +01:00
{
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_You_must_enter_an_integer_value_as_the_correct_answer);
2014-12-01 23:55:08 +01:00
return false;
}
2020-03-19 20:57:54 +01:00
if (!Question->Answer.Options[0].Text[0])
2014-12-01 23:55:08 +01:00
{
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_You_must_enter_an_integer_value_as_the_correct_answer);
2014-12-01 23:55:08 +01:00
return false;
}
2019-03-17 14:47:58 +01:00
2020-03-19 20:57:54 +01:00
Question->Answer.Integer = Tst_GetIntAnsFromStr (Question->Answer.Options[0].Text);
Question->Answer.NumOptions = 1;
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_FLOAT:
2019-03-17 14:47:58 +01:00
/* First two options should be filled */
2020-03-19 20:57:54 +01:00
if (!Question->Answer.Options[0].Text ||
!Question->Answer.Options[1].Text)
2014-12-01 23:55:08 +01:00
{
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_You_must_enter_the_range_of_floating_point_values_allowed_as_answer);
2014-12-01 23:55:08 +01:00
return false;
}
2020-03-19 20:57:54 +01:00
if (!Question->Answer.Options[0].Text[0] ||
!Question->Answer.Options[1].Text[0])
2014-12-01 23:55:08 +01:00
{
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_You_must_enter_the_range_of_floating_point_values_allowed_as_answer);
2014-12-01 23:55:08 +01:00
return false;
}
2019-03-17 14:47:58 +01:00
/* Lower limit should be <= upper limit */
2014-12-01 23:55:08 +01:00
for (i = 0;
i < 2;
i++)
2020-03-19 20:57:54 +01:00
Question->Answer.FloatingPoint[i] = Str_GetDoubleFromStr (Question->Answer.Options[i].Text);
if (Question->Answer.FloatingPoint[0] >
Question->Answer.FloatingPoint[1])
2014-12-01 23:55:08 +01:00
{
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_The_lower_limit_of_correct_answers_must_be_less_than_or_equal_to_the_upper_limit);
2014-12-01 23:55:08 +01:00
return false;
}
2019-03-17 14:47:58 +01:00
2020-03-19 20:57:54 +01:00
Question->Answer.NumOptions = 2;
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_TRUE_FALSE:
2019-03-17 14:47:58 +01:00
/* Answer should be 'T' or 'F' */
2020-03-19 20:57:54 +01:00
if (Question->Answer.TF != 'T' &&
Question->Answer.TF != 'F')
2014-12-01 23:55:08 +01:00
{
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_You_must_select_a_T_F_answer);
2014-12-01 23:55:08 +01:00
return false;
}
2019-03-17 14:47:58 +01:00
2020-03-19 20:57:54 +01:00
Question->Answer.NumOptions = 1;
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
2019-03-17 14:47:58 +01:00
/* No option should be empty before a non-empty option */
2014-12-01 23:55:08 +01:00
for (NumOpt = 0, NumLastOpt = 0, ThereIsEndOfAnswers = false;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
2020-03-19 20:57:54 +01:00
if (Question->Answer.Options[NumOpt].Text)
2014-12-01 23:55:08 +01:00
{
2020-03-19 20:57:54 +01:00
if (Question->Answer.Options[NumOpt].Text[0] || // Text
Question->Answer.Options[NumOpt].Media.Type != Med_TYPE_NONE) // or media
2014-12-01 23:55:08 +01:00
{
if (ThereIsEndOfAnswers)
{
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_You_can_not_leave_empty_intermediate_answers);
2014-12-01 23:55:08 +01:00
return false;
}
NumLastOpt = NumOpt;
2020-03-19 20:57:54 +01:00
Question->Answer.NumOptions++;
2014-12-01 23:55:08 +01:00
}
else
ThereIsEndOfAnswers = true;
}
else
ThereIsEndOfAnswers = true;
2019-03-17 14:47:58 +01:00
/* The two first options must be filled */
2014-12-01 23:55:08 +01:00
if (NumLastOpt < 1)
{
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_You_must_type_at_least_the_first_two_answers);
2014-12-01 23:55:08 +01:00
return false;
}
2019-03-17 14:47:58 +01:00
/* Its mandatory to mark at least one option as correct */
2014-12-01 23:55:08 +01:00
for (NumOpt = 0;
NumOpt <= NumLastOpt;
NumOpt++)
2020-03-19 20:57:54 +01:00
if (Question->Answer.Options[NumOpt].Correct)
2014-12-01 23:55:08 +01:00
break;
if (NumOpt > NumLastOpt)
{
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_You_must_mark_an_answer_as_correct);
2014-12-01 23:55:08 +01:00
return false;
}
break;
case Tst_ANS_TEXT:
2019-03-17 14:47:58 +01:00
/* First option should be filled */
2020-03-19 20:57:54 +01:00
if (!Question->Answer.Options[0].Text) // If the first answer is empty
2014-12-01 23:55:08 +01:00
{
2019-03-17 14:47:58 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_You_must_type_at_least_the_first_answer);
2014-12-01 23:55:08 +01:00
return false;
}
2020-03-19 20:57:54 +01:00
if (!Question->Answer.Options[0].Text[0]) // If the first answer is empty
2014-12-01 23:55:08 +01:00
{
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_You_must_type_at_least_the_first_answer);
2014-12-01 23:55:08 +01:00
return false;
}
2019-03-17 14:47:58 +01:00
/* No option should be empty before a non-empty option */
2014-12-01 23:55:08 +01:00
for (NumOpt=0, ThereIsEndOfAnswers=false;
NumOpt<Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
2020-03-19 20:57:54 +01:00
if (Question->Answer.Options[NumOpt].Text)
2014-12-01 23:55:08 +01:00
{
2020-03-19 20:57:54 +01:00
if (Question->Answer.Options[NumOpt].Text[0])
2014-12-01 23:55:08 +01:00
{
if (ThereIsEndOfAnswers)
{
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_You_can_not_leave_empty_intermediate_answers);
2014-12-01 23:55:08 +01:00
return false;
}
2020-03-19 20:57:54 +01:00
Question->Answer.NumOptions++;
2014-12-01 23:55:08 +01:00
}
else
ThereIsEndOfAnswers = true;
}
else
ThereIsEndOfAnswers = true;
break;
default:
break;
}
return true; // Question format without errors
}
2019-12-15 20:02:34 +01:00
/*****************************************************************************/
/*********** Check if a test question already exists in database *************/
/*****************************************************************************/
2020-03-25 01:36:22 +01:00
bool Tst_CheckIfQuestionExistsInDB (struct Tst_Question *Question)
2019-12-15 20:02:34 +01:00
{
extern const char *Tst_StrAnswerTypesDB[Tst_NUM_ANS_TYPES];
MYSQL_RES *mysql_res_qst;
MYSQL_RES *mysql_res_ans;
MYSQL_ROW row;
bool IdenticalQuestionFound = false;
bool IdenticalAnswers;
unsigned NumQst;
unsigned NumQstsWithThisStem;
unsigned NumOpt;
unsigned NumOptsExistingQstInDB;
unsigned i;
/***** Check if stem exists *****/
NumQstsWithThisStem =
(unsigned) DB_QuerySELECT (&mysql_res_qst,"can not check"
" if a question exists",
"SELECT QstCod FROM tst_questions"
" WHERE CrsCod=%ld AND AnsType='%s' AND Stem='%s'",
Gbl.Hierarchy.Crs.CrsCod,
2020-03-19 20:57:54 +01:00
Tst_StrAnswerTypesDB[Question->Answer.Type],
2020-03-17 14:47:58 +01:00
Question->Stem.Text);
2019-12-15 20:02:34 +01:00
if (NumQstsWithThisStem) // There are questions in database with the same stem that the one of this question
{
/***** Check if the answer exists in any of the questions with the same stem *****/
/* For each question with the same stem */
for (NumQst = 0;
!IdenticalQuestionFound && NumQst < NumQstsWithThisStem;
NumQst++)
{
2020-03-17 00:35:11 +01:00
/* Get question code */
2019-12-15 20:02:34 +01:00
row = mysql_fetch_row (mysql_res_qst);
2020-03-25 01:36:22 +01:00
if ((Question->QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
2019-12-15 20:02:34 +01:00
Lay_ShowErrorAndExit ("Wrong code of question.");
/* Get answers from this question */
NumOptsExistingQstInDB =
(unsigned) DB_QuerySELECT (&mysql_res_ans,"can not get the answer"
" of a question",
"SELECT Answer FROM tst_answers"
" WHERE QstCod=%ld ORDER BY AnsInd",
2020-03-25 01:36:22 +01:00
Question->QstCod);
2019-12-15 20:02:34 +01:00
2020-03-19 20:57:54 +01:00
switch (Question->Answer.Type)
2019-12-15 20:02:34 +01:00
{
case Tst_ANS_INT:
row = mysql_fetch_row (mysql_res_ans);
2020-03-19 20:57:54 +01:00
IdenticalQuestionFound = (Tst_GetIntAnsFromStr (row[0]) == Question->Answer.Integer);
2019-12-15 20:02:34 +01:00
break;
case Tst_ANS_FLOAT:
for (IdenticalAnswers = true, i = 0;
IdenticalAnswers && i < 2;
i++)
{
row = mysql_fetch_row (mysql_res_ans);
2020-03-19 20:57:54 +01:00
IdenticalAnswers = (Str_GetDoubleFromStr (row[0]) == Question->Answer.FloatingPoint[i]);
2019-12-15 20:02:34 +01:00
}
IdenticalQuestionFound = IdenticalAnswers;
break;
case Tst_ANS_TRUE_FALSE:
row = mysql_fetch_row (mysql_res_ans);
2020-03-19 20:57:54 +01:00
IdenticalQuestionFound = (Str_ConvertToUpperLetter (row[0][0]) == Question->Answer.TF);
2019-12-15 20:02:34 +01:00
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
case Tst_ANS_TEXT:
2020-03-19 20:57:54 +01:00
if (NumOptsExistingQstInDB == Question->Answer.NumOptions)
2019-12-15 20:02:34 +01:00
{
for (IdenticalAnswers = true, NumOpt = 0;
IdenticalAnswers && NumOpt < NumOptsExistingQstInDB;
NumOpt++)
{
row = mysql_fetch_row (mysql_res_ans);
2020-03-19 20:57:54 +01:00
if (strcasecmp (row[0],Question->Answer.Options[NumOpt].Text))
2019-12-15 20:02:34 +01:00
IdenticalAnswers = false;
}
}
else // Different number of answers (options)
IdenticalAnswers = false;
IdenticalQuestionFound = IdenticalAnswers;
break;
default:
break;
}
/* Free structure that stores the query result for answers */
DB_FreeMySQLResult (&mysql_res_ans);
}
}
else // Stem does not exist
IdenticalQuestionFound = false;
/* Free structure that stores the query result for questions */
DB_FreeMySQLResult (&mysql_res_qst);
return IdenticalQuestionFound;
}
2016-04-04 21:51:21 +02:00
/*****************************************************************************/
/* Move images associates to a test question to their definitive directories */
/*****************************************************************************/
2020-03-25 01:36:22 +01:00
static void Tst_MoveMediaToDefinitiveDirectories (struct Tst_Question *Question)
2016-04-04 21:51:21 +02:00
{
unsigned NumOpt;
2019-03-19 11:20:29 +01:00
long CurrentMedCodInDB;
2016-04-04 21:51:21 +02:00
2019-03-19 01:41:27 +01:00
/***** Media associated to question stem *****/
2020-03-25 01:36:22 +01:00
CurrentMedCodInDB = Tst_GetMedCodFromDB (Gbl.Hierarchy.Crs.CrsCod,Question->QstCod,
2020-03-17 00:35:11 +01:00
-1L); // Get current media code associated to stem
2020-03-17 14:47:58 +01:00
Med_RemoveKeepOrStoreMedia (CurrentMedCodInDB,&Question->Media);
2019-03-18 15:42:22 +01:00
2019-03-19 01:41:27 +01:00
/****** Move media associated to answers *****/
2020-03-19 20:57:54 +01:00
switch (Question->Answer.Type)
2020-03-18 18:49:45 +01:00
{
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
for (NumOpt = 0;
2020-03-19 20:57:54 +01:00
NumOpt < Question->Answer.NumOptions;
2020-03-18 18:49:45 +01:00
NumOpt++)
{
2020-03-25 01:36:22 +01:00
CurrentMedCodInDB = Tst_GetMedCodFromDB (Gbl.Hierarchy.Crs.CrsCod,Question->QstCod,
2020-03-18 18:49:45 +01:00
NumOpt); // Get current media code associated to this option
2020-03-19 20:57:54 +01:00
Med_RemoveKeepOrStoreMedia (CurrentMedCodInDB,&Question->Answer.Options[NumOpt].Media);
2020-03-18 18:49:45 +01:00
}
break;
default:
break;
}
2016-04-04 21:51:21 +02:00
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
/******************** Get a integer number from a string *********************/
/*****************************************************************************/
long Tst_GetIntAnsFromStr (char *Str)
{
long LongNum;
if (Str == NULL)
return 0.0;
/***** The string is "scanned" as long *****/
if (sscanf (Str,"%ld",&LongNum) != 1) // If the string does not hold a valid integer number...
{
LongNum = 0L; // ...the number is reset to 0
Str[0] = '\0'; // ...and the string is reset to ""
}
return LongNum;
}
/*****************************************************************************/
/***************** Check if this tag exists for current course ***************/
/*****************************************************************************/
static long Tst_GetTagCodFromTagTxt (const char *TagTxt)
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned long NumRows;
long TagCod = -1L; // -1 means that the tag does not exist in database
/***** Get tag code from database *****/
2018-11-02 01:38:44 +01:00
NumRows = DB_QuerySELECT (&mysql_res,"can not get tag",
"SELECT TagCod FROM tst_tags"
" WHERE CrsCod=%ld AND TagTxt='%s'",
2019-04-04 10:45:15 +02:00
Gbl.Hierarchy.Crs.CrsCod,TagTxt);
2014-12-01 23:55:08 +01:00
if (NumRows == 1)
{
/***** Get tag code *****/
row = mysql_fetch_row (mysql_res);
if ((TagCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
2019-03-09 20:12:44 +01:00
Ale_CreateAlert (Ale_ERROR,NULL,
"Wrong code of tag.");
2014-12-01 23:55:08 +01:00
}
else if (NumRows > 1)
2019-03-09 20:12:44 +01:00
Ale_CreateAlert (Ale_ERROR,NULL,
"Duplicated tag.");
2014-12-01 23:55:08 +01:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
2019-03-09 20:12:44 +01:00
/***** Abort on error *****/
if (Ale_GetTypeOfLastAlert () == Ale_ERROR)
Ale_ShowAlertsAndExit ();
2014-12-01 23:55:08 +01:00
return TagCod;
}
/*****************************************************************************/
/********************* Insert new tag into tst_tags table ********************/
/*****************************************************************************/
static long Tst_CreateNewTag (long CrsCod,const char *TagTxt)
{
/***** Insert new tag into tst_tags table *****/
2018-11-03 01:45:36 +01:00
return
DB_QueryINSERTandReturnCode ("can not create new tag",
"INSERT INTO tst_tags"
" (CrsCod,ChangeTime,TagTxt,TagHidden)"
" VALUES"
" (%ld,NOW(),'%s','N')",
CrsCod,TagTxt);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/********** Change visibility of an existing tag into tst_tags table *********/
/*****************************************************************************/
static void Tst_EnableOrDisableTag (long TagCod,bool TagHidden)
{
/***** Insert new tag into tst_tags table *****/
2018-11-03 12:16:40 +01:00
DB_QueryUPDATE ("can not update the visibility of a tag",
"UPDATE tst_tags SET TagHidden='%c',ChangeTime=NOW()"
" WHERE TagCod=%ld AND CrsCod=%ld",
TagHidden ? 'Y' :
'N',
2019-04-04 10:45:15 +02:00
TagCod,Gbl.Hierarchy.Crs.CrsCod);
2014-12-01 23:55:08 +01:00
}
2020-03-17 00:35:11 +01:00
/*****************************************************************************/
/***************** Request the removal of selected questions *****************/
/*****************************************************************************/
void Tst_RequestRemoveSelectedQsts (void)
{
extern const char *Txt_Do_you_really_want_to_remove_the_selected_questions;
extern const char *Txt_Remove_questions;
2020-03-24 00:58:42 +01:00
struct Tst_Test Test;
2020-03-17 00:35:11 +01:00
2020-03-26 21:39:44 +01:00
/***** Create test *****/
Tst_TstConstructor (&Test);
2020-03-17 00:35:11 +01:00
/***** Get parameters *****/
2020-03-24 01:50:39 +01:00
if (Tst_GetParamsTst (&Test,Tst_EDIT_TEST)) // Get parameters from the form
2020-03-21 15:41:25 +01:00
{
2020-03-17 00:35:11 +01:00
/***** Show question and button to remove question *****/
Ale_ShowAlertAndButton (ActRemSevTstQst,NULL,NULL,
2020-03-27 14:56:54 +01:00
Tst_PutParamsRemoveSelectedQsts,&Test,
2020-03-17 00:35:11 +01:00
Btn_REMOVE_BUTTON,Txt_Remove_questions,
Ale_QUESTION,Txt_Do_you_really_want_to_remove_the_selected_questions);
2020-03-21 15:41:25 +01:00
}
2020-03-17 00:35:11 +01:00
else
Ale_ShowAlert (Ale_ERROR,"Wrong parameters.");
/***** Continue editing questions *****/
Tst_ListQuestionsToEdit ();
2020-03-26 21:39:44 +01:00
/***** Destroy test *****/
Tst_TstDestructor (&Test);
2020-03-17 00:35:11 +01:00
}
/*****************************************************************************/
/**************** Put parameters to remove selected questions ****************/
/*****************************************************************************/
2020-03-26 21:39:44 +01:00
static void Tst_PutParamsRemoveSelectedQsts (void *TestPtr)
2020-03-17 00:35:11 +01:00
{
2020-03-26 21:39:44 +01:00
struct Tst_Test *Test;
if (TestPtr)
2020-03-26 02:54:30 +01:00
{
2020-03-26 21:39:44 +01:00
Test = (struct Tst_Test *) TestPtr;
2020-03-26 02:54:30 +01:00
Dat_WriteParamsIniEndDates ();
2020-03-26 21:39:44 +01:00
Tst_WriteParamEditQst (Test);
2020-03-26 02:54:30 +01:00
}
2020-03-17 00:35:11 +01:00
}
/*****************************************************************************/
/************************** Remove several questions *************************/
/*****************************************************************************/
void Tst_RemoveSelectedQsts (void)
{
extern const char *Txt_Questions_removed_X;
2020-03-24 00:58:42 +01:00
struct Tst_Test Test;
2020-03-17 00:35:11 +01:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
2020-03-24 01:50:39 +01:00
unsigned NumQst;
2020-03-17 00:35:11 +01:00
long QstCod;
2020-03-26 21:39:44 +01:00
/***** Create test *****/
Tst_TstConstructor (&Test);
2020-03-17 00:35:11 +01:00
/***** Get parameters *****/
2020-03-24 01:50:39 +01:00
if (Tst_GetParamsTst (&Test,Tst_EDIT_TEST)) // Get parameters
2020-03-17 00:35:11 +01:00
{
/***** Get question codes *****/
2020-03-24 01:50:39 +01:00
Tst_GetQuestions (&Test,&mysql_res); // Query database
2020-03-17 00:35:11 +01:00
/***** Remove questions one by one *****/
2020-03-24 01:50:39 +01:00
for (NumQst = 0;
NumQst < Test.NumQsts;
NumQst++)
2020-03-17 00:35:11 +01:00
{
/* Get question code (row[0]) */
row = mysql_fetch_row (mysql_res);
if ((QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
Lay_ShowErrorAndExit ("Wrong code of question.");
/* Remove test question from database */
Tst_RemoveOneQstFromDB (Gbl.Hierarchy.Crs.CrsCod,QstCod);
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
/***** Write message *****/
2020-03-24 01:50:39 +01:00
Ale_ShowAlert (Ale_SUCCESS,Txt_Questions_removed_X,Test.NumQsts);
2020-03-17 00:35:11 +01:00
}
2020-03-26 21:39:44 +01:00
/***** Destroy test *****/
Tst_TstDestructor (&Test);
2020-03-17 00:35:11 +01:00
}
2016-04-05 10:05:52 +02:00
/*****************************************************************************/
/********************* Put icon to remove one question ***********************/
/*****************************************************************************/
2020-03-26 15:22:51 +01:00
static void Tst_PutIconToRemoveOneQst (void *QstCodPtr)
2016-04-05 10:05:52 +02:00
{
2020-03-26 02:54:30 +01:00
Ico_PutContextualIconToRemove (ActReqRemOneTstQst,
2020-03-26 15:22:51 +01:00
Tst_PutParamsRemoveOnlyThisQst,QstCodPtr);
2016-04-05 10:05:52 +02:00
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-04-05 02:59:34 +02:00
/******************** Request the removal of a question **********************/
/*****************************************************************************/
2020-03-17 00:35:11 +01:00
void Tst_RequestRemoveOneQst (void)
2016-04-05 02:59:34 +02:00
{
extern const char *Txt_Do_you_really_want_to_remove_the_question_X;
extern const char *Txt_Remove_question;
bool EditingOnlyThisQst;
2020-03-24 00:58:42 +01:00
struct Tst_Test Test;
2016-04-05 02:59:34 +02:00
2020-03-26 21:39:44 +01:00
/***** Create test *****/
Tst_TstConstructor (&Test);
2020-03-25 01:36:22 +01:00
2016-04-05 10:47:36 +02:00
/***** Get main parameters from form *****/
/* Get the question code */
2020-03-26 21:39:44 +01:00
Test.Question.QstCod = Tst_GetQstCod ();
if (Test.Question.QstCod <= 0)
2016-04-05 02:59:34 +02:00
Lay_ShowErrorAndExit ("Wrong code of question.");
2016-04-05 10:47:36 +02:00
/* Get a parameter that indicates whether it's necessary
to continue listing the rest of questions */
2017-01-28 20:32:50 +01:00
EditingOnlyThisQst = Par_GetParToBool ("OnlyThisQst");
2016-04-05 02:59:34 +02:00
2017-04-28 14:02:08 +02:00
/* Get other parameters */
if (!EditingOnlyThisQst)
2020-03-24 01:50:39 +01:00
if (!Tst_GetParamsTst (&Test,Tst_EDIT_TEST))
2017-04-28 14:02:08 +02:00
Lay_ShowErrorAndExit ("Wrong test parameters.");
2016-04-05 02:59:34 +02:00
2017-04-28 14:02:08 +02:00
/***** Show question and button to remove question *****/
2020-03-27 14:56:54 +01:00
if (EditingOnlyThisQst)
Ale_ShowAlertAndButton (ActRemOneTstQst,NULL,NULL,
Tst_PutParamsRemoveOnlyThisQst,&Test.Question.QstCod,
Btn_REMOVE_BUTTON,Txt_Remove_question,
Ale_QUESTION,Txt_Do_you_really_want_to_remove_the_question_X,
Test.Question.QstCod);
else
Ale_ShowAlertAndButton (ActRemOneTstQst,NULL,NULL,
Tst_PutParamsRemoveOneQstWhileEditing,&Test,
Btn_REMOVE_BUTTON,Txt_Remove_question,
Ale_QUESTION,Txt_Do_you_really_want_to_remove_the_question_X,
Test.Question.QstCod);
2016-04-05 02:59:34 +02:00
/***** Continue editing questions *****/
if (EditingOnlyThisQst)
2020-03-26 21:39:44 +01:00
Tst_ListOneQstToEdit (&Test);
2020-03-25 01:36:22 +01:00
else
Tst_ListQuestionsToEdit ();
2020-03-26 21:39:44 +01:00
/***** Destroy test *****/
Tst_TstDestructor (&Test);
2016-04-05 02:59:34 +02:00
}
2017-04-28 14:02:08 +02:00
/*****************************************************************************/
2020-03-17 00:35:11 +01:00
/***** Put parameters to remove question when editing only one question ******/
2017-04-28 14:02:08 +02:00
/*****************************************************************************/
2020-03-26 15:22:51 +01:00
static void Tst_PutParamsRemoveOnlyThisQst (void *QstCodPtr)
2017-04-28 14:02:08 +02:00
{
2020-03-26 21:39:44 +01:00
if (QstCodPtr)
{
Tst_PutParamQstCod (QstCodPtr);
Par_PutHiddenParamChar ("OnlyThisQst",'Y');
}
2017-04-28 14:02:08 +02:00
}
/*****************************************************************************/
2020-03-17 00:35:11 +01:00
/***** Put parameters to remove question when editing several questions ******/
2017-04-28 14:02:08 +02:00
/*****************************************************************************/
2020-03-26 21:39:44 +01:00
static void Tst_PutParamsRemoveOneQstWhileEditing (void *TestPtr)
2017-04-28 14:02:08 +02:00
{
2020-03-26 21:39:44 +01:00
struct Tst_Test *Test;
if (TestPtr)
{
Test = (struct Tst_Test *) TestPtr;
2020-03-27 14:56:54 +01:00
Tst_PutParamQstCod (&Test->Question.QstCod);
2020-03-26 21:39:44 +01:00
Dat_WriteParamsIniEndDates ();
Tst_WriteParamEditQst (Test);
}
2017-04-28 14:02:08 +02:00
}
2016-04-05 02:59:34 +02:00
/*****************************************************************************/
/***************************** Remove a question *****************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-03-17 00:35:11 +01:00
void Tst_RemoveOneQst (void)
2014-12-01 23:55:08 +01:00
{
extern const char *Txt_Question_removed;
2020-03-17 00:35:11 +01:00
long QstCod;
2014-12-01 23:55:08 +01:00
bool EditingOnlyThisQst;
/***** Get the question code *****/
2020-03-17 00:35:11 +01:00
QstCod = Tst_GetQstCod ();
if (QstCod <= 0)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Wrong code of question.");
2016-04-05 02:59:34 +02:00
/***** Get a parameter that indicates whether it's necessary
to continue listing the rest of questions ******/
2017-01-28 20:32:50 +01:00
EditingOnlyThisQst = Par_GetParToBool ("OnlyThisQst");
2014-12-01 23:55:08 +01:00
2020-03-17 00:35:11 +01:00
/***** Remove test question from database *****/
Tst_RemoveOneQstFromDB (Gbl.Hierarchy.Crs.CrsCod,QstCod);
/***** Write message *****/
Ale_ShowAlert (Ale_SUCCESS,Txt_Question_removed);
/***** Continue editing questions *****/
if (!EditingOnlyThisQst)
Tst_ListQuestionsToEdit ();
}
/*****************************************************************************/
/********************** Remove a question from database **********************/
/*****************************************************************************/
static void Tst_RemoveOneQstFromDB (long CrsCod,long QstCod)
{
long MedCod;
2019-03-19 11:20:29 +01:00
/***** Remove media associated to question *****/
/* Remove media associated to answers */
2020-03-17 00:35:11 +01:00
Tst_RemoveMediaFromAllAnsOfQst (CrsCod,QstCod);
2019-03-19 11:20:29 +01:00
/* Remove media associated to stem */
2020-03-17 00:35:11 +01:00
MedCod = Tst_GetMedCodFromDB (CrsCod,QstCod,
-1L); // Get current media code associated to stem
2019-03-19 11:20:29 +01:00
Med_RemoveMedia (MedCod);
2016-04-01 21:56:00 +02:00
2014-12-01 23:55:08 +01:00
/***** Remove the question from all the tables *****/
/* Remove answers and tags from this test question */
2020-03-17 00:35:11 +01:00
Tst_RemAnsFromQst (QstCod);
Tst_RemTagsFromQst (QstCod);
Tst_RemoveUnusedTagsFromCrs (CrsCod);
2014-12-01 23:55:08 +01:00
/* Remove the question itself */
2018-11-02 22:27:26 +01:00
DB_QueryDELETE ("can not remove a question",
"DELETE FROM tst_questions"
2020-03-17 00:35:11 +01:00
" WHERE QstCod=%ld AND CrsCod=%ld",
QstCod,CrsCod);
2014-12-01 23:55:08 +01:00
if (!mysql_affected_rows (&Gbl.mysql))
2020-03-07 00:14:35 +01:00
Lay_ShowErrorAndExit ("Wrong question.");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/*********************** Change the shuffle of a question ********************/
/*****************************************************************************/
void Tst_ChangeShuffleQst (void)
{
extern const char *Txt_The_answers_of_the_question_with_code_X_will_appear_shuffled;
extern const char *Txt_The_answers_of_the_question_with_code_X_will_appear_without_shuffling;
2020-03-26 21:39:44 +01:00
struct Tst_Test Test;
2014-12-01 23:55:08 +01:00
bool EditingOnlyThisQst;
bool Shuffle;
2020-03-26 21:39:44 +01:00
/***** Create test *****/
Tst_TstConstructor (&Test);
2020-03-25 01:36:22 +01:00
2014-12-01 23:55:08 +01:00
/***** Get the question code *****/
2020-03-26 21:39:44 +01:00
Test.Question.QstCod = Tst_GetQstCod ();
if (Test.Question.QstCod <= 0)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Wrong code of question.");
/***** Get a parameter that indicates whether it's necessary to continue listing the rest of questions ******/
2017-01-28 20:32:50 +01:00
EditingOnlyThisQst = Par_GetParToBool ("OnlyThisQst");
2014-12-01 23:55:08 +01:00
/***** Get a parameter that indicates whether it's possible to shuffle the answers of this question ******/
2017-01-28 20:32:50 +01:00
Shuffle = Par_GetParToBool ("Shuffle");
2014-12-01 23:55:08 +01:00
/***** Remove the question from all the tables *****/
/* Update the question changing the current shuffle */
2018-11-03 12:16:40 +01:00
DB_QueryUPDATE ("can not update the shuffle type of a question",
"UPDATE tst_questions SET Shuffle='%c'"
" WHERE QstCod=%ld AND CrsCod=%ld",
Shuffle ? 'Y' :
'N',
2020-03-26 21:39:44 +01:00
Test.Question.QstCod,Gbl.Hierarchy.Crs.CrsCod);
2014-12-01 23:55:08 +01:00
/***** Write message *****/
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_SUCCESS,Shuffle ? Txt_The_answers_of_the_question_with_code_X_will_appear_shuffled :
Txt_The_answers_of_the_question_with_code_X_will_appear_without_shuffling,
2020-03-26 21:39:44 +01:00
Test.Question.QstCod);
2014-12-01 23:55:08 +01:00
/***** Continue editing questions *****/
if (EditingOnlyThisQst)
2020-03-26 21:39:44 +01:00
Tst_ListOneQstToEdit (&Test);
2014-12-01 23:55:08 +01:00
else
Tst_ListQuestionsToEdit ();
2020-03-25 01:36:22 +01:00
2020-03-26 21:39:44 +01:00
/***** Destroy test *****/
Tst_TstDestructor (&Test);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/************ Get the parameter with the code of a test question *************/
/*****************************************************************************/
2016-04-06 19:26:09 +02:00
static long Tst_GetQstCod (void)
2014-12-01 23:55:08 +01:00
{
2017-01-28 20:32:50 +01:00
/***** Get code of test question *****/
return Par_GetParToLong ("QstCod");
2014-12-01 23:55:08 +01:00
}
2017-04-28 14:02:08 +02:00
/*****************************************************************************/
/************ Put parameter with question code to edit, remove... ************/
/*****************************************************************************/
2020-03-26 15:22:51 +01:00
void Tst_PutParamQstCod (void *QstCodPtr) // Should be a pointer to long
2020-03-17 00:35:11 +01:00
{
2020-03-26 15:22:51 +01:00
long QstCod;
2020-03-17 00:35:11 +01:00
2020-03-26 15:22:51 +01:00
if (QstCodPtr)
{
QstCod = *((long *) QstCodPtr);
if (QstCod > 0) // If question exists
Par_PutHiddenParamLong (NULL,"QstCod",QstCod);
}
2017-04-28 14:02:08 +02:00
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
/******** Insert or update question, tags and anser in the database **********/
/*****************************************************************************/
2020-03-25 01:36:22 +01:00
void Tst_InsertOrUpdateQstTagsAnsIntoDB (struct Tst_Question *Question)
2014-12-01 23:55:08 +01:00
{
/***** Insert or update question in the table of questions *****/
2020-03-25 01:36:22 +01:00
Tst_InsertOrUpdateQstIntoDB (Question);
if (Question->QstCod > 0)
2020-03-17 00:35:11 +01:00
{
/***** Insert tags in the tags table *****/
2020-03-25 01:36:22 +01:00
Tst_InsertTagsIntoDB (Question);
2014-12-01 23:55:08 +01:00
2020-03-17 00:35:11 +01:00
/***** Remove unused tags in current course *****/
Tst_RemoveUnusedTagsFromCrs (Gbl.Hierarchy.Crs.CrsCod);
2014-12-01 23:55:08 +01:00
2020-03-17 00:35:11 +01:00
/***** Insert answers in the answers table *****/
2020-03-25 01:36:22 +01:00
Tst_InsertAnswersIntoDB (Question);
2020-03-17 00:35:11 +01:00
}
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/*********** Insert or update question in the table of questions *************/
/*****************************************************************************/
2020-03-25 01:36:22 +01:00
static void Tst_InsertOrUpdateQstIntoDB (struct Tst_Question *Question)
2014-12-01 23:55:08 +01:00
{
2020-03-25 01:36:22 +01:00
if (Question->QstCod < 0) // It's a new question
2014-12-01 23:55:08 +01:00
{
2016-04-01 21:56:00 +02:00
/***** Insert question in the table of questions *****/
2020-03-25 01:36:22 +01:00
Question->QstCod =
2018-11-03 01:45:36 +01:00
DB_QueryINSERTandReturnCode ("can not create question",
"INSERT INTO tst_questions"
2020-03-17 14:47:58 +01:00
" (CrsCod,"
"EditTime,"
"AnsType,"
"Shuffle,"
"Stem,"
"Feedback,"
"MedCod,"
"NumHits,"
"Score)"
2018-11-03 01:45:36 +01:00
" VALUES"
2020-03-17 14:47:58 +01:00
" (%ld," // CrsCod
"NOW()," // EditTime
"'%s'," // AnsType
"'%c'," // Shuffle
"'%s'," // Stem
"'%s'," // Feedback
"%ld," // MedCod
"0," // NumHits
"0)", // Score
2019-04-04 10:45:15 +02:00
Gbl.Hierarchy.Crs.CrsCod,
2020-03-19 20:57:54 +01:00
Tst_StrAnswerTypesDB[Question->Answer.Type],
2020-03-17 14:47:58 +01:00
Question->Shuffle ? 'Y' :
'N',
Question->Stem.Text,
Question->Feedback.Text ? Question->Feedback.Text :
"",
Question->Media.MedCod);
2014-12-01 23:55:08 +01:00
}
2020-03-17 00:35:11 +01:00
else // It's an existing question
2014-12-01 23:55:08 +01:00
{
2016-04-01 21:56:00 +02:00
/***** Update existing question *****/
2016-04-03 18:51:48 +02:00
/* Update question in database */
2018-11-03 12:16:40 +01:00
DB_QueryUPDATE ("can not update question",
"UPDATE tst_questions"
2020-03-17 14:47:58 +01:00
" SET EditTime=NOW(),"
"AnsType='%s',"
"Shuffle='%c',"
"Stem='%s',"
"Feedback='%s',"
"MedCod=%ld"
2018-11-03 12:16:40 +01:00
" WHERE QstCod=%ld AND CrsCod=%ld",
2020-03-19 20:57:54 +01:00
Tst_StrAnswerTypesDB[Question->Answer.Type],
2020-03-17 14:47:58 +01:00
Question->Shuffle ? 'Y' :
'N',
Question->Stem.Text,
Question->Feedback.Text ? Question->Feedback.Text :
"",
Question->Media.MedCod,
2020-03-25 01:36:22 +01:00
Question->QstCod,Gbl.Hierarchy.Crs.CrsCod);
2016-04-03 18:51:48 +02:00
2014-12-01 23:55:08 +01:00
/* Remove answers and tags from this test question */
2020-03-25 01:36:22 +01:00
Tst_RemAnsFromQst (Question->QstCod);
Tst_RemTagsFromQst (Question->QstCod);
2014-12-01 23:55:08 +01:00
}
}
/*****************************************************************************/
/*********************** Insert tags in the tags table ***********************/
/*****************************************************************************/
2020-03-25 01:36:22 +01:00
static void Tst_InsertTagsIntoDB (const struct Tst_Question *Question)
2014-12-01 23:55:08 +01:00
{
unsigned NumTag;
unsigned TagIdx;
long TagCod;
/***** For each tag... *****/
for (NumTag = 0, TagIdx = 0;
2020-03-25 01:36:22 +01:00
TagIdx < Question->Tags.Num;
2014-12-01 23:55:08 +01:00
NumTag++)
2020-03-25 01:36:22 +01:00
if (Question->Tags.Txt[NumTag][0])
2014-12-01 23:55:08 +01:00
{
/***** Check if this tag exists for current course *****/
2020-03-25 01:36:22 +01:00
if ((TagCod = Tst_GetTagCodFromTagTxt (Question->Tags.Txt[NumTag])) < 0)
2014-12-01 23:55:08 +01:00
/* This tag is new for current course. Add it to tags table */
2020-03-25 01:36:22 +01:00
TagCod = Tst_CreateNewTag (Gbl.Hierarchy.Crs.CrsCod,Question->Tags.Txt[NumTag]);
2014-12-01 23:55:08 +01:00
/***** Insert tag in tst_question_tags *****/
2018-11-02 19:37:11 +01:00
DB_QueryINSERT ("can not create tag",
"INSERT INTO tst_question_tags"
" (QstCod,TagCod,TagInd)"
" VALUES"
" (%ld,%ld,%u)",
2020-03-25 01:36:22 +01:00
Question->QstCod,TagCod,TagIdx);
2014-12-01 23:55:08 +01:00
TagIdx++;
}
}
/*****************************************************************************/
/******************* Insert answers in the answers table *********************/
/*****************************************************************************/
2020-03-25 01:36:22 +01:00
static void Tst_InsertAnswersIntoDB (struct Tst_Question *Question)
2014-12-01 23:55:08 +01:00
{
unsigned NumOpt;
unsigned i;
/***** Insert answers in the answers table *****/
2020-03-19 20:57:54 +01:00
switch (Question->Answer.Type)
2014-12-01 23:55:08 +01:00
{
case Tst_ANS_INT:
2018-11-02 19:37:11 +01:00
DB_QueryINSERT ("can not create answer",
"INSERT INTO tst_answers"
2019-03-18 15:42:22 +01:00
" (QstCod,AnsInd,Answer,Feedback,MedCod,Correct)"
2018-11-02 19:37:11 +01:00
" VALUES"
2019-03-18 15:42:22 +01:00
" (%ld,0,%ld,'',-1,'Y')",
2020-03-25 01:36:22 +01:00
Question->QstCod,
2020-03-19 20:57:54 +01:00
Question->Answer.Integer);
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_FLOAT:
2019-11-27 09:01:45 +01:00
Str_SetDecimalPointToUS (); // To print the floating point as a dot
2014-12-01 23:55:08 +01:00
for (i = 0;
i < 2;
i++)
2018-11-02 19:37:11 +01:00
DB_QueryINSERT ("can not create answer",
"INSERT INTO tst_answers"
2019-03-18 15:42:22 +01:00
" (QstCod,AnsInd,Answer,Feedback,MedCod,Correct)"
2018-11-02 19:37:11 +01:00
" VALUES"
2020-01-11 15:22:02 +01:00
" (%ld,%u,'%.15lg','',-1,'Y')",
2020-03-25 01:36:22 +01:00
Question->QstCod,i,
2020-03-19 20:57:54 +01:00
Question->Answer.FloatingPoint[i]);
2016-06-04 14:21:01 +02:00
Str_SetDecimalPointToLocal (); // Return to local system
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_TRUE_FALSE:
2018-11-02 19:37:11 +01:00
DB_QueryINSERT ("can not create answer",
"INSERT INTO tst_answers"
2019-03-18 15:42:22 +01:00
" (QstCod,AnsInd,Answer,Feedback,MedCod,Correct)"
2018-11-02 19:37:11 +01:00
" VALUES"
2019-03-18 15:42:22 +01:00
" (%ld,0,'%c','',-1,'Y')",
2020-03-25 01:36:22 +01:00
Question->QstCod,
2020-03-19 20:57:54 +01:00
Question->Answer.TF);
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
case Tst_ANS_TEXT:
for (NumOpt = 0;
2020-03-19 20:57:54 +01:00
NumOpt < Question->Answer.NumOptions;
2014-12-01 23:55:08 +01:00
NumOpt++)
2020-03-19 20:57:54 +01:00
if (Question->Answer.Options[NumOpt].Text[0] || // Text
Question->Answer.Options[NumOpt].Media.Type != Med_TYPE_NONE) // or media
2014-12-01 23:55:08 +01:00
{
2018-11-02 19:37:11 +01:00
DB_QueryINSERT ("can not create answer",
"INSERT INTO tst_answers"
2019-03-18 15:42:22 +01:00
" (QstCod,AnsInd,Answer,Feedback,MedCod,Correct)"
2018-11-02 19:37:11 +01:00
" VALUES"
2019-03-18 15:42:22 +01:00
" (%ld,%u,'%s','%s',%ld,'%c')",
2020-03-25 01:36:22 +01:00
Question->QstCod,NumOpt,
2020-03-19 20:57:54 +01:00
Question->Answer.Options[NumOpt].Text,
Question->Answer.Options[NumOpt].Feedback ? Question->Answer.Options[NumOpt].Feedback :
"",
Question->Answer.Options[NumOpt].Media.MedCod,
Question->Answer.Options[NumOpt].Correct ? 'Y' :
'N');
2016-04-04 21:51:21 +02:00
/* Update image status */
2020-03-19 20:57:54 +01:00
if (Question->Answer.Options[NumOpt].Media.Type != Med_TYPE_NONE)
Question->Answer.Options[NumOpt].Media.Status = Med_STORED_IN_DB;
2014-12-01 23:55:08 +01:00
}
break;
default:
break;
}
}
2020-02-16 13:02:30 +01:00
/*****************************************************************************/
/******************* Remove all test exams made in a course ******************/
/*****************************************************************************/
void Tst_RemoveCrsTests (long CrsCod)
{
/***** Remove tests status in the course *****/
DB_QueryDELETE ("can not remove status of tests of a course",
"DELETE FROM tst_status WHERE CrsCod=%ld",
CrsCod);
/***** Remove test configuration of the course *****/
DB_QueryDELETE ("can not remove configuration of tests of a course",
"DELETE FROM tst_config WHERE CrsCod=%ld",
CrsCod);
/***** Remove associations between test questions
and test tags in the course *****/
DB_QueryDELETE ("can not remove tags associated"
" to questions of tests of a course",
"DELETE FROM tst_question_tags"
" USING tst_questions,tst_question_tags"
" WHERE tst_questions.CrsCod=%ld"
" AND tst_questions.QstCod=tst_question_tags.QstCod",
CrsCod);
/***** Remove test tags in the course *****/
DB_QueryDELETE ("can not remove tags of test of a course",
"DELETE FROM tst_tags WHERE CrsCod=%ld",
CrsCod);
/***** Remove test answers in the course *****/
DB_QueryDELETE ("can not remove answers of tests of a course",
"DELETE FROM tst_answers USING tst_questions,tst_answers"
" WHERE tst_questions.CrsCod=%ld"
" AND tst_questions.QstCod=tst_answers.QstCod",
CrsCod);
/***** Remove media associated to test questions in the course *****/
Tst_RemoveAllMedFilesFromAnsOfAllQstsInCrs (CrsCod);
Tst_RemoveAllMedFilesFromStemOfAllQstsInCrs (CrsCod);
/***** Remove test questions in the course *****/
DB_QueryDELETE ("can not remove test questions of a course",
"DELETE FROM tst_questions WHERE CrsCod=%ld",
CrsCod);
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
/******************** Remove answers from a test question ********************/
/*****************************************************************************/
2020-03-17 00:35:11 +01:00
static void Tst_RemAnsFromQst (long QstCod)
2014-12-01 23:55:08 +01:00
{
/***** Remove answers *****/
2018-11-02 22:27:26 +01:00
DB_QueryDELETE ("can not remove the answers of a question",
"DELETE FROM tst_answers WHERE QstCod=%ld",
2020-03-17 00:35:11 +01:00
QstCod);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/************************** Remove tags from a test question *****************/
/*****************************************************************************/
2020-03-17 00:35:11 +01:00
static void Tst_RemTagsFromQst (long QstCod)
2014-12-01 23:55:08 +01:00
{
/***** Remove tags *****/
2018-11-02 22:27:26 +01:00
DB_QueryDELETE ("can not remove the tags of a question",
"DELETE FROM tst_question_tags WHERE QstCod=%ld",
2020-03-17 00:35:11 +01:00
QstCod);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2020-03-17 00:35:11 +01:00
/********************** Remove unused tags in a course ***********************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-03-17 00:35:11 +01:00
static void Tst_RemoveUnusedTagsFromCrs (long CrsCod)
2014-12-01 23:55:08 +01:00
{
/***** Remove unused tags from tst_tags *****/
2018-11-02 22:27:26 +01:00
DB_QueryDELETE ("can not remove unused tags",
"DELETE FROM tst_tags"
" WHERE CrsCod=%ld AND TagCod NOT IN"
" (SELECT DISTINCT tst_question_tags.TagCod"
" FROM tst_questions,tst_question_tags"
" WHERE tst_questions.CrsCod=%ld"
" AND tst_questions.QstCod=tst_question_tags.QstCod)",
2020-03-17 00:35:11 +01:00
CrsCod,
CrsCod);
2014-12-01 23:55:08 +01:00
}
2016-04-07 12:36:58 +02:00
/*****************************************************************************/
2019-03-18 19:35:23 +01:00
/** Remove all media associated to stems of all test questions in a course ***/
2016-04-07 12:36:58 +02:00
/*****************************************************************************/
2019-03-17 01:38:10 +01:00
static void Tst_RemoveAllMedFilesFromStemOfAllQstsInCrs (long CrsCod)
2016-04-04 21:51:21 +02:00
{
MYSQL_RES *mysql_res;
2019-03-02 21:49:11 +01:00
unsigned NumMedia;
2016-04-04 21:51:21 +02:00
2019-03-18 19:35:23 +01:00
/***** Get media codes associated to stems of test questions from database *****/
2019-03-02 21:49:11 +01:00
NumMedia =
2019-03-18 15:42:22 +01:00
(unsigned) DB_QuerySELECT (&mysql_res,"can not get media",
"SELECT MedCod" // row[0]
2019-03-02 21:49:11 +01:00
" FROM tst_questions"
2018-11-02 01:38:44 +01:00
" WHERE CrsCod=%ld",
CrsCod);
2016-04-04 21:51:21 +02:00
2019-03-02 21:49:11 +01:00
/***** Go over result removing media files *****/
2019-03-18 15:42:22 +01:00
Med_RemoveMediaFromAllRows (NumMedia,mysql_res);
2016-04-04 21:51:21 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
2016-04-07 12:36:58 +02:00
/*****************************************************************************/
2019-03-18 19:35:23 +01:00
/******* Remove all media associated to all answers of a test question *******/
2016-04-04 21:51:21 +02:00
/*****************************************************************************/
2019-03-19 11:20:29 +01:00
static void Tst_RemoveMediaFromAllAnsOfQst (long CrsCod,long QstCod)
2016-04-04 21:51:21 +02:00
{
MYSQL_RES *mysql_res;
2019-03-02 21:49:11 +01:00
unsigned NumMedia;
2016-04-04 21:51:21 +02:00
2019-03-18 19:35:23 +01:00
/***** Get media codes associated to answers of test questions from database *****/
2019-03-02 21:49:11 +01:00
NumMedia =
2019-03-18 15:42:22 +01:00
(unsigned) DB_QuerySELECT (&mysql_res,"can not get media",
"SELECT tst_answers.MedCod" // row[0]
2018-11-02 01:38:44 +01:00
" FROM tst_questions,tst_answers"
" WHERE tst_questions.CrsCod=%ld" // Extra check
" AND tst_questions.QstCod=%ld" // Extra check
" AND tst_questions.QstCod=tst_answers.QstCod"
" AND tst_answers.QstCod=%ld",
CrsCod,QstCod,QstCod);
2016-04-07 12:36:58 +02:00
2019-03-18 19:35:23 +01:00
/***** Go over result removing media *****/
2019-03-18 15:42:22 +01:00
Med_RemoveMediaFromAllRows (NumMedia,mysql_res);
2016-04-07 12:36:58 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
2019-03-18 19:35:23 +01:00
/* Remove media associated to all answers of all test questions in a course **/
2016-04-07 12:36:58 +02:00
/*****************************************************************************/
2019-03-17 01:38:10 +01:00
static void Tst_RemoveAllMedFilesFromAnsOfAllQstsInCrs (long CrsCod)
2016-04-07 12:36:58 +02:00
{
MYSQL_RES *mysql_res;
2019-03-02 21:49:11 +01:00
unsigned NumMedia;
2016-04-07 12:36:58 +02:00
2019-03-02 21:49:11 +01:00
/***** Get names of media files associated to answers of test questions from database *****/
NumMedia =
2019-03-18 15:42:22 +01:00
(unsigned) DB_QuerySELECT (&mysql_res,"can not get media",
"SELECT tst_answers.MedCod" // row[0]
2018-11-02 01:38:44 +01:00
" FROM tst_questions,tst_answers"
" WHERE tst_questions.CrsCod=%ld"
" AND tst_questions.QstCod=tst_answers.QstCod",
CrsCod);
2016-04-04 21:51:21 +02:00
2019-03-02 21:49:11 +01:00
/***** Go over result removing media files *****/
2019-03-18 15:42:22 +01:00
Med_RemoveMediaFromAllRows (NumMedia,mysql_res);
2016-04-04 21:51:21 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
/*********************** Get stats about test questions **********************/
/*****************************************************************************/
void Tst_GetTestStats (Tst_AnswerType_t AnsType,struct Tst_Stats *Stats)
{
Stats->NumQsts = 0;
Stats->NumCoursesWithQuestions = Stats->NumCoursesWithPluggableQuestions = 0;
Stats->AvgQstsPerCourse = 0.0;
Stats->NumHits = 0L;
Stats->AvgHitsPerCourse = 0.0;
Stats->AvgHitsPerQuestion = 0.0;
Stats->TotalScore = 0.0;
Stats->AvgScorePerQuestion = 0.0;
if (Tst_GetNumTstQuestions (Gbl.Scope.Current,AnsType,Stats))
{
if ((Stats->NumCoursesWithQuestions = Tst_GetNumCoursesWithTstQuestions (Gbl.Scope.Current,AnsType)) != 0)
{
Stats->NumCoursesWithPluggableQuestions = Tst_GetNumCoursesWithPluggableTstQuestions (Gbl.Scope.Current,AnsType);
2019-11-11 00:15:44 +01:00
Stats->AvgQstsPerCourse = (double) Stats->NumQsts / (double) Stats->NumCoursesWithQuestions;
Stats->AvgHitsPerCourse = (double) Stats->NumHits / (double) Stats->NumCoursesWithQuestions;
2014-12-01 23:55:08 +01:00
}
2019-11-11 00:15:44 +01:00
Stats->AvgHitsPerQuestion = (double) Stats->NumHits / (double) Stats->NumQsts;
2014-12-01 23:55:08 +01:00
if (Stats->NumHits)
Stats->AvgScorePerQuestion = Stats->TotalScore / (double) Stats->NumHits;
}
}
/*****************************************************************************/
/*********************** Get number of test questions ************************/
/*****************************************************************************/
// Returns the number of test questions
// in this location (all the platform, current degree or current course)
2019-04-03 20:57:04 +02:00
static unsigned Tst_GetNumTstQuestions (Hie_Level_t Scope,Tst_AnswerType_t AnsType,struct Tst_Stats *Stats)
2014-12-01 23:55:08 +01:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
/***** Get number of test questions from database *****/
switch (Scope)
{
2019-04-03 20:57:04 +02:00
case Hie_SYS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of test questions",
"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM tst_questions");
2014-12-01 23:55:08 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of test questions",
"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM tst_questions"
" WHERE AnsType='%s'",
Tst_StrAnswerTypesDB[AnsType]);
2014-12-01 23:55:08 +01:00
break;
2019-04-03 20:57:04 +02:00
case Hie_CTY:
2015-03-09 11:03:55 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of test questions",
"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM institutions,centres,degrees,courses,tst_questions"
" WHERE institutions.CtyCod=%ld"
" AND institutions.InsCod=centres.InsCod"
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Cty.CtyCod);
2015-03-09 11:03:55 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of test questions",
"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM institutions,centres,degrees,courses,tst_questions"
" WHERE institutions.CtyCod=%ld"
" AND institutions.InsCod=centres.InsCod"
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Cty.CtyCod,
2018-11-02 11:33:58 +01:00
Tst_StrAnswerTypesDB[AnsType]);
2015-03-09 11:03:55 +01:00
break;
2019-04-03 20:57:04 +02:00
case Hie_INS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of test questions",
"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM centres,degrees,courses,tst_questions"
" WHERE centres.InsCod=%ld"
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Ins.InsCod);
2014-12-01 23:55:08 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of test questions",
"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM centres,degrees,courses,tst_questions"
" WHERE centres.InsCod=%ld"
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Ins.InsCod,
2018-11-02 11:33:58 +01:00
Tst_StrAnswerTypesDB[AnsType]);
2014-12-01 23:55:08 +01:00
break;
2019-04-03 20:57:04 +02:00
case Hie_CTR:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of test questions",
"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM degrees,courses,tst_questions"
" WHERE degrees.CtrCod=%ld"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Ctr.CtrCod);
2014-12-01 23:55:08 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of test questions",
"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM degrees,courses,tst_questions"
" WHERE degrees.CtrCod=%ld"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Ctr.CtrCod,
2018-11-02 11:33:58 +01:00
Tst_StrAnswerTypesDB[AnsType]);
2014-12-01 23:55:08 +01:00
break;
2019-04-03 20:57:04 +02:00
case Hie_DEG:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of test questions",
"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM courses,tst_questions"
" WHERE courses.DegCod=%ld"
" AND courses.CrsCod=tst_questions.CrsCod",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Deg.DegCod);
2014-12-01 23:55:08 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of test questions",
"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM courses,tst_questions"
" WHERE courses.DegCod=%ld"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Deg.DegCod,
2018-11-02 11:33:58 +01:00
Tst_StrAnswerTypesDB[AnsType]);
2014-12-01 23:55:08 +01:00
break;
2019-04-03 20:57:04 +02:00
case Hie_CRS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of test questions",
"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM tst_questions"
" WHERE CrsCod=%ld",
2019-04-04 10:45:15 +02:00
Gbl.Hierarchy.Crs.CrsCod);
2014-12-01 23:55:08 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of test questions",
"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM tst_questions"
" WHERE CrsCod=%ld AND AnsType='%s'",
2019-04-04 10:45:15 +02:00
Gbl.Hierarchy.Crs.CrsCod,
2018-11-02 11:33:58 +01:00
Tst_StrAnswerTypesDB[AnsType]);
2014-12-01 23:55:08 +01:00
break;
default:
2018-10-24 23:03:11 +02:00
Lay_WrongScopeExit ();
2014-12-01 23:55:08 +01:00
break;
}
/***** Get number of questions *****/
row = mysql_fetch_row (mysql_res);
if (sscanf (row[0],"%u",&(Stats->NumQsts)) != 1)
Lay_ShowErrorAndExit ("Error when getting number of test questions.");
if (Stats->NumQsts)
{
if (sscanf (row[1],"%lu",&(Stats->NumHits)) != 1)
Lay_ShowErrorAndExit ("Error when getting total number of hits in test questions.");
2016-06-04 14:21:01 +02:00
Str_SetDecimalPointToUS (); // To get the decimal point as a dot
2014-12-01 23:55:08 +01:00
if (sscanf (row[2],"%lf",&(Stats->TotalScore)) != 1)
Lay_ShowErrorAndExit ("Error when getting total score in test questions.");
2016-06-04 14:21:01 +02:00
Str_SetDecimalPointToLocal (); // Return to local system
2014-12-01 23:55:08 +01:00
}
else
{
Stats->NumHits = 0L;
Stats->TotalScore = 0.0;
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
return Stats->NumQsts;
}
/*****************************************************************************/
/**************** Get number of courses with test questions ******************/
/*****************************************************************************/
// Returns the number of courses with test questions
// in this location (all the platform, current degree or current course)
2019-04-03 20:57:04 +02:00
static unsigned Tst_GetNumCoursesWithTstQuestions (Hie_Level_t Scope,Tst_AnswerType_t AnsType)
2014-12-01 23:55:08 +01:00
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumCourses;
/***** Get number of courses with test questions from database *****/
switch (Scope)
{
2019-04-03 20:57:04 +02:00
case Hie_SYS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with test questions",
"SELECT COUNT(DISTINCT CrsCod)"
" FROM tst_questions");
2014-12-01 23:55:08 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with test questions",
"SELECT COUNT(DISTINCT CrsCod)"
" FROM tst_questions"
" WHERE AnsType='%s'",
Tst_StrAnswerTypesDB[AnsType]);
2014-12-01 23:55:08 +01:00
break;
2019-04-03 20:57:04 +02:00
case Hie_CTY:
2015-03-09 11:03:55 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM institutions,centres,degrees,courses,tst_questions"
" WHERE institutions.CtyCod=%ld"
" AND institutions.InsCod=centres.InsCod"
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Cty.CtyCod);
2015-03-09 11:03:55 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM institutions,centres,degrees,courses,tst_questions"
" WHERE institutions.CtyCod=%ld"
" AND institutions.InsCod=centres.InsCod"
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Cty.CtyCod,
2018-11-02 11:33:58 +01:00
Tst_StrAnswerTypesDB[AnsType]);
2015-03-09 11:03:55 +01:00
break;
2019-04-03 20:57:04 +02:00
case Hie_INS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM centres,degrees,courses,tst_questions"
" WHERE centres.InsCod=%ld"
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Ins.InsCod);
2014-12-01 23:55:08 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM centres,degrees,courses,tst_questions"
" WHERE centres.InsCod=%ld"
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Ins.InsCod,
2018-11-02 11:33:58 +01:00
Tst_StrAnswerTypesDB[AnsType]);
2014-12-01 23:55:08 +01:00
break;
2019-04-03 20:57:04 +02:00
case Hie_CTR:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM degrees,courses,tst_questions"
" WHERE degrees.CtrCod=%ld"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Ctr.CtrCod);
2014-12-01 23:55:08 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM degrees,courses,tst_questions"
" WHERE degrees.CtrCod=%ld"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Ctr.CtrCod,
2018-11-02 11:33:58 +01:00
Tst_StrAnswerTypesDB[AnsType]);
2014-12-01 23:55:08 +01:00
break;
2019-04-03 20:57:04 +02:00
case Hie_DEG:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with test questions",
"SELECT COUNTDISTINCT (tst_questions.CrsCod)"
" FROM courses,tst_questions"
" WHERE courses.DegCod=%ld"
" AND courses.CrsCod=tst_questions.CrsCod",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Deg.DegCod);
2014-12-01 23:55:08 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM courses,tst_questions"
" WHERE courses.DegCod=%ld"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Deg.DegCod,
2018-11-02 11:33:58 +01:00
Tst_StrAnswerTypesDB[AnsType]);
2014-12-01 23:55:08 +01:00
break;
2019-04-03 20:57:04 +02:00
case Hie_CRS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with test questions",
"SELECT COUNT(DISTINCT CrsCod)"
" FROM tst_questions"
" WHERE CrsCod=%ld",
2019-04-04 10:45:15 +02:00
Gbl.Hierarchy.Crs.CrsCod);
2014-12-01 23:55:08 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with test questions",
"SELECT COUNT(DISTINCT CrsCod)"
" FROM tst_questions"
" WHERE CrsCod=%ld"
" AND AnsType='%s'",
2019-04-04 10:45:15 +02:00
Gbl.Hierarchy.Crs.CrsCod,
2018-11-02 11:33:58 +01:00
Tst_StrAnswerTypesDB[AnsType]);
2014-12-01 23:55:08 +01:00
break;
default:
2018-10-24 23:03:11 +02:00
Lay_WrongScopeExit ();
2014-12-01 23:55:08 +01:00
break;
}
/***** Get number of courses *****/
row = mysql_fetch_row (mysql_res);
if (sscanf (row[0],"%u",&NumCourses) != 1)
Lay_ShowErrorAndExit ("Error when getting number of courses with test questions.");
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
return NumCourses;
}
/*****************************************************************************/
/*********** Get number of courses with pluggable test questions *************/
/*****************************************************************************/
// Returns the number of courses with pluggable test questions
// in this location (all the platform, current degree or current course)
2019-04-03 20:57:04 +02:00
static unsigned Tst_GetNumCoursesWithPluggableTstQuestions (Hie_Level_t Scope,Tst_AnswerType_t AnsType)
2014-12-01 23:55:08 +01:00
{
2020-03-21 15:41:25 +01:00
extern const char *TstCfg_PluggableDB[TstCfg_NUM_OPTIONS_PLUGGABLE];
2014-12-01 23:55:08 +01:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumCourses;
/***** Get number of courses with test questions from database *****/
switch (Scope)
{
2019-04-03 20:57:04 +02:00
case Hie_SYS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with pluggable test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM tst_questions,tst_config"
" WHERE tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
2020-03-21 15:41:25 +01:00
TstCfg_PluggableDB[TstCfg_PLUGGABLE_YES]);
2014-12-01 23:55:08 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with pluggable test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM tst_questions,tst_config"
" WHERE tst_questions.AnsType='%s'"
" AND tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
Tst_StrAnswerTypesDB[AnsType],
2020-03-21 15:41:25 +01:00
TstCfg_PluggableDB[TstCfg_PLUGGABLE_YES]);
2014-12-01 23:55:08 +01:00
break;
2019-04-03 20:57:04 +02:00
case Hie_CTY:
2015-03-09 11:03:55 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with pluggable test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM institutions,centres,degrees,courses,tst_questions,tst_config"
" WHERE institutions.CtyCod=%ld"
" AND institutions.InsCod=centres.InsCod"
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Cty.CtyCod,
2020-03-21 15:41:25 +01:00
TstCfg_PluggableDB[TstCfg_PLUGGABLE_YES]);
2015-03-09 11:03:55 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with pluggable test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM institutions,centres,degrees,courses,tst_questions,tst_config"
" WHERE institutions.CtyCod=%ld"
" AND institutions.InsCod=centres.InsCod"
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'"
" AND tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Cty.CtyCod,
2018-11-02 11:33:58 +01:00
Tst_StrAnswerTypesDB[AnsType],
2020-03-21 15:41:25 +01:00
TstCfg_PluggableDB[TstCfg_PLUGGABLE_YES]);
2015-03-09 11:03:55 +01:00
break;
2019-04-03 20:57:04 +02:00
case Hie_INS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with pluggable test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM centres,degrees,courses,tst_questions,tst_config"
" WHERE centres.InsCod=%ld"
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Ins.InsCod,
2020-03-21 15:41:25 +01:00
TstCfg_PluggableDB[TstCfg_PLUGGABLE_YES]);
2014-12-01 23:55:08 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with pluggable test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM centres,degrees,courses,tst_questions,tst_config"
" WHERE centres.InsCod=%ld"
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'"
" AND tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Ins.InsCod,
2018-11-02 11:33:58 +01:00
Tst_StrAnswerTypesDB[AnsType],
2020-03-21 15:41:25 +01:00
TstCfg_PluggableDB[TstCfg_PLUGGABLE_YES]);
2014-12-01 23:55:08 +01:00
break;
2019-04-03 20:57:04 +02:00
case Hie_CTR:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with pluggable test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM degrees,courses,tst_questions,tst_config"
" WHERE degrees.CtrCod=%ld"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Ctr.CtrCod,
2020-03-21 15:41:25 +01:00
TstCfg_PluggableDB[TstCfg_PLUGGABLE_YES]);
2014-12-01 23:55:08 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with pluggable test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM degrees,courses,tst_questions,tst_config"
" WHERE degrees.CtrCod=%ld"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'"
" AND tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Ctr.CtrCod,
2018-11-02 11:33:58 +01:00
Tst_StrAnswerTypesDB[AnsType],
2020-03-21 15:41:25 +01:00
TstCfg_PluggableDB[TstCfg_PLUGGABLE_YES]);
2014-12-01 23:55:08 +01:00
break;
2019-04-03 20:57:04 +02:00
case Hie_DEG:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with pluggable test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM courses,tst_questions,tst_config"
" WHERE courses.DegCod=%ld"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Deg.DegCod,
2020-03-21 15:41:25 +01:00
TstCfg_PluggableDB[TstCfg_PLUGGABLE_YES]);
2014-12-01 23:55:08 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with pluggable test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM courses,tst_questions,tst_config"
" WHERE courses.DegCod=%ld"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'"
" AND tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
2019-04-03 20:57:04 +02:00
Gbl.Hierarchy.Deg.DegCod,
2018-11-02 11:33:58 +01:00
Tst_StrAnswerTypesDB[AnsType],
2020-03-21 15:41:25 +01:00
TstCfg_PluggableDB[TstCfg_PLUGGABLE_YES]);
2014-12-01 23:55:08 +01:00
break;
2019-04-03 20:57:04 +02:00
case Hie_CRS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with pluggable test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM tst_questions,tst_config"
" WHERE tst_questions.CrsCod=%ld"
" AND tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
2019-04-04 10:45:15 +02:00
Gbl.Hierarchy.Crs.CrsCod,
2020-03-21 15:41:25 +01:00
TstCfg_PluggableDB[TstCfg_PLUGGABLE_YES]);
2014-12-01 23:55:08 +01:00
else
2018-11-02 11:33:58 +01:00
DB_QuerySELECT (&mysql_res,"can not get number of courses"
" with pluggable test questions",
"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM tst_questions,tst_config"
" WHERE tst_questions.CrsCod=%ld"
" AND tst_questions.AnsType='%s'"
" AND tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
2019-04-04 10:45:15 +02:00
Gbl.Hierarchy.Crs.CrsCod,
2018-11-02 11:33:58 +01:00
Tst_StrAnswerTypesDB[AnsType],
2020-03-21 15:41:25 +01:00
TstCfg_PluggableDB[TstCfg_PLUGGABLE_YES]);
2014-12-01 23:55:08 +01:00
break;
default:
2018-10-24 23:03:11 +02:00
Lay_WrongScopeExit ();
2014-12-01 23:55:08 +01:00
break;
}
/***** Get number of courses *****/
row = mysql_fetch_row (mysql_res);
if (sscanf (row[0],"%u",&NumCourses) != 1)
Lay_ShowErrorAndExit ("Error when getting number of courses with pluggable test questions.");
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
return NumCourses;
}