swad-core/swad_test.c

8105 lines
297 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.
2017-01-13 01:51:23 +01:00
Copyright (C) 1999-2017 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 *********************************/
/*****************************************************************************/
#include <limits.h> // For UINT_MAX
#include <linux/limits.h> // For PATH_MAX
#include <linux/stddef.h> // For NULL
#include <mysql/mysql.h> // To access MySQL databases
2015-10-16 02:24:29 +02:00
#include <stdbool.h> // For boolean type
2014-12-01 23:55:08 +01:00
#include <stdio.h> // For fprintf, etc.
#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"
#include "swad_database.h"
#include "swad_global.h"
#include "swad_ID.h"
2016-04-03 01:24:20 +02:00
#include "swad_image.h"
2014-12-01 23:55:08 +01:00
#include "swad_parameter.h"
#include "swad_theme.h"
#include "swad_test.h"
#include "swad_test_import.h"
#include "swad_text.h"
#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_FEEDBACK_TYPE bytes
2014-12-01 23:55:08 +01:00
const char *Tst_FeedbackXML[Tst_NUM_TYPES_FEEDBACK] =
{
"nothing",
"totalResult",
"eachResult",
"eachGoodBad",
"fullFeedback",
};
2017-01-15 18:02:52 +01:00
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] =
{
"int",
"float",
"TF",
"uniqueChoice",
"multipleChoice",
"text",
};
/*****************************************************************************/
/**************************** 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
const char *Tst_PluggableDB[Tst_NUM_OPTIONS_PLUGGABLE] =
{
"unknown",
"N",
"Y",
};
2016-11-21 13:15:08 +01:00
// Feedback to students in tests
2014-12-01 23:55:08 +01:00
const char *Tst_FeedbackDB[Tst_NUM_TYPES_FEEDBACK] =
{
"nothing", // No feedback
"total_result", // Little
"each_result", // Medium
"each_good_bad", // High
"full_feedback", // Maximum
};
const char *Tst_StrAnswerTypesDB[Tst_NUM_ANS_TYPES] =
{
"int",
"float",
"true_false",
"unique_choice",
"multiple_choice",
"text",
};
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
#define Tst_IMAGE_SAVED_MAX_HEIGHT 512
#define Tst_IMAGE_SAVED_QUALITY 75 // 1 to 100
2016-04-01 13:37:49 +02:00
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
/******************************* Internal types ******************************/
/*****************************************************************************/
#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;
/*****************************************************************************/
/************************* Internal global variables *************************/
/*****************************************************************************/
/*****************************************************************************/
/***************************** Internal prototypes ***************************/
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_PutFormToViewResultsOfUsersTests (Act_Action_t Action);
2014-12-01 23:55:08 +01:00
static void Tst_GetQuestionsAndAnswersFromForm (void);
static void Tst_ShowTstTotalMark (double TotalScore);
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);
static void Tst_ShowTestQuestionsWhenSeeing (MYSQL_RES *mysql_res);
2016-11-21 13:15:08 +01:00
static void Tst_ShowTestResultAfterAssess (long TstCod,unsigned *NumQstsNotBlank,double *TotalScore);
static void Tst_WriteQstAndAnsTest (unsigned NumQst,long QstCod,MYSQL_ROW row,
2014-12-01 23:55:08 +01:00
double *ScoreThisQst,bool *AnswerIsNotBlank);
2016-04-14 21:33:24 +02:00
static void Tst_PutFormToEditQstImage (struct Image *Image,int NumImgInForm,
2016-04-15 14:30:28 +02:00
const char *ClassContainer,
2016-04-14 18:59:18 +02:00
const char *ClassImg,
2016-04-15 10:58:35 +02:00
const char *ClassImgTitURL,
2016-04-05 10:47:36 +02:00
bool OptionsDisabled);
2014-12-01 23:55:08 +01:00
static void Tst_UpdateScoreQst (long QstCod,float ScoreThisQst,bool AnswerIsNotBlank);
static void Tst_UpdateMyNumAccessTst (unsigned NumAccessesTst);
static void Tst_UpdateLastAccTst (void);
2016-11-07 10:35:36 +01:00
static bool Tst_CheckIfICanEditTests (void);
static void Tst_PutIconsTests (void);
2016-03-21 02:13:19 +01:00
static void Tst_PutButtonToAddQuestion (void);
2014-12-01 23:55:08 +01:00
static long Tst_GetParamTagCode (void);
static bool Tst_CheckIfCurrentCrsHasTestTags (void);
static unsigned long Tst_GetAllTagsFromCurrentCrs (MYSQL_RES **mysql_res);
static unsigned long Tst_GetEnabledTagsFromThisCrs (MYSQL_RES **mysql_res);
2016-03-21 01:28:16 +01:00
static void Tst_ShowFormSelTags (unsigned long NumRows,MYSQL_RES *mysql_res,
bool ShowOnlyEnabledTags,unsigned NumCols);
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);
2016-12-26 15:17:30 +01:00
static void Tst_PutInputFieldNumQst (const char *Field,const char *Label,
unsigned Value);
2014-12-01 23:55:08 +01:00
static void Tst_GetConfigTstFromDB (void);
static Tst_Pluggable_t Tst_GetPluggableFromForm (void);
static Tst_Feedback_t Tst_GetFeedbackTypeFromForm (void);
static void Tst_CheckAndCorrectNumbersQst (void);
2016-03-21 01:28:16 +01:00
static void Tst_ShowFormAnswerTypes (unsigned NumCols);
2014-12-01 23:55:08 +01:00
static unsigned long Tst_GetQuestionsForEdit (MYSQL_RES **mysql_res);
2016-11-21 13:15:08 +01:00
static unsigned long Tst_GetQuestionsForTest (MYSQL_RES **mysql_res);
2014-12-01 23:55:08 +01:00
static void Tst_ListOneQstToEdit (void);
static bool Tst_GetOneQuestionByCod (long QstCod,MYSQL_RES **mysql_res);
static void Tst_ListOneOrMoreQuestionsToEdit (unsigned long NumRows,MYSQL_RES *mysql_res);
static void Tst_WriteAnswersOfAQstEdit (long QstCod);
2016-11-21 13:15:08 +01:00
static void Tst_WriteAnswersOfAQstViewTest (unsigned NumQst,long QstCod,bool Shuffle);
static void Tst_WriteAnswersOfAQstAssessTest (unsigned NumQst,long QstCod,
2014-12-01 23:55:08 +01:00
double *ScoreThisQst,bool *AnswerIsNotBlank);
static void Tst_WriteFormAnsTF (unsigned NumQst);
2016-11-21 13:15:08 +01:00
static void Tst_WriteTFAnsAssessTest (unsigned NumQst,MYSQL_RES *mysql_res,
2014-12-01 23:55:08 +01:00
double *ScoreThisQst,bool *AnswerIsNotBlank);
2016-11-21 13:15:08 +01:00
static void Tst_WriteChoiceAnsViewTest (unsigned NumQst,long QstCod,bool Shuffle);
static void Tst_WriteChoiceAnsAssessTest (unsigned NumQst,MYSQL_RES *mysql_res,
2014-12-01 23:55:08 +01:00
double *ScoreThisQst,bool *AnswerIsNotBlank);
2016-11-21 13:15:08 +01:00
static void Tst_WriteTextAnsViewTest (unsigned NumQst);
static void Tst_WriteTextAnsAssessTest (unsigned NumQst,MYSQL_RES *mysql_res,
2014-12-01 23:55:08 +01:00
double *ScoreThisQst,bool *AnswerIsNotBlank);
2016-11-21 13:15:08 +01:00
static void Tst_WriteIntAnsViewTest (unsigned NumQst);
static void Tst_WriteIntAnsAssessTest (unsigned NumQst,MYSQL_RES *mysql_res,
2014-12-01 23:55:08 +01:00
double *ScoreThisQst,bool *AnswerIsNotBlank);
2016-11-21 13:15:08 +01:00
static void Tst_WriteFloatAnsViewTest (unsigned NumQst);
static void Tst_WriteFloatAnsAssessTest (unsigned NumQst,MYSQL_RES *mysql_res,
2014-12-01 23:55:08 +01:00
double *ScoreThisQst,bool *AnswerIsNotBlank);
static void Tst_WriteHeadUserCorrect (void);
static void Tst_WriteScoreStart (unsigned ColSpan);
static void Tst_WriteScoreEnd (void);
static void Tst_WriteParamQstCod (unsigned NumQst,long QstCod);
static void Tst_GetAndWriteTagsQst (long QstCod);
2016-04-05 02:59:34 +02:00
static bool Tst_GetParamsTst (void);
2014-12-01 23:55:08 +01:00
static unsigned Tst_GetAndCheckParamNumTst (void);
static void Tst_GetParamNumQst (void);
static bool Tst_GetCreateXMLFromForm (void);
static int Tst_CountNumTagsInList (void);
static int Tst_CountNumAnswerTypesInList (void);
2017-01-17 03:10:43 +01:00
static void Tst_PutFormEditOneQst (char Stem[Cns_MAX_BYTES_TEXT + 1],
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,
double Value);
2016-12-26 16:30:46 +01:00
static void Tst_PutTFInputField (const char *Label,char Value);
2016-04-03 01:24:20 +02:00
2016-04-06 19:26:09 +02:00
static void Tst_FreeTextChoiceAnswers (void);
static void Tst_FreeTextChoiceAnswer (unsigned NumOpt);
2016-04-09 02:04:45 +02:00
static void Tst_InitImagesOfQuestion (void);
2016-04-06 01:10:04 +02:00
static void Tst_FreeImagesOfQuestion (void);
2016-04-04 21:51:21 +02:00
2017-01-17 03:10:43 +01:00
static void Tst_GetQstDataFromDB (char Stem[Cns_MAX_BYTES_TEXT + 1],
char Feedback[Cns_MAX_BYTES_TEXT + 1]);
2016-04-14 19:05:52 +02:00
static void Tst_GetImageFromDB (int NumOpt,struct Image *Image);
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);
static void Tst_GetQstFromForm (char *Stem,char *Feedback);
2016-04-04 21:51:21 +02:00
static void Tst_MoveImagesToDefinitiveDirectories (void);
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
2016-04-05 13:07:33 +02:00
static void Tst_PutIconToRemoveOneQst (void);
2016-04-05 10:05:52 +02:00
static void Tst_PutParamsRemoveOneQst (void);
2017-04-28 14:02:08 +02:00
static void Tst_PutParamsRemoveQst (void);
2016-04-05 10:05:52 +02:00
2016-04-06 19:26:09 +02:00
static long Tst_GetQstCod (void);
2017-04-28 14:02:08 +02:00
static void Tst_PutParamQstCod (void);
2014-12-01 23:55:08 +01:00
static void Tst_InsertOrUpdateQstIntoDB (void);
static void Tst_InsertTagsIntoDB (void);
static void Tst_InsertAnswersIntoDB (void);
static void Tst_RemAnsFromQst (void);
static void Tst_RemTagsFromQst (void);
static void Tst_RemoveUnusedTagsFromCurrentCrs (void);
2016-04-04 21:51:21 +02:00
2016-04-07 12:36:58 +02:00
static void Tst_RemoveImgFileFromStemOfQst (long CrsCod,long QstCod);
static void Tst_RemoveAllImgFilesFromStemOfAllQstsInCrs (long CrsCod);
static void Tst_RemoveImgFileFromAnsOfQst (long CrsCod,long QstCod,unsigned AnsInd);
static void Tst_RemoveAllImgFilesFromAnsOfQst (long CrsCod,long QstCod);
static void Tst_RemoveAllImgFilesFromAnsOfAllQstsInCrs (long CrsCod);
2016-04-04 21:51:21 +02:00
2014-12-01 23:55:08 +01:00
static unsigned Tst_GetNumTstQuestions (Sco_Scope_t Scope,Tst_AnswerType_t AnsType,struct Tst_Stats *Stats);
static unsigned Tst_GetNumCoursesWithTstQuestions (Sco_Scope_t Scope,Tst_AnswerType_t AnsType);
static unsigned Tst_GetNumCoursesWithPluggableTstQuestions (Sco_Scope_t Scope,Tst_AnswerType_t AnsType);
2016-11-21 13:15:08 +01:00
static long Tst_CreateTestResultInDB (void);
static void Tst_StoreScoreOfTestResultInDB (long TstCod,
2014-12-01 23:55:08 +01:00
unsigned NumQstsNotBlank,double Score);
static void Tst_ShowHeaderTestResults (void);
2016-11-21 13:15:08 +01:00
static void Tst_ShowTestResults (struct UsrData *UsrDat);
static void Tst_ShowDataUsr (struct UsrData *UsrDat,unsigned NumTestResults);
2014-12-01 23:55:08 +01:00
static void Tst_PutParamTstCod (long TstCod);
static long Tst_GetParamTstCod (void);
2016-11-21 13:15:08 +01:00
static void Tst_ShowTestResult (time_t TstTimeUTC);
static void Tst_GetTestResultDataByTstCod (long TstCod,time_t *TstTimeUTC,
unsigned *NumQsts,unsigned *NumQstsNotBlank,double *Score);
static void Tst_StoreOneTestResultQstInDB (long TstCod,long QstCod,unsigned NumQst,double Score);
static void Tst_GetTestResultQuestionsFromDB (long TstCod);
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
/*************** Show form to generate a self-assessment test ****************/
/*****************************************************************************/
void Tst_ShowFormAskTst (void)
{
2016-11-13 20:18:49 +01:00
extern const char *Hlp_ASSESSMENT_Tests;
2015-07-28 00:16:09 +02:00
extern const char *The_ClassForm[The_NUM_THEMES];
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;
unsigned long NumRows;
/***** Read test configuration from database *****/
Tst_GetConfigTstFromDB ();
2016-11-21 13:15:08 +01:00
/***** Put link to view tests results *****/
2016-05-30 18:44:42 +02:00
switch (Gbl.Usrs.Me.LoggedRole)
2014-12-01 23:55:08 +01:00
{
2016-05-30 18:44:42 +02:00
case Rol_STUDENT:
2016-11-21 13:15:08 +01:00
Tst_PutFormToViewResultsOfUsersTests (ActReqSeeMyTstRes);
2016-05-30 18:44:42 +02:00
break;
case Rol_TEACHER:
case Rol_SYS_ADM:
2016-11-21 13:15:08 +01:00
Tst_PutFormToViewResultsOfUsersTests (ActReqSeeUsrTstRes);
2016-05-30 18:44:42 +02:00
break;
default:
break;
2014-12-01 23:55:08 +01:00
}
2016-03-20 23:38:22 +01:00
/***** Start frame *****/
2016-11-13 20:18:49 +01:00
Lay_StartRoundFrame (NULL,Txt_Take_a_test,
Tst_PutIconsTests,Hlp_ASSESSMENT_Tests);
2016-03-20 23:38:22 +01:00
2014-12-01 23:55:08 +01:00
/***** Get tags *****/
if ((NumRows = Tst_GetEnabledTagsFromThisCrs (&mysql_res)) != 0)
{
/***** Check if minimum date-time of next access to test is older than now *****/
if (Tst_CheckIfNextTstAllowed ())
{
Act_FormStart (ActSeeTst);
2016-03-21 01:28:16 +01:00
fprintf (Gbl.F.Out,"<table class=\"CELLS_PAD_2\">");
2014-12-01 23:55:08 +01:00
/***** Selection of tags *****/
2016-03-21 01:28:16 +01:00
Tst_ShowFormSelTags (NumRows,mysql_res,true,1);
2014-12-01 23:55:08 +01:00
/***** Selection of types of answers *****/
2016-03-21 01:28:16 +01:00
Tst_ShowFormAnswerTypes (1);
2014-12-01 23:55:08 +01:00
/***** Number of questions to generate ****/
2016-03-21 01:28:16 +01:00
fprintf (Gbl.F.Out,"<tr>"
"<td class=\"RIGHT_MIDDLE\">"
2016-12-20 02:18:50 +01:00
"<label for=\"NumQst\" class=\"%s\">"
2016-03-21 01:28:16 +01:00
"%s:"
2015-07-27 19:42:24 +02:00
"</label>"
2016-03-21 01:28:16 +01:00
"</td>"
"<td class=\"LEFT_MIDDLE\">"
2016-12-20 02:18:50 +01:00
"<input type=\"number\""
" id=\"NumQst\" name=\"NumQst\""
" min=\"%u\" max=\"%u\" value=\"%u\"",
2015-07-28 00:16:09 +02:00
The_ClassForm[Gbl.Prefs.Theme],Txt_No_of_questions,
2016-12-20 02:18:50 +01:00
Gbl.Test.Config.Min,
Gbl.Test.Config.Max,
2014-12-01 23:55:08 +01:00
Gbl.Test.Config.Def);
if (Gbl.Test.Config.Min == Gbl.Test.Config.Max)
fprintf (Gbl.F.Out," disabled=\"disabled\"");
fprintf (Gbl.F.Out," />"
2016-03-21 01:28:16 +01:00
"</td>"
"</tr>"
"</table>");
2014-12-01 23:55:08 +01:00
/***** Send button *****/
2016-11-21 13:15:08 +01:00
Lay_PutConfirmButton (Txt_Generate_test);
2015-03-13 00:16:02 +01:00
Act_FormEnd ();
2014-12-01 23:55:08 +01:00
}
}
else
{
2016-03-21 02:13:19 +01:00
/***** Warning message *****/
Lay_ShowAlert (Lay_INFO,Txt_No_test_questions);
/***** 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
}
2016-03-20 23:38:22 +01:00
/***** End frame *****/
Lay_EndRoundFrame ();
2014-12-01 23:55:08 +01:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
2014-12-30 20:42:11 +01:00
/*****************************************************************************/
/*************** Write a form to go to result of users' tests ****************/
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_PutFormToViewResultsOfUsersTests (Act_Action_t Action)
2014-12-30 20:42:11 +01:00
{
2016-11-21 13:15:08 +01:00
extern const char *Txt_Test_results;
2014-12-30 20:42:11 +01:00
2016-05-30 18:44:42 +02:00
fprintf (Gbl.F.Out,"<div class=\"CONTEXT_MENU\">");
2017-04-17 19:03:21 +02:00
Lay_PutContextualLink (Action,NULL,NULL,
"exam64x64.png",
2016-11-21 13:15:08 +01:00
Txt_Test_results,Txt_Test_results,
2016-07-01 16:32:42 +02:00
NULL);
2016-05-30 18:44:42 +02:00
fprintf (Gbl.F.Out,"</div>");
2014-12-30 20:42:11 +01:00
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
/********************** 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;
2015-07-28 00:16:09 +02:00
extern const char *The_ClassForm[The_NUM_THEMES];
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_Allow_teachers_to_consult_this_test;
extern const char *Txt_Done_assess_test;
2014-12-01 23:55:08 +01:00
MYSQL_RES *mysql_res;
unsigned long NumRows;
unsigned NumAccessesTst;
/***** Read test configuration from database *****/
Tst_GetConfigTstFromDB ();
if (Tst_CheckIfNextTstAllowed ())
{
/***** Check that all parameters used to generate a test are valid *****/
if (Tst_GetParamsTst ()) // Get parameters from form
{
/***** Get questions *****/
2016-11-21 13:15:08 +01:00
if ((NumRows = Tst_GetQuestionsForTest (&mysql_res)) == 0) // Query database
2014-12-01 23:55:08 +01:00
{
Lay_ShowAlert (Lay_INFO,Txt_No_questions_found_matching_your_search_criteria);
Tst_ShowFormAskTst (); // Show the form again
}
else
{
/***** Get and update number of hits *****/
NumAccessesTst = Tst_GetNumAccessesTst () + 1;
if (Gbl.Usrs.Me.IBelongToCurrentCrs)
Tst_UpdateMyNumAccessTst (NumAccessesTst);
2016-03-21 01:28:16 +01:00
/***** Start frame *****/
2016-11-13 20:18:49 +01:00
Lay_StartRoundFrame (NULL,Txt_Test,NULL,Hlp_ASSESSMENT_Tests);
2016-03-21 01:28:16 +01:00
Lay_WriteHeaderClassPhoto (false,false,
Gbl.CurrentIns.Ins.InsCod,
Gbl.CurrentDeg.Deg.DegCod,
Gbl.CurrentCrs.Crs.CrsCod);
/***** Start form *****/
2014-12-01 23:55:08 +01:00
Act_FormStart (ActAssTst);
Gbl.Test.NumQsts = (unsigned) NumRows;
Par_PutHiddenParamUnsigned ("NumTst",NumAccessesTst);
Par_PutHiddenParamUnsigned ("NumQst",Gbl.Test.NumQsts);
2016-03-21 01:28:16 +01:00
/***** List the questions *****/
2017-05-01 12:36:24 +02:00
Lay_StartTableWideMargin (10);
2014-12-01 23:55:08 +01:00
Tst_ShowTestQuestionsWhenSeeing (mysql_res);
2017-05-01 12:36:24 +02:00
Lay_EndTable ();
2014-12-01 23:55:08 +01:00
2016-11-21 13:15:08 +01:00
/***** Test result will be saved? *****/
2016-12-26 14:02:19 +01:00
fprintf (Gbl.F.Out,"<div class=\"CENTER_MIDDLE\""
2016-03-21 01:28:16 +01:00
" style=\"margin-top:20px;\">"
2016-12-26 14:02:19 +01:00
"<label class=\"%s\">"
"<input type=\"checkbox\" name=\"Save\""
" value=\"Y\"",
2015-07-28 00:16:09 +02:00
The_ClassForm[Gbl.Prefs.Theme]);
2014-12-01 23:55:08 +01:00
if (Gbl.Test.AllowTeachers)
fprintf (Gbl.F.Out," checked=\"checked\"");
2016-12-26 14:02:19 +01:00
fprintf (Gbl.F.Out," />"
"&nbsp;%s"
"</label>"
2014-12-01 23:55:08 +01:00
"</div>",
2016-11-21 13:15:08 +01:00
Txt_Allow_teachers_to_consult_this_test);
2014-12-01 23:55:08 +01:00
2016-03-21 01:28:16 +01:00
/***** End form *****/
2016-11-21 13:15:08 +01:00
Lay_PutConfirmButton (Txt_Done_assess_test);
2015-03-13 00:16:02 +01:00
Act_FormEnd ();
2014-12-01 23:55:08 +01:00
2016-03-21 01:28:16 +01:00
/***** End frame *****/
Lay_EndRoundFrame ();
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 *****/
2015-04-07 21:44:24 +02:00
if (Gbl.Usrs.Me.LoggedRole == Rol_STUDENT)
2014-12-01 23:55:08 +01:00
Tst_UpdateLastAccTst ();
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
else
Tst_ShowFormAskTst (); // Show the form again
/***** Free memory used for by the list of tags *****/
Tst_FreeTagsList ();
}
}
/*****************************************************************************/
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;
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;
long TstCod = -1L; // Initialized to avoid warning
unsigned NumQstsNotBlank;
double TotalScore;
/***** Read test configuration from database *****/
Tst_GetConfigTstFromDB ();
/***** 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 the parameters of the form *****/
/* Get number of questions */
Tst_GetParamNumQst ();
2016-11-21 13:15:08 +01:00
/***** Get if test must be saved *****/
2017-01-28 20:32:50 +01:00
Gbl.Test.AllowTeachers = Par_GetParToBool ("Save");
2014-12-01 23:55:08 +01:00
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
Tst_GetQuestionsAndAnswersFromForm ();
2016-11-21 13:15:08 +01:00
/***** Create new test in database to store the result *****/
TstCod = Tst_CreateTestResultInDB ();
2014-12-01 23:55:08 +01:00
2016-03-21 01:28:16 +01:00
/***** Start frame *****/
2016-11-13 20:18:49 +01:00
Lay_StartRoundFrame (NULL,Txt_Test_result,NULL,Hlp_ASSESSMENT_Tests);
2016-03-21 01:28:16 +01:00
Lay_WriteHeaderClassPhoto (false,false,
Gbl.CurrentIns.Ins.InsCod,
Gbl.CurrentDeg.Deg.DegCod,
Gbl.CurrentCrs.Crs.CrsCod);
/***** Header *****/
if (Gbl.Usrs.Me.IBelongToCurrentCrs)
{
2016-04-01 18:37:16 +02:00
fprintf (Gbl.F.Out,"<div class=\"TEST_SUBTITLE\">");
2016-03-21 01:28:16 +01:00
fprintf (Gbl.F.Out,Txt_Test_No_X_that_you_make_in_this_course,NumTst);
fprintf (Gbl.F.Out,"</div>");
}
2014-12-01 23:55:08 +01:00
/***** Write answers and solutions *****/
2017-05-01 12:36:24 +02:00
Lay_StartTableWideMargin (10);
2016-11-21 13:15:08 +01:00
Tst_ShowTestResultAfterAssess (TstCod,&NumQstsNotBlank,&TotalScore);
2017-05-01 12:36:24 +02:00
Lay_EndTable ();
2014-12-01 23:55:08 +01:00
/***** Write total mark of test *****/
if (Gbl.Test.Config.FeedbackType != Tst_FEEDBACK_NOTHING)
Tst_ShowTstTotalMark (TotalScore);
2016-03-21 01:28:16 +01:00
/***** End frame *****/
Lay_EndRoundFrame ();
2014-12-01 23:55:08 +01:00
/***** Store test result in database *****/
2016-11-21 13:15:08 +01:00
Tst_StoreScoreOfTestResultInDB (TstCod,
2014-12-01 23:55:08 +01:00
NumQstsNotBlank,TotalScore);
/***** Set test status *****/
Tst_SetTstStatus (NumTst,Tst_STATUS_ASSESSED);
break;
case Tst_STATUS_ASSESSED:
sprintf (Gbl.Message,Txt_The_test_X_has_already_been_assessed_previously,
NumTst);
Lay_ShowAlert (Lay_WARNING,Gbl.Message);
break;
case Tst_STATUS_ERROR:
sprintf (Gbl.Message,Txt_There_was_an_error_in_assessing_the_test_X,
NumTst);
Lay_ShowAlert (Lay_WARNING,Gbl.Message);
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
/*****************************************************************************/
static void Tst_GetQuestionsAndAnswersFromForm (void)
{
unsigned NumQst;
2017-01-28 15:58:46 +01:00
char StrQstIndOrAns[3 + 10 + 1]; // "Qstxx...x", "Indxx...x" or "Ansxx...x"
2014-12-01 23:55:08 +01:00
/***** Get questions and answers *****/
for (NumQst = 0;
NumQst < Gbl.Test.NumQsts;
NumQst++)
{
/* Get question code */
sprintf (StrQstIndOrAns,"Qst%06u",NumQst);
2017-01-28 20:32:50 +01:00
if ((Gbl.Test.QstCodes[NumQst] = Par_GetParToLong (StrQstIndOrAns)) <= 0)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Code of question is missing.");
/* Get indexes for this question */
sprintf (StrQstIndOrAns,"Ind%06u",NumQst);
2017-03-13 19:02:15 +01:00
Par_GetParMultiToText (StrQstIndOrAns,Gbl.Test.StrIndexesOneQst[NumQst],
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 */
sprintf (StrQstIndOrAns,"Ans%06u",NumQst);
2017-03-13 19:02:15 +01:00
Par_GetParMultiToText (StrQstIndOrAns,Gbl.Test.StrAnswersOneQst[NumQst],
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
}
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/************************** Show total mark of a test ************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
static void Tst_ShowTstTotalMark (double TotalScore)
{
extern const char *Txt_Score;
extern const char *Txt_out_of_PART_OF_A_SCORE;
double TotalScoreOverSCORE_MAX = TotalScore * Tst_SCORE_MAX / (double) Gbl.Test.NumQsts;
/***** Write total mark ****/
2016-03-21 01:28:16 +01:00
fprintf (Gbl.F.Out,"<div class=\"DAT CENTER_MIDDLE\""
" style=\"margin-top:20px;\">"
2014-12-26 14:45:14 +01:00
"%s: <span class=\"%s\">%.2lf (%.2lf %s %u)</span>"
2016-03-21 01:28:16 +01:00
"</div>",
2014-12-01 23:55:08 +01:00
Txt_Score,
(TotalScoreOverSCORE_MAX >= (double) TotalScoreOverSCORE_MAX / 2.0) ? "ANS_OK" :
"ANS_BAD",
TotalScore,
TotalScoreOverSCORE_MAX,Txt_out_of_PART_OF_A_SCORE,Tst_SCORE_MAX);
}
/*****************************************************************************/
/************** Check minimum date-time of next access to test ***************/
/*****************************************************************************/
// Return true if allowed date-time of next access to test is older than now
static bool Tst_CheckIfNextTstAllowed (void)
{
2016-11-13 20:18:49 +01:00
extern const char *Hlp_ASSESSMENT_Tests;
2015-11-11 03:00:22 +01:00
extern const char *Txt_Test;
2016-12-26 14:02:19 +01:00
extern const char *Txt_You_can_not_take_a_new_test_until;
2015-12-29 11:35:01 +01:00
extern const char *Txt_Today;
2014-12-01 23:55:08 +01:00
char Query[512];
MYSQL_RES *mysql_res;
MYSQL_ROW row;
long NumSecondsFromNowToNextAccTst = -1L; // Access allowed when this number <= 0
2015-11-11 03:00:22 +01:00
time_t TimeNextTestUTC = (time_t) 0;
2014-12-01 23:55:08 +01:00
2016-07-20 20:24:16 +02:00
/***** Teachers and superusers are allowed to do all tests they want *****/
if (Gbl.Usrs.Me.LoggedRole == Rol_TEACHER ||
Gbl.Usrs.Me.LoggedRole == Rol_SYS_ADM)
2014-12-01 23:55:08 +01:00
return true;
/***** Get date of next allowed access to test from database *****/
sprintf (Query,"SELECT UNIX_TIMESTAMP(LastAccTst+INTERVAL (NumQstsLastTst*%lu) SECOND)-UNIX_TIMESTAMP(),"
2015-11-11 03:00:22 +01:00
"UNIX_TIMESTAMP(LastAccTst+INTERVAL (NumQstsLastTst*%lu) SECOND)"
2014-12-01 23:55:08 +01:00
" FROM crs_usr"
2017-03-24 01:09:27 +01:00
" WHERE CrsCod=%ld AND UsrCod=%ld",
2014-12-01 23:55:08 +01:00
Gbl.Test.Config.MinTimeNxtTstPerQst,
Gbl.Test.Config.MinTimeNxtTstPerQst,
Gbl.CurrentCrs.Crs.CrsCod,Gbl.Usrs.Me.UsrDat.UsrCod);
if (DB_QuerySELECT (Query,&mysql_res,"can not get last access to test") == 1)
{
/* 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)
2015-11-11 03:00:22 +01:00
/* Time UTC of next access allowed (row[1]) */
TimeNextTestUTC = Dat_GetUNIXTimeFromStr (row[1]);
2014-12-01 23:55:08 +01:00
}
else
Lay_ShowErrorAndExit ("Error when reading date of next allowed access to test.");
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
/***** Check if access is allowed *****/
if (NumSecondsFromNowToNextAccTst > 0)
{
2015-11-11 03:00:22 +01:00
/***** Write warning *****/
2016-12-26 14:02:19 +01:00
sprintf (Gbl.Message,"%s:<br /><span id=\"date_next_test\"></span>."
"<script type=\"text/javascript\">"
"writeLocalDateHMSFromUTC('date_next_test',"
"%ld,',&nbsp;','%s',true,true,true);"
"</script>",
Txt_You_can_not_take_a_new_test_until,
2015-12-29 11:35:01 +01:00
(long) TimeNextTestUTC,Txt_Today);
2016-12-26 14:02:19 +01:00
Lay_ShowAlert (Lay_WARNING,Gbl.Message);
2015-11-11 03:00:22 +01:00
2014-12-01 23:55:08 +01:00
return false;
}
return true;
}
/*****************************************************************************/
/****************************** Update test status ***************************/
/*****************************************************************************/
static void Tst_SetTstStatus (unsigned NumTst,Tst_Status_t TstStatus)
{
2017-03-13 19:02:15 +01:00
char Query[256 + Ses_BYTES_SESSION_ID];
2014-12-01 23:55:08 +01:00
/***** Delete old status from expired sessions *****/
sprintf (Query,"DELETE FROM tst_status"
" WHERE SessionId NOT IN (SELECT SessionId FROM sessions)");
DB_QueryDELETE (Query,"can not remove old status of tests");
/***** Update database *****/
2017-03-13 13:17:53 +01:00
sprintf (Query,"REPLACE INTO tst_status"
" (SessionId,CrsCod,NumTst,Status)"
" VALUES"
2017-03-24 01:09:27 +01:00
" ('%s',%ld,%u,%u)",
2017-03-13 19:02:15 +01:00
Gbl.Session.Id,Gbl.CurrentCrs.Crs.CrsCod,
NumTst,(unsigned) TstStatus);
2014-12-01 23:55:08 +01:00
DB_QueryREPLACE (Query,"can not update status of test");
}
/*****************************************************************************/
/****************************** Update test status ***************************/
/*****************************************************************************/
static Tst_Status_t Tst_GetTstStatus (unsigned NumTst)
{
2017-03-13 19:02:15 +01:00
char Query[256 + Ses_BYTES_SESSION_ID];
2014-12-01 23:55:08 +01:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned long NumRows;
unsigned UnsignedNum;
Tst_Status_t TstStatus = Tst_STATUS_ERROR;
/***** Get status of test from database *****/
sprintf (Query,"SELECT Status FROM tst_status"
2017-03-24 01:09:27 +01:00
" WHERE SessionId='%s' AND CrsCod=%ld AND NumTst=%u",
2014-12-01 23:55:08 +01:00
Gbl.Session.Id,Gbl.CurrentCrs.Crs.CrsCod,NumTst);
NumRows = DB_QuerySELECT (Query,&mysql_res,"can not get status of test");
if (NumRows == 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;
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
return TstStatus;
}
/*****************************************************************************/
/************************* Get number of hits to test ************************/
/*****************************************************************************/
static unsigned Tst_GetNumAccessesTst (void)
{
2017-03-13 19:02:15 +01:00
char Query[256];
2014-12-01 23:55:08 +01:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned long NumRows;
unsigned NumAccessesTst = 0;
if (Gbl.Usrs.Me.IBelongToCurrentCrs)
{
/***** Get number of hits to test from database *****/
sprintf (Query,"SELECT NumAccTst FROM crs_usr"
2017-03-24 01:09:27 +01:00
" WHERE CrsCod=%ld AND UsrCod=%ld",
2014-12-01 23:55:08 +01:00
Gbl.CurrentCrs.Crs.CrsCod,Gbl.Usrs.Me.UsrDat.UsrCod);
NumRows = DB_QuerySELECT (Query,&mysql_res,"can not get number of hits to test");
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.");
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
return NumAccessesTst;
}
/*****************************************************************************/
/*************************** Write the test questions ************************/
/*****************************************************************************/
// NumRows must hold the number of rows of a MySQL query
// In each row mysql_res holds: in the column 0 the code of a question, in the column 1 the type of answer, and in the column 2 the stem
static void Tst_ShowTestQuestionsWhenSeeing (MYSQL_RES *mysql_res)
{
unsigned NumQst;
long QstCod;
MYSQL_ROW row;
double ScoreThisQst; // Not used here
bool AnswerIsNotBlank; // Not used here
/***** Write rows *****/
for (NumQst = 0;
NumQst < Gbl.Test.NumQsts;
NumQst++)
{
Gbl.RowEvenOdd = NumQst % 2;
/***** Get the row next of the result of the query in the database *****/
row = mysql_fetch_row (mysql_res);
/***** Get the code of question (row[0]) *****/
if ((QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
Lay_ShowErrorAndExit ("Wrong code of question.");
2016-11-21 13:15:08 +01:00
Tst_WriteQstAndAnsTest (NumQst,QstCod,row,
2014-12-01 23:55:08 +01:00
&ScoreThisQst, // Not used here
&AnswerIsNotBlank); // Not used here
}
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/******************** Show test tags in this test result *********************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_ShowTstTagsPresentInATestResult (long TstCod)
2014-12-01 23:55:08 +01:00
{
extern const char *Txt_no_tags;
char Query[512];
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned long NumRows;
unsigned long NumRow;
2016-11-21 13:15:08 +01:00
/***** Get all tags of questions in this test *****/
2014-12-01 23:55:08 +01:00
sprintf (Query,"SELECT tst_tags.TagTxt FROM"
" (SELECT DISTINCT(tst_question_tags.TagCod)"
" FROM tst_question_tags,tst_exam_questions"
2017-03-24 01:09:27 +01:00
" WHERE tst_exam_questions.TstCod=%ld"
2014-12-01 23:55:08 +01:00
" AND tst_exam_questions.QstCod=tst_question_tags.QstCod)"
" AS TagsCods,tst_tags"
" WHERE TagsCods.TagCod=tst_tags.TagCod"
" ORDER BY tst_tags.TagTxt",
TstCod);
2016-11-21 13:15:08 +01:00
if ((NumRows = DB_QuerySELECT (Query,&mysql_res,"can not get tags present in a test result")))
2014-12-01 23:55:08 +01:00
{
/***** Write the tags *****/
fprintf (Gbl.F.Out,"<ul>");
for (NumRow = 0;
NumRow < NumRows;
NumRow++)
{
row = mysql_fetch_row (mysql_res);
fprintf (Gbl.F.Out,"<li>%s</li>",
row[0]);
}
fprintf (Gbl.F.Out,"</ul>");
}
else
fprintf (Gbl.F.Out,"%s",
Txt_no_tags);
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
/******************* Show the result of assessing a test *********************/
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_ShowTestResultAfterAssess (long TstCod,unsigned *NumQstsNotBlank,double *TotalScore)
2014-12-01 23:55:08 +01:00
{
extern const char *Txt_Question_removed;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumQst;
long QstCod;
double ScoreThisQst;
bool AnswerIsNotBlank;
/***** Initialize score and number of questions not blank *****/
*TotalScore = 0.0;
*NumQstsNotBlank = 0;
for (NumQst = 0;
NumQst < Gbl.Test.NumQsts;
NumQst++)
{
Gbl.RowEvenOdd = NumQst % 2;
/***** Query database *****/
if (Tst_GetOneQuestionByCod (Gbl.Test.QstCodes[NumQst],&mysql_res)) // Question exists
{
/***** Get row of the result of the query *****/
row = mysql_fetch_row (mysql_res);
/*
2016-04-06 01:10:04 +02:00
row[ 0] QstCod
row[ 1] UNIX_TIMESTAMP(EditTime)
row[ 2] AnsType
row[ 3] Shuffle
row[ 4] Stem
row[ 5] Feedback
row[ 6] ImageName
row[ 7] ImageTitle
2016-04-15 02:33:16 +02:00
row[ 8] ImageURL
row[ 9] NumHits
row[10] NumHitsNotBlank
row[11] Score
2014-12-01 23:55:08 +01:00
*/
/***** Get the code of question (row[0]) *****/
if ((QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
Lay_ShowErrorAndExit ("Wrong code of question.");
2016-11-21 13:15:08 +01:00
/***** Write question and answers *****/
Tst_WriteQstAndAnsTest (NumQst,QstCod,row,
2014-12-01 23:55:08 +01:00
&ScoreThisQst,&AnswerIsNotBlank);
2016-11-21 13:15:08 +01:00
/***** Store test result question in database *****/
Tst_StoreOneTestResultQstInDB (TstCod,QstCod,
NumQst, // 0, 1, 2, 3...
ScoreThisQst);
2014-12-01 23:55:08 +01:00
/***** Compute total score *****/
*TotalScore += ScoreThisQst;
if (AnswerIsNotBlank)
(*NumQstsNotBlank)++;
/***** Update the number of accesses and the score of this question *****/
2015-04-07 21:44:24 +02:00
if (Gbl.Usrs.Me.LoggedRole == Rol_STUDENT)
2014-12-01 23:55:08 +01:00
Tst_UpdateScoreQst (QstCod,ScoreThisQst,AnswerIsNotBlank);
}
else
/***** Question does not exists *****/
fprintf (Gbl.F.Out,"<tr>"
2016-04-01 19:15:03 +02:00
"<td class=\"TEST_NUM_QST RIGHT_TOP COLOR%u\">"
"%u"
2014-12-26 14:45:14 +01:00
"</td>"
2015-09-03 17:14:30 +02:00
"<td class=\"DAT_LIGHT LEFT_TOP COLOR%u\">"
2014-12-26 14:45:14 +01:00
"%s"
"</td>"
2014-12-01 23:55:08 +01:00
"</tr>",
2015-09-03 17:14:30 +02:00
Gbl.RowEvenOdd,NumQst + 1,
Gbl.RowEvenOdd,Txt_Question_removed);
2014-12-01 23:55:08 +01:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/********** Write a row of a test, with one question and its answer **********/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_WriteQstAndAnsTest (unsigned NumQst,long QstCod,MYSQL_ROW row,
2014-12-01 23:55:08 +01:00
double *ScoreThisQst,bool *AnswerIsNotBlank)
{
extern const char *Txt_TST_STR_ANSWER_TYPES[Tst_NUM_ANS_TYPES];
/*
2016-04-06 01:10:04 +02:00
row[ 0] QstCod
row[ 1] UNIX_TIMESTAMP(EditTime)
row[ 2] AnsType
row[ 3] Shuffle
row[ 4] Stem
row[ 5] Feedback
row[ 6] ImageName
row[ 7] ImageTitle
2016-04-15 02:33:16 +02:00
row[ 8] ImageURL
row[ 9] NumHits
row[10] NumHitsNotBlank
row[11] Score
2014-12-01 23:55:08 +01:00
*/
2016-04-06 19:26:09 +02:00
/***** Create test question *****/
Tst_QstConstructor ();
Gbl.Test.QstCod = QstCod;
2014-12-01 23:55:08 +01:00
/***** Write number of question *****/
fprintf (Gbl.F.Out,"<tr>"
2016-04-01 19:15:03 +02:00
"<td class=\"RIGHT_TOP COLOR%u\">"
"<div class=\"TEST_NUM_QST\">%u</div>",
2015-09-03 17:14:30 +02:00
Gbl.RowEvenOdd,
2014-12-01 23:55:08 +01:00
NumQst + 1);
/***** Write answer type (row[2]) *****/
Gbl.Test.AnswerType = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[2]);
2016-04-01 19:15:03 +02:00
fprintf (Gbl.F.Out,"<div class=\"DAT_SMALL\">%s</div>"
2014-12-26 14:45:14 +01:00
"</td>",
2014-12-01 23:55:08 +01:00
Txt_TST_STR_ANSWER_TYPES[Gbl.Test.AnswerType]);
2016-04-15 02:33:16 +02:00
/***** Write stem (row[4]), image (row[6], row[7], row[8]),
2016-04-06 01:10:04 +02:00
answers depending on shuffle (row[3]) and feedback (row[5]) *****/
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"LEFT_TOP COLOR%u\">",
Gbl.RowEvenOdd);
2014-12-01 23:55:08 +01:00
Tst_WriteQstStem (row[4],"TEST_EXA");
2016-04-15 02:33:16 +02:00
Img_GetImageNameTitleAndURLFromRow (row[6],row[7],row[8],&Gbl.Test.Image);
2016-04-15 14:30:28 +02:00
Img_ShowImage (&Gbl.Test.Image,
"TEST_IMG_SHOW_STEM_CONTAINER",
"TEST_IMG_SHOW_STEM");
2016-04-06 19:26:09 +02:00
2016-01-17 15:10:54 +01:00
if (Gbl.Action.Act == ActSeeTst)
2016-11-21 13:15:08 +01:00
Tst_WriteAnswersOfAQstViewTest (NumQst,QstCod,(row[3][0] == 'Y'));
else // Assessing test / Viewing old test result
2014-12-01 23:55:08 +01:00
{
2016-11-21 13:15:08 +01:00
Tst_WriteAnswersOfAQstAssessTest (NumQst,QstCod,ScoreThisQst,AnswerIsNotBlank);
2016-04-06 15:00:15 +02:00
/* Write question feedback (row[5]) */
2014-12-01 23:55:08 +01:00
if (Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK)
2016-04-06 15:00:15 +02:00
Tst_WriteQstFeedback (row[5],"TEST_EXA_LIGHT");
2014-12-01 23:55:08 +01:00
}
fprintf (Gbl.F.Out,"</td>"
"</tr>");
2016-04-06 01:10:04 +02:00
2016-04-06 19:26:09 +02:00
/***** Destroy test question *****/
Tst_QstDestructor ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2016-03-30 00:15:26 +02:00
/********************* Write the stem of a test question *********************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
void Tst_WriteQstStem (const char *Stem,const char *ClassStem)
{
unsigned long StemLength;
char *StemRigorousHTML;
/***** Convert the stem, that is in HTML, to rigorous HTML *****/
2017-03-08 03:48:23 +01:00
StemLength = strlen (Stem) * Str_MAX_BYTES_PER_CHAR;
2017-01-17 03:10:43 +01:00
if ((StemRigorousHTML = malloc (StemLength + 1)) == NULL)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Not enough memory to store stem of question.");
2017-01-17 03:10:43 +01:00
Str_Copy (StemRigorousHTML,Stem,
StemLength);
2014-12-01 23:55:08 +01:00
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
StemRigorousHTML,StemLength,false);
/***** Write the stem *****/
2015-09-06 11:36:34 +02:00
fprintf (Gbl.F.Out,"<div class=\"%s\">"
2015-04-11 13:05:44 +02:00
"%s"
"</div>",
2014-12-01 23:55:08 +01:00
ClassStem,StemRigorousHTML);
/***** Free memory allocated for the stem *****/
free ((void *) StemRigorousHTML);
}
2016-03-30 01:28:58 +02:00
/*****************************************************************************/
/************* Put form to upload a new image for a test question ************/
/*****************************************************************************/
2016-04-14 21:33:24 +02:00
static void Tst_PutFormToEditQstImage (struct Image *Image,int NumImgInForm,
2016-04-15 14:30:28 +02:00
const char *ClassContainer,
2016-04-14 18:59:18 +02:00
const char *ClassImg,
2016-04-15 10:58:35 +02:00
const char *ClassImgTitURL,
2016-04-05 10:47:36 +02:00
bool OptionsDisabled)
2016-03-30 01:28:58 +02:00
{
2016-04-03 14:42:22 +02:00
extern const char *The_ClassForm[The_NUM_THEMES];
2016-04-03 15:53:27 +02:00
extern const char *Txt_No_image;
extern const char *Txt_Current_image;
2016-04-03 14:42:22 +02:00
extern const char *Txt_Change_image;
2016-04-05 21:44:06 +02:00
extern const char *Txt_Image_title_attribution;
2016-04-15 10:58:35 +02:00
extern const char *Txt_Link;
2016-04-14 18:59:18 +02:00
extern const char *Txt_optional;
2016-04-04 10:11:05 +02:00
static unsigned UniqueId = 0;
2016-04-14 21:33:24 +02:00
struct ParamUploadImg ParamUploadImg;
2016-04-03 14:42:22 +02:00
2016-04-14 18:59:18 +02:00
if (Image->Name[0])
2016-04-11 15:32:59 +02:00
{
2016-04-14 21:33:24 +02:00
/***** Set names of parameters depending on number of image in form *****/
Img_SetParamNames (&ParamUploadImg,NumImgInForm);
2016-04-14 18:59:18 +02:00
/***** Start container *****/
2016-04-15 14:30:28 +02:00
fprintf (Gbl.F.Out,"<div class=\"TEST_FORM_EDIT_IMG\">");
2016-04-14 18:59:18 +02:00
2016-04-11 15:32:59 +02:00
/***** Choice 1: No image *****/
2016-12-20 02:18:50 +01:00
fprintf (Gbl.F.Out,"<label class=\"%s\">"
"<input type=\"radio\" name=\"%s\" value=\"%u\"",
The_ClassForm[Gbl.Prefs.Theme],
2016-04-14 21:33:24 +02:00
ParamUploadImg.Action,Img_ACTION_NO_IMAGE);
2016-04-11 15:32:59 +02:00
if (OptionsDisabled)
fprintf (Gbl.F.Out," disabled=\"disabled\"");
fprintf (Gbl.F.Out," />"
"%s"
2016-12-26 14:02:19 +01:00
"</label><br />",
2016-04-11 15:32:59 +02:00
Txt_No_image);
/***** Choice 2: Current image *****/
2016-12-20 02:18:50 +01:00
fprintf (Gbl.F.Out,"<label class=\"%s\">"
"<input type=\"radio\" name=\"%s\" value=\"%u\""
" checked=\"checked\"",
The_ClassForm[Gbl.Prefs.Theme],
2016-04-14 21:33:24 +02:00
ParamUploadImg.Action,Img_ACTION_KEEP_IMAGE);
2016-04-05 10:47:36 +02:00
if (OptionsDisabled)
2016-04-11 15:32:59 +02:00
fprintf (Gbl.F.Out," disabled=\"disabled\"");
2016-04-05 10:47:36 +02:00
fprintf (Gbl.F.Out," />"
2016-04-03 15:53:27 +02:00
"%s"
2016-04-11 15:32:59 +02:00
"</label>",
2016-04-03 15:53:27 +02:00
Txt_Current_image);
2016-04-15 14:30:28 +02:00
Img_ShowImage (Image,ClassContainer,ClassImg);
2016-04-03 14:42:22 +02:00
2016-04-11 15:32:59 +02:00
/***** Choice 3: Change/new image *****/
UniqueId++;
2016-12-20 02:18:50 +01:00
fprintf (Gbl.F.Out,"<label class=\"%s\">"
"<input type=\"radio\" id=\"chg_img_%u\" name=\"%s\""
2016-04-05 10:47:36 +02:00
" value=\"%u\"",
2016-12-20 02:18:50 +01:00
The_ClassForm[Gbl.Prefs.Theme],
UniqueId,ParamUploadImg.Action,
Img_ACTION_CHANGE_IMAGE); // Replace existing image by new image
2016-04-05 10:47:36 +02:00
if (OptionsDisabled)
2016-04-11 15:32:59 +02:00
fprintf (Gbl.F.Out," disabled=\"disabled\"");
2016-04-05 10:47:36 +02:00
fprintf (Gbl.F.Out," />"
2016-04-04 12:13:37 +02:00
"%s: "
2016-04-11 15:32:59 +02:00
"</label>"
"<input type=\"file\" name=\"%s\" accept=\"image/*\"",
2016-12-20 02:18:50 +01:00
Txt_Change_image,
2016-04-14 21:33:24 +02:00
ParamUploadImg.File);
2016-04-05 10:47:36 +02:00
if (OptionsDisabled)
2016-04-11 15:32:59 +02:00
fprintf (Gbl.F.Out," disabled=\"disabled\"");
fprintf (Gbl.F.Out," onchange=\"document.getElementById('chg_img_%u').checked = true;\" />",
UniqueId);
2016-04-05 21:44:06 +02:00
2016-04-14 18:59:18 +02:00
/***** Image title/attribution *****/
fprintf (Gbl.F.Out,"<br />"
"<input type=\"text\" name=\"%s\""
2016-04-15 10:58:35 +02:00
" placeholder=\"%s (%s)\""
2016-04-14 18:59:18 +02:00
" class=\"%s\" maxlength=\"%u\" value=\"%s\">",
2016-04-14 21:33:24 +02:00
ParamUploadImg.Title,Txt_Image_title_attribution,Txt_optional,
2017-03-08 14:12:33 +01:00
ClassImgTitURL,Img_MAX_CHARS_TITLE,
Image->Title ? Image->Title :
"");
2016-04-15 10:58:35 +02:00
/***** Image URL *****/
fprintf (Gbl.F.Out,"<br />"
"<input type=\"text\" name=\"%s\""
" placeholder=\"%s (%s)\""
" class=\"%s\" maxlength=\"%u\" value=\"%s\">",
ParamUploadImg.URL,Txt_Link,Txt_optional,
2017-03-08 14:12:33 +01:00
ClassImgTitURL,Cns_MAX_CHARS_WWW,
Image->URL ? Image->URL :
"");
2016-04-05 21:44:06 +02:00
2016-04-14 18:59:18 +02:00
/***** End container *****/
fprintf (Gbl.F.Out,"</div>");
}
else // No current image
/***** Attached image (optional) *****/
2016-04-15 10:58:35 +02:00
Img_PutImageUploader (NumImgInForm,ClassImgTitURL);
2016-03-30 01:28:58 +02:00
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
/******************* Write the feedback of a test question *******************/
/*****************************************************************************/
void Tst_WriteQstFeedback (const char *Feedback,const char *ClassFeedback)
{
unsigned long FeedbackLength;
char *FeedbackRigorousHTML;
if (Feedback)
if (Feedback[0])
{
/***** Convert the feedback, that is in HTML, to rigorous HTML *****/
2017-03-08 03:48:23 +01:00
FeedbackLength = strlen (Feedback) * Str_MAX_BYTES_PER_CHAR;
2017-01-17 03:10:43 +01:00
if ((FeedbackRigorousHTML = malloc (FeedbackLength + 1)) == NULL)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Not enough memory to store stem of question.");
2017-01-17 03:10:43 +01:00
Str_Copy (FeedbackRigorousHTML,Feedback,
FeedbackLength);
2014-12-01 23:55:08 +01:00
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
FeedbackRigorousHTML,FeedbackLength,false);
/***** Write the feedback *****/
2015-09-06 11:36:34 +02:00
fprintf (Gbl.F.Out,"<div class=\"%s\">"
2015-04-11 13:05:44 +02:00
"%s"
"</div>",
2014-12-01 23:55:08 +01:00
ClassFeedback,FeedbackRigorousHTML);
/***** Free memory allocated for the feedback *****/
free ((void *) FeedbackRigorousHTML);
}
}
/*****************************************************************************/
/*********************** Update the score of a question **********************/
/*****************************************************************************/
static void Tst_UpdateScoreQst (long QstCod,float ScoreThisQst,bool AnswerIsNotBlank)
{
char Query[512];
/***** Update number of clicks and score of the question *****/
2016-06-04 14:21:01 +02:00
Str_SetDecimalPointToUS (); // To print the floating point as a dot
2014-12-01 23:55:08 +01:00
if (AnswerIsNotBlank)
sprintf (Query,"UPDATE tst_questions"
2017-03-13 19:02:15 +01:00
" SET NumHits=NumHits+1,NumHitsNotBlank=NumHitsNotBlank+1,"
"Score=Score+(%lf)"
2017-03-24 01:09:27 +01:00
" WHERE QstCod=%ld",
2014-12-01 23:55:08 +01:00
ScoreThisQst,QstCod);
else // The answer is blank
sprintf (Query,"UPDATE tst_questions"
" SET NumHits=NumHits+1"
2017-03-24 01:09:27 +01:00
" WHERE QstCod=%ld",
2014-12-01 23:55:08 +01:00
QstCod);
2016-06-04 14:21:01 +02:00
Str_SetDecimalPointToLocal (); // Return to local system
2014-12-01 23:55:08 +01:00
DB_QueryUPDATE (Query,"can not update the score of a question");
}
/*****************************************************************************/
/*********** Update my number of accesses to test in this course *************/
/*****************************************************************************/
static void Tst_UpdateMyNumAccessTst (unsigned NumAccessesTst)
{
2017-03-13 19:02:15 +01:00
char Query[256];
2014-12-01 23:55:08 +01:00
/***** Update my number of accesses to test in this course *****/
2017-03-24 01:09:27 +01:00
sprintf (Query,"UPDATE crs_usr SET NumAccTst=%u"
" WHERE CrsCod=%ld AND UsrCod=%ld",
2014-12-01 23:55:08 +01:00
NumAccessesTst,
Gbl.CurrentCrs.Crs.CrsCod,Gbl.Usrs.Me.UsrDat.UsrCod);
DB_QueryUPDATE (Query,"can not update the number of accesses to test");
}
/*****************************************************************************/
/************ Update date-time of my next allowed access to test *************/
/*****************************************************************************/
static void Tst_UpdateLastAccTst (void)
{
2017-03-13 19:02:15 +01:00
char Query[256];
2014-12-01 23:55:08 +01:00
/***** Update date-time and number of questions of this test *****/
2017-03-24 01:09:27 +01:00
sprintf (Query,"UPDATE crs_usr SET LastAccTst=NOW(),NumQstsLastTst=%u"
" WHERE CrsCod=%ld AND UsrCod=%ld",
2014-12-01 23:55:08 +01:00
Gbl.Test.NumQsts,
Gbl.CurrentCrs.Crs.CrsCod,
Gbl.Usrs.Me.UsrDat.UsrCod);
DB_QueryUPDATE (Query,"can not update time and number of questions of this test");
}
2015-10-26 13:06:26 +01:00
/*****************************************************************************/
/************ Set end date to current date ************/
/************ and set initial date to end date minus several days ************/
/*****************************************************************************/
void Tst_SetIniEndDates (void)
{
Gbl.DateRange.TimeUTC[0] = (time_t) 0;
2015-10-27 19:00:21 +01:00
Gbl.DateRange.TimeUTC[1] = Gbl.StartExecutionTimeUTC;
2015-10-26 13:06:26 +01:00
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
/******* Select tags and dates for edition of the self-assessment test *******/
/*****************************************************************************/
void Tst_ShowFormAskEditTsts (void)
{
2016-11-13 20:18:49 +01:00
extern const char *Hlp_ASSESSMENT_Tests;
2016-03-21 02:13:19 +01:00
extern const char *Txt_No_test_questions;
2016-04-07 12:55:58 +02:00
extern const char *Txt_List_edit_questions;
2014-12-01 23:55:08 +01:00
extern const char *Txt_Show_questions;
MYSQL_RES *mysql_res;
unsigned long NumRows;
2016-03-21 12:53:02 +01:00
/***** Contextual menu *****/
2015-12-13 21:30:28 +01:00
fprintf (Gbl.F.Out,"<div class=\"CONTEXT_MENU\">");
2014-12-01 23:55:08 +01:00
TsI_PutFormToImportQuestions (); // Put link (form) to import questions from XML file
fprintf (Gbl.F.Out,"</div>");
2016-03-21 02:13:19 +01:00
/***** Start frame *****/
2016-11-13 20:18:49 +01:00
Lay_StartRoundFrame (NULL,Txt_List_edit_questions,
Tst_PutIconsTests,Hlp_ASSESSMENT_Tests);
2016-03-21 02:13:19 +01:00
2014-12-01 23:55:08 +01:00
/***** Get tags already present in the table of questions *****/
2016-03-21 02:13:19 +01:00
if ((NumRows = Tst_GetAllTagsFromCurrentCrs (&mysql_res)))
2014-12-01 23:55:08 +01:00
{
Act_FormStart (ActLstTstQst);
Par_PutHiddenParamUnsigned ("Order",(unsigned) Tst_ORDER_STEM);
2016-03-21 01:28:16 +01:00
fprintf (Gbl.F.Out,"<table class=\"CELLS_PAD_2\">");
2014-12-01 23:55:08 +01:00
/***** Selection of tags *****/
2016-03-21 01:28:16 +01:00
Tst_ShowFormSelTags (NumRows,mysql_res,false,2);
2014-12-01 23:55:08 +01:00
/***** Selection of types of answers *****/
2016-03-21 01:28:16 +01:00
Tst_ShowFormAnswerTypes (2);
2014-12-01 23:55:08 +01:00
/***** Starting and ending dates in the search *****/
2017-02-26 20:09:21 +01:00
Dat_PutFormStartEndClientLocalDateTimesWithYesterdayToday (false);
2016-03-21 01:28:16 +01:00
fprintf (Gbl.F.Out,"</table>");
2014-12-01 23:55:08 +01:00
/***** Send button *****/
2015-03-24 17:47:26 +01:00
Lay_PutConfirmButton (Txt_Show_questions);
2015-03-13 00:16:02 +01:00
Act_FormEnd ();
2016-03-21 02:13:19 +01:00
}
else // No test questions
{
/***** Warning message *****/
Lay_ShowAlert (Lay_INFO,Txt_No_test_questions);
2016-03-21 01:28:16 +01:00
2016-03-21 02:13:19 +01:00
/***** Button to create a new question *****/
Tst_PutButtonToAddQuestion ();
2014-12-01 23:55:08 +01:00
}
2016-03-21 02:13:19 +01:00
/***** End frame *****/
Lay_EndRoundFrame ();
2014-12-01 23:55:08 +01:00
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
2016-11-07 10:35:36 +01:00
/************************* Check if I can edit tests *************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-07 10:35:36 +01:00
static bool Tst_CheckIfICanEditTests (void)
{
return (bool) (Gbl.Usrs.Me.LoggedRole == Rol_TEACHER ||
Gbl.Usrs.Me.LoggedRole == Rol_SYS_ADM);
}
/*****************************************************************************/
/********************* Put contextual icons in tests *************************/
/*****************************************************************************/
static void Tst_PutIconsTests (void)
2014-12-01 23:55:08 +01:00
{
extern const char *Txt_New_question;
2016-05-05 12:37:45 +02:00
extern const char *Txt_Configure;
2016-11-07 10:35:36 +01:00
if (Tst_CheckIfICanEditTests ())
{
/***** Put form to edit existing test questions *****/
if (Gbl.Action.Act != ActEdiTstQst)
2017-04-30 23:48:48 +02:00
Lay_PutContextualIconToEdit (ActEdiTstQst,NULL);
2016-11-07 10:35:36 +01:00
/***** Put form to create a new test question *****/
if (Gbl.Action.Act != ActEdiOneTstQst)
2017-04-17 19:03:21 +02:00
Lay_PutContextualLink (ActEdiOneTstQst,NULL,NULL,
2016-11-07 10:35:36 +01:00
"plus64x64.png",
Txt_New_question,NULL,
NULL);
/***** Put form to go to test configuration *****/
if (Gbl.Action.Act != ActCfgTst)
2017-04-17 19:03:21 +02:00
Lay_PutContextualLink (ActCfgTst,NULL,NULL,
2016-11-07 10:35:36 +01:00
"config64x64.gif",
Txt_Configure,NULL,
NULL);
}
/***** Put icon to show a figure *****/
Gbl.Stat.FigureType = Sta_TESTS;
Sta_PutIconToShowFigure ();
2014-12-01 23:55:08 +01:00
}
2016-03-21 02:13:19 +01:00
/*****************************************************************************/
/**************** Put button to create a new test question *******************/
/*****************************************************************************/
static void Tst_PutButtonToAddQuestion (void)
{
extern const char *Txt_New_question;
Act_FormStart (ActEdiOneTstQst);
Lay_PutConfirmButton (Txt_New_question);
Act_FormEnd ();
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
/***************************** Form to rename tags ***************************/
/*****************************************************************************/
void Tst_ShowFormConfig (void)
{
extern const char *Txt_Please_specify_if_you_allow_access_to_test_questions_from_mobile_applications;
/***** If current course has tests and pluggable is unknown... *****/
if (Tst_CheckIfCourseHaveTestsAndPluggableIsUnknown ())
Lay_ShowAlert (Lay_WARNING,Txt_Please_specify_if_you_allow_access_to_test_questions_from_mobile_applications);
/***** Form to configure test *****/
Tst_ShowFormConfigTst ();
/***** Form to edit tags *****/
Tst_ShowFormEditTags ();
}
/*****************************************************************************/
/******************************* Enable a test tag ***************************/
/*****************************************************************************/
void Tst_EnableTag (void)
{
long TagCod = Tst_GetParamTagCode ();
/***** Change tag status to enabled *****/
Tst_EnableOrDisableTag (TagCod,false);
/***** Show again the form to configure test *****/
Tst_ShowFormConfig ();
}
/*****************************************************************************/
/****************************** Disable a test tag ***************************/
/*****************************************************************************/
void Tst_DisableTag (void)
{
long TagCod = Tst_GetParamTagCode ();
/***** Change tag status to disabled *****/
Tst_EnableOrDisableTag (TagCod,true);
/***** Show again the form to configure test *****/
Tst_ShowFormConfig ();
}
/*****************************************************************************/
/************************* Get parameter with tag code ***********************/
/*****************************************************************************/
static long Tst_GetParamTagCode (void)
{
long TagCod;
2017-01-28 20:32:50 +01:00
/***** Get tag code *****/
if ((TagCod = Par_GetParToLong ("TagCod")) <= 0)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Code of tag is missing.");
return TagCod;
}
/*****************************************************************************/
/************************ Rename a tag of test questions *********************/
/*****************************************************************************/
void Tst_RenameTag (void)
{
extern const char *Txt_You_can_not_leave_the_name_of_the_tag_X_empty;
extern const char *Txt_The_tag_X_has_been_renamed_as_Y;
extern const char *Txt_The_tag_X_has_not_changed;
2017-01-28 15:58:46 +01:00
char OldTagTxt[Tst_MAX_BYTES_TAG + 1];
char NewTagTxt[Tst_MAX_BYTES_TAG + 1];
char Query[1024 + 2 * Tst_MAX_BYTES_TAG];
2016-05-31 20:28:17 +02:00
long ExistingTagCod;
2014-12-01 23:55:08 +01:00
long OldTagCod;
2016-05-31 20:28:17 +02:00
bool ComplexRenaming;
2014-12-01 23:55:08 +01: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 *****/
2016-05-31 20:28:17 +02:00
if (!NewTagTxt[0]) // New tag empty
2014-12-01 23:55:08 +01:00
{
2016-05-31 20:28:17 +02:00
sprintf (Gbl.Message,Txt_You_can_not_leave_the_name_of_the_tag_X_empty,
OldTagTxt);
Lay_ShowAlert (Lay_WARNING,Gbl.Message);
}
else // 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.
2014-12-01 23:55:08 +01:00
{
sprintf (Gbl.Message,Txt_The_tag_X_has_not_changed,
NewTagTxt);
Lay_ShowAlert (Lay_INFO,Gbl.Message);
}
2016-05-31 20:28:17 +02:00
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 */
sprintf (Query,"DROP TEMPORARY TABLE IF EXISTS tst_question_tags_tmp");
if (mysql_query (&Gbl.mysql,Query))
DB_ExitOnMySQLError ("can not remove temporary table");
2017-03-13 19:02:15 +01:00
sprintf (Query,"CREATE TEMPORARY TABLE tst_question_tags_tmp"
" ENGINE=MEMORY"
" SELECT QstCod FROM tst_question_tags"
2017-03-24 01:09:27 +01:00
" WHERE TagCod=%ld",
2016-05-31 20:28:17 +02:00
ExistingTagCod);
if (mysql_query (&Gbl.mysql,Query))
DB_ExitOnMySQLError ("can not create temporary table");
/* Remove old tag in questions where it would be repeated */
2017-03-13 19:02:15 +01:00
// New tag existed for a question ==> delete old tag
2016-05-31 20:28:17 +02:00
sprintf (Query,"DELETE FROM tst_question_tags"
2017-03-24 01:09:27 +01:00
" WHERE TagCod=%ld"
2016-05-31 20:28:17 +02:00
" AND QstCod IN"
2017-03-13 19:02:15 +01:00
" (SELECT QstCod FROM tst_question_tags_tmp)",
2016-05-31 20:28:17 +02:00
OldTagCod);
DB_QueryDELETE (Query,"can not remove a tag from some questions");
/* Change old tag to new tag in questions where it would not be repeated */
2017-03-13 19:02:15 +01:00
// New tag did not exist for a question ==> change old tag to new tag
2016-05-31 20:28:17 +02:00
sprintf (Query,"UPDATE tst_question_tags"
2017-03-24 01:09:27 +01:00
" SET TagCod=%ld"
" WHERE TagCod=%ld"
2016-05-31 20:28:17 +02:00
" AND QstCod NOT IN"
2017-03-13 19:02:15 +01:00
" (SELECT QstCod FROM tst_question_tags_tmp)",
2016-05-31 20:28:17 +02:00
ExistingTagCod,
OldTagCod);
DB_QueryUPDATE (Query,"can not update a tag in some questions");
/* Drop temporary table, no longer necessary */
sprintf (Query,"DROP TEMPORARY TABLE IF EXISTS tst_question_tags_tmp");
if (mysql_query (&Gbl.mysql,Query))
DB_ExitOnMySQLError ("can not remove temporary table");
/***** Delete old tag from tst_tags
because it is not longer used *****/
2017-03-24 01:09:27 +01:00
sprintf (Query,"DELETE FROM tst_tags WHERE TagCod=%ld",
2016-05-31 20:28:17 +02:00
OldTagCod);
DB_QueryDELETE (Query,"can not remove old tag");
}
else // Renaming is easy
{
/***** Simple update replacing each instance of the old tag by the new tag *****/
sprintf (Query,"UPDATE tst_tags SET TagTxt='%s',ChangeTime=NOW()"
2017-03-24 01:09:27 +01:00
" WHERE tst_tags.CrsCod=%ld"
2016-05-31 20:28:17 +02:00
" AND tst_tags.TagTxt='%s'",
NewTagTxt,Gbl.CurrentCrs.Crs.CrsCod,OldTagTxt);
DB_QueryUPDATE (Query,"can not update tag");
}
/***** Write message to show the change made *****/
sprintf (Gbl.Message,Txt_The_tag_X_has_been_renamed_as_Y,
OldTagTxt,NewTagTxt);
Lay_ShowAlert (Lay_SUCCESS,Gbl.Message);
}
2016-05-31 11:21:04 +02:00
}
2014-12-01 23:55:08 +01:00
/***** Show again the form to configure test *****/
Tst_ShowFormConfig ();
}
/*****************************************************************************/
/******************* Check if current course has test tags *******************/
/*****************************************************************************/
// Return the number of rows of the result
static bool Tst_CheckIfCurrentCrsHasTestTags (void)
{
2017-03-13 19:02:15 +01:00
char Query[128];
2014-12-01 23:55:08 +01:00
/***** Get available tags from database *****/
2017-03-24 01:09:27 +01:00
sprintf (Query,"SELECT COUNT(*) FROM tst_tags WHERE CrsCod=%ld",
2014-12-01 23:55:08 +01:00
Gbl.CurrentCrs.Crs.CrsCod);
return (DB_QueryCOUNT (Query,"can not check if course has tags") != 0);
}
/*****************************************************************************/
/********* Get all (enabled or disabled) test tags for this course ***********/
/*****************************************************************************/
// Return the number of rows of the result
static unsigned long Tst_GetAllTagsFromCurrentCrs (MYSQL_RES **mysql_res)
{
2017-03-13 19:02:15 +01:00
char Query[256];
2014-12-01 23:55:08 +01:00
/***** Get available tags from database *****/
sprintf (Query,"SELECT TagCod,TagTxt,TagHidden FROM tst_tags"
2017-03-24 01:09:27 +01:00
" WHERE CrsCod=%ld ORDER BY TagTxt",
2014-12-01 23:55:08 +01:00
Gbl.CurrentCrs.Crs.CrsCod);
return DB_QuerySELECT (Query,mysql_res,"can not get available tags");
}
/*****************************************************************************/
/********************** Get enabled test tags for this course ****************/
/*****************************************************************************/
// Return the number of rows of the result
static unsigned long Tst_GetEnabledTagsFromThisCrs (MYSQL_RES **mysql_res)
{
2017-03-13 19:02:15 +01:00
char Query[256];
2014-12-01 23:55:08 +01:00
/***** Get available not hidden tags from database *****/
sprintf (Query,"SELECT TagCod,TagTxt FROM tst_tags"
2017-03-24 01:09:27 +01:00
" WHERE CrsCod=%ld AND TagHidden='N' ORDER BY TagTxt",
2014-12-01 23:55:08 +01:00
Gbl.CurrentCrs.Crs.CrsCod);
return DB_QuerySELECT (Query,mysql_res,"can not get available enabled tags");
}
/*****************************************************************************/
/********************* Show a form to select test tags ***********************/
/*****************************************************************************/
2016-03-21 01:28:16 +01:00
static void Tst_ShowFormSelTags (unsigned long NumRows,MYSQL_RES *mysql_res,
bool ShowOnlyEnabledTags,unsigned NumCols)
2014-12-01 23:55:08 +01:00
{
2015-07-28 00:16:09 +02:00
extern const char *The_ClassForm[The_NUM_THEMES];
2015-04-01 12:54:25 +02:00
extern const char *Txt_Tags;
extern const char *Txt_All_tags;
2014-12-01 23:55:08 +01:00
extern const char *Txt_Tag_not_allowed;
extern const char *Txt_Tag_allowed;
unsigned long NumRow;
MYSQL_ROW row;
bool TagHidden = false;
const char *Ptr;
2017-01-28 15:58:46 +01:00
char TagText[Tst_MAX_BYTES_TAG + 1];
2014-12-01 23:55:08 +01:00
2016-03-21 01:28:16 +01:00
/***** Label *****/
fprintf (Gbl.F.Out,"<tr>"
2016-12-20 02:18:50 +01:00
"<td class=\"RIGHT_TOP %s\">"
2016-03-21 01:28:16 +01:00
"%s:"
"</td>",
The_ClassForm[Gbl.Prefs.Theme],Txt_Tags);
2014-12-01 23:55:08 +01:00
/***** Select all tags *****/
2016-03-21 01:28:16 +01:00
fprintf (Gbl.F.Out,"<td");
if (NumCols > 1)
fprintf (Gbl.F.Out," colspan=\"%u\"",NumCols);
fprintf (Gbl.F.Out," class=\"LEFT_TOP\">"
2016-04-01 18:37:16 +02:00
"<table class=\"CELLS_PAD_2\">"
2016-03-21 01:28:16 +01:00
"<tr>");
2014-12-01 23:55:08 +01:00
if (!ShowOnlyEnabledTags)
fprintf (Gbl.F.Out,"<td></td>");
2016-12-20 02:18:50 +01:00
fprintf (Gbl.F.Out,"<td class=\"LEFT_MIDDLE\">"
"<label class=\"%s\">"
2014-12-01 23:55:08 +01:00
"<input type=\"checkbox\" name=\"AllTags\" value=\"Y\"",
2015-07-28 00:16:09 +02:00
The_ClassForm[Gbl.Prefs.Theme]);
2016-04-06 22:27:33 +02:00
if (Gbl.Test.Tags.All)
2016-04-07 14:40:50 +02:00
fprintf (Gbl.F.Out," checked=\"checked\"");
2016-04-07 17:14:35 +02:00
fprintf (Gbl.F.Out," onclick=\"togglecheckChildren(this,'ChkTag');\" />"
2016-12-20 02:18:50 +01:00
"&nbsp;%s"
"</label>"
2014-12-01 23:55:08 +01:00
"</td>"
"</tr>",
Txt_All_tags);
/***** Select tags one by one *****/
for (NumRow = 1;
NumRow <= NumRows;
NumRow++)
{
row = mysql_fetch_row (mysql_res);
fprintf (Gbl.F.Out,"<tr>");
if (!ShowOnlyEnabledTags)
{
2016-09-07 18:48:10 +02:00
TagHidden = (row[2][0] == 'Y');
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"<td class=\"LEFT_MIDDLE\">"
2014-12-01 23:55:08 +01:00
"<img src=\"%s/",
Gbl.Prefs.IconsURL);
if (TagHidden)
2015-12-26 21:46:56 +01:00
fprintf (Gbl.F.Out,"eye-slash-off64x64.png\" alt=\"%s\" title=\"%s",
2015-07-22 18:59:44 +02:00
Txt_Tag_not_allowed,
2014-12-01 23:55:08 +01:00
Txt_Tag_not_allowed);
else
2015-12-08 22:20:44 +01:00
fprintf (Gbl.F.Out,"eye-off64x64.png\" alt=\"%s\" title=\"%s",
2015-07-22 18:59:44 +02:00
Txt_Tag_allowed,
2014-12-01 23:55:08 +01:00
Txt_Tag_allowed);
2016-11-14 10:05:41 +01:00
fprintf (Gbl.F.Out,"\" class=\"ICO20x20\" />"
2014-12-01 23:55:08 +01:00
"</td>");
}
2016-12-20 02:18:50 +01:00
fprintf (Gbl.F.Out,"<td class=\"LEFT_MIDDLE\">"
"<label class=\"DAT\">"
2014-12-01 23:55:08 +01:00
"<input type=\"checkbox\" name=\"ChkTag\" value=\"%s\"",
row[1]);
2016-04-06 22:27:33 +02:00
if (Gbl.Test.Tags.List)
2014-12-01 23:55:08 +01:00
{
2016-04-06 22:27:33 +02:00
Ptr = Gbl.Test.Tags.List;
2014-12-01 23:55:08 +01:00
while (*Ptr)
{
Par_GetNextStrUntilSeparParamMult (&Ptr,TagText,Tst_MAX_BYTES_TAG);
if (!strcmp (row[1],TagText))
fprintf (Gbl.F.Out," checked=\"checked\"");
}
}
2016-04-07 17:14:35 +02:00
fprintf (Gbl.F.Out," onclick=\"checkParent(this,'AllTags');\" />"
2016-12-20 02:18:50 +01:00
"&nbsp;%s"
"</label>"
2014-12-01 23:55:08 +01:00
"</td>"
"</tr>",
row[1]);
}
2016-03-21 01:28:16 +01:00
fprintf (Gbl.F.Out,"</table>"
"</td>"
"</tr>");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/************* Show a form to enable/disable and rename test tags ************/
/*****************************************************************************/
static void Tst_ShowFormEditTags (void)
{
2016-11-13 20:18:49 +01:00
extern const char *Hlp_ASSESSMENT_Tests;
2016-03-21 02:13:19 +01:00
extern const char *Txt_No_test_questions;
2014-12-01 23:55:08 +01:00
extern const char *Txt_Tags;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned long NumRow,NumRows;
long TagCod;
/***** Get current tags in current course *****/
2016-03-21 02:13:19 +01:00
if ((NumRows = Tst_GetAllTagsFromCurrentCrs (&mysql_res)))
2014-12-01 23:55:08 +01:00
{
/***** Start table *****/
2016-11-14 10:05:41 +01:00
Lay_StartRoundFrameTable (NULL,Txt_Tags,NULL,Hlp_ASSESSMENT_Tests,2);
2014-12-01 23:55:08 +01:00
/***** Show tags *****/
for (NumRow = 0;
NumRow < NumRows;
NumRow++)
{
row = mysql_fetch_row (mysql_res);
if ((TagCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
Lay_ShowErrorAndExit ("Wrong code of tag.");
fprintf (Gbl.F.Out,"<tr>");
/* Form to enable / disable this tag */
2016-09-07 18:48:10 +02:00
if (row[2][0] == 'Y') // Tag disabled
2014-12-01 23:55:08 +01:00
Tst_PutIconEnable (TagCod,row[1]);
else
Tst_PutIconDisable (TagCod,row[1]);
/* Form to rename this tag */
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"<td class=\"LEFT_MIDDLE\">");
2014-12-01 23:55:08 +01:00
Act_FormStart (ActRenTag);
Par_PutHiddenParamString ("OldTagTxt",row[1]);
fprintf (Gbl.F.Out,"<input type=\"text\" name=\"NewTagTxt\""
" size=\"36\" maxlength=\"%u\" value=\"%s\""
2015-10-22 14:49:48 +02:00
" onchange=\"document.getElementById('%s').submit();\" />",
2017-03-08 03:48:23 +01:00
Tst_MAX_CHARS_TAG,row[1],Gbl.Form.Id);
2015-03-13 00:16:02 +01:00
Act_FormEnd ();
fprintf (Gbl.F.Out,"</td>"
2014-12-01 23:55:08 +01:00
"</tr>");
}
/***** End table *****/
2015-04-12 18:01:06 +02:00
Lay_EndRoundFrameTable ();
2014-12-01 23:55:08 +01:00
}
2016-03-21 02:13:19 +01:00
else
Lay_ShowAlert (Lay_INFO,Txt_No_test_questions);
2014-12-01 23:55:08 +01:00
/* 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;
fprintf (Gbl.F.Out,"<td class=\"BM\">");
Act_FormStart (ActEnableTag);
Par_PutHiddenParamLong ("TagCod",TagCod);
sprintf (Gbl.Title,Txt_Tag_X_not_allowed_Click_to_allow_it,TagTxt);
2015-12-08 22:20:44 +01:00
fprintf (Gbl.F.Out,"<input type=\"image\" src=\"%s/eye-slash-on64x64.png\""
2015-09-05 12:04:30 +02:00
" alt=\"%s\" title=\"%s\""
2016-11-14 10:05:41 +01:00
" class=\"ICO20x20\" />",
2014-12-01 23:55:08 +01:00
Gbl.Prefs.IconsURL,
Gbl.Title,
Gbl.Title);
2015-03-13 00:16:02 +01:00
Act_FormEnd ();
fprintf (Gbl.F.Out,"</td>");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/****************** 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;
fprintf (Gbl.F.Out,"<td class=\"BM\">");
Act_FormStart (ActDisableTag);
Par_PutHiddenParamLong ("TagCod",TagCod);
sprintf (Gbl.Title,Txt_Tag_X_allowed_Click_to_disable_it,TagTxt);
2015-12-08 22:20:44 +01:00
fprintf (Gbl.F.Out,"<input type=\"image\" src=\"%s/eye-on64x64.png\""
2015-09-05 12:04:30 +02:00
" alt=\"%s\" title=\"%s\""
2016-11-14 10:05:41 +01:00
" class=\"ICO20x20\" />",
2014-12-01 23:55:08 +01:00
Gbl.Prefs.IconsURL,
Gbl.Title,
Gbl.Title);
2015-03-13 00:16:02 +01:00
Act_FormEnd ();
fprintf (Gbl.F.Out,"</td>");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/********************* Show a form to to configure test **********************/
/*****************************************************************************/
static void Tst_ShowFormConfigTst (void)
{
2016-11-13 20:18:49 +01:00
extern const char *Hlp_ASSESSMENT_Tests;
2015-07-28 00:16:09 +02:00
extern const char *The_ClassForm[The_NUM_THEMES];
2014-12-01 23:55:08 +01:00
extern const char *Txt_Configure_tests;
extern const char *Txt_Plugins;
extern const char *Txt_TST_PLUGGABLE[Tst_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_Feedback_to_students;
extern const char *Txt_TST_STR_FEEDBACK[Tst_NUM_TYPES_FEEDBACK];
extern const char *Txt_Save;
Tst_Pluggable_t Pluggable;
Tst_Feedback_t FeedbTyp;
/***** Read test configuration from database *****/
Tst_GetConfigTstFromDB ();
2016-05-05 12:37:45 +02:00
/***** Start frame *****/
2016-11-13 20:18:49 +01:00
Lay_StartRoundFrame (NULL,Txt_Configure_tests,
Tst_PutIconsTests,Hlp_ASSESSMENT_Tests);
2016-05-05 12:37:45 +02:00
2014-12-01 23:55:08 +01:00
/***** Start form *****/
Act_FormStart (ActRcvCfgTst);
/***** Tests are visible from plugins? *****/
2016-05-05 12:37:45 +02:00
fprintf (Gbl.F.Out,"<table class=\"CELLS_PAD_2\">"
"<tr>"
2015-07-28 00:16:09 +02:00
"<td class=\"%s RIGHT_TOP\">"
2014-12-26 14:45:14 +01:00
"%s:"
"</td>"
2016-12-26 15:17:30 +01:00
"<td class=\"LEFT_BOTTOM\">",
2015-07-28 00:16:09 +02:00
The_ClassForm[Gbl.Prefs.Theme],
2015-07-27 19:42:24 +02:00
Txt_Plugins);
2014-12-01 23:55:08 +01:00
for (Pluggable = Tst_PLUGGABLE_NO;
Pluggable <= Tst_PLUGGABLE_YES;
Pluggable++)
{
2016-12-26 15:17:30 +01:00
fprintf (Gbl.F.Out,"<label class=\"DAT\">"
"<input type=\"radio\" name=\"Pluggable\" value=\"%u\"",
2014-12-01 23:55:08 +01:00
(unsigned) Pluggable);
if (Pluggable == Gbl.Test.Config.Pluggable)
fprintf (Gbl.F.Out," checked=\"checked\"");
2016-12-26 15:17:30 +01:00
fprintf (Gbl.F.Out," />"
"%s"
"</label><br />",
2014-12-01 23:55:08 +01:00
Txt_TST_PLUGGABLE[Pluggable]);
}
fprintf (Gbl.F.Out,"</td>"
"</tr>");
/***** Number of questions *****/
fprintf (Gbl.F.Out,"<tr>"
2015-07-28 00:16:09 +02:00
"<td class=\"%s RIGHT_TOP\">"
2014-12-26 14:45:14 +01:00
"%s:"
"</td>"
2016-12-26 15:17:30 +01:00
"<td class=\"LEFT_BOTTOM\">"
2015-09-28 18:28:29 +02:00
"<table style=\"border-spacing:2px;\">",
2015-07-28 00:16:09 +02:00
The_ClassForm[Gbl.Prefs.Theme],
2014-12-01 23:55:08 +01:00
Txt_No_of_questions);
2016-12-26 15:17:30 +01:00
Tst_PutInputFieldNumQst ("NumQstMin",Txt_minimum,
Gbl.Test.Config.Min); // Minimum number of questions
Tst_PutInputFieldNumQst ("NumQstDef",Txt_default,
Gbl.Test.Config.Def); // Default number of questions
Tst_PutInputFieldNumQst ("NumQstMax",Txt_maximum,
Gbl.Test.Config.Max); // Maximum number of questions
fprintf (Gbl.F.Out,"</table>"
2014-12-01 23:55:08 +01:00
"</td>"
2016-12-26 15:17:30 +01:00
"</tr>");
2014-12-01 23:55:08 +01:00
2016-11-21 13:15:08 +01:00
/***** Minimum time between consecutive tests, per question *****/
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"<tr>"
2016-12-26 15:17:30 +01:00
"<td class=\"RIGHT_TOP\">"
"<label for=\"MinTimeNxtTstPerQst\" class=\"%s\">"
2014-12-26 14:45:14 +01:00
"%s:"
2016-12-26 15:17:30 +01:00
"</label>"
2014-12-26 14:45:14 +01:00
"</td>"
2016-12-26 15:17:30 +01:00
"<td class=\"LEFT_BOTTOM\">"
"<input type=\"text\""
" id=\"MinTimeNxtTstPerQst\" name=\"MinTimeNxtTstPerQst\""
2016-11-19 16:46:43 +01:00
" size=\"7\" maxlength=\"7\" value=\"%lu\""
" required=\"required\" />"
2014-12-01 23:55:08 +01:00
"</td>"
"</tr>",
2015-07-28 00:16:09 +02:00
The_ClassForm[Gbl.Prefs.Theme],
2014-12-01 23:55:08 +01:00
Txt_Minimum_time_seconds_per_question_between_two_tests,
Gbl.Test.Config.MinTimeNxtTstPerQst);
/***** Feedback to students *****/
fprintf (Gbl.F.Out,"<tr>"
2015-07-28 00:16:09 +02:00
"<td class=\"%s RIGHT_TOP\">"
2014-12-26 14:45:14 +01:00
"%s:"
"</td>"
2016-12-26 15:17:30 +01:00
"<td class=\"LEFT_BOTTOM\">",
2015-07-28 00:16:09 +02:00
The_ClassForm[Gbl.Prefs.Theme],Txt_Feedback_to_students);
2014-12-01 23:55:08 +01:00
for (FeedbTyp = (Tst_Feedback_t) 0;
FeedbTyp < Tst_NUM_TYPES_FEEDBACK;
FeedbTyp++)
{
2016-12-26 15:17:30 +01:00
fprintf (Gbl.F.Out,"<label class=\"DAT\">"
"<input type=\"radio\" name=\"Feedback\" value=\"%u\"",
2014-12-01 23:55:08 +01:00
(unsigned) FeedbTyp);
if (FeedbTyp == Gbl.Test.Config.FeedbackType)
fprintf (Gbl.F.Out," checked=\"checked\"");
2016-12-26 15:17:30 +01:00
fprintf (Gbl.F.Out," />"
"%s"
"</label><br />",
2014-12-01 23:55:08 +01:00
Txt_TST_STR_FEEDBACK[FeedbTyp]);
}
fprintf (Gbl.F.Out,"</td>"
2016-05-05 12:37:45 +02:00
"</tr>"
"</table>");
2014-12-01 23:55:08 +01:00
2016-05-05 12:37:45 +02:00
/***** Send button *****/
Lay_PutConfirmButton (Txt_Save);
2015-04-11 23:46:21 +02:00
2014-12-01 23:55:08 +01:00
/***** End form *****/
2015-03-13 00:16:02 +01:00
Act_FormEnd ();
2016-05-05 12:37:45 +02:00
/***** End frame *****/
Lay_EndRoundFrame ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/*************** Get configuration of test for current course ****************/
/*****************************************************************************/
2016-12-26 15:17:30 +01:00
static void Tst_PutInputFieldNumQst (const char *Field,const char *Label,
unsigned Value)
{
fprintf (Gbl.F.Out,"<tr>"
"<td class=\"RIGHT_MIDDLE\">"
"<label for=\"%s\" class=\"DAT\">%s</label>"
"</td>"
"<td class=\"LEFT_MIDDLE\">"
"<input type=\"text\""
" id=\"%s\" name=\"%s\""
" size=\"3\" maxlength=\"3\" value=\"%u\""
" required=\"required\" />"
"</td>"
"</tr>",
Field,Label,
Field,Field,
Value);
}
/*****************************************************************************/
/*************** Get configuration of test for current course ****************/
/*****************************************************************************/
2014-12-01 23:55:08 +01:00
static void Tst_GetConfigTstFromDB (void)
{
2017-03-13 19:02:15 +01:00
char Query[256];
2014-12-01 23:55:08 +01:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned long NumRows;
/***** Get configuration of test for current course from database *****/
sprintf (Query,"SELECT Pluggable,Min,Def,Max,MinTimeNxtTstPerQst,Feedback"
2017-03-24 01:09:27 +01:00
" FROM tst_config WHERE CrsCod=%ld",
2014-12-01 23:55:08 +01:00
Gbl.CurrentCrs.Crs.CrsCod);
NumRows = DB_QuerySELECT (Query,&mysql_res,"can not get configuration of test");
Gbl.Test.Config.FeedbackType = Tst_FEEDBACK_DEFAULT;
Gbl.Test.Config.MinTimeNxtTstPerQst = 0UL;
if (NumRows == 0)
{
Gbl.Test.Config.Pluggable = Tst_PLUGGABLE_UNKNOWN;
Gbl.Test.Config.Min = Tst_CONFIG_DEFAULT_MIN_QUESTIONS;
Gbl.Test.Config.Def = Tst_CONFIG_DEFAULT_DEF_QUESTIONS;
Gbl.Test.Config.Max = Tst_CONFIG_DEFAULT_MAX_QUESTIONS;
}
else // NumRows == 1
{
/***** Get minimun, default and maximum *****/
row = mysql_fetch_row (mysql_res);
Tst_GetConfigFromRow (row);
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
/************ Get configuration values from a database table row *************/
/*****************************************************************************/
void Tst_GetConfigFromRow (MYSQL_ROW row)
{
int IntNum;
long LongNum;
Tst_Pluggable_t Pluggable;
Tst_Feedback_t FeedbTyp;
/***** Get whether test are visible via plugins or not *****/
Gbl.Test.Config.Pluggable = Tst_PLUGGABLE_UNKNOWN;
for (Pluggable = Tst_PLUGGABLE_NO;
Pluggable <= Tst_PLUGGABLE_YES;
Pluggable++)
if (!strcmp (row[0],Tst_PluggableDB[Pluggable]))
{
Gbl.Test.Config.Pluggable = Pluggable;
break;
}
/***** Get number of questions *****/
if (sscanf (row[1],"%d",&IntNum) == 1)
Gbl.Test.Config.Min = (IntNum < 1) ? 1 :
(unsigned) IntNum;
else
Gbl.Test.Config.Min = Tst_CONFIG_DEFAULT_MIN_QUESTIONS;
if (sscanf (row[2],"%d",&IntNum) == 1)
Gbl.Test.Config.Def = (IntNum < 1) ? 1 :
(unsigned) IntNum;
else
Gbl.Test.Config.Def = Tst_CONFIG_DEFAULT_DEF_QUESTIONS;
if (sscanf (row[3],"%d",&IntNum) == 1)
Gbl.Test.Config.Max = (IntNum < 1) ? 1 :
(unsigned) IntNum;
else
Gbl.Test.Config.Max = Tst_CONFIG_DEFAULT_MAX_QUESTIONS;
/***** Check and correct numbers *****/
Tst_CheckAndCorrectNumbersQst ();
2016-11-21 13:15:08 +01:00
/***** Get minimum time between consecutive tests, per question (row[4]) *****/
2014-12-01 23:55:08 +01:00
if (sscanf (row[4],"%ld",&LongNum) == 1)
Gbl.Test.Config.MinTimeNxtTstPerQst = (LongNum < 1L) ? 0UL :
(unsigned long) LongNum;
/***** Get feedback type (row[5]) *****/
for (FeedbTyp = (Tst_Feedback_t) 0;
FeedbTyp < Tst_NUM_TYPES_FEEDBACK;
FeedbTyp++)
if (!strcmp (row[5],Tst_FeedbackDB[FeedbTyp]))
{
Gbl.Test.Config.FeedbackType = FeedbTyp;
break;
}
}
/*****************************************************************************/
/*************** 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)
{
2017-03-13 19:02:15 +01:00
char Query[128];
2014-12-01 23:55:08 +01:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned long NumRows;
Tst_Pluggable_t Pluggable;
/***** Get pluggability of tests for current course from database *****/
2017-03-24 01:09:27 +01:00
sprintf (Query,"SELECT Pluggable FROM tst_config WHERE CrsCod=%ld",
2014-12-01 23:55:08 +01:00
Gbl.CurrentCrs.Crs.CrsCod);
NumRows = DB_QuerySELECT (Query,&mysql_res,"can not get configuration of test");
if (NumRows == 0)
Gbl.Test.Config.Pluggable = Tst_PLUGGABLE_UNKNOWN;
else // NumRows == 1
{
/***** Get whether test are visible via plugins or not *****/
row = mysql_fetch_row (mysql_res);
Gbl.Test.Config.Pluggable = Tst_PLUGGABLE_UNKNOWN;
for (Pluggable = Tst_PLUGGABLE_NO;
Pluggable <= Tst_PLUGGABLE_YES;
Pluggable++)
if (!strcmp (row[0],Tst_PluggableDB[Pluggable]))
{
Gbl.Test.Config.Pluggable = Pluggable;
break;
}
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
/***** Get if current course has tests from database *****/
if (Gbl.Test.Config.Pluggable == Tst_PLUGGABLE_UNKNOWN)
return Tst_CheckIfCurrentCrsHasTestTags (); // Return true if course has tests
return false; // Pluggable is not unknown
}
/*****************************************************************************/
/************* Receive configuration of test for current course **************/
/*****************************************************************************/
void Tst_ReceiveConfigTst (void)
{
extern const char *Txt_The_test_configuration_has_been_updated;
char Query[512];
/***** Get whether test are visible via plugins or not *****/
Gbl.Test.Config.Pluggable = Tst_GetPluggableFromForm ();
/***** Get number of questions *****/
/* Get minimum number of questions */
2017-01-29 21:41:08 +01:00
Gbl.Test.Config.Min = (unsigned)
Par_GetParToUnsignedLong ("NumQstMin",
1,
UINT_MAX,
1);
2014-12-01 23:55:08 +01:00
/* Get default number of questions */
2017-01-29 21:41:08 +01:00
Gbl.Test.Config.Def = (unsigned)
Par_GetParToUnsignedLong ("NumQstDef",
1,
UINT_MAX,
1);
2014-12-01 23:55:08 +01:00
/* Get maximum number of questions */
2017-01-29 21:41:08 +01:00
Gbl.Test.Config.Max = (unsigned)
Par_GetParToUnsignedLong ("NumQstMax",
1,
UINT_MAX,
1);
2014-12-01 23:55:08 +01:00
/* Check and correct numbers */
Tst_CheckAndCorrectNumbersQst ();
2016-11-21 13:15:08 +01:00
/***** Get minimum time between consecutive tests, per question *****/
2017-01-29 21:41:08 +01:00
Gbl.Test.Config.MinTimeNxtTstPerQst = Par_GetParToUnsignedLong ("MinTimeNxtTstPerQst",
0,
ULONG_MAX,
0);
2014-12-01 23:55:08 +01:00
/***** Get type of feedback from form *****/
Gbl.Test.Config.FeedbackType = Tst_GetFeedbackTypeFromForm ();
/***** Update database *****/
2016-11-19 16:46:43 +01:00
sprintf (Query,"REPLACE INTO tst_config"
" (CrsCod,Pluggable,Min,Def,Max,MinTimeNxtTstPerQst,Feedback)"
2017-03-13 13:17:53 +01:00
" VALUES"
2017-03-24 01:09:27 +01:00
" (%ld,'%s',%u,%u,%u,'%lu','%s')",
2014-12-01 23:55:08 +01:00
Gbl.CurrentCrs.Crs.CrsCod,
Tst_PluggableDB[Gbl.Test.Config.Pluggable],
Gbl.Test.Config.Min,Gbl.Test.Config.Def,Gbl.Test.Config.Max,
Gbl.Test.Config.MinTimeNxtTstPerQst,
Tst_FeedbackDB[Gbl.Test.Config.FeedbackType]);
2016-11-19 16:46:43 +01:00
DB_QueryREPLACE (Query,"can not save configuration of tests");
2014-12-01 23:55:08 +01:00
/***** Show confirmation message *****/
Lay_ShowAlert (Lay_SUCCESS,Txt_The_test_configuration_has_been_updated);
/***** Show again the form to configure test *****/
Tst_ShowFormConfig ();
}
/*****************************************************************************/
/******************* Get if tests are pluggable from form ********************/
/*****************************************************************************/
static Tst_Pluggable_t Tst_GetPluggableFromForm (void)
{
2017-01-29 21:41:08 +01:00
return (Tst_Pluggable_t)
Par_GetParToUnsignedLong ("Pluggable",
0,
Tst_NUM_OPTIONS_PLUGGABLE - 1,
(unsigned long) Tst_PLUGGABLE_UNKNOWN);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/*********************** Get type of feedback from form **********************/
/*****************************************************************************/
static Tst_Feedback_t Tst_GetFeedbackTypeFromForm (void)
{
2017-01-29 21:41:08 +01:00
return (Tst_Feedback_t)
Par_GetParToUnsignedLong ("Feedback",
0,
Tst_NUM_TYPES_FEEDBACK - 1,
(unsigned long) Tst_FEEDBACK_DEFAULT);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/**** Check and correct minimum, default and maximum numbers of questions ****/
/*****************************************************************************/
static void Tst_CheckAndCorrectNumbersQst (void)
{
/***** Check if minimum is correct *****/
if (Gbl.Test.Config.Min < 1)
Gbl.Test.Config.Min = 1;
2016-11-21 13:15:08 +01:00
else if (Gbl.Test.Config.Min > Tst_MAX_QUESTIONS_PER_TEST)
Gbl.Test.Config.Min = Tst_MAX_QUESTIONS_PER_TEST;
2014-12-01 23:55:08 +01:00
/***** Check if maximum is correct *****/
if (Gbl.Test.Config.Max < 1)
Gbl.Test.Config.Max = 1;
2016-11-21 13:15:08 +01:00
else if (Gbl.Test.Config.Max > Tst_MAX_QUESTIONS_PER_TEST)
Gbl.Test.Config.Max = Tst_MAX_QUESTIONS_PER_TEST;
2014-12-01 23:55:08 +01:00
/***** Check if minimum is lower than maximum *****/
if (Gbl.Test.Config.Min > Gbl.Test.Config.Max)
Gbl.Test.Config.Min = Gbl.Test.Config.Max;
/***** Check if default is correct *****/
if (Gbl.Test.Config.Def < Gbl.Test.Config.Min)
Gbl.Test.Config.Def = Gbl.Test.Config.Min;
else if (Gbl.Test.Config.Def > Gbl.Test.Config.Max)
Gbl.Test.Config.Def = Gbl.Test.Config.Max;
}
/*****************************************************************************/
/***************** Show form for select the types of answers *****************/
/*****************************************************************************/
2016-03-21 01:28:16 +01:00
static void Tst_ShowFormAnswerTypes (unsigned NumCols)
2014-12-01 23:55:08 +01:00
{
2015-07-28 00:16:09 +02:00
extern const char *The_ClassForm[The_NUM_THEMES];
2015-04-01 12:54:25 +02:00
extern const char *Txt_Types_of_answers;
2014-12-01 23:55:08 +01:00
extern const char *Txt_All_types_of_answers;
extern const char *Txt_TST_STR_ANSWER_TYPES[Tst_NUM_ANS_TYPES];
Tst_AnswerType_t AnsType;
2017-01-28 15:58:46 +01:00
char UnsignedStr[10 + 1];
2014-12-01 23:55:08 +01:00
const char *Ptr;
2016-03-21 01:28:16 +01:00
/***** Label *****/
fprintf (Gbl.F.Out,"<tr>"
2016-12-20 02:18:50 +01:00
"<td class=\"RIGHT_TOP %s\">"
2016-03-21 01:28:16 +01:00
"%s:"
"</td>",
The_ClassForm[Gbl.Prefs.Theme],Txt_Types_of_answers);
2014-12-01 23:55:08 +01:00
/***** Select all types of answers *****/
2016-03-21 01:28:16 +01:00
fprintf (Gbl.F.Out,"<td");
if (NumCols > 1)
fprintf (Gbl.F.Out," colspan=\"%u\"",NumCols);
fprintf (Gbl.F.Out," class=\"LEFT_TOP\">"
2016-04-01 18:37:16 +02:00
"<table class=\"CELLS_PAD_2\">"
2016-03-21 01:28:16 +01:00
"<tr>"
2016-12-20 02:18:50 +01:00
"<td class=\"LEFT_MIDDLE\">"
"<label class=\"%s\">"
2014-12-01 23:55:08 +01:00
"<input type=\"checkbox\" name=\"AllAnsTypes\" value=\"Y\"",
2015-07-28 00:16:09 +02:00
The_ClassForm[Gbl.Prefs.Theme]);
2014-12-01 23:55:08 +01:00
if (Gbl.Test.AllAnsTypes)
fprintf (Gbl.F.Out," checked=\"checked\"");
2016-04-07 17:14:35 +02:00
fprintf (Gbl.F.Out," onclick=\"togglecheckChildren(this,'AnswerType');\" />"
2016-12-20 02:18:50 +01:00
"&nbsp;%s"
"</label>"
2014-12-01 23:55:08 +01:00
"</td>"
"</tr>",
Txt_All_types_of_answers);
/***** Type of answer *****/
for (AnsType = (Tst_AnswerType_t) 0;
AnsType < Tst_NUM_ANS_TYPES;
AnsType++)
{
fprintf (Gbl.F.Out,"<tr>"
2016-12-20 02:18:50 +01:00
"<td class=\"LEFT_MIDDLE\">"
"<label class=\"DAT\">"
2014-12-01 23:55:08 +01:00
"<input type=\"checkbox\" name=\"AnswerType\" value=\"%u\"",
(unsigned) AnsType);
Ptr = Gbl.Test.ListAnsTypes;
while (*Ptr)
{
Par_GetNextStrUntilSeparParamMult (&Ptr,UnsignedStr,10);
if (Tst_ConvertFromUnsignedStrToAnsTyp (UnsignedStr) == AnsType)
fprintf (Gbl.F.Out," checked=\"checked\"");
}
2016-04-07 17:14:35 +02:00
fprintf (Gbl.F.Out," onclick=\"checkParent(this,'AllAnsTypes');\" />"
2016-12-20 02:18:50 +01:00
"&nbsp;%s"
"</label>"
2014-12-01 23:55:08 +01:00
"</td>"
"</tr>",
Txt_TST_STR_ANSWER_TYPES[AnsType]);
}
2016-03-21 01:28:16 +01:00
fprintf (Gbl.F.Out,"</table>"
"</td>"
"</tr>");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/***************** List several test questions for edition *******************/
/*****************************************************************************/
void Tst_ListQuestionsToEdit (void)
{
MYSQL_RES *mysql_res;
unsigned long NumRows;
/***** Get parameters, query the database and list the questions *****/
2017-03-13 19:02:15 +01:00
if (Tst_GetParamsTst ()) // Get parameters from the form
2014-12-01 23:55:08 +01:00
{
2017-03-13 19:02:15 +01:00
if ((NumRows = Tst_GetQuestionsForEdit (&mysql_res)) != 0) // Query database
2014-12-01 23:55:08 +01:00
{
2017-03-13 19:02:15 +01:00
/* Buttons for edition */
2015-12-13 21:30:28 +01:00
fprintf (Gbl.F.Out,"<div class=\"CONTEXT_MENU\">");
2014-12-01 23:55:08 +01:00
if (Gbl.Test.XML.CreateXML)
2017-03-13 19:02:15 +01:00
/* Create XML file for exporting questions
and put a link to download it */
TsI_CreateXML (NumRows,mysql_res);
2014-12-01 23:55:08 +01:00
else
2017-03-13 19:02:15 +01:00
/* Button to export questions */
TsI_PutFormToExportQuestions ();
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"</div>");
2017-03-13 19:02:15 +01:00
/* Show the table with the questions */
Tst_ListOneOrMoreQuestionsToEdit (NumRows,mysql_res);
2014-12-01 23:55:08 +01:00
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
else
2017-03-13 19:02:15 +01:00
/* Show the form again */
Tst_ShowFormAskEditTsts ();
2014-12-01 23:55:08 +01:00
/***** Free memory used by the list of tags *****/
Tst_FreeTagsList ();
}
/*****************************************************************************/
/********** Get from the database several test questions for listing *********/
/*****************************************************************************/
2017-03-08 03:48:23 +01:00
#define Tst_MAX_BYTES_QUERY_TEST (16 * 1024 - 1)
2014-12-01 23:55:08 +01:00
static unsigned long Tst_GetQuestionsForEdit (MYSQL_RES **mysql_res)
{
extern const char *Txt_No_questions_found_matching_your_search_criteria;
unsigned long NumRows;
2017-03-08 03:48:23 +01:00
char Query[Tst_MAX_BYTES_QUERY_TEST + 1];
2014-12-01 23:55:08 +01:00
long LengthQuery;
unsigned NumItemInList;
const char *Ptr;
2017-01-16 01:51:01 +01:00
char TagText[Tst_MAX_BYTES_TAG + 1];
2017-01-28 15:58:46 +01:00
char LongStr[1 + 10 + 1];
char UnsignedStr[10 + 1];
2014-12-01 23:55:08 +01:00
Tst_AnswerType_t AnsType;
2017-01-28 15:58:46 +01:00
char CrsCodStr[1 + 10 + 1];
2014-12-01 23:55:08 +01:00
/***** Select questions *****/
/* Start query */
/*
2016-04-06 01:10:04 +02:00
row[ 0] QstCod
row[ 1] UNIX_TIMESTAMP(EditTime)
row[ 2] AnsType
row[ 3] Shuffle
row[ 4] Stem
row[ 5] Feedback
row[ 6] ImageName
row[ 7] ImageTitle
2016-04-15 02:33:16 +02:00
row[ 8] ImageURL
row[ 9] NumHits
row[10] NumHitsNotBlank
row[11] Score
2014-12-01 23:55:08 +01:00
*/
2016-03-29 22:24:03 +02:00
sprintf (Query,"SELECT tst_questions.QstCod,"
"UNIX_TIMESTAMP(tst_questions.EditTime) AS F,"
"tst_questions.AnsType,tst_questions.Shuffle,"
2016-04-06 01:10:04 +02:00
"tst_questions.Stem,tst_questions.Feedback,"
2016-04-15 02:33:16 +02:00
"tst_questions.ImageName,"
"tst_questions.ImageTitle,"
"tst_questions.ImageURL,"
2016-03-29 22:24:03 +02:00
"tst_questions.NumHits,tst_questions.NumHitsNotBlank,"
"tst_questions.Score"
2014-12-01 23:55:08 +01:00
" FROM tst_questions");
2016-04-06 22:27:33 +02:00
if (!Gbl.Test.Tags.All)
2017-01-17 03:33:05 +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
2017-01-17 03:33:05 +01:00
Str_Concat (Query," WHERE tst_questions.CrsCod='",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2014-12-01 23:55:08 +01:00
sprintf (CrsCodStr,"%ld",Gbl.CurrentCrs.Crs.CrsCod);
2017-01-17 03:33:05 +01:00
Str_Concat (Query,CrsCodStr,
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2017-01-17 03:33:05 +01:00
Str_Concat (Query,"' AND tst_questions.EditTime>=FROM_UNIXTIME('",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2015-10-26 13:36:02 +01:00
sprintf (LongStr,"%ld",(long) Gbl.DateRange.TimeUTC[0]);
2017-01-17 03:33:05 +01:00
Str_Concat (Query,LongStr,
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2017-01-17 03:33:05 +01:00
Str_Concat (Query,"') AND tst_questions.EditTime<=FROM_UNIXTIME('",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2015-10-26 13:36:02 +01:00
sprintf (LongStr,"%ld",(long) Gbl.DateRange.TimeUTC[1]);
2017-01-17 03:33:05 +01:00
Str_Concat (Query,LongStr,
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2017-01-17 03:33:05 +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 */
2016-04-06 22:27:33 +02:00
if (!Gbl.Test.Tags.All)
2014-12-01 23:55:08 +01:00
{
2017-01-16 01:51:01 +01:00
Str_Concat (Query," AND tst_questions.QstCod=tst_question_tags.QstCod"
" 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);
2017-01-17 03:33:05 +01:00
Str_Concat (Query,CrsCodStr,
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2017-01-17 03:33:05 +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
LengthQuery = strlen (Query);
NumItemInList = 0;
2016-04-06 22:27:33 +02:00
Ptr = Gbl.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.");
2017-01-16 01:51:01 +01:00
Str_Concat (Query,
NumItemInList ? " OR tst_tags.TagTxt='" :
" AND (tst_tags.TagTxt='",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2017-01-17 03:33:05 +01:00
Str_Concat (Query,TagText,
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2017-01-17 03:33:05 +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++;
}
2017-01-17 03:33:05 +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 */
if (!Gbl.Test.AllAnsTypes)
{
LengthQuery = strlen (Query);
NumItemInList = 0;
Ptr = Gbl.Test.ListAnsTypes;
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.");
2017-01-16 01:51:01 +01:00
Str_Concat (Query,
NumItemInList ? " OR tst_questions.AnsType='" :
" AND (tst_questions.AnsType='",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2017-01-17 03:33:05 +01:00
Str_Concat (Query,Tst_StrAnswerTypesDB[AnsType],
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2017-01-17 03:33:05 +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++;
}
2017-01-17 03:33:05 +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 */
2017-01-17 03:33:05 +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
2017-01-29 12:42:19 +01:00
switch (Gbl.Test.SelectedOrder)
2014-12-01 23:55:08 +01:00
{
case Tst_ORDER_STEM:
2017-01-17 03:33:05 +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:
2017-01-16 01:51:01 +01:00
Str_Concat (Query," ORDER BY tst_questions.NumHits DESC,"
2017-01-17 03:33:05 +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:
2017-01-16 01:51:01 +01:00
Str_Concat (Query," ORDER BY tst_questions.Score/tst_questions.NumHits DESC,"
"tst_questions.NumHits DESC,"
2017-01-17 03:33:05 +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_NUM_HITS_NOT_BLANK:
2017-01-16 01:51:01 +01:00
Str_Concat (Query," ORDER BY tst_questions.NumHitsNotBlank DESC,"
2017-01-17 03:33:05 +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:
2017-01-16 01:51:01 +01:00
Str_Concat (Query," ORDER BY tst_questions.Score/tst_questions.NumHitsNotBlank DESC,"
"tst_questions.NumHitsNotBlank DESC,"
2017-01-17 03:33:05 +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;
}
/* Make the query */
NumRows = DB_QuerySELECT (Query,mysql_res,"can not get questions");
if (NumRows == 0)
Lay_ShowAlert (Lay_INFO,Txt_No_questions_found_matching_your_search_criteria);
return NumRows;
}
/*****************************************************************************/
/********* Get from the database several test questions to list them *********/
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static unsigned long Tst_GetQuestionsForTest (MYSQL_RES **mysql_res)
2014-12-01 23:55:08 +01:00
{
2017-03-08 03:48:23 +01:00
char Query[Tst_MAX_BYTES_QUERY_TEST + 1];
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];
char UnsignedStr[10 + 1];
2014-12-01 23:55:08 +01:00
Tst_AnswerType_t AnsType;
2017-01-28 15:58:46 +01:00
char StrNumQsts[10 + 1];
2014-12-01 23:55:08 +01:00
/***** Select questions without hidden tags *****/
/*
2016-04-06 01:10:04 +02:00
row[ 0] QstCod
row[ 1] UNIX_TIMESTAMP(EditTime)
row[ 2] AnsType
row[ 3] Shuffle
row[ 4] Stem
row[ 5] Feedback
row[ 6] ImageName
row[ 7] ImageTitle
2016-04-15 02:33:16 +02:00
row[ 8] ImageURL
row[ 9] NumHits
row[10] NumHitsNotBlank
row[11] Score
2014-12-01 23:55:08 +01:00
*/
/* 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
2015-06-15 12:30:09 +02:00
sprintf (Query,"SELECT DISTINCTROW tst_questions.QstCod,"
2015-10-26 14:11:30 +01:00
"UNIX_TIMESTAMP(tst_questions.EditTime),"
2015-06-15 12:30:09 +02:00
"tst_questions.AnsType,tst_questions.Shuffle,"
2016-04-06 01:10:04 +02:00
"tst_questions.Stem,tst_questions.Feedback,"
2016-04-15 02:33:16 +02:00
"tst_questions.ImageName,"
"tst_questions.ImageTitle,"
"tst_questions.ImageURL,"
2015-06-15 12:30:09 +02:00
"tst_questions.NumHits,tst_questions.NumHitsNotBlank,"
"tst_questions.Score"
2014-12-01 23:55:08 +01:00
" FROM tst_questions,tst_question_tags,tst_tags"
2017-03-24 01:09:27 +01:00
" WHERE tst_questions.CrsCod=%ld"
2015-06-15 12:30:09 +02:00
" AND tst_questions.QstCod NOT IN"
" (SELECT tst_question_tags.QstCod"
" FROM tst_tags,tst_question_tags"
2017-03-24 01:09:27 +01:00
" WHERE tst_tags.CrsCod=%ld AND tst_tags.TagHidden='Y'"
2014-12-01 23:55:08 +01:00
" AND tst_tags.TagCod=tst_question_tags.TagCod)"
" AND tst_questions.QstCod=tst_question_tags.QstCod"
" AND tst_question_tags.TagCod=tst_tags.TagCod"
2017-03-24 01:09:27 +01:00
" AND tst_tags.CrsCod=%ld",
2014-12-01 23:55:08 +01:00
Gbl.CurrentCrs.Crs.CrsCod,
Gbl.CurrentCrs.Crs.CrsCod,
Gbl.CurrentCrs.Crs.CrsCod);
2016-04-06 22:27:33 +02:00
if (!Gbl.Test.Tags.All) // User has not selected all the tags
2014-12-01 23:55:08 +01:00
{
/* Add selected tags */
LengthQuery = strlen (Query);
NumItemInList = 0;
2016-04-06 22:27:33 +02:00
Ptr = Gbl.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.");
2017-01-16 01:51:01 +01:00
Str_Concat (Query,
NumItemInList ? " OR tst_tags.TagTxt='" :
" AND (tst_tags.TagTxt='",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2017-01-17 03:33:05 +01:00
Str_Concat (Query,TagText,
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2017-01-17 03:33:05 +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++;
}
2017-01-17 03:33:05 +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 */
if (!Gbl.Test.AllAnsTypes)
{
LengthQuery = strlen (Query);
NumItemInList = 0;
Ptr = Gbl.Test.ListAnsTypes;
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 - 128)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Query size exceed.");
2017-01-16 01:51:01 +01:00
Str_Concat (Query,
NumItemInList ? " OR tst_questions.AnsType='" :
" AND (tst_questions.AnsType='",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2017-01-17 03:33:05 +01:00
Str_Concat (Query,Tst_StrAnswerTypesDB[AnsType],
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2017-01-17 03:33:05 +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++;
}
2017-01-17 03:33:05 +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 query */
2017-01-17 03:33:05 +01:00
Str_Concat (Query," ORDER BY RAND(NOW()) LIMIT ",
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2014-12-01 23:55:08 +01:00
sprintf (StrNumQsts,"%u",Gbl.Test.NumQsts);
2017-01-17 03:33:05 +01:00
Str_Concat (Query,StrNumQsts,
2017-03-08 03:48:23 +01:00
Tst_MAX_BYTES_QUERY_TEST);
2015-06-15 12:30:09 +02:00
/*
if (Gbl.Usrs.Me.LoggedRole == Rol_SYS_ADM)
2014-12-01 23:55:08 +01:00
Lay_ShowAlert (Lay_INFO,Query);
2015-06-15 12:30:09 +02:00
*/
2014-12-01 23:55:08 +01:00
/* Make the query */
return DB_QuerySELECT (Query,mysql_res,"can not get questions");
}
/*****************************************************************************/
/*********************** List a test question for edition ********************/
/*****************************************************************************/
static void Tst_ListOneQstToEdit (void)
{
MYSQL_RES *mysql_res;
/***** Query database *****/
if (Tst_GetOneQuestionByCod (Gbl.Test.QstCod,&mysql_res))
/***** Show the question ready to edit it *****/
Tst_ListOneOrMoreQuestionsToEdit (1,mysql_res);
else
Lay_ShowErrorAndExit ("Can not get question.");
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
/*********************** Get data of one test question ***********************/
/*****************************************************************************/
// Return true on success, false on error
static bool Tst_GetOneQuestionByCod (long QstCod,MYSQL_RES **mysql_res)
{
char Query[512];
/***** Get data of a question from database *****/
2016-03-29 22:24:03 +02:00
/*
2016-04-06 01:10:04 +02:00
row[ 0] QstCod
row[ 1] UNIX_TIMESTAMP(EditTime)
row[ 2] AnsType
row[ 3] Shuffle
row[ 4] Stem
row[ 5] Feedback
row[ 6] ImageName
row[ 7] ImageTitle
2016-04-15 02:33:16 +02:00
row[ 8] ImageURL
row[ 9] NumHits
row[10] NumHitsNotBlank
row[11] Score
2014-12-01 23:55:08 +01:00
*/
2015-10-26 14:11:30 +01:00
sprintf (Query,"SELECT QstCod,UNIX_TIMESTAMP(EditTime),"
2016-04-15 02:33:16 +02:00
"AnsType,Shuffle,Stem,Feedback,"
"ImageName,ImageTitle,ImageURL,"
2014-12-01 23:55:08 +01:00
"NumHits,NumHitsNotBlank,Score"
" FROM tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE QstCod=%ld",
2014-12-01 23:55:08 +01:00
QstCod);
return (DB_QuerySELECT (Query,mysql_res,"can not get data of a question") == 1);
}
/*****************************************************************************/
/****************** List for edition one or more test questions **************/
/*****************************************************************************/
static void Tst_ListOneOrMoreQuestionsToEdit (unsigned long NumRows,MYSQL_RES *mysql_res)
{
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;
2014-12-01 23:55:08 +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_Type;
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];
extern const char *Txt_TST_STR_ANSWER_TYPES[Tst_NUM_ANS_TYPES];
extern const char *Txt_Shuffle;
extern const char *Txt_Edit_question;
2015-12-29 11:35:01 +01:00
extern const char *Txt_Today;
2014-12-01 23:55:08 +01:00
Tst_QuestionsOrder_t Order;
unsigned long NumRow;
MYSQL_ROW row;
2015-10-26 13:36:02 +01:00
unsigned UniqueId;
time_t TimeUTC;
2014-12-01 23:55:08 +01:00
unsigned long NumHitsThisQst;
unsigned long NumHitsNotBlankThisQst;
double TotalScoreThisQst;
/***** Table start *****/
2016-11-13 20:18:49 +01:00
Lay_StartRoundFrame (NULL,Txt_Questions,
Tst_PutIconsTests,Hlp_ASSESSMENT_Tests);
2014-12-01 23:55:08 +01:00
/***** Write the heading *****/
2017-05-01 12:36:24 +02:00
Lay_StartTableWideMargin (2);
fprintf (Gbl.F.Out,"<tr>"
2014-12-27 00:28:19 +01:00
"<th colspan=\"2\"></th>"
2015-09-06 20:02:14 +02:00
"<th class=\"CENTER_TOP\">"
2014-12-27 00:28:19 +01:00
"%s"
"</th>"
2015-09-06 20:02:14 +02:00
"<th class=\"CENTER_TOP\">"
2014-12-27 00:28:19 +01:00
"%s"
"</th>"
2015-09-06 20:02:14 +02:00
"<th class=\"CENTER_TOP\">"
2014-12-27 00:28:19 +01:00
"%s"
"</th>"
2015-09-06 20:02:14 +02:00
"<th class=\"LEFT_TOP\">"
2014-12-27 00:28:19 +01:00
"%s"
"</th>"
2015-09-06 20:02:14 +02:00
"<th class=\"CENTER_TOP\">"
2014-12-27 00:28:19 +01:00
"%s"
"</th>"
2015-09-06 20:02:14 +02:00
"<th class=\"CENTER_TOP\">"
2014-12-27 00:28:19 +01:00
"%s"
"</th>",
2014-12-01 23:55:08 +01:00
Txt_No_INDEX,
Txt_Code,
Txt_Date,
Txt_Tags,
Txt_Type,
Txt_Shuffle);
/* Stem and answers of question */
/* Number of times that the question has been answered */
/* Average score */
for (Order = (Tst_QuestionsOrder_t) 0;
Order < (Tst_QuestionsOrder_t) Tst_NUM_TYPES_ORDER_QST;
Order++)
{
2015-09-06 20:02:14 +02:00
fprintf (Gbl.F.Out,"<th class=\"LEFT_TOP\">");
2014-12-01 23:55:08 +01:00
if (NumRows > 1)
{
Act_FormStart (ActLstTstQst);
Sta_WriteParamsDatesSeeAccesses ();
Tst_WriteParamEditQst ();
Par_PutHiddenParamUnsigned ("Order",(unsigned) Order);
2016-07-01 17:13:41 +02:00
Act_LinkFormSubmit (Txt_TST_STR_ORDER_FULL[Order],"TIT_TBL",NULL);
2017-01-29 12:42:19 +01:00
if (Order == Gbl.Test.SelectedOrder)
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"<u>");
}
fprintf (Gbl.F.Out,"%s",Txt_TST_STR_ORDER_SHORT[Order]);
if (NumRows > 1)
{
2017-01-29 12:42:19 +01:00
if (Order == Gbl.Test.SelectedOrder)
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"</u>");
2015-03-13 00:16:02 +01:00
fprintf (Gbl.F.Out,"</a>");
Act_FormEnd ();
2014-12-01 23:55:08 +01:00
}
fprintf (Gbl.F.Out,"</th>");
}
fprintf (Gbl.F.Out,"</tr>");
/***** Write rows *****/
2015-10-26 13:36:02 +01:00
for (NumRow = 0, UniqueId = 1;
2014-12-01 23:55:08 +01:00
NumRow < NumRows;
2015-10-26 13:36:02 +01:00
NumRow++, UniqueId++)
2014-12-01 23:55:08 +01:00
{
Gbl.RowEvenOdd = NumRow % 2;
row = mysql_fetch_row (mysql_res);
2016-03-29 22:24:03 +02:00
/*
2016-04-06 01:10:04 +02:00
row[ 0] QstCod
row[ 1] UNIX_TIMESTAMP(EditTime)
row[ 2] AnsType
row[ 3] Shuffle
row[ 4] Stem
row[ 5] Feedback
row[ 6] ImageName
row[ 7] ImageTitle
2016-04-15 02:33:16 +02:00
row[ 8] ImageURL
row[ 9] NumHits
row[10] NumHitsNotBlank
row[11] Score
2016-03-29 22:24:03 +02:00
*/
2016-04-06 19:26:09 +02:00
/***** Create test question *****/
Tst_QstConstructor ();
2014-12-01 23:55:08 +01:00
/* row[0] holds the code of the question */
2016-04-06 19:26:09 +02:00
if ((Gbl.Test.QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Wrong code of question.");
/* Write icon to remove the question */
fprintf (Gbl.F.Out,"<tr>"
2015-09-03 17:14:30 +02:00
"<td class=\"BT%u\">",Gbl.RowEvenOdd);
2016-04-05 02:59:34 +02:00
Act_FormStart (ActReqRemTstQst);
2017-04-28 14:02:08 +02:00
Tst_PutParamQstCod ();
2016-04-05 02:59:34 +02:00
if (NumRows == 1)
Par_PutHiddenParamChar ("OnlyThisQst",'Y'); // If there are only one row, don't list again after removing
2017-04-28 14:02:08 +02:00
Sta_WriteParamsDatesSeeAccesses ();
Tst_WriteParamEditQst ();
2015-07-22 19:59:28 +02:00
Lay_PutIconRemove ();
2015-03-13 00:16:02 +01:00
Act_FormEnd ();
fprintf (Gbl.F.Out,"</td>");
2014-12-01 23:55:08 +01:00
/* Write icon to edit the question */
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"BT%u\">",Gbl.RowEvenOdd);
2014-12-01 23:55:08 +01:00
Act_FormStart (ActEdiOneTstQst);
2017-04-28 14:02:08 +02:00
Tst_PutParamQstCod ();
2015-12-08 22:20:44 +01:00
fprintf (Gbl.F.Out,"<input type=\"image\" src=\"%s/edit64x64.png\""
2015-09-05 12:04:30 +02:00
" alt=\"%s\" title=\"%s\""
2016-11-14 10:05:41 +01:00
" class=\"ICO20x20\" />",
2014-12-01 23:55:08 +01:00
Gbl.Prefs.IconsURL,
Txt_Edit_question,
Txt_Edit_question);
2015-03-13 00:16:02 +01:00
Act_FormEnd ();
fprintf (Gbl.F.Out,"</td>");
2014-12-01 23:55:08 +01:00
/* Write number of question */
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"DAT_SMALL CENTER_TOP COLOR%u\">"
2014-12-26 14:45:14 +01:00
"%lu&nbsp;"
"</td>",
2015-09-03 17:14:30 +02:00
Gbl.RowEvenOdd,NumRow + 1);
2014-12-01 23:55:08 +01:00
/* Write question code */
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"DAT_SMALL CENTER_TOP COLOR%u\">"
2014-12-26 14:45:14 +01:00
"%ld&nbsp;"
"</td>",
2016-04-06 19:26:09 +02:00
Gbl.RowEvenOdd,Gbl.Test.QstCod);
2014-12-01 23:55:08 +01:00
2015-10-26 13:36:02 +01:00
/* Write the date (row[1] has the UTC date-time) */
TimeUTC = Dat_GetUNIXTimeFromStr (row[1]);
fprintf (Gbl.F.Out,"<td id=\"tst_date_%u\""
" class=\"DAT_SMALL CENTER_TOP COLOR%u\">"
"<script type=\"text/javascript\">"
2016-12-12 23:10:11 +01:00
"writeLocalDateHMSFromUTC('tst_date_%u',"
"%ld,'<br />','%s',true,false,true);"
2015-10-26 14:11:30 +01:00
"</script>"
"</td>",
2015-10-26 13:36:02 +01:00
UniqueId,Gbl.RowEvenOdd,
2015-12-29 11:35:01 +01:00
UniqueId,(long) TimeUTC,Txt_Today);
2014-12-01 23:55:08 +01:00
/* Write the question tags */
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"LEFT_TOP COLOR%u\">",
Gbl.RowEvenOdd);
2016-04-06 19:26:09 +02:00
Tst_GetAndWriteTagsQst (Gbl.Test.QstCod);
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"</td>");
/* Write the question type (row[2]) */
Gbl.Test.AnswerType = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[2]);
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"DAT_SMALL CENTER_TOP COLOR%u\">"
2014-12-26 14:45:14 +01:00
"%s&nbsp;"
"</td>",
2015-09-03 17:14:30 +02:00
Gbl.RowEvenOdd,
2014-12-01 23:55:08 +01:00
Txt_TST_STR_ANSWER_TYPES[Gbl.Test.AnswerType]);
/* Write if shuffle is enabled (row[3]) */
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"DAT_SMALL CENTER_TOP COLOR%u\">",
Gbl.RowEvenOdd);
2014-12-01 23:55:08 +01:00
if (Gbl.Test.AnswerType == Tst_ANS_UNIQUE_CHOICE ||
Gbl.Test.AnswerType == Tst_ANS_MULTIPLE_CHOICE)
{
Act_FormStart (ActShfTstQst);
2017-04-28 14:02:08 +02:00
Tst_PutParamQstCod ();
2014-12-01 23:55:08 +01:00
Sta_WriteParamsDatesSeeAccesses ();
Tst_WriteParamEditQst ();
2016-04-05 02:59:34 +02:00
if (NumRows == 1)
Par_PutHiddenParamChar ("OnlyThisQst",'Y'); // If editing only one question, don't edit others
2017-01-29 12:42:19 +01:00
Par_PutHiddenParamUnsigned ("Order",(unsigned) Gbl.Test.SelectedOrder);
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"<input type=\"checkbox\" name=\"Shuffle\" value=\"Y\"");
2016-09-07 18:48:10 +02:00
if (row[3][0] == 'Y')
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out," checked=\"checked\"");
2015-10-22 14:49:48 +02:00
fprintf (Gbl.F.Out," onclick=\"document.getElementById('%s').submit();\" />",
2016-01-14 10:31:09 +01:00
Gbl.Form.Id);
2015-03-13 00:16:02 +01:00
Act_FormEnd ();
2014-12-01 23:55:08 +01:00
}
fprintf (Gbl.F.Out,"</td>");
2016-04-15 02:33:16 +02:00
/* Write the stem (row[4]), the image (row[6], row[7], row[8]),
2016-04-06 01:10:04 +02:00
the feedback (row[5]) and the answers */
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"LEFT_TOP COLOR%u\">",
Gbl.RowEvenOdd);
2014-12-01 23:55:08 +01:00
Tst_WriteQstStem (row[4],"TEST_EDI");
2016-04-15 02:33:16 +02:00
Img_GetImageNameTitleAndURLFromRow (row[6],row[7],row[8],&Gbl.Test.Image);
2016-04-15 14:30:28 +02:00
Img_ShowImage (&Gbl.Test.Image,
"TEST_IMG_EDIT_LIST_STEM_CONTAINER",
"TEST_IMG_EDIT_LIST_STEM");
2016-04-06 01:10:04 +02:00
Tst_WriteQstFeedback (row[5],"TEST_EDI_LIGHT");
2016-04-06 19:26:09 +02:00
Tst_WriteAnswersOfAQstEdit (Gbl.Test.QstCod);
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"</td>");
2016-03-29 22:24:03 +02:00
/* Get number of hits
(number of times that the question has been answered,
2016-04-15 02:33:16 +02:00
including blank answers) (row[9]) */
if (sscanf (row[9],"%lu",&NumHitsThisQst) != 1)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Wrong number of hits to a question.");
2016-03-29 22:24:03 +02:00
/* Get number of hits not blank
(number of times that the question has been answered
2016-04-15 02:33:16 +02:00
with a not blank answer) (row[10]) */
if (sscanf (row[10],"%lu",&NumHitsNotBlankThisQst) != 1)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Wrong number of hits not blank to a question.");
2016-04-15 02:33:16 +02:00
/* Get the acumulated score of the question (row[11]) */
2016-06-04 14:21:01 +02:00
Str_SetDecimalPointToUS (); // To get the decimal point as a dot
2016-04-15 02:33:16 +02:00
if (sscanf (row[11],"%lf",&TotalScoreThisQst) != 1)
2014-12-01 23:55:08 +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
/* Write number of times this question has been answered */
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"DAT_SMALL CENTER_TOP COLOR%u\">"
2014-12-26 14:45:14 +01:00
"%lu"
"</td>",
2015-09-03 17:14:30 +02:00
Gbl.RowEvenOdd,NumHitsThisQst);
2014-12-01 23:55:08 +01:00
/* Write average score */
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"DAT_SMALL CENTER_TOP COLOR%u\">",
Gbl.RowEvenOdd);
2014-12-01 23:55:08 +01:00
if (NumHitsThisQst)
fprintf (Gbl.F.Out,"%.2f",TotalScoreThisQst /
(double) NumHitsThisQst);
else
fprintf (Gbl.F.Out,"N.A.");
fprintf (Gbl.F.Out,"</td>");
/* Write number of times this question has been answered (not blank) */
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"DAT_SMALL CENTER_TOP COLOR%u\">"
2014-12-26 14:45:14 +01:00
"%lu"
"</td>",
2015-09-03 17:14:30 +02:00
Gbl.RowEvenOdd,
2014-12-01 23:55:08 +01:00
NumHitsNotBlankThisQst);
/* Write average score (not blank) */
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"DAT_SMALL CENTER_TOP COLOR%u\">",
Gbl.RowEvenOdd);
2014-12-01 23:55:08 +01:00
if (NumHitsNotBlankThisQst)
fprintf (Gbl.F.Out,"%.2f",TotalScoreThisQst /
(double) NumHitsNotBlankThisQst);
else
fprintf (Gbl.F.Out,"N.A.");
fprintf (Gbl.F.Out,"</td>"
"</tr>");
2016-04-06 19:26:09 +02:00
/***** Destroy test question *****/
Tst_QstDestructor ();
2014-12-01 23:55:08 +01:00
}
2016-03-20 13:47:46 +01:00
/***** End table *****/
2017-05-01 12:36:24 +02:00
Lay_EndTable ();
2016-03-21 12:53:02 +01:00
/***** Button to add a new question *****/
Tst_PutButtonToAddQuestion ();
/***** End frame *****/
Lay_EndRoundFrame ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/*********** Write hidden parameters for edition of test questions ***********/
/*****************************************************************************/
void Tst_WriteParamEditQst (void)
{
Par_PutHiddenParamChar ("AllTags",
2016-04-06 22:27:33 +02:00
Gbl.Test.Tags.All ? 'Y' :
2014-12-01 23:55:08 +01:00
'N');
Par_PutHiddenParamString ("ChkTag",
2016-04-06 22:27:33 +02:00
Gbl.Test.Tags.List ? Gbl.Test.Tags.List :
2014-12-01 23:55:08 +01:00
"");
Par_PutHiddenParamChar ("AllAnsTypes",
Gbl.Test.AllAnsTypes ? 'Y' :
'N');
Par_PutHiddenParamString ("AnswerType",Gbl.Test.ListAnsTypes);
}
/*****************************************************************************/
/*************** Get answers of a test question from database ****************/
/*****************************************************************************/
unsigned Tst_GetAnswersQst (long QstCod,MYSQL_RES **mysql_res,bool Shuffle)
{
2016-04-06 19:26:09 +02:00
char Query[512];
2014-12-01 23:55:08 +01:00
unsigned long NumRows;
/***** Get answers of a question from database *****/
2016-04-15 02:33:16 +02:00
sprintf (Query,"SELECT AnsInd,Answer,Feedback,"
"ImageName,ImageTitle,ImageURL,Correct"
2017-03-24 01:09:27 +01:00
" FROM tst_answers WHERE QstCod=%ld ORDER BY %s",
2014-12-01 23:55:08 +01:00
QstCod,
Shuffle ? "RAND(NOW())" :
"AnsInd");
if (!(NumRows = DB_QuerySELECT (Query,mysql_res,"can not get answers of a question")))
2016-04-06 19:26:09 +02:00
Lay_ShowAlert (Lay_ERROR,"Error when getting answers of a question.");
2014-12-01 23:55:08 +01:00
return (unsigned) NumRows;
}
/*****************************************************************************/
/**************** Get and write the answers of a test question ***************/
/*****************************************************************************/
static void Tst_WriteAnswersOfAQstEdit (long QstCod)
{
extern const char *Txt_TEST_Correct_answer;
unsigned NumOpt;
unsigned i;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
char *Answer;
char *Feedback;
size_t LengthAnswer;
size_t LengthFeedback;
double FloatNum[2];
Gbl.Test.Answer.NumOptions = Tst_GetAnswersQst (QstCod,&mysql_res,false); // Result: AnsInd,Answer,Correct,Feedback
2016-04-06 14:41:47 +02:00
/*
row[ 0] AnsInd
row[ 1] Answer
row[ 2] Feedback
row[ 3] ImageName
row[ 4] ImageTitle
2016-04-15 02:33:16 +02:00
row[ 5] ImageURL
row[ 6] Correct
2016-04-06 14:41:47 +02:00
*/
2014-12-01 23:55:08 +01:00
/***** Write the answers *****/
switch (Gbl.Test.AnswerType)
{
case Tst_ANS_INT:
Tst_CheckIfNumberOfAnswersIsOne ();
row = mysql_fetch_row (mysql_res);
2016-01-18 00:33:52 +01:00
fprintf (Gbl.F.Out,"<span class=\"TEST_EDI\">(%ld)</span>",
2014-12-01 23:55:08 +01:00
Tst_GetIntAnsFromStr (row[1]));
break;
case Tst_ANS_FLOAT:
if (Gbl.Test.Answer.NumOptions != 2)
Lay_ShowErrorAndExit ("Wrong float range.");
for (i = 0;
i < 2;
i++)
{
row = mysql_fetch_row (mysql_res);
FloatNum[i] = Tst_GetFloatAnsFromStr (row[1]);
}
2016-01-18 00:33:52 +01:00
fprintf (Gbl.F.Out,"<span class=\"TEST_EDI\">([%lg; %lg])</span>",
2014-12-01 23:55:08 +01:00
FloatNum[0],FloatNum[1]);
break;
case Tst_ANS_TRUE_FALSE:
Tst_CheckIfNumberOfAnswersIsOne ();
row = mysql_fetch_row (mysql_res);
2016-01-18 00:33:52 +01:00
fprintf (Gbl.F.Out,"<span class=\"TEST_EDI\">(");
2014-12-01 23:55:08 +01:00
Tst_WriteAnsTF (row[1][0]);
2016-01-18 00:33:52 +01:00
fprintf (Gbl.F.Out,")</span>");
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
case Tst_ANS_TEXT:
2014-12-21 19:43:39 +01:00
fprintf (Gbl.F.Out,"<table class=\"CELLS_PAD_2\">");
2014-12-01 23:55:08 +01:00
for (NumOpt = 0;
NumOpt < Gbl.Test.Answer.NumOptions;
NumOpt++)
{
row = mysql_fetch_row (mysql_res);
/* Convert the answer (row[1]), that is in HTML, to rigorous HTML */
2017-03-08 03:48:23 +01:00
LengthAnswer = strlen (row[1]) * Str_MAX_BYTES_PER_CHAR;
2017-01-17 03:10:43 +01:00
if ((Answer = malloc (LengthAnswer + 1)) == NULL)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Not enough memory to store answer.");
2017-01-17 03:10:43 +01:00
Str_Copy (Answer,row[1],
LengthAnswer);
2014-12-01 23:55:08 +01:00
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Answer,LengthAnswer,false);
2016-04-06 14:41:47 +02:00
/* Convert the feedback (row[2]), that is in HTML, to rigorous HTML */
2014-12-01 23:55:08 +01:00
LengthFeedback = 0;
Feedback = NULL;
2016-04-06 14:41:47 +02:00
if (row[2])
if (row[2][0])
2014-12-01 23:55:08 +01:00
{
2017-03-08 03:48:23 +01:00
LengthFeedback = strlen (row[2]) * Str_MAX_BYTES_PER_CHAR;
2017-01-17 03:10:43 +01:00
if ((Feedback = malloc (LengthFeedback + 1)) == NULL)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Not enough memory to store feedback.");
2017-01-17 03:10:43 +01:00
Str_Copy (Feedback,row[2],
LengthFeedback);
2014-12-01 23:55:08 +01:00
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Feedback,LengthFeedback,false);
}
2016-04-04 21:51:21 +02:00
/* Copy image */
2016-04-15 02:33:16 +02:00
Img_GetImageNameTitleAndURLFromRow (row[3],row[4],row[5],&Gbl.Test.Answer.Options[NumOpt].Image);
2016-04-04 21:51:21 +02:00
2014-12-01 23:55:08 +01:00
/* Put an icon that indicates whether the answer is correct or wrong */
fprintf (Gbl.F.Out,"<tr>"
2016-04-04 21:51:21 +02:00
"<td class=\"BT%u\">",
Gbl.RowEvenOdd);
2016-09-07 18:48:10 +02:00
if (row[6][0] == 'Y')
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"<img src=\"%s/ok_on16x16.gif\""
2015-07-22 18:59:44 +02:00
" alt=\"%s\" title=\"%s\""
2016-11-14 10:05:41 +01:00
" class=\"ICO20x20\" />",
2015-07-22 18:59:44 +02:00
Gbl.Prefs.IconsURL,
Txt_TEST_Correct_answer,
Txt_TEST_Correct_answer);
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"</td>");
/* Write the number of option */
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"<td class=\"DAT_SMALL LEFT_TOP\">"
2014-12-26 14:45:14 +01:00
"%c)&nbsp;"
"</td>",
2014-12-01 23:55:08 +01:00
'a' + (char) NumOpt);
2016-04-06 14:41:47 +02:00
/* Write the text of the answer and the image */
2016-04-04 21:51:21 +02:00
fprintf (Gbl.F.Out,"<td class=\"LEFT_TOP\">"
"<div class=\"TEST_EDI\">"
"%s",
2014-12-01 23:55:08 +01:00
Answer);
2016-04-15 14:30:28 +02:00
Img_ShowImage (&Gbl.Test.Answer.Options[NumOpt].Image,
"TEST_IMG_EDIT_LIST_ANS_CONTAINER",
"TEST_IMG_EDIT_LIST_ANS");
2016-04-04 21:51:21 +02:00
fprintf (Gbl.F.Out,"</div>");
2014-12-01 23:55:08 +01:00
/* Write the text of the feedback */
2016-04-04 21:51:21 +02:00
fprintf (Gbl.F.Out,"<div class=\"TEST_EDI_LIGHT\">");
2014-12-01 23:55:08 +01:00
if (LengthFeedback)
2015-09-06 11:36:34 +02:00
fprintf (Gbl.F.Out,"%s",Feedback);
2016-04-04 21:51:21 +02:00
fprintf (Gbl.F.Out,"</div>"
"</td>"
2014-12-01 23:55:08 +01:00
"</tr>");
/* Free memory allocated for the answer and the feedback */
free ((void *) Answer);
if (LengthFeedback)
free ((void *) Feedback);
}
fprintf (Gbl.F.Out,"</table>");
break;
default:
break;
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/************** Write answers of a question when viewing a test **************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_WriteAnswersOfAQstViewTest (unsigned NumQst,long QstCod,bool Shuffle)
2014-12-01 23:55:08 +01:00
{
/***** Write parameter with question code *****/
Tst_WriteParamQstCod (NumQst,QstCod);
/***** Write answer depending on type *****/
switch (Gbl.Test.AnswerType)
{
case Tst_ANS_INT:
2016-11-21 13:15:08 +01:00
Tst_WriteIntAnsViewTest (NumQst);
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_FLOAT:
2016-11-21 13:15:08 +01:00
Tst_WriteFloatAnsViewTest (NumQst);
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_TRUE_FALSE:
Tst_WriteFormAnsTF (NumQst);
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
2016-11-21 13:15:08 +01:00
Tst_WriteChoiceAnsViewTest (NumQst,QstCod,Shuffle);
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_TEXT:
2016-11-21 13:15:08 +01:00
Tst_WriteTextAnsViewTest (NumQst);
2014-12-01 23:55:08 +01:00
break;
default:
break;
}
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/************* Write answers of a question when assessing a test *************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_WriteAnswersOfAQstAssessTest (unsigned NumQst,long QstCod,
2014-12-01 23:55:08 +01:00
double *ScoreThisQst,bool *AnswerIsNotBlank)
{
MYSQL_RES *mysql_res;
/***** Get answers of a question from database *****/
Gbl.Test.Answer.NumOptions = Tst_GetAnswersQst (QstCod,&mysql_res,false); // Result: AnsInd,Answer,Correct
2016-04-06 14:41:47 +02:00
/*
row[ 0] AnsInd
row[ 1] Answer
row[ 2] Feedback
row[ 3] ImageName
row[ 4] ImageTitle
2016-04-15 02:33:16 +02:00
row[ 5] ImageURL
row[ 6] Correct
2016-04-06 14:41:47 +02:00
*/
2014-12-01 23:55:08 +01:00
/***** Write answer depending on type *****/
switch (Gbl.Test.AnswerType)
{
case Tst_ANS_INT:
2016-11-21 13:15:08 +01:00
Tst_WriteIntAnsAssessTest (NumQst,mysql_res,ScoreThisQst,AnswerIsNotBlank);
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_FLOAT:
2016-11-21 13:15:08 +01:00
Tst_WriteFloatAnsAssessTest (NumQst,mysql_res,ScoreThisQst,AnswerIsNotBlank);
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_TRUE_FALSE:
2016-11-21 13:15:08 +01:00
Tst_WriteTFAnsAssessTest (NumQst,mysql_res,ScoreThisQst,AnswerIsNotBlank);
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
2016-11-21 13:15:08 +01:00
Tst_WriteChoiceAnsAssessTest (NumQst,mysql_res,ScoreThisQst,AnswerIsNotBlank);
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_TEXT:
2016-11-21 13:15:08 +01:00
Tst_WriteTextAnsAssessTest (NumQst,mysql_res,ScoreThisQst,AnswerIsNotBlank);
2014-12-01 23:55:08 +01:00
break;
default:
break;
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/************** Write false / true answer when viewing a test ****************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
static void Tst_WriteFormAnsTF (unsigned NumQst)
{
extern const char *Txt_TF_QST[2];
/***** Write selector for the answer *****/
fprintf (Gbl.F.Out,"<select name=\"Ans%06u\">"
"<option value=\"\" selected=\"selected\">&nbsp;</option>"
"<option value=\"T\">%s</option>"
"<option value=\"F\">%s</option>"
"</select>",
NumQst,
Txt_TF_QST[0],
Txt_TF_QST[1]);
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/************** Write false / true answer when viewing a test ****************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
void Tst_WriteAnsTF (char AnsTF)
{
extern const char *Txt_TF_QST[2];
switch (AnsTF)
{
case 'T': // true
fprintf (Gbl.F.Out,"%s",Txt_TF_QST[0]);
break;
case 'F': // false
fprintf (Gbl.F.Out,"%s",Txt_TF_QST[1]);
break;
default: // no answer
fprintf (Gbl.F.Out,"&nbsp;");
break;
}
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/************** Write false / true answer when assessing a test **************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_WriteTFAnsAssessTest (unsigned NumQst,MYSQL_RES *mysql_res,
2014-12-01 23:55:08 +01:00
double *ScoreThisQst,bool *AnswerIsNotBlank)
{
MYSQL_ROW row;
char AnsTF;
2016-04-06 14:41:47 +02:00
/*
row[ 0] AnsInd
row[ 1] Answer
row[ 2] Feedback
row[ 3] ImageName
row[ 4] ImageTitle
2016-04-15 02:33:16 +02:00
row[ 5] ImageURL
row[ 6] Correct
2016-04-06 14:41:47 +02:00
*/
2014-12-01 23:55:08 +01:00
/***** Check if number of rows is correct *****/
Tst_CheckIfNumberOfAnswersIsOne ();
/***** Get answer true or false *****/
row = mysql_fetch_row (mysql_res);
/***** Compute the mark for this question *****/
AnsTF = Gbl.Test.StrAnswersOneQst[NumQst][0];
if (AnsTF == '\0') // User has omitted the answer (the answer is blank)
{
*AnswerIsNotBlank = false;
*ScoreThisQst = 0.0;
}
else
{
*AnswerIsNotBlank = true;
if (AnsTF == row[1][0]) // Correct
*ScoreThisQst = 1.0;
else // Wrong
*ScoreThisQst = -1.0;
}
/***** Header with the title of each column *****/
2016-04-01 18:37:16 +02:00
fprintf (Gbl.F.Out,"<table class=\"CELLS_PAD_2\">"
2014-12-21 19:43:39 +01:00
"<tr>");
2014-12-01 23:55:08 +01:00
Tst_WriteHeadUserCorrect ();
fprintf (Gbl.F.Out,"</tr>");
/***** Write the user answer *****/
fprintf (Gbl.F.Out,"<tr>"
2015-09-02 23:26:18 +02:00
"<td class=\"%s CENTER_MIDDLE\">",
2014-12-01 23:55:08 +01:00
(Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_GOOD_BAD ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK) ?
(AnsTF == row[1][0] ? "ANS_OK" :
"ANS_BAD") :
"ANS");
Tst_WriteAnsTF (AnsTF);
fprintf (Gbl.F.Out,"</td>");
/***** Write the correct answer *****/
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"<td class=\"ANS CENTER_MIDDLE\">");
2014-12-01 23:55:08 +01:00
if (Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_GOOD_BAD ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK)
Tst_WriteAnsTF (row[1][0]);
else
fprintf (Gbl.F.Out,"?");
fprintf (Gbl.F.Out,"</td>"
"</tr>");
/***** Write the mark *****/
if (Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_RESULT ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_GOOD_BAD ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK)
{
Tst_WriteScoreStart (2);
if (AnsTF == '\0') // If user has omitted the answer
fprintf (Gbl.F.Out,"ANS\">%.2lf",0.0);
else if (AnsTF == row[1][0]) // If correct
fprintf (Gbl.F.Out,"ANS_OK\">%.2lf",1.0);
else // If wrong
fprintf (Gbl.F.Out,"ANS_BAD\">%.2lf",-1.0);
Tst_WriteScoreEnd ();
}
fprintf (Gbl.F.Out,"</table>");
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/******** Write single or multiple choice answer when viewing a test *********/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_WriteChoiceAnsViewTest (unsigned NumQst,long QstCod,bool Shuffle)
2014-12-01 23:55:08 +01:00
{
unsigned NumOpt;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned Index;
bool ErrorInIndex = false;
2017-01-28 15:58:46 +01:00
char ParamName[3 + 6 + 1];
2014-12-01 23:55:08 +01:00
/***** Get answers of a question from database *****/
Gbl.Test.Answer.NumOptions = Tst_GetAnswersQst (QstCod,&mysql_res,Shuffle); // Result: AnsInd,Answer,Correct
2016-04-06 14:41:47 +02:00
/*
row[ 0] AnsInd
row[ 1] Answer
row[ 2] Feedback
row[ 3] ImageName
row[ 4] ImageTitle
2016-04-15 02:33:16 +02:00
row[ 5] ImageURL
row[ 6] Correct
2016-04-06 14:41:47 +02:00
*/
2016-04-06 19:26:09 +02:00
/***** Start of table *****/
fprintf (Gbl.F.Out,"<table class=\"CELLS_PAD_2\">");
2014-12-01 23:55:08 +01:00
for (NumOpt = 0;
NumOpt < Gbl.Test.Answer.NumOptions;
NumOpt++)
{
/***** Get next answer *****/
row = mysql_fetch_row (mysql_res);
/***** Allocate memory for text in this choice answer *****/
if (!Tst_AllocateTextChoiceAnswer (NumOpt))
Lay_ShowErrorAndExit (Gbl.Message);
/***** Assign index (row[0]). Index is 0,1,2,3... if no shuffle or 1,3,0,2... (example) if shuffle *****/
// Index is got from databse query
if (sscanf (row[0],"%u",&Index) == 1)
{
if (Index >= Tst_MAX_OPTIONS_PER_QUESTION)
ErrorInIndex = true;
}
else
ErrorInIndex = true;
if (ErrorInIndex)
2016-11-21 13:15:08 +01:00
Lay_ShowErrorAndExit ("Wrong index of answer when showing a test.");
2014-12-01 23:55:08 +01:00
/***** Copy text (row[1]) and convert it, that is in HTML, to rigorous HTML ******/
2017-01-15 18:02:52 +01:00
Str_Copy (Gbl.Test.Answer.Options[NumOpt].Text,row[1],
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2014-12-01 23:55:08 +01:00
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
2017-01-15 18:02:52 +01:00
Gbl.Test.Answer.Options[NumOpt].Text,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false);
2014-12-01 23:55:08 +01:00
2016-04-04 21:51:21 +02:00
/***** Copy image *****/
2016-04-15 02:33:16 +02:00
Img_GetImageNameTitleAndURLFromRow (row[3],row[4],row[5],&Gbl.Test.Answer.Options[NumOpt].Image);
2016-04-04 21:51:21 +02:00
2014-12-01 23:55:08 +01:00
/***** Write selectors and letter of this option *****/
fprintf (Gbl.F.Out,"<tr>"
2015-09-02 23:26:18 +02:00
"<td class=\"LEFT_TOP\">");
2014-12-01 23:55:08 +01:00
sprintf (ParamName,"Ind%06u",NumQst);
Par_PutHiddenParamUnsigned (ParamName,Index);
fprintf (Gbl.F.Out,"<input type=\"");
if (Gbl.Test.AnswerType == Tst_ANS_UNIQUE_CHOICE)
2016-12-26 15:17:30 +01:00
fprintf (Gbl.F.Out,"radio\""
" onclick=\"selectUnselectRadio(this,this.form.Ans%06u,%u);\"",
2014-12-01 23:55:08 +01:00
NumQst,Gbl.Test.Answer.NumOptions);
else // Gbl.Test.AnswerType == Tst_ANS_MULTIPLE_CHOICE
fprintf (Gbl.F.Out,"checkbox\"");
2016-12-26 15:17:30 +01:00
fprintf (Gbl.F.Out," id=\"Ans%06u_%u\" name=\"Ans%06u\" value=\"%u\" />"
"</td>",
NumQst,NumOpt,
2016-05-13 14:29:34 +02:00
NumQst,Index);
2016-12-26 15:17:30 +01:00
fprintf (Gbl.F.Out,"<td class=\"LEFT_TOP\">"
"<label for=\"Ans%06u_%u\" class=\"TEST\">"
2014-12-26 14:45:14 +01:00
"%c)&nbsp;"
2016-12-26 15:17:30 +01:00
"</label>"
2014-12-26 14:45:14 +01:00
"</td>",
2016-12-26 15:17:30 +01:00
NumQst,NumOpt,
'a' + (char) NumOpt);
2014-12-01 23:55:08 +01:00
/***** Write the option text *****/
2016-12-26 15:17:30 +01:00
fprintf (Gbl.F.Out,"<td class=\"LEFT_TOP\">"
"<label for=\"Ans%06u_%u\" class=\"TEST_EXA\">"
"%s"
"</label>",
NumQst,NumOpt,
2014-12-01 23:55:08 +01:00
Gbl.Test.Answer.Options[NumOpt].Text);
2016-04-15 14:30:28 +02:00
Img_ShowImage (&Gbl.Test.Answer.Options[NumOpt].Image,
"TEST_IMG_SHOW_ANS_CONTAINER",
"TEST_IMG_SHOW_ANS");
2016-04-04 21:51:21 +02:00
fprintf (Gbl.F.Out,"</td>"
"</tr>");
2014-12-01 23:55:08 +01:00
}
/***** End of table *****/
fprintf (Gbl.F.Out,"</table>");
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/******* Write single or multiple choice answer when assessing a test ********/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_WriteChoiceAnsAssessTest (unsigned NumQst,MYSQL_RES *mysql_res,
2014-12-01 23:55:08 +01:00
double *ScoreThisQst,bool *AnswerIsNotBlank)
{
extern const char *Txt_TEST_User_answer;
extern const char *Txt_TEST_Correct_answer;
unsigned NumOpt;
MYSQL_ROW row;
2017-01-28 15:58:46 +01:00
char StrOneIndex[10 + 1];
2014-12-01 23:55:08 +01:00
const char *Ptr;
unsigned Indexes[Tst_MAX_OPTIONS_PER_QUESTION]; // Indexes of all answers of this question
int AnsUsr;
bool AnswersUsr[Tst_MAX_OPTIONS_PER_QUESTION];
unsigned NumOptTotInQst = 0;
unsigned NumOptCorrInQst = 0;
unsigned NumAnsGood = 0;
unsigned NumAnsBad = 0;
/***** Get text and correctness of answers for this question
from database (one row per answer) *****/
/*
2016-04-06 14:41:47 +02:00
row[ 0] AnsInd
row[ 1] Answer
row[ 2] Feedback
row[ 3] ImageName
row[ 4] ImageTitle
2016-04-15 02:33:16 +02:00
row[ 5] ImageURL
row[ 6] Correct
2014-12-01 23:55:08 +01:00
*/
for (NumOpt = 0;
NumOpt < Gbl.Test.Answer.NumOptions;
NumOpt++)
{
/***** Get next answer *****/
row = mysql_fetch_row (mysql_res);
/***** Allocate memory for text in this choice option *****/
if (!Tst_AllocateTextChoiceAnswer (NumOpt))
Lay_ShowErrorAndExit (Gbl.Message);
/***** Copy answer text (row[1]) and convert it,
that is in HTML, to rigorous HTML ******/
2017-01-15 18:02:52 +01:00
Str_Copy (Gbl.Test.Answer.Options[NumOpt].Text,row[1],
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2014-12-01 23:55:08 +01:00
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
2017-01-15 18:02:52 +01:00
Gbl.Test.Answer.Options[NumOpt].Text,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false);
2014-12-01 23:55:08 +01:00
2016-04-06 14:41:47 +02:00
/***** Copy answer feedback (row[2]) and convert it,
2014-12-01 23:55:08 +01:00
that is in HTML, to rigorous HTML ******/
if (Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK)
2016-04-06 14:41:47 +02:00
if (row[2])
if (row[2][0])
2014-12-01 23:55:08 +01:00
{
2017-01-15 18:02:52 +01:00
Str_Copy (Gbl.Test.Answer.Options[NumOpt].Feedback,row[2],
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2014-12-01 23:55:08 +01:00
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
2017-01-15 18:02:52 +01:00
Gbl.Test.Answer.Options[NumOpt].Feedback,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false);
2014-12-01 23:55:08 +01:00
}
2016-04-04 21:51:21 +02:00
/***** Copy image *****/
2016-04-15 02:33:16 +02:00
Img_GetImageNameTitleAndURLFromRow (row[3],row[4],row[5],&Gbl.Test.Answer.Options[NumOpt].Image);
2016-04-04 21:51:21 +02:00
2016-04-15 02:33:16 +02:00
/***** Assign correctness (row[6]) of this answer (this option) *****/
2016-09-07 18:48:10 +02:00
Gbl.Test.Answer.Options[NumOpt].Correct = (row[6][0] == 'Y');
2014-12-01 23:55:08 +01:00
}
/***** Get indexes for this question from string *****/
for (NumOpt = 0, Ptr = Gbl.Test.StrIndexesOneQst[NumQst];
NumOpt < Gbl.Test.Answer.NumOptions;
NumOpt++)
{
Par_GetNextStrUntilSeparParamMult (&Ptr,StrOneIndex,10);
if (sscanf (StrOneIndex,"%u",&(Indexes[NumOpt])) != 1)
2016-11-21 13:15:08 +01:00
Lay_ShowErrorAndExit ("Wrong index of answer when assessing a test.");
2014-12-01 23:55:08 +01:00
}
/***** Get the user's answers for this question from string *****/
for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
AnswersUsr[NumOpt] = false;
for (NumOpt = 0, Ptr = Gbl.Test.StrAnswersOneQst[NumQst];
NumOpt < Gbl.Test.Answer.NumOptions;
NumOpt++)
if (*Ptr)
{
Par_GetNextStrUntilSeparParamMult (&Ptr,StrOneIndex,10);
if (sscanf (StrOneIndex,"%d",&AnsUsr) != 1)
Lay_ShowErrorAndExit ("Bad user's answer.");
if (AnsUsr < 0 || AnsUsr >= Tst_MAX_OPTIONS_PER_QUESTION)
Lay_ShowErrorAndExit ("Bad user's answer.");
AnswersUsr[AnsUsr] = true;
}
/***** Start of table *****/
2016-04-01 18:37:16 +02:00
fprintf (Gbl.F.Out,"<table class=\"CELLS_PAD_2\">"
2014-12-01 23:55:08 +01:00
"<tr>");
Tst_WriteHeadUserCorrect ();
2014-12-26 14:45:14 +01:00
fprintf (Gbl.F.Out,"<td></td>"
"<td></td>"
2014-12-01 23:55:08 +01:00
"</tr>");
/***** Write answers (one row per answer) *****/
for (NumOpt = 0;
NumOpt < Gbl.Test.Answer.NumOptions;
NumOpt++)
{
/* Draw icon depending on user's answer */
fprintf (Gbl.F.Out,"<tr>"
2015-09-02 23:26:18 +02:00
"<td class=\"CENTER_TOP\">");
2014-12-01 23:55:08 +01:00
if (AnswersUsr[Indexes[NumOpt]] == true) // This answer has been selected by the user
fprintf (Gbl.F.Out,"<img src=\"%s/%s16x16.gif\""
2015-07-22 18:59:44 +02:00
" alt=\"%s\" title=\"%s\""
2016-11-14 10:05:41 +01:00
" class=\"ICO20x20\" />",
2014-12-01 23:55:08 +01:00
Gbl.Prefs.IconsURL,
(Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_GOOD_BAD ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK) ?
(Gbl.Test.Answer.Options[Indexes[NumOpt]].Correct ? "ok_green" :
"ok_red") :
"ok_on",
2015-07-22 18:59:44 +02:00
Txt_TEST_User_answer,
Txt_TEST_User_answer);
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"</td>");
/* Draw icon that indicates whether the answer is correct */
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"<td class=\"ANS CENTER_TOP\">");
2014-12-01 23:55:08 +01:00
if (Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_GOOD_BAD ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK)
{
if (Gbl.Test.Answer.Options[Indexes[NumOpt]].Correct)
fprintf (Gbl.F.Out,"<img src=\"%s/ok_on16x16.gif\""
2015-07-22 18:59:44 +02:00
" alt=\"%s\" title=\"%s\""
2016-11-14 10:05:41 +01:00
" class=\"ICO20x20\" />",
2014-12-01 23:55:08 +01:00
Gbl.Prefs.IconsURL,
2015-07-22 18:59:44 +02:00
Txt_TEST_Correct_answer,
Txt_TEST_Correct_answer);
2014-12-01 23:55:08 +01:00
}
else
fprintf (Gbl.F.Out,"?");
fprintf (Gbl.F.Out,"</td>");
/* Answer letter (a, b, c,...) */
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"<td class=\"TEST LEFT_TOP\">"
2014-12-26 14:45:14 +01:00
"%c)&nbsp;"
"</td>",
2014-12-01 23:55:08 +01:00
'a' + (char) NumOpt);
/* Answer text and feedback */
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"<td class=\"LEFT_TOP\">"
2015-09-06 11:36:34 +02:00
"<div class=\"TEST_EXA\">"
2016-04-04 21:51:21 +02:00
"%s",
2014-12-01 23:55:08 +01:00
Gbl.Test.Answer.Options[Indexes[NumOpt]].Text);
2016-04-15 14:30:28 +02:00
Img_ShowImage (&Gbl.Test.Answer.Options[Indexes[NumOpt]].Image,
"TEST_IMG_SHOW_ANS_CONTAINER",
"TEST_IMG_SHOW_ANS");
2016-04-04 21:51:21 +02:00
fprintf (Gbl.F.Out,"</div>");
2014-12-01 23:55:08 +01:00
if (Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK)
if (Gbl.Test.Answer.Options[Indexes[NumOpt]].Feedback)
if (Gbl.Test.Answer.Options[Indexes[NumOpt]].Feedback[0])
2015-09-06 11:36:34 +02:00
fprintf (Gbl.F.Out,"<div class=\"TEST_EXA_LIGHT\">"
2015-04-11 13:05:44 +02:00
"%s"
"</div>",
2014-12-01 23:55:08 +01:00
Gbl.Test.Answer.Options[Indexes[NumOpt]].Feedback);
fprintf (Gbl.F.Out,"</td>"
"</tr>");
NumOptTotInQst++;
if (AnswersUsr[Indexes[NumOpt]] == true) // This answer has been selected by the user
{
if (Gbl.Test.Answer.Options[Indexes[NumOpt]].Correct)
NumAnsGood++;
else
NumAnsBad++;
}
if (Gbl.Test.Answer.Options[Indexes[NumOpt]].Correct)
NumOptCorrInQst++;
}
/***** The answer is blank? *****/
*AnswerIsNotBlank = NumAnsGood != 0 || NumAnsBad != 0;
/***** Compute and write the total score of this question *****/
if (*AnswerIsNotBlank)
{
/* Compute the score */
if (Gbl.Test.AnswerType == Tst_ANS_UNIQUE_CHOICE)
{
if (NumOptTotInQst >= 2) // It should be 2 options at least
*ScoreThisQst = (double) NumAnsGood -
(double) NumAnsBad / (double) (NumOptTotInQst - 1);
else // 0 or 1 options (impossible)
*ScoreThisQst = (double) NumAnsGood;
}
else // Gbl.Test.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)
*ScoreThisQst = (double) NumAnsGood / (double) NumOptCorrInQst -
(double) NumAnsBad / (double) (NumOptTotInQst-NumOptCorrInQst);
else // Si todas the opciones son correctas (caso raro)
*ScoreThisQst = (double) NumAnsGood / (double) NumOptCorrInQst;
}
else
{
if (NumOptTotInQst) // There are options but none is correct (extrange case)
*ScoreThisQst = - (double) NumAnsBad / (double) NumOptTotInQst;
else // There are no options (impossible!)
*ScoreThisQst = 0.0;
}
}
}
else // Answer is blank
*ScoreThisQst = 0.0;
/* Write the score */
if (Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_RESULT ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_GOOD_BAD ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK)
{
Tst_WriteScoreStart (4);
if (*ScoreThisQst == 0.0)
fprintf (Gbl.F.Out,"ANS");
else if (*ScoreThisQst > 0.0)
fprintf (Gbl.F.Out,"ANS_OK");
else
fprintf (Gbl.F.Out,"ANS_BAD");
fprintf (Gbl.F.Out,"\">%.2lf",*ScoreThisQst);
Tst_WriteScoreEnd ();
}
/***** End of table *****/
fprintf (Gbl.F.Out,"</table>");
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/******************** Write text answer when viewing a test ******************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_WriteTextAnsViewTest (unsigned NumQst)
2014-12-01 23:55:08 +01:00
{
/***** Write input field for the answer *****/
2015-10-23 01:31:08 +02:00
fprintf (Gbl.F.Out,"<input type=\"text\" name=\"Ans%06u\""
" size=\"40\" maxlength=\"%u\" value=\"\" />",
2017-03-13 19:02:15 +01:00
NumQst,Tst_MAX_BYTES_ANSWERS_ONE_QST);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/***************** Write text answer when assessing a test *******************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_WriteTextAnsAssessTest (unsigned NumQst,MYSQL_RES *mysql_res,
2014-12-01 23:55:08 +01:00
double *ScoreThisQst,bool *AnswerIsNotBlank)
{
unsigned NumOpt;
MYSQL_ROW row;
2017-03-13 19:02:15 +01:00
char TextAnsUsr[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1];
char TextAnsOK[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1];
2014-12-01 23:55:08 +01:00
bool Correct = false;
2016-04-06 14:41:47 +02:00
/*
row[ 0] AnsInd
row[ 1] Answer
row[ 2] Feedback
row[ 3] ImageName
row[ 4] ImageTitle
2016-04-15 02:33:16 +02:00
row[ 5] ImageURL
row[ 6] Correct
2016-04-06 14:41:47 +02:00
*/
2014-12-01 23:55:08 +01:00
/***** Get text and correctness of answers for this question from database (one row per answer) *****/
for (NumOpt = 0;
NumOpt < Gbl.Test.Answer.NumOptions;
NumOpt++)
{
/***** Get next answer *****/
row = mysql_fetch_row (mysql_res);
/***** Allocate memory for text in this choice answer *****/
if (!Tst_AllocateTextChoiceAnswer (NumOpt))
Lay_ShowErrorAndExit (Gbl.Message);
/***** Copy answer text (row[1]) and convert it, that is in HTML, to rigorous HTML ******/
2017-01-15 18:02:52 +01:00
Str_Copy (Gbl.Test.Answer.Options[NumOpt].Text,row[1],
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Gbl.Test.Answer.Options[NumOpt].Text,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false);
2014-12-01 23:55:08 +01:00
2016-04-06 14:41:47 +02:00
/***** Copy answer feedback (row[2]) and convert it, that is in HTML, to rigorous HTML ******/
2014-12-01 23:55:08 +01:00
if (Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK)
2016-04-06 14:41:47 +02:00
if (row[2])
if (row[2][0])
2014-12-01 23:55:08 +01:00
{
2017-01-15 18:02:52 +01:00
Str_Copy (Gbl.Test.Answer.Options[NumOpt].Feedback,row[2],
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2014-12-01 23:55:08 +01:00
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
2017-01-15 18:02:52 +01:00
Gbl.Test.Answer.Options[NumOpt].Feedback,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false);
2014-12-01 23:55:08 +01:00
}
2016-04-15 02:33:16 +02:00
/***** Assign correctness (row[6]) of this answer (this option) *****/
2016-09-07 18:48:10 +02:00
Gbl.Test.Answer.Options[NumOpt].Correct = (row[6][0] == 'Y');
2014-12-01 23:55:08 +01:00
}
/***** Header with the title of each column *****/
2016-04-01 18:37:16 +02:00
fprintf (Gbl.F.Out,"<table class=\"CELLS_PAD_2\">"
2014-12-01 23:55:08 +01:00
"<tr>");
Tst_WriteHeadUserCorrect ();
fprintf (Gbl.F.Out,"</tr>");
/***** Write the user answer *****/
fprintf (Gbl.F.Out,"<tr>"
2015-09-02 23:26:18 +02:00
"<td class=\"");
2014-12-26 14:45:14 +01:00
if (Gbl.Test.StrAnswersOneQst[NumQst][0]) // If user has answered the question
2014-12-01 23:55:08 +01:00
{
/* Filter the user answer */
2017-01-15 22:58:26 +01:00
Str_Copy (TextAnsUsr,Gbl.Test.StrAnswersOneQst[NumQst],
2017-03-13 19:02:15 +01:00
Tst_MAX_BYTES_ANSWERS_ONE_QST);
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 */
Str_ReplaceSeveralSpacesForOne (TextAnsUsr);
2014-12-01 23:55:08 +01:00
Str_ConvertToComparable (TextAnsUsr);
for (NumOpt = 0;
NumOpt < Gbl.Test.Answer.NumOptions;
NumOpt++)
{
/* Filter this correct answer */
2017-01-15 22:58:26 +01:00
Str_Copy (TextAnsOK,Gbl.Test.Answer.Options[NumOpt].Text,
2017-03-13 19:02:15 +01:00
Tst_MAX_BYTES_ANSWERS_ONE_QST);
2014-12-01 23:55:08 +01:00
Str_ConvertToComparable (TextAnsOK);
/* Check is user answer is correct */
if (!strcoll (TextAnsUsr,TextAnsOK))
{
Correct = true;
break;
}
}
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"%s CENTER_TOP\">"
2014-12-26 14:45:14 +01:00
"&nbsp;%s&nbsp;",
2014-12-01 23:55:08 +01:00
(Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_GOOD_BAD ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK) ?
(Correct ? "ANS_OK" :
"ANS_BAD") :
"ANS",
Gbl.Test.StrAnswersOneQst[NumQst]);
}
2014-12-26 14:45:14 +01:00
else // If user has omitted the answer
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"ANS CENTER_TOP\">"
2014-12-26 14:45:14 +01:00
"&nbsp;");
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"</td>");
/***** Write the correct answers *****/
if (Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_GOOD_BAD ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK)
{
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"<td class=\"CENTER_TOP\">"
2016-04-01 18:37:16 +02:00
"<table class=\"CELLS_PAD_2\">");
2014-12-01 23:55:08 +01:00
for (NumOpt = 0;
NumOpt < Gbl.Test.Answer.NumOptions;
NumOpt++)
{
/* Answer letter (a, b, c,...) */
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"<td class=\"TEST LEFT_TOP\">"
2014-12-26 14:45:14 +01:00
"%c)&nbsp;"
"</td>",
2014-12-01 23:55:08 +01:00
'a' + (char) NumOpt);
/* Answer text and feedback */
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"<td class=\"LEFT_TOP\">"
2015-09-06 11:36:34 +02:00
"<div class=\"ANS\">"
2014-12-26 14:45:14 +01:00
"%s"
2015-04-11 13:05:44 +02:00
"</div>",
2014-12-01 23:55:08 +01:00
Gbl.Test.Answer.Options[NumOpt].Text);
if (Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK)
if (Gbl.Test.Answer.Options[NumOpt].Feedback)
if (Gbl.Test.Answer.Options[NumOpt].Feedback[0])
2015-09-06 11:36:34 +02:00
fprintf (Gbl.F.Out,"<div class=\"TEST_EXA_LIGHT\">"
2015-04-11 13:05:44 +02:00
"%s"
"</div>",
2014-12-01 23:55:08 +01:00
Gbl.Test.Answer.Options[NumOpt].Feedback);
fprintf (Gbl.F.Out,"</td>"
"</tr>");
}
fprintf (Gbl.F.Out,"</table>");
}
else
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"<td class=\"ANS CENTER_TOP\">"
2014-12-26 14:45:14 +01:00
"?");
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"</td>"
"</tr>");
/***** Compute the mark *****/
if (!Gbl.Test.StrAnswersOneQst[NumQst][0]) // If user has omitted the answer
{
*AnswerIsNotBlank = false;
*ScoreThisQst = 0.0;
}
else
{
*AnswerIsNotBlank = true;
if (Correct) // If correct
*ScoreThisQst = 1.0;
else // If wrong
*ScoreThisQst = 0.0;
}
/***** Write the mark *****/
if (Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_RESULT ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_GOOD_BAD ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK)
{
Tst_WriteScoreStart (4);
if (!Gbl.Test.StrAnswersOneQst[NumQst][0]) // If user has omitted the answer
fprintf (Gbl.F.Out,"ANS\">%.2lf",0.0);
else if (Correct) // If correct
fprintf (Gbl.F.Out,"ANS_OK\">%.2lf",1.0);
else // If wrong
fprintf (Gbl.F.Out,"ANS_BAD\">%.2lf",0.0);
Tst_WriteScoreEnd ();
}
fprintf (Gbl.F.Out,"</table>");
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/****************** Write integer answer when viewing a test *****************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_WriteIntAnsViewTest (unsigned NumQst)
2014-12-01 23:55:08 +01:00
{
/***** Write input field for the answer *****/
2015-10-23 01:31:08 +02:00
fprintf (Gbl.F.Out,"<input type=\"text\" name=\"Ans%06u\""
" size=\"11\" maxlength=\"11\" value=\"\" />",
2014-12-01 23:55:08 +01:00
NumQst);
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/**************** Write integer answer when assessing a test *****************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_WriteIntAnsAssessTest (unsigned NumQst,MYSQL_RES *mysql_res,
2014-12-01 23:55:08 +01:00
double *ScoreThisQst,bool *AnswerIsNotBlank)
{
MYSQL_ROW row;
long IntAnswerUsr,IntAnswerCorr;
2016-04-06 14:41:47 +02:00
/*
row[ 0] AnsInd
row[ 1] Answer
row[ 2] Feedback
row[ 3] ImageName
row[ 4] ImageTitle
2016-04-15 02:33:16 +02:00
row[ 5] ImageURL
row[ 6] Correct
2016-04-06 14:41:47 +02:00
*/
2014-12-01 23:55:08 +01:00
/***** Check if number of rows is correct *****/
Tst_CheckIfNumberOfAnswersIsOne ();
/***** 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.");
/***** Header with the title of each column *****/
2016-04-01 18:37:16 +02:00
fprintf (Gbl.F.Out,"<table class=\"CELLS_PAD_2\">"
2014-12-01 23:55:08 +01:00
"<tr>");
Tst_WriteHeadUserCorrect ();
fprintf (Gbl.F.Out,"</tr>");
/***** Write the user answer *****/
fprintf (Gbl.F.Out,"<tr>"
2015-09-02 23:26:18 +02:00
"<td class=\"");
2014-12-01 23:55:08 +01:00
if (Gbl.Test.StrAnswersOneQst[NumQst][0]) // If user has answered the question
{
if (sscanf (Gbl.Test.StrAnswersOneQst[NumQst],"%ld",&IntAnswerUsr) == 1)
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"%s CENTER_MIDDLE\">"
2014-12-26 14:45:14 +01:00
"&nbsp;%ld&nbsp;",
2014-12-01 23:55:08 +01:00
(Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_GOOD_BAD ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK) ?
(IntAnswerUsr == IntAnswerCorr ? "ANS_OK" :
"ANS_BAD") :
"ANS",
IntAnswerUsr);
else
{
Gbl.Test.StrAnswersOneQst[NumQst][0] = '\0';
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"ANS CENTER_MIDDLE\">"
2014-12-26 14:45:14 +01:00
"?");
2014-12-01 23:55:08 +01:00
}
}
else // If user has omitted the answer
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"ANS CENTER_MIDDLE\">&nbsp;");
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"</td>");
/***** Write the correct answer *****/
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"<td class=\"ANS CENTER_MIDDLE\">");
2014-12-01 23:55:08 +01:00
if (Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_GOOD_BAD ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK)
fprintf (Gbl.F.Out,"&nbsp;%ld&nbsp;",IntAnswerCorr);
else
fprintf (Gbl.F.Out,"?");
fprintf (Gbl.F.Out,"</td>"
"</tr>");
/***** Compute the score *****/
if (!Gbl.Test.StrAnswersOneQst[NumQst][0]) // If user has omitted the answer
{
*AnswerIsNotBlank = false;
*ScoreThisQst = 0.0;
}
else
{
*AnswerIsNotBlank = true;
if (IntAnswerUsr == IntAnswerCorr) // If correct
*ScoreThisQst = 1.0;
else // If wrong
*ScoreThisQst = 0.0;
}
/***** Write the score *****/
if (Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_RESULT ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_GOOD_BAD ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK)
{
Tst_WriteScoreStart (2);
if (!Gbl.Test.StrAnswersOneQst[NumQst][0]) // If user has omitted the answer
fprintf (Gbl.F.Out,"ANS\">%.2lf",0.0);
else if (IntAnswerUsr == IntAnswerCorr) // If correct
fprintf (Gbl.F.Out,"ANS_OK\">%.2lf",1.0);
else // If wrong
fprintf (Gbl.F.Out,"ANS_BAD\">%.2lf",0.0);
Tst_WriteScoreEnd ();
}
fprintf (Gbl.F.Out,"</table>");
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/****************** Write float answer when viewing a test *******************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_WriteFloatAnsViewTest (unsigned NumQst)
2014-12-01 23:55:08 +01:00
{
/***** Write input field for the answer *****/
2015-10-23 01:31:08 +02:00
fprintf (Gbl.F.Out,"<input type=\"text\" name=\"Ans%06u\""
" size=\"11\" maxlength=\"%u\" value=\"\" />",
2014-12-01 23:55:08 +01:00
NumQst,Tst_MAX_BYTES_FLOAT_ANSWER);
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/***************** Write float answer when assessing a test ******************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_WriteFloatAnsAssessTest (unsigned NumQst,MYSQL_RES *mysql_res,
2014-12-01 23:55:08 +01:00
double *ScoreThisQst,bool *AnswerIsNotBlank)
{
MYSQL_ROW row;
unsigned i;
double FloatAnsUsr = 0.0,Tmp;
double FloatAnsCorr[2];
2016-04-06 14:41:47 +02:00
/*
row[ 0] AnsInd
row[ 1] Answer
row[ 2] Feedback
row[ 3] ImageName
row[ 4] ImageTitle
2016-04-15 02:33:16 +02:00
row[ 5] ImageURL
row[ 6] Correct
2016-04-06 14:41:47 +02:00
*/
2014-12-01 23:55:08 +01:00
/***** Check if number of rows is correct *****/
if (Gbl.Test.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++)
{
row = mysql_fetch_row (mysql_res);
FloatAnsCorr[i] = Tst_GetFloatAnsFromStr (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;
}
/***** Header with the title of each column *****/
2016-04-01 18:37:16 +02:00
fprintf (Gbl.F.Out,"<table class=\"CELLS_PAD_2\">"
2014-12-01 23:55:08 +01:00
"<tr>");
Tst_WriteHeadUserCorrect ();
fprintf (Gbl.F.Out,"</tr>");
/***** Write the user answer *****/
fprintf (Gbl.F.Out,"<tr>"
2015-09-02 23:26:18 +02:00
"<td class=\"");
2014-12-01 23:55:08 +01:00
if (Gbl.Test.StrAnswersOneQst[NumQst][0]) // If user has answered the question
{
FloatAnsUsr = Tst_GetFloatAnsFromStr (Gbl.Test.StrAnswersOneQst[NumQst]);
if (Gbl.Test.StrAnswersOneQst[NumQst][0]) // It's a correct floating point number
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"%s CENTER_MIDDLE\">&nbsp;%lg&nbsp;",
2014-12-01 23:55:08 +01:00
(Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_GOOD_BAD ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK) ?
((FloatAnsUsr >= FloatAnsCorr[0] &&
FloatAnsUsr <= FloatAnsCorr[1]) ? "ANS_OK" :
"ANS_BAD") :
"ANS",
FloatAnsUsr);
else // Not a floating point number
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"ANS CENTER_MIDDLE\">?");
2014-12-01 23:55:08 +01:00
}
else // If user has omitted the answer
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"ANS CENTER_MIDDLE\">&nbsp;");
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"</td>");
/***** Write the correct answer *****/
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"<td class=\"ANS CENTER_MIDDLE\">");
2014-12-01 23:55:08 +01:00
if (Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_GOOD_BAD ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK)
fprintf (Gbl.F.Out,"&nbsp;[%lg; %lg]&nbsp;",FloatAnsCorr[0],FloatAnsCorr[1]);
else
fprintf (Gbl.F.Out,"?");
fprintf (Gbl.F.Out,"</td>"
"</tr>");
/***** Compute mark *****/
if (!Gbl.Test.StrAnswersOneQst[NumQst][0]) // If user has omitted the answer
{
*AnswerIsNotBlank = false;
*ScoreThisQst = 0.0;
}
else
{
*AnswerIsNotBlank = true;
if (FloatAnsUsr >= FloatAnsCorr[0] &&
FloatAnsUsr <= FloatAnsCorr[1]) // If correct (inside the interval)
*ScoreThisQst = 1.0;
else // If wrong (outside the interval)
*ScoreThisQst = 0.0;
}
/***** Write mark *****/
if (Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_RESULT ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_EACH_GOOD_BAD ||
Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK)
{
Tst_WriteScoreStart (2);
if (!Gbl.Test.StrAnswersOneQst[NumQst][0]) // If user has omitted the answer
fprintf (Gbl.F.Out,"ANS\">%.2lf",0.0);
else if (FloatAnsUsr >= FloatAnsCorr[0] &&
FloatAnsUsr <= FloatAnsCorr[1]) // If correct (inside the interval)
fprintf (Gbl.F.Out,"ANS_OK\">%.2lf",1.0);
else // If wrong (outside the interval)
fprintf (Gbl.F.Out,"ANS_BAD\">%.2lf",0.0);
Tst_WriteScoreEnd ();
}
fprintf (Gbl.F.Out,"</table>");
}
/*****************************************************************************/
/********* Write head with two columns: ********/
/********* one for the user's answer and other for the correct answer ********/
/*****************************************************************************/
static void Tst_WriteHeadUserCorrect (void)
{
2016-06-15 14:42:28 +02:00
extern const char *Txt_User[Usr_NUM_SEXS];
2014-12-01 23:55:08 +01:00
extern const char *Txt_TST_Correct_ANSWER;
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"<td class=\"DAT_SMALL CENTER_MIDDLE\">"
2014-12-26 14:45:14 +01:00
"&nbsp;%s&nbsp;"
"</td>"
2015-09-02 23:26:18 +02:00
"<td class=\"DAT_SMALL CENTER_MIDDLE\">"
2014-12-26 14:45:14 +01:00
"&nbsp;%s&nbsp;"
"</td>",
2016-06-15 14:42:28 +02:00
Txt_User[Usr_SEX_UNKNOWN],Txt_TST_Correct_ANSWER);
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;
fprintf (Gbl.F.Out,"<tr>"
2015-09-02 23:26:18 +02:00
"<td colspan=\"%u\" class=\"DAT_SMALL LEFT_MIDDLE\">"
2014-12-01 23:55:08 +01:00
"%s: <span class=\"",
ColSpan,Txt_Score);
}
static void Tst_WriteScoreEnd (void)
{
fprintf (Gbl.F.Out,"</span>"
"</td>"
"</tr>");
}
/*****************************************************************************/
/*************** Write parameter with the code of a question *****************/
/*****************************************************************************/
static void Tst_WriteParamQstCod (unsigned NumQst,long QstCod)
{
2017-01-28 15:58:46 +01:00
char ParamName[3 + 6 + 1];
2014-12-01 23:55:08 +01:00
sprintf (ParamName,"Qst%06u",NumQst);
Par_PutHiddenParamLong (ParamName,QstCod);
}
/*****************************************************************************/
/********************* Check if number of answers is one *********************/
/*****************************************************************************/
void Tst_CheckIfNumberOfAnswersIsOne (void)
{
if (Gbl.Test.Answer.NumOptions != 1)
Lay_ShowErrorAndExit ("Wrong answer.");
}
/*****************************************************************************/
/************************* Get tags of a test question ***********************/
/*****************************************************************************/
unsigned long Tst_GetTagsQst (long QstCod,MYSQL_RES **mysql_res)
{
char Query[512];
/***** Get the tags of a question from database *****/
sprintf (Query,"SELECT tst_tags.TagTxt FROM tst_question_tags,tst_tags"
2017-03-24 01:09:27 +01:00
" WHERE tst_question_tags.QstCod=%ld"
2017-03-13 19:02:15 +01:00
" AND tst_question_tags.TagCod=tst_tags.TagCod"
2017-03-24 01:09:27 +01:00
" AND tst_tags.CrsCod=%ld"
2014-12-01 23:55:08 +01:00
" ORDER BY tst_question_tags.TagInd",
QstCod,Gbl.CurrentCrs.Crs.CrsCod);
return DB_QuerySELECT (Query,mysql_res,"can not get the tags of a question");
}
/*****************************************************************************/
/******************** Get and write tags of a test question ******************/
/*****************************************************************************/
static void Tst_GetAndWriteTagsQst (long QstCod)
{
extern const char *Txt_no_tags;
unsigned long NumRow,NumRows;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
if ((NumRows = Tst_GetTagsQst (QstCod,&mysql_res))) // Result: TagTxt
{
/***** Write the tags *****/
2016-04-01 18:37:16 +02:00
fprintf (Gbl.F.Out,"<table class=\"CELLS_PAD_2\">");
2014-12-01 23:55:08 +01:00
for (NumRow = 0;
NumRow < NumRows;
NumRow++)
{
row = mysql_fetch_row (mysql_res);
fprintf (Gbl.F.Out,"<tr>"
2015-09-02 23:26:18 +02:00
"<td class=\"DAT_SMALL LEFT_TOP\">"
2014-12-26 14:45:14 +01:00
"&nbsp;&#8226;&nbsp;"
"</td>"
2015-09-02 23:26:18 +02:00
"<td class=\"DAT_SMALL LEFT_TOP\">"
2014-12-26 14:45:14 +01:00
"%s"
"</td>"
2014-12-01 23:55:08 +01:00
"</tr>",
row[0]);
}
fprintf (Gbl.F.Out,"</table>");
}
else
fprintf (Gbl.F.Out,"<span class=\"DAT_SMALL\">&nbsp;(%s)&nbsp;</span>",
Txt_no_tags);
/***** 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
2016-04-05 02:59:34 +02:00
static bool Tst_GetParamsTst (void)
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;
2017-01-28 15:58:46 +01:00
char UnsignedStr[10 + 1];
2014-12-01 23:55:08 +01:00
unsigned UnsignedNum;
/***** Tags *****/
/* Get parameter that indicates whether all tags are selected */
2017-01-28 20:32:50 +01:00
Gbl.Test.Tags.All = Par_GetParToBool ("AllTags");
2014-12-01 23:55:08 +01:00
/* Get the tags */
2017-01-28 15:58:46 +01:00
if ((Gbl.Test.Tags.List = malloc (Tst_MAX_BYTES_TAGS_LIST + 1)) == NULL)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Not enough memory to store tags.");
2016-04-06 22:27:33 +02:00
Par_GetParMultiToText ("ChkTag",Gbl.Test.Tags.List,Tst_MAX_BYTES_TAGS_LIST);
2014-12-01 23:55:08 +01:00
/* Check number of tags selected */
if (Tst_CountNumTagsInList () == 0) // If no tags selected...
{ // ...write alert
Lay_ShowAlert (Lay_WARNING,Txt_You_must_select_one_ore_more_tags);
Error = true;
}
/***** Types of answer *****/
/* Get parameter that indicates if all types of answer are selected */
2017-01-28 20:32:50 +01:00
Gbl.Test.AllAnsTypes = Par_GetParToBool ("AllAnsTypes");
2014-12-01 23:55:08 +01:00
/* Get types of answer */
Par_GetParMultiToText ("AnswerType",Gbl.Test.ListAnsTypes,Tst_MAX_BYTES_LIST_ANSWER_TYPES);
/* Check number of types of answer */
if (Tst_CountNumAnswerTypesInList () == 0) // If no types of answer selected...
{ // ...write warning alert
Lay_ShowAlert (Lay_WARNING,Txt_You_must_select_one_ore_more_types_of_answer);
Error = true;
}
/***** Get other parameters, depending on action *****/
2016-01-17 15:10:54 +01:00
if (Gbl.Action.Act == ActSeeTst)
2014-12-01 23:55:08 +01:00
{
Tst_GetParamNumQst ();
if (Gbl.Test.NumQsts < Gbl.Test.Config.Min ||
Gbl.Test.NumQsts > Gbl.Test.Config.Max)
{
sprintf (Gbl.Message,Txt_The_number_of_questions_must_be_in_the_interval_X,
Gbl.Test.Config.Min,Gbl.Test.Config.Max);
Lay_ShowAlert (Lay_WARNING,Gbl.Message);
Error = true;
}
}
else
{
/* Get starting and ending dates */
Dat_GetIniEndDatesFromForm ();
/* Get ordering criteria */
Par_GetParMultiToText ("Order",UnsignedStr,10);
if (sscanf (UnsignedStr,"%u",&UnsignedNum) == 1)
2017-01-29 12:42:19 +01:00
Gbl.Test.SelectedOrder = (Tst_QuestionsOrder_t) ((UnsignedNum < Tst_NUM_TYPES_ORDER_QST) ? UnsignedNum :
2014-12-01 23:55:08 +01:00
0);
else
2017-01-29 12:42:19 +01:00
Gbl.Test.SelectedOrder = (Tst_QuestionsOrder_t) 0;
2014-12-01 23:55:08 +01:00
/* Get whether we must create the XML file or not */
Gbl.Test.XML.CreateXML = Tst_GetCreateXMLFromForm ();
}
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 *****/
/*****************************************************************************/
static void Tst_GetParamNumQst (void)
{
2017-01-29 21:41:08 +01:00
Gbl.Test.NumQsts = (unsigned)
Par_GetParToUnsignedLong ("NumQst",
(unsigned long) Gbl.Test.Config.Min,
(unsigned long) Gbl.Test.Config.Max,
(unsigned long) Gbl.Test.Config.Def);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/****************** Get whether to create XML file from form *****************/
/*****************************************************************************/
static bool Tst_GetCreateXMLFromForm (void)
{
2017-01-28 20:32:50 +01:00
return Par_GetParToBool ("CreateXML");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/***************** Count number of tags in the list of tags ******************/
/*****************************************************************************/
static int Tst_CountNumTagsInList (void)
{
const char *Ptr;
int 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
2016-04-06 22:27:33 +02:00
/***** Go over the list Gbl.Test.Tags.List counting the number of tags *****/
if (Gbl.Test.Tags.List)
2014-12-01 23:55:08 +01:00
{
2016-04-06 22:27:33 +02:00
Ptr = Gbl.Test.Tags.List;
2014-12-01 23:55:08 +01:00
while (*Ptr)
{
Par_GetNextStrUntilSeparParamMult (&Ptr,TagText,Tst_MAX_BYTES_TAG);
NumTags++;
}
}
return NumTags;
}
/*****************************************************************************/
/**** Count the number of types of answers in the list of types of answers ***/
/*****************************************************************************/
static int Tst_CountNumAnswerTypesInList (void)
{
const char *Ptr;
int NumAnsTypes = 0;
2017-01-28 15:58:46 +01:00
char UnsignedStr[10 + 1];
2014-12-01 23:55:08 +01:00
/***** Go over the list Gbl.Test.ListAnsTypes counting the number of types of answer *****/
Ptr = Gbl.Test.ListAnsTypes;
while (*Ptr)
{
Par_GetNextStrUntilSeparParamMult (&Ptr,UnsignedStr,10);
Tst_ConvertFromUnsignedStrToAnsTyp (UnsignedStr);
NumAnsTypes++;
}
return NumAnsTypes;
}
/*****************************************************************************/
/**************** Free memory allocated for the list of tags *****************/
/*****************************************************************************/
void Tst_FreeTagsList (void)
{
2016-04-06 22:27:33 +02:00
if (Gbl.Test.Tags.List)
2014-12-01 23:55:08 +01:00
{
2016-04-06 22:27:33 +02:00
free ((void *) Gbl.Test.Tags.List);
Gbl.Test.Tags.List = NULL;
Gbl.Test.Tags.Num = 0;
2014-12-01 23:55:08 +01:00
}
}
/*****************************************************************************/
/******************** Show form to edit one test question ********************/
/*****************************************************************************/
void Tst_ShowFormEditOneQst (void)
{
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 *****/
Tst_QstConstructor ();
Gbl.Test.QstCod = Tst_GetQstCod ();
2014-12-01 23:55:08 +01:00
Stem[0] = Feedback[0] = '\0';
2016-04-06 19:26:09 +02:00
if (Gbl.Test.QstCod > 0) // If question already exists in the database
Tst_GetQstDataFromDB (Stem,Feedback);
/***** Put form to edit question *****/
2014-12-01 23:55:08 +01:00
Tst_PutFormEditOneQst (Stem,Feedback);
2016-04-06 19:26:09 +02:00
/***** Destroy test question *****/
Tst_QstDestructor ();
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
2017-01-17 03:10:43 +01:00
static void Tst_PutFormEditOneQst (char Stem[Cns_MAX_BYTES_TEXT + 1],
char Feedback[Cns_MAX_BYTES_TEXT + 1])
2014-12-01 23:55:08 +01:00
{
2016-11-13 20:18:49 +01:00
extern const char *Hlp_ASSESSMENT_Tests;
2015-07-28 00:16:09 +02:00
extern const char *The_ClassForm[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;
extern const char *Txt_Stem;
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;
2014-12-01 23:55:08 +01:00
extern const char *Txt_Save;
2015-04-11 20:18:30 +02:00
extern const char *Txt_Create_question;
2015-04-11 13:05:44 +02:00
char Title[512];
2014-12-01 23:55:08 +01:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
2016-04-03 01:24:20 +02:00
unsigned long NumRows;
unsigned long NumRow;
2014-12-01 23:55:08 +01:00
unsigned NumOpt;
Tst_AnswerType_t AnsType;
unsigned NumTag;
bool TagNotFound;
bool OptionsDisabled;
2016-04-07 10:23:28 +02:00
bool AnswerHasContent;
2016-04-07 17:14:35 +02:00
bool DisplayRightColumn;
2014-12-01 23:55:08 +01:00
2016-04-05 10:05:52 +02:00
/***** Start frame *****/
2015-04-11 13:05:44 +02:00
if (Gbl.Test.QstCod > 0) // The question already has assigned a code
{
sprintf (Title,Txt_Question_code_X,Gbl.Test.QstCod);
2016-11-13 20:18:49 +01:00
Lay_StartRoundFrame (NULL,Title,
Tst_PutIconToRemoveOneQst,Hlp_ASSESSMENT_Tests);
2015-04-11 13:05:44 +02:00
}
else
2016-11-13 20:18:49 +01:00
Lay_StartRoundFrame (NULL,Txt_New_question,NULL,Hlp_ASSESSMENT_Tests);
2016-04-05 10:05:52 +02:00
/***** Start form *****/
Act_FormStart (ActRcvTstQst);
if (Gbl.Test.QstCod > 0) // The question already has assigned a code
2017-04-28 14:02:08 +02:00
Tst_PutParamQstCod ();
2016-04-05 10:05:52 +02:00
/***** Start table *****/
fprintf (Gbl.F.Out,"<table class=\"CELLS_PAD_2\">");
2015-04-11 13:05:44 +02:00
/***** Help for text editor *****/
fprintf (Gbl.F.Out,"<tr>"
"<td colspan=\"2\">");
Lay_HelpPlainEditor ();
fprintf (Gbl.F.Out,"</td>"
"</tr>");
2014-12-01 23:55:08 +01:00
/***** Get tags already existing for questions in current course *****/
NumRows = Tst_GetAllTagsFromCurrentCrs (&mysql_res);
/***** Write the tags *****/
fprintf (Gbl.F.Out,"<tr>"
2016-12-20 02:18:50 +01:00
"<td class=\"RIGHT_TOP %s\">"
2014-12-26 14:45:14 +01:00
"%s:"
"</td>"
2015-09-02 23:26:18 +02:00
"<td class=\"LEFT_TOP\">"
2016-04-01 18:37:16 +02:00
"<table class=\"CELLS_PAD_2\">",
2015-07-28 00:16:09 +02:00
The_ClassForm[Gbl.Prefs.Theme],Txt_Tags);
2014-12-01 23:55:08 +01:00
for (NumTag = 0;
NumTag < Tst_MAX_TAGS_PER_QUESTION;
NumTag++)
{
fprintf (Gbl.F.Out,"<tr>");
/***** Write the tags already existing in a selector *****/
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"<td class=\"LEFT_MIDDLE\">"
2014-12-01 23:55:08 +01:00
"<select id=\"SelDesc%u\" name=\"SelDesc%u\""
2016-03-29 10:24:14 +02:00
" class=\"TAG_SEL\" onchange=\"changeTxtTag('%u')\">",
2014-12-01 23:55:08 +01:00
NumTag,NumTag,NumTag);
fprintf (Gbl.F.Out,"<option value=\"\">&nbsp;</option>");
2017-04-25 14:48:47 +02:00
mysql_data_seek (mysql_res,0);
2014-12-01 23:55:08 +01:00
TagNotFound = true;
for (NumRow = 1;
NumRow <= NumRows;
NumRow++)
{
row = mysql_fetch_row (mysql_res);
fprintf (Gbl.F.Out,"<option value=\"%s\"",row[1]);
2016-04-06 22:27:33 +02:00
if (!strcasecmp (Gbl.Test.Tags.Txt[NumTag],row[1]))
2014-12-01 23:55:08 +01:00
{
fprintf (Gbl.F.Out," selected=\"selected\"");
TagNotFound = false;
}
fprintf (Gbl.F.Out,">%s</option>",row[1]);
}
/* If it's a new tag received from the form */
2016-04-06 22:27:33 +02:00
if (TagNotFound && Gbl.Test.Tags.Txt[NumTag][0])
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"<option value=\"%s\" selected=\"selected\">%s</option>",
2016-04-06 22:27:33 +02:00
Gbl.Test.Tags.Txt[NumTag],Gbl.Test.Tags.Txt[NumTag]);
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"<option value=\"\">[%s]</option>"
"</select>"
"</td>",
Txt_new_tag);
/***** Input of a new tag *****/
2015-09-02 23:26:18 +02:00
fprintf (Gbl.F.Out,"<td class=\"RIGHT_MIDDLE\">"
2015-10-23 01:31:08 +02:00
"<input type=\"text\" id=\"TagTxt%u\" name=\"TagTxt%u\""
2016-03-29 10:24:14 +02:00
" class=\"TAG_TXT\" maxlength=\"%u\" value=\"%s\""
2014-12-26 14:45:14 +01:00
" onchange=\"changeSelTag('%u')\" />"
2014-12-01 23:55:08 +01:00
"</td>",
2017-03-08 03:48:23 +01:00
NumTag,NumTag,Tst_MAX_CHARS_TAG,Gbl.Test.Tags.Txt[NumTag],NumTag);
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"</tr>");
}
fprintf (Gbl.F.Out,"</table>"
"</td>"
"</tr>");
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
2016-04-04 10:11:05 +02:00
/***** Stem and image *****/
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"<tr>"
2016-03-29 10:24:14 +02:00
"<td class=\"RIGHT_TOP\">"
2016-12-27 14:04:47 +01:00
"<label for=\"Stem\" class=\"%s\">%s:</label>"
2014-12-26 14:45:14 +01:00
"</td>"
2015-09-02 23:26:18 +02:00
"<td class=\"LEFT_TOP\">"
2016-12-20 02:18:50 +01:00
"<textarea id=\"Stem\" name=\"Stem\""
" class=\"STEM\" rows=\"5\" required=\"required\">"
2014-12-01 23:55:08 +01:00
"%s"
2016-04-04 10:11:05 +02:00
"</textarea><br />",
2015-07-28 00:16:09 +02:00
The_ClassForm[Gbl.Prefs.Theme],
2014-12-01 23:55:08 +01:00
Txt_Stem,
Stem);
2016-04-14 21:33:24 +02:00
Tst_PutFormToEditQstImage (&Gbl.Test.Image,-1,
2016-04-15 14:30:28 +02:00
"TEST_IMG_EDIT_ONE_STEM_CONTAINER",
2016-04-14 21:33:24 +02:00
"TEST_IMG_EDIT_ONE_STEM",
2016-04-06 14:41:47 +02:00
"STEM", // Title / attribution
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 *****/
2016-04-05 21:44:06 +02:00
fprintf (Gbl.F.Out,"<label class=\"%s\">"
2016-12-20 02:18:50 +01:00
"%s (%s):<br />"
2016-04-05 21:44:06 +02:00
"<textarea name=\"Feedback\" class=\"STEM\" rows=\"2\">",
2015-07-28 00:16:09 +02:00
The_ClassForm[Gbl.Prefs.Theme],
2016-04-05 17:33:33 +02:00
Txt_Feedback,Txt_optional);
2014-12-01 23:55:08 +01:00
if (Feedback)
if (Feedback[0])
fprintf (Gbl.F.Out,"%s",Feedback);
fprintf (Gbl.F.Out,"</textarea>"
2016-12-20 02:18:50 +01:00
"</label>"
2014-12-01 23:55:08 +01:00
"</td>"
"</tr>");
/***** Type of answer *****/
fprintf (Gbl.F.Out,"<tr>"
2016-12-20 02:18:50 +01:00
"<td class=\"RIGHT_TOP %s\">"
2014-12-26 14:45:14 +01:00
"%s:"
"</td>"
2015-07-28 00:16:09 +02:00
"<td class=\"%s LEFT_TOP\">",
The_ClassForm[Gbl.Prefs.Theme],
2014-12-01 23:55:08 +01:00
Txt_Type,
2015-07-28 00:16:09 +02:00
The_ClassForm[Gbl.Prefs.Theme]);
2014-12-01 23:55:08 +01:00
for (AnsType = (Tst_AnswerType_t) 0;
AnsType < Tst_NUM_ANS_TYPES;
AnsType++)
{
2016-12-20 02:18:50 +01:00
fprintf (Gbl.F.Out,"<label>"
"<input type=\"radio\" name=\"AnswerType\" value=\"%u\"",
2014-12-01 23:55:08 +01:00
(unsigned) AnsType);
if (AnsType == Gbl.Test.AnswerType)
fprintf (Gbl.F.Out," checked=\"checked\"");
2016-04-07 17:14:35 +02:00
fprintf (Gbl.F.Out," onclick=\"enableDisableAns(this.form);\" />"
2016-12-20 02:18:50 +01:00
"%s&nbsp;"
"</label><br />",
2014-12-01 23:55:08 +01:00
Txt_TST_STR_ANSWER_TYPES[AnsType]);
}
fprintf (Gbl.F.Out,"</td>"
"</tr>");
/***** Answers *****/
/* Integer answer */
fprintf (Gbl.F.Out,"<tr>"
2016-12-26 15:17:30 +01:00
"<td class=\"RIGHT_TOP %s\">"
2014-12-26 14:45:14 +01:00
"%s:"
"</td>"
2016-12-26 15:17:30 +01:00
"<td class=\"LEFT_TOP\">"
"<label class=\"%s\">"
"%s:&nbsp;"
"<input type=\"text\" name=\"AnsInt\""
2015-10-23 01:31:08 +02:00
" size=\"11\" maxlength=\"11\" value=\"%ld\"",
2015-07-28 00:16:09 +02:00
The_ClassForm[Gbl.Prefs.Theme],Txt_Answers,
The_ClassForm[Gbl.Prefs.Theme],Txt_Integer_number,
2014-12-01 23:55:08 +01:00
Gbl.Test.Answer.Integer);
if (Gbl.Test.AnswerType != Tst_ANS_INT)
fprintf (Gbl.F.Out," disabled=\"disabled\"");
2016-11-19 19:39:17 +01:00
fprintf (Gbl.F.Out," required=\"required\" />"
2016-12-26 15:17:30 +01:00
"</label>"
2016-11-19 19:39:17 +01:00
"</td>"
2014-12-01 23:55:08 +01:00
"</tr>");
/* Floating point answer */
fprintf (Gbl.F.Out,"<tr>"
2014-12-26 14:45:14 +01:00
"<td></td>"
2016-12-26 15:17:30 +01:00
"<td class=\"LEFT_TOP\">");
Tst_PutFloatInputField (Txt_Real_number_between_A_and_B_1,"AnsFloatMin",
Gbl.Test.Answer.FloatingPoint[0]);
Tst_PutFloatInputField (Txt_Real_number_between_A_and_B_2,"AnsFloatMax",
Gbl.Test.Answer.FloatingPoint[1]);
fprintf (Gbl.F.Out,"</td>"
2014-12-01 23:55:08 +01:00
"</tr>");
/* T/F answer */
fprintf (Gbl.F.Out,"<tr>"
2014-12-26 14:45:14 +01:00
"<td></td>"
2016-12-26 16:30:46 +01:00
"<td class=\"LEFT_TOP\">");
Tst_PutTFInputField (Txt_TF_QST[0],'T');
Tst_PutTFInputField (Txt_TF_QST[1],'F');
fprintf (Gbl.F.Out,"</td>"
"</tr>");
2014-12-01 23:55:08 +01:00
/* Questions can be shuffled? */
fprintf (Gbl.F.Out,"<tr>"
2014-12-26 14:45:14 +01:00
"<td></td>"
2016-12-26 16:30:46 +01:00
"<td class=\"LEFT_TOP\">"
"<label class=\"%s\">"
2014-12-01 23:55:08 +01:00
"<input type=\"checkbox\" name=\"Shuffle\" value=\"Y\"",
2015-07-28 00:16:09 +02:00
The_ClassForm[Gbl.Prefs.Theme]);
2016-04-03 01:24:20 +02:00
if (Gbl.Test.Shuffle)
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out," checked=\"checked\"");
if (Gbl.Test.AnswerType != Tst_ANS_UNIQUE_CHOICE &&
Gbl.Test.AnswerType != Tst_ANS_MULTIPLE_CHOICE)
fprintf (Gbl.F.Out," disabled=\"disabled\"");
2016-12-26 16:30:46 +01:00
fprintf (Gbl.F.Out," />"
"%s"
"</label>"
"</td>"
2014-12-01 23:55:08 +01:00
"</tr>",
Txt_Shuffle);
/* Simple or multiple choice answers */
fprintf (Gbl.F.Out,"<tr>"
2014-12-26 14:45:14 +01:00
"<td></td>"
2015-07-28 00:16:09 +02:00
"<td class=\"LEFT_TOP\">"
2016-04-05 14:32:02 +02:00
"<table class=\"CELLS_PAD_2\">");
2014-12-01 23:55:08 +01:00
OptionsDisabled = Gbl.Test.AnswerType != Tst_ANS_UNIQUE_CHOICE &&
Gbl.Test.AnswerType != Tst_ANS_MULTIPLE_CHOICE &&
Gbl.Test.AnswerType != Tst_ANS_TEXT;
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;
if (Gbl.Test.Answer.Options[NumOpt].Text)
if (Gbl.Test.Answer.Options[NumOpt].Text[0])
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
2016-04-07 17:14:35 +02:00
/***** Left column: selectors *****/
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"<tr>"
2016-11-19 19:39:17 +01:00
"<td class=\"TEST_EDI_ANS_LEFT_COL COLOR%u\">",
Gbl.RowEvenOdd);
/* Radio selector for unique choice answers */
fprintf (Gbl.F.Out,"<input type=\"radio\" name=\"AnsUni\" value=\"%u\"",
2014-12-01 23:55:08 +01:00
NumOpt);
if (Gbl.Test.AnswerType != Tst_ANS_UNIQUE_CHOICE)
fprintf (Gbl.F.Out," disabled=\"disabled\"");
if (Gbl.Test.Answer.Options[NumOpt].Correct)
fprintf (Gbl.F.Out," checked=\"checked\"");
2016-11-19 19:39:17 +01:00
if (NumOpt < 2) // First or second options required
fprintf (Gbl.F.Out," required=\"required\"");
fprintf (Gbl.F.Out," />");
/* Checkbox for multiple choice answers */
fprintf (Gbl.F.Out,"<input type=\"checkbox\" name=\"AnsMulti\" value=\"%u\"",
2014-12-01 23:55:08 +01:00
NumOpt);
if (Gbl.Test.AnswerType != Tst_ANS_MULTIPLE_CHOICE)
fprintf (Gbl.F.Out," disabled=\"disabled\"");
if (Gbl.Test.Answer.Options[NumOpt].Correct)
fprintf (Gbl.F.Out," checked=\"checked\"");
2016-11-19 19:39:17 +01:00
fprintf (Gbl.F.Out," />");
fprintf (Gbl.F.Out,"</td>");
2016-04-07 17:14:35 +02:00
/***** Center column: letter of the answer and expand / contract icon *****/
fprintf (Gbl.F.Out,"<td class=\"%s TEST_EDI_ANS_CENTER_COL COLOR%u\">"
"%c)",
The_ClassForm[Gbl.Prefs.Theme],Gbl.RowEvenOdd,
2014-12-01 23:55:08 +01:00
'a' + (char) NumOpt);
2016-04-07 17:14:35 +02:00
/* Icon to expand (show the answer) */
sprintf (Gbl.Title,"%s %c)",Txt_Expand,'a' + (char) NumOpt);
fprintf (Gbl.F.Out,"<a href=\"\" id=\"exp_%u\"",NumOpt);
if (DisplayRightColumn) // Answer does not have content
2016-04-07 14:40:50 +02:00
fprintf (Gbl.F.Out," style=\"display:none;\""); // Hide icon
2016-04-07 17:14:35 +02:00
fprintf (Gbl.F.Out," onclick=\"toggleAnswer('%u'); return false;\" />"
2016-04-07 14:40:50 +02:00
"<img src=\"%s/expand64x64.png\""
2016-11-14 10:05:41 +01:00
" alt=\"%s\" title=\"%s\" class=\"ICO20x20\" />"
2016-04-07 17:14:35 +02:00
"</a>",
NumOpt,Gbl.Prefs.IconsURL,
Gbl.Title,Gbl.Title);
/* Icon to contract (hide the answer) */
sprintf (Gbl.Title,"%s %c)",Txt_Contract,'a' + (char) NumOpt);
fprintf (Gbl.F.Out,"<a href=\"\" id=\"con_%u\"",NumOpt);
if (!DisplayRightColumn) // Answer does not have content
2016-04-07 14:40:50 +02:00
fprintf (Gbl.F.Out," style=\"display:none;\""); // Hide icon
2016-04-07 17:14:35 +02:00
fprintf (Gbl.F.Out," onclick=\"toggleAnswer(%u); return false;\" />"
2016-04-07 14:40:50 +02:00
"<img src=\"%s/contract64x64.png\""
2016-11-14 10:05:41 +01:00
" alt=\"%s\" title=\"%s\" class=\"ICO20x20\" />"
2016-04-07 17:14:35 +02:00
"</a>",
NumOpt,Gbl.Prefs.IconsURL,
Gbl.Title,Gbl.Title);
2016-04-07 10:23:28 +02:00
2016-04-07 14:40:50 +02:00
fprintf (Gbl.F.Out,"</td>");
/***** Right column: content of the answer *****/
2016-04-07 10:23:28 +02:00
fprintf (Gbl.F.Out,"<td class=\"TEST_EDI_ANS_RIGHT_COL COLOR%u\">"
2016-04-07 17:14:35 +02:00
"<div id=\"ans_%u\"",
2016-04-05 14:32:02 +02:00
Gbl.RowEvenOdd,
2014-12-01 23:55:08 +01:00
NumOpt);
2016-04-07 17:14:35 +02:00
if (!DisplayRightColumn) // Answer does not have content
2016-04-07 10:23:28 +02:00
fprintf (Gbl.F.Out," style=\"display:none;\""); // Hide column
fprintf (Gbl.F.Out,">");
/* Answer text */
fprintf (Gbl.F.Out,"<textarea name=\"AnsStr%u\""
" class=\"ANS_STR\" rows=\"5\"",NumOpt);
2014-12-01 23:55:08 +01:00
if (OptionsDisabled)
fprintf (Gbl.F.Out," disabled=\"disabled\"");
2016-11-19 19:39:17 +01:00
if (NumOpt == 0) // First textarea required
fprintf (Gbl.F.Out," required=\"required\"");
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,">");
2016-04-07 10:23:28 +02:00
if (AnswerHasContent)
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"%s",Gbl.Test.Answer.Options[NumOpt].Text);
2016-04-07 09:25:50 +02:00
fprintf (Gbl.F.Out,"</textarea>");
2016-04-05 14:32:02 +02:00
/* Image */
Tst_PutFormToEditQstImage (&Gbl.Test.Answer.Options[NumOpt].Image,
2016-04-14 21:33:24 +02:00
(int) NumOpt,
2016-04-15 14:30:28 +02:00
"TEST_IMG_EDIT_ONE_ANS_CONTAINER",
2016-04-05 14:32:02 +02:00
"TEST_IMG_EDIT_ONE_ANS",
2016-04-06 14:41:47 +02:00
"ANS_STR", // Title / attribution
2016-04-05 21:44:06 +02:00
OptionsDisabled);
2014-12-01 23:55:08 +01:00
/* Feedback */
2016-12-26 16:30:46 +01:00
fprintf (Gbl.F.Out,"<label class=\"%s\">%s (%s):<br />"
2016-04-05 17:25:34 +02:00
"<textarea name=\"FbStr%u\" class=\"ANS_STR\" rows=\"2\"",
2016-04-05 17:33:33 +02:00
The_ClassForm[Gbl.Prefs.Theme],Txt_Feedback,Txt_optional,
2014-12-01 23:55:08 +01:00
NumOpt);
if (OptionsDisabled)
fprintf (Gbl.F.Out," disabled=\"disabled\"");
fprintf (Gbl.F.Out,">");
if (Gbl.Test.Answer.Options[NumOpt].Feedback)
if (Gbl.Test.Answer.Options[NumOpt].Feedback[0])
fprintf (Gbl.F.Out,"%s",Gbl.Test.Answer.Options[NumOpt].Feedback);
2016-12-26 16:30:46 +01:00
fprintf (Gbl.F.Out,"</textarea>"
"</label>");
2016-04-07 10:23:28 +02:00
/* End of right column */
fprintf (Gbl.F.Out,"</div>"
"</td>"
2014-12-01 23:55:08 +01:00
"</tr>");
}
fprintf (Gbl.F.Out,"</table>"
2015-04-11 13:05:44 +02:00
"</td>"
"</tr>");
2016-04-05 10:05:52 +02:00
/***** End table *****/
fprintf (Gbl.F.Out,"</table>");
/***** Send button *****/
2015-04-11 20:18:30 +02:00
if (Gbl.Test.QstCod > 0) // The question already has assigned a code
2016-04-05 10:05:52 +02:00
Lay_PutConfirmButton (Txt_Save);
2015-04-11 20:18:30 +02:00
else
2016-04-05 10:05:52 +02:00
Lay_PutCreateButton (Txt_Create_question);
2015-04-11 13:05:44 +02:00
/***** End form *****/
2015-03-13 00:16:02 +01:00
Act_FormEnd ();
2014-12-01 23:55:08 +01:00
2016-04-05 10:05:52 +02:00
/***** End frame *****/
Lay_EndRoundFrame ();
2016-12-26 15:17:30 +01:00
}
/*****************************************************************************/
/********************* Put input field for floating answer *******************/
/*****************************************************************************/
static void Tst_PutFloatInputField (const char *Label,const char *Field,
double Value)
{
extern const char *The_ClassForm[The_NUM_THEMES];
fprintf (Gbl.F.Out,"<label class=\"%s\">%s&nbsp;"
"<input type=\"text\" name=\"%s\""
" size=\"11\" maxlength=\"%u\""
" value=\"%lg\"",
The_ClassForm[Gbl.Prefs.Theme],Label,
Field,
Tst_MAX_BYTES_FLOAT_ANSWER,
Value);
if (Gbl.Test.AnswerType != Tst_ANS_FLOAT)
fprintf (Gbl.F.Out," disabled=\"disabled\"");
fprintf (Gbl.F.Out," required=\"required\" />"
"</label>");
2016-12-26 16:30:46 +01:00
}
/*****************************************************************************/
/*********************** Put input field for T/F answer **********************/
/*****************************************************************************/
static void Tst_PutTFInputField (const char *Label,char Value)
{
extern const char *The_ClassForm[The_NUM_THEMES];
fprintf (Gbl.F.Out,"<label class=\"%s\">"
"<input type=\"radio\" name=\"AnsTF\" value=\"%c\"",
The_ClassForm[Gbl.Prefs.Theme],Value);
if (Gbl.Test.Answer.TF == Value)
fprintf (Gbl.F.Out," checked=\"checked\"");
if (Gbl.Test.AnswerType != Tst_ANS_TRUE_FALSE)
fprintf (Gbl.F.Out," disabled=\"disabled\"");
fprintf (Gbl.F.Out," required=\"required\" />"
"%s"
"</label>",
Label);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/********************* Initialize a new question to zero *********************/
/*****************************************************************************/
2016-04-06 19:26:09 +02:00
void Tst_QstConstructor (void)
2014-12-01 23:55:08 +01:00
{
unsigned NumOpt;
2016-04-06 19:26:09 +02:00
Gbl.Test.QstCod = -1L;
2014-12-01 23:55:08 +01:00
Gbl.Test.Stem.Text = NULL;
Gbl.Test.Stem.Length = 0;
Gbl.Test.Feedback.Text = NULL;
Gbl.Test.Feedback.Length = 0;
2016-04-03 01:24:20 +02:00
Gbl.Test.Shuffle = false;
2014-12-01 23:55:08 +01:00
Gbl.Test.AnswerType = Tst_ANS_UNIQUE_CHOICE;
Gbl.Test.Answer.NumOptions = 0;
Gbl.Test.Answer.TF = ' ';
2016-04-08 23:30:43 +02:00
/***** Initialize image attached to stem *****/
Img_ImageConstructor (&Gbl.Test.Image);
2014-12-01 23:55:08 +01:00
for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
{
2016-04-04 10:11:05 +02:00
Gbl.Test.Answer.Options[NumOpt].Correct = false;
Gbl.Test.Answer.Options[NumOpt].Text = NULL;
Gbl.Test.Answer.Options[NumOpt].Feedback = NULL;
2016-04-08 23:30:43 +02:00
/***** Initialize image attached to option *****/
Img_ImageConstructor (&Gbl.Test.Answer.Options[NumOpt].Image);
2014-12-01 23:55:08 +01:00
}
Gbl.Test.Answer.Integer = 0;
Gbl.Test.Answer.FloatingPoint[0] =
Gbl.Test.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 *******************/
/*****************************************************************************/
void Tst_QstDestructor (void)
{
Tst_FreeTextChoiceAnswers ();
Tst_FreeImagesOfQuestion ();
}
/*****************************************************************************/
/******************* Allocate memory for a choice answer *********************/
/*****************************************************************************/
int Tst_AllocateTextChoiceAnswer (unsigned NumOpt)
{
Tst_FreeTextChoiceAnswer (NumOpt);
if ((Gbl.Test.Answer.Options[NumOpt].Text =
malloc (Tst_MAX_BYTES_ANSWER_OR_FEEDBACK + 1)) == NULL)
{
sprintf (Gbl.Message,"Not enough memory to store answer.");
return 0;
}
if ((Gbl.Test.Answer.Options[NumOpt].Feedback =
malloc (Tst_MAX_BYTES_ANSWER_OR_FEEDBACK + 1)) == NULL)
{
sprintf (Gbl.Message,"Not enough memory to store feedback.");
return 0;
}
Gbl.Test.Answer.Options[NumOpt].Text[0] =
Gbl.Test.Answer.Options[NumOpt].Feedback[0] = '\0';
return 1;
}
/*****************************************************************************/
/******************** Free memory of all choice answers **********************/
/*****************************************************************************/
static void Tst_FreeTextChoiceAnswers (void)
{
unsigned NumOpt;
for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
Tst_FreeTextChoiceAnswer (NumOpt);
}
/*****************************************************************************/
/********************** Free memory of a choice answer ***********************/
/*****************************************************************************/
static void Tst_FreeTextChoiceAnswer (unsigned NumOpt)
{
if (Gbl.Test.Answer.Options[NumOpt].Text)
{
free ((void *) Gbl.Test.Answer.Options[NumOpt].Text);
Gbl.Test.Answer.Options[NumOpt].Text = NULL;
}
if (Gbl.Test.Answer.Options[NumOpt].Feedback)
{
free ((void *) Gbl.Test.Answer.Options[NumOpt].Feedback);
Gbl.Test.Answer.Options[NumOpt].Feedback = NULL;
}
}
2016-04-04 21:51:21 +02:00
/*****************************************************************************/
/***************** Initialize images of a question to zero *******************/
/*****************************************************************************/
2016-04-09 02:04:45 +02:00
2016-04-04 21:51:21 +02:00
static void Tst_InitImagesOfQuestion (void)
{
unsigned NumOpt;
2016-04-09 02:04:45 +02:00
/***** Initialize image *****/
2016-04-15 02:33:16 +02:00
Img_ResetImageExceptTitleAndURL (&Gbl.Test.Image);
2016-04-15 10:58:35 +02:00
Img_FreeImageTitle (&Gbl.Test.Image);
Img_FreeImageURL (&Gbl.Test.Image);
2016-04-08 23:30:43 +02:00
2016-04-04 21:51:21 +02:00
for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
2016-04-09 02:04:45 +02:00
{
/***** Initialize image *****/
2016-04-15 02:33:16 +02:00
Img_ResetImageExceptTitleAndURL (&Gbl.Test.Answer.Options[NumOpt].Image);
2016-04-15 10:58:35 +02:00
Img_FreeImageTitle (&Gbl.Test.Image);
Img_FreeImageURL (&Gbl.Test.Image);
2016-04-09 02:04:45 +02:00
}
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 ***************************/
/*****************************************************************************/
static void Tst_FreeImagesOfQuestion (void)
{
unsigned NumOpt;
2016-04-08 23:30:43 +02:00
Img_ImageDestructor (&Gbl.Test.Image);
2016-04-06 01:10:04 +02:00
for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
2016-04-08 23:30:43 +02:00
Img_ImageDestructor (&Gbl.Test.Answer.Options[NumOpt].Image);
2016-04-06 01:10:04 +02:00
}
2016-04-03 01:24:20 +02:00
/*****************************************************************************/
/****************** Get data of a question from database *********************/
/*****************************************************************************/
2017-01-17 03:10:43 +01:00
static void Tst_GetQstDataFromDB (char Stem[Cns_MAX_BYTES_TEXT + 1],
char Feedback[Cns_MAX_BYTES_TEXT + 1])
2016-04-03 01:24:20 +02:00
{
char Query[512];
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned long NumRows;
unsigned long NumRow;
unsigned NumOpt;
/***** Get the type of answer and the stem from the database *****/
/* Get the question from database */
2016-04-15 02:33:16 +02:00
sprintf (Query,"SELECT AnsType,Shuffle,Stem,Feedback,"
"ImageName,ImageTitle,ImageURL"
2016-04-03 01:24:20 +02:00
" FROM tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE QstCod=%ld AND CrsCod=%ld",
2016-04-03 01:24:20 +02:00
Gbl.Test.QstCod,Gbl.CurrentCrs.Crs.CrsCod);
DB_QuerySELECT (Query,&mysql_res,"can not get a question");
row = mysql_fetch_row (mysql_res);
2016-04-06 14:41:47 +02:00
/*
row[ 0] AnsType
row[ 1] Shuffle
row[ 2] Stem
row[ 3] Feedback
row[ 4] ImageName
row[ 5] ImageTitle
2016-04-15 02:33:16 +02:00
row[ 6] ImageURL
2016-04-06 14:41:47 +02:00
*/
2016-04-03 01:24:20 +02:00
/* Get the type of answer */
Gbl.Test.AnswerType = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[0]);
/* Get shuffle (row[1]) */
2016-09-07 18:48:10 +02:00
Gbl.Test.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
2016-04-15 02:33:16 +02:00
/* Get the image name, title and URL of the question
from the database (row[4], row[5], row[6]) */
Img_GetImageNameTitleAndURLFromRow (row[4],row[5],row[6],&Gbl.Test.Image);
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 *****/
NumRows = Tst_GetTagsQst (Gbl.Test.QstCod,&mysql_res);
for (NumRow = 0;
NumRow < NumRows;
NumRow++)
{
row = mysql_fetch_row (mysql_res);
2017-01-17 03:10:43 +01:00
Str_Copy (Gbl.Test.Tags.Txt[NumRow],row[0],
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 *****/
Gbl.Test.Answer.NumOptions = Tst_GetAnswersQst (Gbl.Test.QstCod,&mysql_res,false); // Result: AnsInd,Answer,Correct,Feedback
2016-04-06 14:41:47 +02:00
/*
row[ 0] AnsInd
row[ 1] Answer
row[ 2] Feedback
row[ 3] ImageName
row[ 4] ImageTitle
2016-04-15 02:33:16 +02:00
row[ 5] ImageURL
row[ 6] Correct
2016-04-06 14:41:47 +02:00
*/
2016-04-03 01:24:20 +02:00
for (NumOpt = 0;
NumOpt < Gbl.Test.Answer.NumOptions;
NumOpt++)
{
row = mysql_fetch_row (mysql_res);
switch (Gbl.Test.AnswerType)
{
case Tst_ANS_INT:
if (Gbl.Test.Answer.NumOptions != 1)
Lay_ShowErrorAndExit ("Wrong answer.");
Gbl.Test.Answer.Integer = Tst_GetIntAnsFromStr (row[1]);
break;
case Tst_ANS_FLOAT:
if (Gbl.Test.Answer.NumOptions != 2)
Lay_ShowErrorAndExit ("Wrong answer.");
Gbl.Test.Answer.FloatingPoint[NumOpt] = Tst_GetFloatAnsFromStr (row[1]);
break;
case Tst_ANS_TRUE_FALSE:
if (Gbl.Test.Answer.NumOptions != 1)
Lay_ShowErrorAndExit ("Wrong answer.");
Gbl.Test.Answer.TF = row[1][0];
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
case Tst_ANS_TEXT:
if (Gbl.Test.Answer.NumOptions > Tst_MAX_OPTIONS_PER_QUESTION)
Lay_ShowErrorAndExit ("Wrong answer.");
if (!Tst_AllocateTextChoiceAnswer (NumOpt))
Lay_ShowErrorAndExit (Gbl.Message);
2017-01-15 18:02:52 +01:00
Str_Copy (Gbl.Test.Answer.Options[NumOpt].Text,row[1],
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])
2017-01-15 18:02:52 +01:00
Str_Copy (Gbl.Test.Answer.Options[NumOpt].Feedback,row[2],
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2016-04-03 01:24:20 +02:00
2016-04-04 21:51:21 +02:00
/* Copy image */
2016-04-15 02:33:16 +02:00
Img_GetImageNameTitleAndURLFromRow (row[3],row[4],row[5],&Gbl.Test.Answer.Options[NumOpt].Image);
2016-04-04 21:51:21 +02:00
2016-09-07 18:48:10 +02:00
Gbl.Test.Answer.Options[NumOpt].Correct = (row[6][0] == 'Y');
2016-04-03 01:24:20 +02:00
break;
default:
break;
}
}
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
/***** Get possible image associated with a test question from database ******/
/*****************************************************************************/
2016-04-14 19:05:52 +02:00
// NumOpt < 0 ==> image associated to stem
// NumOpt >= 0 ==> image associated to answer
2016-04-03 01:24:20 +02:00
2016-04-14 19:05:52 +02:00
static void Tst_GetImageFromDB (int NumOpt,struct Image *Image)
2016-04-03 01:24:20 +02:00
{
char Query[256];
MYSQL_RES *mysql_res;
MYSQL_ROW row;
2016-04-04 14:48:12 +02:00
/***** Build query depending on NumOpt *****/
2016-04-14 19:05:52 +02:00
if (NumOpt < 0)
2016-04-04 14:48:12 +02:00
// Get image associated to stem
2016-04-15 02:33:16 +02:00
sprintf (Query,"SELECT ImageName,ImageTitle,ImageURL FROM tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE QstCod=%ld AND CrsCod=%ld",
2016-04-14 19:05:52 +02:00
Gbl.Test.QstCod,Gbl.CurrentCrs.Crs.CrsCod); // Get image associated to answer
else
2016-04-15 02:33:16 +02:00
sprintf (Query,"SELECT ImageName,ImageTitle,ImageURL FROM tst_answers"
2017-03-24 01:09:27 +01:00
" WHERE QstCod=%ld AND AnsInd=%u",
2016-04-14 19:05:52 +02:00
Gbl.Test.QstCod,(unsigned) NumOpt);
2016-04-04 14:48:12 +02:00
/***** Query database *****/
DB_QuerySELECT (Query,&mysql_res,"can not get image name");
2016-04-03 01:24:20 +02:00
row = mysql_fetch_row (mysql_res);
2016-04-15 02:33:16 +02:00
/***** Get the image name, title and URL (row[0], row[1], row[2]) *****/
Img_GetImageNameTitleAndURLFromRow (row[0],row[1],row[2],Image);
2016-04-03 01:24:20 +02:00
2016-04-04 14:48:12 +02:00
/***** Free structure that stores the query result *****/
2016-04-03 01:24:20 +02:00
DB_FreeMySQLResult (&mysql_res);
}
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)
for (AnsType = (Tst_AnswerType_t) 0;
AnsType < Tst_NUM_ANS_TYPES;
AnsType++)
if (!strcmp (StrAnsTypeBD,Tst_StrAnswerTypesDB[AnsType]))
return AnsType;
Lay_ShowErrorAndExit ("Wrong type of answer.");
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)
Lay_ShowErrorAndExit ("Wrong type of answer.");
if (AnsType >= Tst_NUM_ANS_TYPES)
Lay_ShowErrorAndExit ("Wrong type of answer.");
return (Tst_AnswerType_t) AnsType;
}
/*****************************************************************************/
/*************** Receive a question of the self-assessment test **************/
/*****************************************************************************/
void Tst_ReceiveQst (void)
{
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
2016-04-06 19:26:09 +02:00
/***** Create test question *****/
Tst_QstConstructor ();
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';
2014-12-01 23:55:08 +01:00
Tst_GetQstFromForm (Stem,Feedback);
/***** Make sure that tags, text and answer are not empty *****/
if (Tst_CheckIfQstFormatIsCorrectAndCountNumOptions ())
{
2016-04-04 21:51:21 +02:00
/***** Move images to definitive directories *****/
Tst_MoveImagesToDefinitiveDirectories ();
2016-04-04 01:02:41 +02:00
/***** Insert or update question, tags and answer in the database *****/
2014-12-01 23:55:08 +01:00
Tst_InsertOrUpdateQstTagsAnsIntoDB ();
/***** Show the question just inserted in the database *****/
Tst_ListOneQstToEdit ();
}
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 *****/
2016-04-09 02:04:45 +02:00
Tst_InitImagesOfQuestion ();
2016-04-04 03:00:12 +02:00
2016-04-03 01:24:20 +02:00
/***** Put form to edit question again *****/
2014-12-01 23:55:08 +01:00
Tst_PutFormEditOneQst (Stem,Feedback);
2016-04-04 03:00:12 +02:00
}
2014-12-01 23:55:08 +01:00
2016-04-06 19:26:09 +02:00
/***** Destroy test question *****/
Tst_QstDestructor ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/**************** Get parameters of a test question from form ****************/
/*****************************************************************************/
static void Tst_GetQstFromForm (char *Stem,char *Feedback)
{
unsigned NumTag;
unsigned NumTagRead;
unsigned NumOpt;
2017-01-29 21:41:08 +01:00
char UnsignedStr[10 + 1];
2017-01-28 15:58:46 +01:00
char TagStr[6 + 10 + 1];
char AnsStr[6 + 10 + 1];
char FbStr[5 + 10 + 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 *****/
2016-04-06 19:26:09 +02:00
Gbl.Test.QstCod = Tst_GetQstCod ();
2014-12-01 23:55:08 +01:00
/***** Get answer type *****/
2017-01-29 21:41:08 +01:00
Gbl.Test.AnswerType = (Tst_AnswerType_t)
Par_GetParToUnsignedLong ("AnswerType",
0,
Tst_NUM_ANS_TYPES - 1,
(unsigned long) Tst_ANS_ALL);
if (Gbl.Test.AnswerType == Tst_ANS_ALL)
Lay_ShowErrorAndExit ("Wrong type of answer.");
2014-12-01 23:55:08 +01:00
/***** Get question tags *****/
for (NumTag = 0;
NumTag < Tst_MAX_TAGS_PER_QUESTION;
NumTag++)
{
sprintf (TagStr,"TagTxt%u",NumTag);
2016-04-06 22:27:33 +02:00
Par_GetParToText (TagStr,Gbl.Test.Tags.Txt[NumTag],Tst_MAX_BYTES_TAG);
2016-03-30 01:28:58 +02:00
2016-04-06 22:27:33 +02:00
if (Gbl.Test.Tags.Txt[NumTag][0])
2014-12-01 23:55:08 +01:00
{
Str_ChangeFormat (Str_FROM_FORM,Str_TO_TEXT,
2016-04-06 22:27:33 +02:00
Gbl.Test.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++)
2016-04-06 22:27:33 +02:00
if (!strcmp (Gbl.Test.Tags.Txt[NumTagRead],Gbl.Test.Tags.Txt[NumTag]))
2014-12-01 23:55:08 +01:00
{
2016-04-06 22:27:33 +02:00
Gbl.Test.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);
2016-04-08 16:37:59 +02:00
/***** Get image associated to the stem (action, file and title) *****/
2016-04-14 19:19:55 +02:00
Gbl.Test.Image.Width = Tst_IMAGE_SAVED_MAX_WIDTH;
Gbl.Test.Image.Height = Tst_IMAGE_SAVED_MAX_HEIGHT;
Gbl.Test.Image.Quality = Tst_IMAGE_SAVED_QUALITY;
2016-04-18 11:45:07 +02:00
Img_GetImageFromForm (-1, // < 0 ==> the image associated to the stem
&Gbl.Test.Image,Tst_GetImageFromDB);
2016-04-04 12:13:37 +02:00
2014-12-01 23:55:08 +01:00
/***** Get answers *****/
Gbl.Test.Shuffle = false;
switch (Gbl.Test.AnswerType)
{
case Tst_ANS_INT:
if (!Tst_AllocateTextChoiceAnswer (0))
Lay_ShowErrorAndExit (Gbl.Message);
2017-01-28 15:58:46 +01:00
Par_GetParToText ("AnsInt",Gbl.Test.Answer.Options[0].Text,1 + 10);
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_FLOAT:
if (!Tst_AllocateTextChoiceAnswer (0))
Lay_ShowErrorAndExit (Gbl.Message);
2017-01-29 21:41:08 +01:00
Par_GetParToText ("AnsFloatMin",Gbl.Test.Answer.Options[0].Text,
Tst_MAX_BYTES_FLOAT_ANSWER);
2014-12-01 23:55:08 +01:00
if (!Tst_AllocateTextChoiceAnswer (1))
Lay_ShowErrorAndExit (Gbl.Message);
2017-01-29 21:41:08 +01:00
Par_GetParToText ("AnsFloatMax",Gbl.Test.Answer.Options[1].Text,
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);
Gbl.Test.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 */
2017-01-28 20:32:50 +01:00
Gbl.Test.Shuffle = Par_GetParToBool ("Shuffle");
2014-12-01 23:55:08 +01:00
// No break
case Tst_ANS_TEXT:
/* Get the texts of the answers */
for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
{
if (!Tst_AllocateTextChoiceAnswer (NumOpt))
Lay_ShowErrorAndExit (Gbl.Message);
/* Get answer */
sprintf (AnsStr,"AnsStr%u",NumOpt);
2017-03-08 03:48:23 +01:00
Par_GetParToHTML (AnsStr,Gbl.Test.Answer.Options[NumOpt].Text,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2016-11-27 14:34:36 +01:00
if (Gbl.Test.AnswerType == Tst_ANS_TEXT)
/* In order to compare student answer to stored answer,
the text answers are stored avoiding two or more consecurive spaces */
Str_ReplaceSeveralSpacesForOne (Gbl.Test.Answer.Options[NumOpt].Text);
2014-12-01 23:55:08 +01:00
/* Get feedback */
sprintf (FbStr,"FbStr%u",NumOpt);
2017-03-08 03:48:23 +01:00
Par_GetParToHTML (FbStr,Gbl.Test.Answer.Options[NumOpt].Feedback,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2016-04-04 14:48:12 +02:00
2016-04-08 16:37:59 +02:00
/* Get image associated to the answer (action, file and title) */
2016-04-04 14:48:12 +02:00
if (Gbl.Test.AnswerType == Tst_ANS_UNIQUE_CHOICE ||
Gbl.Test.AnswerType == Tst_ANS_MULTIPLE_CHOICE)
{
2016-04-14 19:19:55 +02:00
Gbl.Test.Answer.Options[NumOpt].Image.Width = Tst_IMAGE_SAVED_MAX_WIDTH;
Gbl.Test.Answer.Options[NumOpt].Image.Height = Tst_IMAGE_SAVED_MAX_HEIGHT;
Gbl.Test.Answer.Options[NumOpt].Image.Quality = Tst_IMAGE_SAVED_QUALITY;
2016-04-18 11:45:07 +02:00
Img_GetImageFromForm ((int) NumOpt, // >= 0 ==> the image associated to an answer
&Gbl.Test.Answer.Options[NumOpt].Image,
2016-04-14 21:33:24 +02:00
Tst_GetImageFromDB);
2016-04-04 14:48:12 +02:00
}
2014-12-01 23:55:08 +01:00
}
/* Get the numbers of correct answers */
if (Gbl.Test.AnswerType == Tst_ANS_UNIQUE_CHOICE)
{
2017-01-29 21:41:08 +01:00
NumCorrectAns = (unsigned) Par_GetParToUnsignedLong ("AnsUni",
0,
Tst_MAX_OPTIONS_PER_QUESTION - 1,
0);
Gbl.Test.Answer.Options[NumCorrectAns].Correct = true;
2014-12-01 23:55:08 +01:00
}
else if (Gbl.Test.AnswerType == Tst_ANS_MULTIPLE_CHOICE)
{
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)
{
Par_GetNextStrUntilSeparParamMult (&Ptr,UnsignedStr,10);
if (sscanf (UnsignedStr,"%u",&NumCorrectAns) != 1)
Lay_ShowErrorAndExit ("Wrong selected answer.");
if (NumCorrectAns >= Tst_MAX_OPTIONS_PER_QUESTION)
Lay_ShowErrorAndExit ("Wrong selected answer.");
Gbl.Test.Answer.Options[NumCorrectAns].Correct = true;
}
}
else // Tst_ANS_TEXT
for (NumOpt = 0;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
if (Gbl.Test.Answer.Options[NumOpt].Text[0])
Gbl.Test.Answer.Options[NumOpt].Correct = true; // All the answers are correct
break;
default:
break;
}
/***** Adjust global variables related to this test question *****/
2016-04-06 22:27:33 +02:00
for (NumTag = 0, Gbl.Test.Tags.Num = 0;
2014-12-01 23:55:08 +01:00
NumTag < Tst_MAX_TAGS_PER_QUESTION;
NumTag++)
2016-04-06 22:27:33 +02:00
if (Gbl.Test.Tags.Txt[NumTag][0])
Gbl.Test.Tags.Num++;
2014-12-01 23:55:08 +01:00
Gbl.Test.Stem.Text = Stem;
Gbl.Test.Stem.Length = strlen (Gbl.Test.Stem.Text);
Gbl.Test.Feedback.Text = Feedback;
Gbl.Test.Feedback.Length = strlen (Gbl.Test.Feedback.Text);
}
/*****************************************************************************/
/*********************** Check if a question is correct **********************/
/*****************************************************************************/
// Returns false if question format is wrong
// Counts Gbl.Test.Answer.NumOptions
// Computes Gbl.Test.Answer.Integer and Gbl.Test.Answer.FloatingPoint[0..1]
bool Tst_CheckIfQstFormatIsCorrectAndCountNumOptions (void)
{
2016-04-04 03:00:12 +02:00
extern const char *Txt_Error_receiving_or_processing_image;
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;
2016-04-04 14:48:12 +02:00
if ((Gbl.Test.Image.Action == Img_ACTION_NEW_IMAGE || // Upload new image
Gbl.Test.Image.Action == Img_ACTION_CHANGE_IMAGE) && // Replace existing image by new image
Gbl.Test.Image.Status != Img_FILE_PROCESSED)
2016-04-04 03:00:12 +02:00
{
Lay_ShowAlert (Lay_WARNING,Txt_Error_receiving_or_processing_image);
return false;
}
2014-12-01 23:55:08 +01:00
/***** This function also counts the number of options. Initialize this number to 0. *****/
Gbl.Test.Answer.NumOptions = 0;
/***** A question must have at least one tag *****/
2016-04-06 22:27:33 +02:00
if (!Gbl.Test.Tags.Num) // There are no tags with text
2014-12-01 23:55:08 +01:00
{
Lay_ShowAlert (Lay_WARNING,Txt_You_must_type_at_least_one_tag_for_the_question);
return false;
}
/***** A question must have a stem*****/
if (!Gbl.Test.Stem.Length)
{
Lay_ShowAlert (Lay_WARNING,Txt_You_must_type_the_stem_of_the_question);
return false;
}
/***** Check answer *****/
switch (Gbl.Test.AnswerType)
{
case Tst_ANS_INT:
if (!Gbl.Test.Answer.Options[0].Text)
{
Lay_ShowAlert (Lay_WARNING,Txt_You_must_enter_an_integer_value_as_the_correct_answer);
return false;
}
if (!Gbl.Test.Answer.Options[0].Text[0])
{
Lay_ShowAlert (Lay_WARNING,Txt_You_must_enter_an_integer_value_as_the_correct_answer);
return false;
}
Gbl.Test.Answer.Integer = Tst_GetIntAnsFromStr (Gbl.Test.Answer.Options[0].Text);
Gbl.Test.Answer.NumOptions = 1;
break;
case Tst_ANS_FLOAT:
if (!Gbl.Test.Answer.Options[0].Text ||
!Gbl.Test.Answer.Options[1].Text)
{
Lay_ShowAlert (Lay_WARNING,Txt_You_must_enter_the_range_of_floating_point_values_allowed_as_answer);
return false;
}
if (!Gbl.Test.Answer.Options[0].Text[0] ||
!Gbl.Test.Answer.Options[1].Text[0])
{
Lay_ShowAlert (Lay_WARNING,Txt_You_must_enter_the_range_of_floating_point_values_allowed_as_answer);
return false;
}
for (i = 0;
i < 2;
i++)
Gbl.Test.Answer.FloatingPoint[i] = Tst_GetFloatAnsFromStr (Gbl.Test.Answer.Options[i].Text);
if (Gbl.Test.Answer.FloatingPoint[0] >
Gbl.Test.Answer.FloatingPoint[1])
{
Lay_ShowAlert (Lay_WARNING,Txt_The_lower_limit_of_correct_answers_must_be_less_than_or_equal_to_the_upper_limit);
return false;
}
Gbl.Test.Answer.NumOptions = 2;
break;
case Tst_ANS_TRUE_FALSE:
if (Gbl.Test.Answer.TF != 'T' &&
Gbl.Test.Answer.TF != 'F')
{
Lay_ShowAlert (Lay_WARNING,Txt_You_must_select_a_T_F_answer);
return false;
}
Gbl.Test.Answer.NumOptions = 1;
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
if (!Gbl.Test.Answer.Options[0].Text) // If the first answer is empty
{
Lay_ShowAlert (Lay_WARNING,Txt_You_must_type_at_least_the_first_two_answers);
return false;
}
if (!Gbl.Test.Answer.Options[0].Text[0]) // If the first answer is empty
{
Lay_ShowAlert (Lay_WARNING,Txt_You_must_type_at_least_the_first_two_answers);
return false;
}
for (NumOpt = 0, NumLastOpt = 0, ThereIsEndOfAnswers = false;
NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
if (Gbl.Test.Answer.Options[NumOpt].Text)
{
if (Gbl.Test.Answer.Options[NumOpt].Text[0])
{
if (ThereIsEndOfAnswers)
{
Lay_ShowAlert (Lay_WARNING,Txt_You_can_not_leave_empty_intermediate_answers);
return false;
}
NumLastOpt = NumOpt;
Gbl.Test.Answer.NumOptions++;
}
else
ThereIsEndOfAnswers = true;
}
else
ThereIsEndOfAnswers = true;
if (NumLastOpt < 1)
{
Lay_ShowAlert (Lay_WARNING,Txt_You_must_type_at_least_the_first_two_answers);
return false;
}
for (NumOpt = 0;
NumOpt <= NumLastOpt;
NumOpt++)
if (Gbl.Test.Answer.Options[NumOpt].Correct)
break;
if (NumOpt > NumLastOpt)
{
Lay_ShowAlert (Lay_WARNING,Txt_You_must_mark_an_answer_as_correct);
return false;
}
break;
case Tst_ANS_TEXT:
if (!Gbl.Test.Answer.Options[0].Text) // If the first answer is empty
{
Lay_ShowAlert (Lay_WARNING,Txt_You_must_type_at_least_the_first_two_answers);
return false;
}
if (!Gbl.Test.Answer.Options[0].Text[0]) // If the first answer is empty
{
Lay_ShowAlert (Lay_WARNING,Txt_You_must_type_at_least_the_first_answer);
return false;
}
for (NumOpt=0, ThereIsEndOfAnswers=false;
NumOpt<Tst_MAX_OPTIONS_PER_QUESTION;
NumOpt++)
if (Gbl.Test.Answer.Options[NumOpt].Text)
{
if (Gbl.Test.Answer.Options[NumOpt].Text[0])
{
if (ThereIsEndOfAnswers)
{
Lay_ShowAlert (Lay_WARNING,Txt_You_can_not_leave_empty_intermediate_answers);
return false;
}
Gbl.Test.Answer.NumOptions++;
}
else
ThereIsEndOfAnswers = true;
}
else
ThereIsEndOfAnswers = true;
break;
default:
break;
}
return true; // Question format without errors
}
2016-04-04 21:51:21 +02:00
/*****************************************************************************/
/* Move images associates to a test question to their definitive directories */
/*****************************************************************************/
static void Tst_MoveImagesToDefinitiveDirectories (void)
{
unsigned NumOpt;
/****** Move image associated to question stem *****/
2016-04-07 12:36:58 +02:00
if (Gbl.Test.QstCod > 0 && // Question already exists
Gbl.Test.Image.Action != Img_ACTION_KEEP_IMAGE) // Don't keep the current image
2016-04-04 21:51:21 +02:00
/* Remove possible file with the old image
(the new image file is already processed
and moved to the definitive directory) */
2016-04-07 12:36:58 +02:00
Tst_RemoveImgFileFromStemOfQst (Gbl.CurrentCrs.Crs.CrsCod,Gbl.Test.QstCod);
2016-04-04 21:51:21 +02:00
if ((Gbl.Test.Image.Action == Img_ACTION_NEW_IMAGE || // Upload new image
Gbl.Test.Image.Action == Img_ACTION_CHANGE_IMAGE) && // Replace existing image by new image
Gbl.Test.Image.Status == Img_FILE_PROCESSED) // The new image received has been processed
/* Move processed image to definitive directory */
Img_MoveImageToDefinitiveDirectory (&Gbl.Test.Image);
/****** Move images associated to answers *****/
if (Gbl.Test.AnswerType == Tst_ANS_UNIQUE_CHOICE ||
Gbl.Test.AnswerType == Tst_ANS_MULTIPLE_CHOICE)
for (NumOpt = 0;
NumOpt < Gbl.Test.Answer.NumOptions;
NumOpt++)
{
2016-04-07 12:36:58 +02:00
if (Gbl.Test.QstCod > 0 && // Question already exists
Gbl.Test.Answer.Options[NumOpt].Image.Action != Img_ACTION_KEEP_IMAGE) // Don't keep the current image
2016-04-04 21:51:21 +02:00
/* Remove possible file with the old image
(the new image file is already processed
and moved to the definitive directory) */
2016-04-07 12:36:58 +02:00
Tst_RemoveImgFileFromAnsOfQst (Gbl.CurrentCrs.Crs.CrsCod,Gbl.Test.QstCod,NumOpt);
2016-04-04 21:51:21 +02:00
if ((Gbl.Test.Answer.Options[NumOpt].Image.Action == Img_ACTION_NEW_IMAGE || // Upload new image
Gbl.Test.Answer.Options[NumOpt].Image.Action == Img_ACTION_CHANGE_IMAGE) && // Replace existing image by new image
Gbl.Test.Answer.Options[NumOpt].Image.Status == Img_FILE_PROCESSED) // The new image received has been processed
/* Move processed image to definitive directory */
Img_MoveImageToDefinitiveDirectory (&Gbl.Test.Answer.Options[NumOpt].Image);
}
}
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;
}
/*****************************************************************************/
/************ Get a float number from a string in floating point *************/
/*****************************************************************************/
double Tst_GetFloatAnsFromStr (char *Str)
{
double DoubleNum;
if (Str == NULL)
return 0.0;
/***** Change commnas to points *****/
Str_ConvertStrFloatCommaToStrFloatPoint (Str);
/***** The string is "scanned" in floating point (it must have a point, not a colon as decimal separator) *****/
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 (Str,"%lg",&DoubleNum) != 1) // If the string does not hold a valid floating point number...
{
DoubleNum = 0.0; // ...the number is reset to 0
Str[0] = '\0'; // ...and the string is reset to ""
}
2016-06-04 14:21:01 +02:00
Str_SetDecimalPointToLocal (); // Return to local system
2014-12-01 23:55:08 +01:00
return DoubleNum;
}
/*****************************************************************************/
/***************** Check if this tag exists for current course ***************/
/*****************************************************************************/
static long Tst_GetTagCodFromTagTxt (const char *TagTxt)
{
2017-01-28 15:58:46 +01:00
char Query[256 + Tst_MAX_BYTES_TAG];
2014-12-01 23:55:08 +01:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned long NumRows;
long TagCod = -1L; // -1 means that the tag does not exist in database
bool Error = false;
/***** Get tag code from database *****/
sprintf (Query,"SELECT TagCod FROM tst_tags"
2017-03-24 01:09:27 +01:00
" WHERE CrsCod=%ld AND TagTxt='%s'",
2014-12-01 23:55:08 +01:00
Gbl.CurrentCrs.Crs.CrsCod,TagTxt);
NumRows = DB_QuerySELECT (Query,&mysql_res,"can not get tag");
if (NumRows == 1)
{
/***** Get tag code *****/
row = mysql_fetch_row (mysql_res);
if ((TagCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
{
2017-01-17 03:10:43 +01:00
sprintf (Gbl.Message,"%s","Wrong code of tag.");
2014-12-01 23:55:08 +01:00
Error = true;
}
}
else if (NumRows > 1)
{
2017-01-17 03:10:43 +01:00
sprintf (Gbl.Message,"%s","Duplicated tag.");
2014-12-01 23:55:08 +01:00
Error = true;
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
if (Error)
Lay_ShowErrorAndExit (Gbl.Message);
return TagCod;
}
/*****************************************************************************/
/********************* Insert new tag into tst_tags table ********************/
/*****************************************************************************/
static long Tst_CreateNewTag (long CrsCod,const char *TagTxt)
{
2017-01-28 15:58:46 +01:00
char Query[256 + Tst_MAX_BYTES_TAG];
2014-12-01 23:55:08 +01:00
/***** Insert new tag into tst_tags table *****/
2017-03-13 13:17:53 +01:00
sprintf (Query,"INSERT INTO tst_tags"
" (CrsCod,ChangeTime,TagTxt,TagHidden)"
" VALUES"
2017-03-24 01:09:27 +01:00
" (%ld,NOW(),'%s','N')",
2014-12-01 23:55:08 +01:00
CrsCod,TagTxt);
return DB_QueryINSERTandReturnCode (Query,"can not create new tag");
}
/*****************************************************************************/
/********** Change visibility of an existing tag into tst_tags table *********/
/*****************************************************************************/
static void Tst_EnableOrDisableTag (long TagCod,bool TagHidden)
{
char Query[512];
/***** Insert new tag into tst_tags table *****/
sprintf (Query,"UPDATE tst_tags SET TagHidden='%c',ChangeTime=NOW()"
2017-03-24 01:09:27 +01:00
" WHERE TagCod=%ld AND CrsCod=%ld",
2014-12-01 23:55:08 +01:00
TagHidden ? 'Y' :
'N',
TagCod,Gbl.CurrentCrs.Crs.CrsCod);
DB_QueryUPDATE (Query,"can not update the visibility of a tag");
}
2016-04-05 10:05:52 +02:00
/*****************************************************************************/
/********************* Put icon to remove one question ***********************/
/*****************************************************************************/
2016-04-05 13:07:33 +02:00
static void Tst_PutIconToRemoveOneQst (void)
2016-04-05 10:05:52 +02:00
{
2017-04-30 23:48:48 +02:00
Lay_PutContextualIconToRemove (ActReqRemTstQst,Tst_PutParamsRemoveOneQst);
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 **********************/
/*****************************************************************************/
void Tst_RequestRemoveQst (void)
{
extern const char *Txt_Do_you_really_want_to_remove_the_question_X;
extern const char *Txt_Remove_question;
bool EditingOnlyThisQst;
2016-04-05 10:47:36 +02:00
/***** Get main parameters from form *****/
/* Get the question code */
2016-04-06 19:26:09 +02:00
Gbl.Test.QstCod = Tst_GetQstCod ();
if (Gbl.Test.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)
if (!Tst_GetParamsTst ())
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 *****/
/* Start alert */
2016-04-05 02:59:34 +02:00
sprintf (Gbl.Message,Txt_Do_you_really_want_to_remove_the_question_X,
2016-04-05 10:47:36 +02:00
(unsigned long) Gbl.Test.QstCod);
2017-04-28 14:02:08 +02:00
Lay_ShowAlertAndButton1 (Lay_QUESTION,Gbl.Message);
2016-04-05 02:59:34 +02:00
2017-04-28 14:02:08 +02:00
/* End alert */
if (EditingOnlyThisQst)
Lay_ShowAlertAndButton2 (ActRemTstQst,NULL,
Tst_PutParamsRemoveOneQst,
Lay_REMOVE_BUTTON,Txt_Remove_question);
else
{
Lay_ShowAlertAndButton2 (ActRemTstQst,NULL,
Tst_PutParamsRemoveQst,
Lay_REMOVE_BUTTON,Txt_Remove_question);
Tst_FreeTagsList ();
}
2016-04-05 02:59:34 +02:00
/***** Continue editing questions *****/
if (EditingOnlyThisQst)
Tst_ListOneQstToEdit ();
else
Tst_ListQuestionsToEdit ();
}
2017-04-28 14:02:08 +02:00
/*****************************************************************************/
/***** Put parameter to remove question when editing only one question *******/
/*****************************************************************************/
static void Tst_PutParamsRemoveOneQst (void)
{
Tst_PutParamQstCod ();
Par_PutHiddenParamChar ("OnlyThisQst",'Y');
}
/*****************************************************************************/
/***** Put parameter to remove question when editing several questions *******/
/*****************************************************************************/
static void Tst_PutParamsRemoveQst (void)
{
Tst_PutParamQstCod ();
Sta_WriteParamsDatesSeeAccesses ();
Tst_WriteParamEditQst ();
}
2016-04-05 02:59:34 +02:00
/*****************************************************************************/
/***************************** Remove a question *****************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
void Tst_RemoveQst (void)
{
extern const char *Txt_Question_removed;
2017-03-13 19:02:15 +01:00
char Query[256];
2014-12-01 23:55:08 +01:00
bool EditingOnlyThisQst;
/***** Get the question code *****/
2016-04-06 19:26:09 +02:00
Gbl.Test.QstCod = Tst_GetQstCod ();
if (Gbl.Test.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
2016-04-04 21:51:21 +02:00
/***** Remove images associated to question *****/
2016-04-07 12:36:58 +02:00
Tst_RemoveAllImgFilesFromAnsOfQst (Gbl.CurrentCrs.Crs.CrsCod,Gbl.Test.QstCod);
Tst_RemoveImgFileFromStemOfQst (Gbl.CurrentCrs.Crs.CrsCod,Gbl.Test.QstCod);
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 */
Tst_RemAnsFromQst ();
Tst_RemTagsFromQst ();
Tst_RemoveUnusedTagsFromCurrentCrs ();
/* Remove the question itself */
sprintf (Query,"DELETE FROM tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE QstCod=%ld AND CrsCod=%ld",
2014-12-01 23:55:08 +01:00
Gbl.Test.QstCod,Gbl.CurrentCrs.Crs.CrsCod);
DB_QueryDELETE (Query,"can not remove a question");
if (!mysql_affected_rows (&Gbl.mysql))
Lay_ShowErrorAndExit ("The question to be removed does not exist or belongs to another course.");
/***** Write message *****/
Lay_ShowAlert (Lay_SUCCESS,Txt_Question_removed);
/***** Continue editing questions *****/
if (!EditingOnlyThisQst)
Tst_ListQuestionsToEdit ();
}
/*****************************************************************************/
/*********************** 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;
2017-03-13 19:02:15 +01:00
char Query[256];
2014-12-01 23:55:08 +01:00
bool EditingOnlyThisQst;
bool Shuffle;
/***** Get the question code *****/
2016-04-06 19:26:09 +02:00
Gbl.Test.QstCod = Tst_GetQstCod ();
if (Gbl.Test.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 */
sprintf (Query,"UPDATE tst_questions SET Shuffle='%c'"
2017-03-24 01:09:27 +01:00
" WHERE QstCod=%ld AND CrsCod=%ld",
2014-12-01 23:55:08 +01:00
Shuffle ? 'Y' :
'N',
Gbl.Test.QstCod,Gbl.CurrentCrs.Crs.CrsCod);
DB_QueryUPDATE (Query,"can not update the shuffle type of a question");
/***** Write message *****/
sprintf (Gbl.Message,
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,
Gbl.Test.QstCod);
Lay_ShowAlert (Lay_SUCCESS,Gbl.Message);
/***** Continue editing questions *****/
if (EditingOnlyThisQst)
Tst_ListOneQstToEdit ();
else
Tst_ListQuestionsToEdit ();
}
/*****************************************************************************/
/************ 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... ************/
/*****************************************************************************/
static void Tst_PutParamQstCod (void)
{
Par_PutHiddenParamLong ("QstCod",Gbl.Test.QstCod);
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
/******** Insert or update question, tags and anser in the database **********/
/*****************************************************************************/
void Tst_InsertOrUpdateQstTagsAnsIntoDB (void)
{
/***** Insert or update question in the table of questions *****/
Tst_InsertOrUpdateQstIntoDB ();
/***** Insert tags in the tags table *****/
Tst_InsertTagsIntoDB ();
/***** Remove unused tags in current course *****/
Tst_RemoveUnusedTagsFromCurrentCrs ();
/***** Insert answers in the answers table *****/
Tst_InsertAnswersIntoDB ();
}
/*****************************************************************************/
/*********** Insert or update question in the table of questions *************/
/*****************************************************************************/
static void Tst_InsertOrUpdateQstIntoDB (void)
{
char *Query;
/***** Allocate space for query *****/
2016-04-06 19:26:09 +02:00
if ((Query = malloc (512 +
2014-12-01 23:55:08 +01:00
Gbl.Test.Stem.Length +
2016-04-06 19:26:09 +02:00
Gbl.Test.Feedback.Length +
2017-03-13 14:22:36 +01:00
Img_BYTES_NAME +
2017-03-13 19:02:15 +01:00
Img_MAX_BYTES_TITLE +
Cns_MAX_BYTES_WWW)) == NULL)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Not enough memory to store database query.");
if (Gbl.Test.QstCod < 0) // It's a new question
{
2016-04-01 21:56:00 +02:00
/***** Insert question in the table of questions *****/
2016-04-01 13:37:49 +02:00
sprintf (Query,"INSERT INTO tst_questions"
2016-04-06 01:10:04 +02:00
" (CrsCod,EditTime,AnsType,Shuffle,"
2016-04-15 02:33:16 +02:00
"Stem,Feedback,ImageName,ImageTitle,ImageURL,"
2016-04-06 01:10:04 +02:00
"NumHits,Score)"
2017-03-13 13:17:53 +01:00
" VALUES"
2017-03-24 01:09:27 +01:00
" (%ld,NOW(),'%s','%c',"
2016-04-15 02:33:16 +02:00
"'%s','%s','%s','%s','%s',"
2017-03-24 01:09:27 +01:00
"0,0)",
2014-12-01 23:55:08 +01:00
Gbl.CurrentCrs.Crs.CrsCod,
Tst_StrAnswerTypesDB[Gbl.Test.AnswerType],
Gbl.Test.Shuffle ? 'Y' :
'N',
Gbl.Test.Stem.Text,
2016-04-06 01:10:04 +02:00
Gbl.Test.Feedback.Text ? Gbl.Test.Feedback.Text : "",
2016-04-04 14:48:12 +02:00
Gbl.Test.Image.Name,
2016-04-15 02:33:16 +02:00
Gbl.Test.Image.Title ? Gbl.Test.Image.Title : "",
Gbl.Test.Image.URL ? Gbl.Test.Image.URL : "");
2014-12-01 23:55:08 +01:00
Gbl.Test.QstCod = DB_QueryINSERTandReturnCode (Query,"can not create question");
2016-04-03 01:24:20 +02:00
/* Update image status */
2016-04-04 14:48:12 +02:00
if (Gbl.Test.Image.Name[0])
Gbl.Test.Image.Status = Img_NAME_STORED_IN_DB;
2014-12-01 23:55:08 +01:00
}
else // It's an existing question
{
2016-04-01 21:56:00 +02:00
/***** Update existing question *****/
2016-04-03 18:51:48 +02:00
/* Update question in database */
sprintf (Query,"UPDATE tst_questions"
" SET EditTime=NOW(),AnsType='%s',Shuffle='%c',"
2016-04-15 02:33:16 +02:00
"Stem='%s',Feedback='%s',"
"ImageName='%s',ImageTitle='%s',ImageURL='%s'"
2017-03-24 01:09:27 +01:00
" WHERE QstCod=%ld AND CrsCod=%ld",
2016-04-03 18:51:48 +02:00
Tst_StrAnswerTypesDB[Gbl.Test.AnswerType],
Gbl.Test.Shuffle ? 'Y' :
'N',
Gbl.Test.Stem.Text,
Gbl.Test.Feedback.Text ? Gbl.Test.Feedback.Text : "",
2016-04-06 01:10:04 +02:00
Gbl.Test.Image.Name,
Gbl.Test.Image.Title ? Gbl.Test.Image.Title : "",
2016-04-15 02:33:16 +02:00
Gbl.Test.Image.URL ? Gbl.Test.Image.URL : "",
2016-04-03 18:51:48 +02:00
Gbl.Test.QstCod,Gbl.CurrentCrs.Crs.CrsCod);
2016-04-04 21:51:21 +02:00
DB_QueryUPDATE (Query,"can not update question");
2016-04-03 18:51:48 +02:00
/* Update image status */
2016-04-04 14:48:12 +02:00
if (Gbl.Test.Image.Name[0])
Gbl.Test.Image.Status = Img_NAME_STORED_IN_DB;
2014-12-01 23:55:08 +01:00
/* Remove answers and tags from this test question */
Tst_RemAnsFromQst ();
Tst_RemTagsFromQst ();
}
2016-04-08 16:37:59 +02:00
/***** Free space used for query *****/
2014-12-01 23:55:08 +01:00
free ((void *) Query);
}
/*****************************************************************************/
/*********************** Insert tags in the tags table ***********************/
/*****************************************************************************/
static void Tst_InsertTagsIntoDB (void)
{
char Query[256];
unsigned NumTag;
unsigned TagIdx;
long TagCod;
/***** For each tag... *****/
for (NumTag = 0, TagIdx = 0;
2016-04-06 22:27:33 +02:00
TagIdx < Gbl.Test.Tags.Num;
2014-12-01 23:55:08 +01:00
NumTag++)
2016-04-06 22:27:33 +02:00
if (Gbl.Test.Tags.Txt[NumTag][0])
2014-12-01 23:55:08 +01:00
{
/***** Check if this tag exists for current course *****/
2016-04-06 22:27:33 +02:00
if ((TagCod = Tst_GetTagCodFromTagTxt (Gbl.Test.Tags.Txt[NumTag])) < 0)
2014-12-01 23:55:08 +01:00
/* This tag is new for current course. Add it to tags table */
2016-04-06 22:27:33 +02:00
TagCod = Tst_CreateNewTag (Gbl.CurrentCrs.Crs.CrsCod,Gbl.Test.Tags.Txt[NumTag]);
2014-12-01 23:55:08 +01:00
/***** Insert tag in tst_question_tags *****/
2017-03-13 13:17:53 +01:00
sprintf (Query,"INSERT INTO tst_question_tags"
" (QstCod,TagCod,TagInd)"
" VALUES"
2017-03-24 01:09:27 +01:00
" (%ld,%ld,%u)",
2014-12-01 23:55:08 +01:00
Gbl.Test.QstCod,TagCod,TagIdx);
DB_QueryINSERT (Query,"can not create tag");
TagIdx++;
}
}
/*****************************************************************************/
/******************* Insert answers in the answers table *********************/
/*****************************************************************************/
static void Tst_InsertAnswersIntoDB (void)
{
char *Query;
unsigned NumOpt;
unsigned i;
/***** Allocate space for query *****/
2017-03-13 19:02:15 +01:00
if ((Query = malloc (256 +
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK * 2 +
Img_BYTES_NAME +
Img_MAX_BYTES_TITLE +
Cns_MAX_BYTES_WWW)) == NULL)
2014-12-01 23:55:08 +01:00
Lay_ShowErrorAndExit ("Not enough memory to store database query.");
/***** Insert answers in the answers table *****/
switch (Gbl.Test.AnswerType)
{
case Tst_ANS_INT:
2016-04-06 14:41:47 +02:00
sprintf (Query,"INSERT INTO tst_answers"
2016-04-15 02:33:16 +02:00
" (QstCod,AnsInd,Answer,Feedback,"
"ImageName,ImageTitle,ImageURL,Correct)"
2017-03-13 13:17:53 +01:00
" VALUES"
2017-03-24 01:09:27 +01:00
" (%ld,0,%ld,'','','','','Y')",
2014-12-01 23:55:08 +01:00
Gbl.Test.QstCod,
Gbl.Test.Answer.Integer);
DB_QueryINSERT (Query,"can not create answer");
break;
case Tst_ANS_FLOAT:
2016-06-04 14:21:01 +02: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++)
{
2016-04-06 14:41:47 +02:00
sprintf (Query,"INSERT INTO tst_answers"
2016-04-15 02:33:16 +02:00
" (QstCod,AnsInd,Answer,Feedback,"
"ImageName,ImageTitle,ImageURL,Correct)"
2017-03-13 13:17:53 +01:00
" VALUES"
" (%ld,%u,'%lg','','','','','Y')",
2014-12-01 23:55:08 +01:00
Gbl.Test.QstCod,i,
Gbl.Test.Answer.FloatingPoint[i]);
DB_QueryINSERT (Query,"can not create answer");
}
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:
2016-04-06 14:41:47 +02:00
sprintf (Query,"INSERT INTO tst_answers"
2016-04-15 02:33:16 +02:00
" (QstCod,AnsInd,Answer,Feedback,"
"ImageName,ImageTitle,ImageURL,Correct)"
2017-03-13 13:17:53 +01:00
" VALUES"
" (%ld,0,'%c','','','','','Y')",
2014-12-01 23:55:08 +01:00
Gbl.Test.QstCod,
Gbl.Test.Answer.TF);
DB_QueryINSERT (Query,"can not create answer");
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
case Tst_ANS_TEXT:
for (NumOpt = 0;
NumOpt < Gbl.Test.Answer.NumOptions;
NumOpt++)
if (Gbl.Test.Answer.Options[NumOpt].Text[0])
{
2016-04-06 14:41:47 +02:00
sprintf (Query,"INSERT INTO tst_answers"
2016-04-15 02:33:16 +02:00
" (QstCod,AnsInd,Answer,Feedback,"
"ImageName,ImageTitle,ImageURL,Correct)"
" VALUES"
2017-03-24 01:09:27 +01:00
" (%ld,%u,'%s','%s','%s','%s','%s','%c')",
2014-12-01 23:55:08 +01:00
Gbl.Test.QstCod,NumOpt,
Gbl.Test.Answer.Options[NumOpt].Text,
2016-04-06 01:10:04 +02:00
Gbl.Test.Answer.Options[NumOpt].Feedback ? Gbl.Test.Answer.Options[NumOpt].Feedback : "",
2016-04-04 21:51:21 +02:00
Gbl.Test.Answer.Options[NumOpt].Image.Name,
2016-04-06 01:10:04 +02:00
Gbl.Test.Answer.Options[NumOpt].Image.Title ? Gbl.Test.Answer.Options[NumOpt].Image.Title : "",
2016-04-15 02:33:16 +02:00
Gbl.Test.Answer.Options[NumOpt].Image.URL ? Gbl.Test.Answer.Options[NumOpt].Image.URL : "",
2014-12-01 23:55:08 +01:00
Gbl.Test.Answer.Options[NumOpt].Correct ? 'Y' :
'N');
DB_QueryINSERT (Query,"can not create answer");
2016-04-04 21:51:21 +02:00
/* Update image status */
if (Gbl.Test.Answer.Options[NumOpt].Image.Name[0])
Gbl.Test.Answer.Options[NumOpt].Image.Status = Img_NAME_STORED_IN_DB;
2014-12-01 23:55:08 +01:00
}
break;
default:
break;
}
/***** Free space allocated for query *****/
free ((void *) Query);
}
/*****************************************************************************/
/******************** Remove answers from a test question ********************/
/*****************************************************************************/
static void Tst_RemAnsFromQst (void)
{
2017-03-13 19:02:15 +01:00
char Query[128];
2014-12-01 23:55:08 +01:00
/***** Remove answers *****/
2017-03-24 01:09:27 +01:00
sprintf (Query,"DELETE FROM tst_answers WHERE QstCod=%ld",
2014-12-01 23:55:08 +01:00
Gbl.Test.QstCod);
DB_QueryDELETE (Query,"can not remove the answers of a question");
}
/*****************************************************************************/
/************************** Remove tags from a test question *****************/
/*****************************************************************************/
static void Tst_RemTagsFromQst (void)
{
2017-03-13 19:02:15 +01:00
char Query[128];
2014-12-01 23:55:08 +01:00
/***** Remove tags *****/
2017-03-24 01:09:27 +01:00
sprintf (Query,"DELETE FROM tst_question_tags WHERE QstCod=%ld",
2014-12-01 23:55:08 +01:00
Gbl.Test.QstCod);
DB_QueryDELETE (Query,"can not remove the tags of a question");
}
/*****************************************************************************/
/******************** Remove unused tags in current course *******************/
/*****************************************************************************/
static void Tst_RemoveUnusedTagsFromCurrentCrs (void)
{
2017-03-13 19:02:15 +01:00
char Query[512];
2014-12-01 23:55:08 +01:00
/***** Remove unused tags from tst_tags *****/
2016-05-31 11:21:04 +02:00
sprintf (Query,"DELETE FROM tst_tags"
2017-03-24 01:09:27 +01:00
" WHERE CrsCod=%ld AND TagCod NOT IN"
2016-05-31 11:21:04 +02:00
" (SELECT DISTINCT tst_question_tags.TagCod"
" FROM tst_questions,tst_question_tags"
2017-03-24 01:09:27 +01:00
" WHERE tst_questions.CrsCod=%ld"
2016-05-31 11:21:04 +02:00
" AND tst_questions.QstCod=tst_question_tags.QstCod)",
Gbl.CurrentCrs.Crs.CrsCod,
Gbl.CurrentCrs.Crs.CrsCod);
2014-12-01 23:55:08 +01:00
DB_QueryDELETE (Query,"can not remove unused tags");
}
2016-04-04 21:51:21 +02:00
/*****************************************************************************/
2016-04-07 12:36:58 +02:00
/******** Remove one file associated to one stem of one test question ********/
2016-04-04 21:51:21 +02:00
/*****************************************************************************/
2016-04-07 12:36:58 +02:00
static void Tst_RemoveImgFileFromStemOfQst (long CrsCod,long QstCod)
{
char Query[256];
MYSQL_RES *mysql_res;
MYSQL_ROW row;
/***** Get names of images associated to stems of test questions from database *****/
sprintf (Query,"SELECT ImageName FROM tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE QstCod=%ld AND CrsCod=%ld",
2016-04-07 12:36:58 +02:00
QstCod,CrsCod);
2016-04-08 20:21:33 +02:00
if (DB_QuerySELECT (Query,&mysql_res,"can not get image"))
2016-04-07 12:36:58 +02:00
{
/***** Get image name (row[0]) *****/
row = mysql_fetch_row (mysql_res);
/***** Remove image file *****/
Img_RemoveImageFile (row[0]);
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
/****** Remove all image files associated to stems of all test questions *****/
/****** in a course *****/
/*****************************************************************************/
static void Tst_RemoveAllImgFilesFromStemOfAllQstsInCrs (long CrsCod)
2016-04-04 21:51:21 +02:00
{
2017-03-13 19:02:15 +01:00
char Query[128];
2016-04-04 21:51:21 +02:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumImages;
unsigned NumImg;
/***** Get names of images associated to stems of test questions from database *****/
2017-03-24 01:09:27 +01:00
sprintf (Query,"SELECT ImageName FROM tst_questions WHERE CrsCod=%ld",
2016-04-07 12:36:58 +02:00
CrsCod);
2016-04-08 20:21:33 +02:00
NumImages = (unsigned) DB_QuerySELECT (Query,&mysql_res,"can not get images");
2016-04-04 21:51:21 +02:00
/***** Go over result removing image files *****/
for (NumImg = 0;
NumImg < NumImages;
NumImg++)
{
/***** Get image name (row[0]) *****/
row = mysql_fetch_row (mysql_res);
/***** Remove image file *****/
Img_RemoveImageFile (row[0]);
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
2016-04-07 12:36:58 +02:00
/**** Remove one image file associated to one answer of one test question ****/
/*****************************************************************************/
static void Tst_RemoveImgFileFromAnsOfQst (long CrsCod,long QstCod,unsigned AnsInd)
{
char Query[512];
MYSQL_RES *mysql_res;
MYSQL_ROW row;
/***** Get names of images associated to answers of test questions from database *****/
sprintf (Query,"SELECT tst_answers.ImageName"
" FROM tst_questions,tst_answers"
2017-03-24 01:09:27 +01:00
" WHERE tst_questions.CrsCod=%ld" // Extra check
" AND tst_questions.QstCod=%ld" // Extra check
2016-04-07 12:36:58 +02:00
" AND tst_questions.QstCod=tst_answers.QstCod"
2017-03-24 01:09:27 +01:00
" AND tst_answers.QstCod=%ld"
" AND tst_answers.AnsInd=%u",
2016-04-07 12:36:58 +02:00
CrsCod,QstCod,QstCod,AnsInd);
2016-04-08 20:21:33 +02:00
if (DB_QuerySELECT (Query,&mysql_res,"can not get images"))
2016-04-07 12:36:58 +02:00
{
/***** Get image name (row[0]) *****/
row = mysql_fetch_row (mysql_res);
/***** Remove image file *****/
Img_RemoveImageFile (row[0]);
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
/*** Remove all image files associated to all answers of one test question ***/
2016-04-04 21:51:21 +02:00
/*****************************************************************************/
2016-04-07 12:36:58 +02:00
static void Tst_RemoveAllImgFilesFromAnsOfQst (long CrsCod,long QstCod)
2016-04-04 21:51:21 +02:00
{
char Query[512];
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumImages;
unsigned NumImg;
/***** Get names of images associated to answers of test questions from database *****/
2016-04-07 12:36:58 +02:00
sprintf (Query,"SELECT tst_answers.ImageName"
" FROM tst_questions,tst_answers"
2017-03-24 01:09:27 +01:00
" WHERE tst_questions.CrsCod=%ld" // Extra check
" AND tst_questions.QstCod=%ld" // Extra check
2016-04-07 12:36:58 +02:00
" AND tst_questions.QstCod=tst_answers.QstCod"
2017-03-24 01:09:27 +01:00
" AND tst_answers.QstCod=%ld",
2016-04-07 12:36:58 +02:00
CrsCod,QstCod,QstCod);
2016-04-08 20:21:33 +02:00
NumImages = (unsigned) DB_QuerySELECT (Query,&mysql_res,"can not get images");
2016-04-07 12:36:58 +02:00
/***** Go over result removing image files *****/
for (NumImg = 0;
NumImg < NumImages;
NumImg++)
2016-04-04 21:51:21 +02:00
{
2016-04-07 12:36:58 +02:00
/***** Get image name (row[0]) *****/
row = mysql_fetch_row (mysql_res);
/***** Remove image file *****/
Img_RemoveImageFile (row[0]);
2016-04-04 21:51:21 +02:00
}
2016-04-07 12:36:58 +02:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
/** Remove all image files associated to all answers of all test questions ***/
/** in a course ***/
/*****************************************************************************/
static void Tst_RemoveAllImgFilesFromAnsOfAllQstsInCrs (long CrsCod)
{
char Query[512];
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumImages;
unsigned NumImg;
/***** Get names of images associated to answers of test questions from database *****/
sprintf (Query,"SELECT tst_answers.ImageName"
" FROM tst_questions,tst_answers"
2017-03-24 01:09:27 +01:00
" WHERE tst_questions.CrsCod=%ld"
2016-04-07 12:36:58 +02:00
" AND tst_questions.QstCod=tst_answers.QstCod",
CrsCod);
2016-04-08 20:21:33 +02:00
NumImages = (unsigned) DB_QuerySELECT (Query,&mysql_res,"can not get images");
2016-04-04 21:51:21 +02:00
/***** Go over result removing image files *****/
for (NumImg = 0;
NumImg < NumImages;
NumImg++)
{
/***** Get image name (row[0]) *****/
row = mysql_fetch_row (mysql_res);
/***** Remove image file *****/
Img_RemoveImageFile (row[0]);
}
/***** 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);
Stats->AvgQstsPerCourse = (float) Stats->NumQsts / (float) Stats->NumCoursesWithQuestions;
Stats->AvgHitsPerCourse = (float) Stats->NumHits / (float) Stats->NumCoursesWithQuestions;
}
Stats->AvgHitsPerQuestion = (float) Stats->NumHits / (float) Stats->NumQsts;
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)
static unsigned Tst_GetNumTstQuestions (Sco_Scope_t Scope,Tst_AnswerType_t AnsType,struct Tst_Stats *Stats)
{
char Query[1024];
MYSQL_RES *mysql_res;
MYSQL_ROW row;
/***** Get number of test questions from database *****/
switch (Scope)
{
2015-02-01 20:17:24 +01:00
case Sco_SCOPE_SYS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
sprintf (Query,"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM tst_questions");
else
sprintf (Query,"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM tst_questions"
" WHERE AnsType='%s'",
Tst_StrAnswerTypesDB[AnsType]);
break;
2015-03-09 11:03:55 +01:00
case Sco_SCOPE_CTY:
if (AnsType == Tst_ANS_ALL)
sprintf (Query,"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM institutions,centres,degrees,courses,tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE institutions.CtyCod=%ld"
2015-03-09 11:03:55 +01:00
" AND institutions.InsCod=centres.InsCod"
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod",
Gbl.CurrentCty.Cty.CtyCod);
else
sprintf (Query,"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM institutions,centres,degrees,courses,tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE institutions.CtyCod=%ld"
2015-03-09 11:03:55 +01:00
" 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'",
Gbl.CurrentCty.Cty.CtyCod,
Tst_StrAnswerTypesDB[AnsType]);
break;
2015-02-01 20:17:24 +01:00
case Sco_SCOPE_INS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
sprintf (Query,"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM centres,degrees,courses,tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE centres.InsCod=%ld"
2014-12-01 23:55:08 +01:00
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod",
Gbl.CurrentIns.Ins.InsCod);
else
sprintf (Query,"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM centres,degrees,courses,tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE centres.InsCod=%ld"
2014-12-01 23:55:08 +01:00
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'",
Gbl.CurrentIns.Ins.InsCod,
Tst_StrAnswerTypesDB[AnsType]);
break;
2015-02-01 20:17:24 +01:00
case Sco_SCOPE_CTR:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
sprintf (Query,"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM degrees,courses,tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE degrees.CtrCod=%ld"
2014-12-01 23:55:08 +01:00
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod",
Gbl.CurrentCtr.Ctr.CtrCod);
else
sprintf (Query,"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM degrees,courses,tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE degrees.CtrCod=%ld"
2014-12-01 23:55:08 +01:00
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'",
Gbl.CurrentCtr.Ctr.CtrCod,
Tst_StrAnswerTypesDB[AnsType]);
break;
2015-02-01 20:17:24 +01:00
case Sco_SCOPE_DEG:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
sprintf (Query,"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM courses,tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE courses.DegCod=%ld"
2014-12-01 23:55:08 +01:00
" AND courses.CrsCod=tst_questions.CrsCod",
Gbl.CurrentDeg.Deg.DegCod);
else
sprintf (Query,"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM courses,tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE courses.DegCod=%ld"
2014-12-01 23:55:08 +01:00
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'",
Gbl.CurrentDeg.Deg.DegCod,
Tst_StrAnswerTypesDB[AnsType]);
break;
2015-02-01 20:17:24 +01:00
case Sco_SCOPE_CRS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
sprintf (Query,"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE CrsCod=%ld",
2014-12-01 23:55:08 +01:00
Gbl.CurrentCrs.Crs.CrsCod);
else
sprintf (Query,"SELECT COUNT(*),SUM(NumHits),SUM(Score)"
" FROM tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE CrsCod=%ld AND AnsType='%s'",
2014-12-01 23:55:08 +01:00
Gbl.CurrentCrs.Crs.CrsCod,
Tst_StrAnswerTypesDB[AnsType]);
break;
default:
Lay_ShowErrorAndExit ("Wrong scope.");
break;
}
DB_QuerySELECT (Query,&mysql_res,"can not get number of test questions");
/***** 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)
static unsigned Tst_GetNumCoursesWithTstQuestions (Sco_Scope_t Scope,Tst_AnswerType_t AnsType)
{
char Query[1024];
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumCourses;
/***** Get number of courses with test questions from database *****/
switch (Scope)
{
2015-02-01 20:17:24 +01:00
case Sco_SCOPE_SYS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT CrsCod)"
2014-12-01 23:55:08 +01:00
" FROM tst_questions");
else
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT CrsCod)"
2014-12-01 23:55:08 +01:00
" FROM tst_questions"
" WHERE AnsType='%s'",
Tst_StrAnswerTypesDB[AnsType]);
break;
2015-03-09 11:03:55 +01:00
case Sco_SCOPE_CTY:
if (AnsType == Tst_ANS_ALL)
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
2015-03-09 11:03:55 +01:00
" FROM institutions,centres,degrees,courses,tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE institutions.CtyCod=%ld"
2015-03-09 11:03:55 +01:00
" AND institutions.InsCod=centres.InsCod"
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod",
Gbl.CurrentCty.Cty.CtyCod);
else
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
2015-03-09 11:03:55 +01:00
" FROM institutions,centres,degrees,courses,tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE institutions.CtyCod=%ld"
2015-03-09 11:03:55 +01:00
" 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'",
Gbl.CurrentCty.Cty.CtyCod,
Tst_StrAnswerTypesDB[AnsType]);
break;
2015-02-01 20:17:24 +01:00
case Sco_SCOPE_INS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
2014-12-01 23:55:08 +01:00
" FROM centres,degrees,courses,tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE centres.InsCod=%ld"
2014-12-01 23:55:08 +01:00
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod",
Gbl.CurrentIns.Ins.InsCod);
else
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
2014-12-01 23:55:08 +01:00
" FROM centres,degrees,courses,tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE centres.InsCod=%ld"
2014-12-01 23:55:08 +01:00
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'",
Gbl.CurrentIns.Ins.InsCod,
Tst_StrAnswerTypesDB[AnsType]);
break;
2015-02-01 20:17:24 +01:00
case Sco_SCOPE_CTR:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
2014-12-01 23:55:08 +01:00
" FROM degrees,courses,tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE degrees.CtrCod=%ld"
2014-12-01 23:55:08 +01:00
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod",
Gbl.CurrentCtr.Ctr.CtrCod);
else
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
2014-12-01 23:55:08 +01:00
" FROM degrees,courses,tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE degrees.CtrCod=%ld"
2014-12-01 23:55:08 +01:00
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'",
Gbl.CurrentCtr.Ctr.CtrCod,
Tst_StrAnswerTypesDB[AnsType]);
break;
2015-02-01 20:17:24 +01:00
case Sco_SCOPE_DEG:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNTDISTINCT (tst_questions.CrsCod)"
2014-12-01 23:55:08 +01:00
" FROM courses,tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE courses.DegCod=%ld"
2014-12-01 23:55:08 +01:00
" AND courses.CrsCod=tst_questions.CrsCod",
Gbl.CurrentDeg.Deg.DegCod);
else
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
2014-12-01 23:55:08 +01:00
" FROM courses,tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE courses.DegCod=%ld"
2014-12-01 23:55:08 +01:00
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'",
Gbl.CurrentDeg.Deg.DegCod,
Tst_StrAnswerTypesDB[AnsType]);
break;
2015-02-01 20:17:24 +01:00
case Sco_SCOPE_CRS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT CrsCod)"
2014-12-01 23:55:08 +01:00
" FROM tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE CrsCod=%ld",
2014-12-01 23:55:08 +01:00
Gbl.CurrentCrs.Crs.CrsCod);
else
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT CrsCod)"
2014-12-01 23:55:08 +01:00
" FROM tst_questions"
2017-03-24 01:09:27 +01:00
" WHERE CrsCod=%ld"
2014-12-01 23:55:08 +01:00
" AND AnsType='%s'",
Gbl.CurrentCrs.Crs.CrsCod,
Tst_StrAnswerTypesDB[AnsType]);
break;
default:
Lay_ShowErrorAndExit ("Wrong scope.");
break;
}
DB_QuerySELECT (Query,&mysql_res,"can not get number of courses with test questions");
/***** 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)
static unsigned Tst_GetNumCoursesWithPluggableTstQuestions (Sco_Scope_t Scope,Tst_AnswerType_t AnsType)
{
char Query[1024];
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumCourses;
/***** Get number of courses with test questions from database *****/
switch (Scope)
{
2015-02-01 20:17:24 +01:00
case Sco_SCOPE_SYS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM tst_questions,tst_config"
" WHERE tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
Tst_PluggableDB[Tst_PLUGGABLE_YES]);
else
sprintf (Query,"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],
Tst_PluggableDB[Tst_PLUGGABLE_YES]);
break;
2015-03-09 11:03:55 +01:00
case Sco_SCOPE_CTY:
if (AnsType == Tst_ANS_ALL)
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
2015-03-09 11:03:55 +01:00
" FROM institutions,centres,degrees,courses,tst_questions,tst_config"
2017-03-24 01:09:27 +01:00
" WHERE institutions.CtyCod=%ld"
2015-03-09 11:03:55 +01:00
" 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'",
Gbl.CurrentCty.Cty.CtyCod,
Tst_PluggableDB[Tst_PLUGGABLE_YES]);
else
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
2015-03-09 11:03:55 +01:00
" FROM institutions,centres,degrees,courses,tst_questions,tst_config"
2017-03-24 01:09:27 +01:00
" WHERE institutions.CtyCod=%ld"
2015-03-09 11:03:55 +01:00
" 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'",
Gbl.CurrentCty.Cty.CtyCod,
Tst_StrAnswerTypesDB[AnsType],
Tst_PluggableDB[Tst_PLUGGABLE_YES]);
break;
2015-02-01 20:17:24 +01:00
case Sco_SCOPE_INS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
2014-12-01 23:55:08 +01:00
" FROM centres,degrees,courses,tst_questions,tst_config"
2017-03-24 01:09:27 +01:00
" WHERE centres.InsCod=%ld"
2014-12-01 23:55:08 +01:00
" 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'",
Gbl.CurrentIns.Ins.InsCod,
Tst_PluggableDB[Tst_PLUGGABLE_YES]);
else
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
2014-12-01 23:55:08 +01:00
" FROM centres,degrees,courses,tst_questions,tst_config"
2017-03-24 01:09:27 +01:00
" WHERE centres.InsCod=%ld"
2014-12-01 23:55:08 +01:00
" 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'",
Gbl.CurrentIns.Ins.InsCod,
Tst_StrAnswerTypesDB[AnsType],
Tst_PluggableDB[Tst_PLUGGABLE_YES]);
break;
2015-02-01 20:17:24 +01:00
case Sco_SCOPE_CTR:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
2014-12-01 23:55:08 +01:00
" FROM degrees,courses,tst_questions,tst_config"
2017-03-24 01:09:27 +01:00
" WHERE degrees.CtrCod=%ld"
2014-12-01 23:55:08 +01:00
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
Gbl.CurrentCtr.Ctr.CtrCod,
Tst_PluggableDB[Tst_PLUGGABLE_YES]);
else
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
2014-12-01 23:55:08 +01:00
" FROM degrees,courses,tst_questions,tst_config"
2017-03-24 01:09:27 +01:00
" WHERE degrees.CtrCod=%ld"
2014-12-01 23:55:08 +01:00
" 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'",
Gbl.CurrentCtr.Ctr.CtrCod,
Tst_StrAnswerTypesDB[AnsType],
Tst_PluggableDB[Tst_PLUGGABLE_YES]);
break;
2015-02-01 20:17:24 +01:00
case Sco_SCOPE_DEG:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
2014-12-01 23:55:08 +01:00
" FROM courses,tst_questions,tst_config"
2017-03-24 01:09:27 +01:00
" WHERE courses.DegCod=%ld"
2014-12-01 23:55:08 +01:00
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
Gbl.CurrentDeg.Deg.DegCod,
Tst_PluggableDB[Tst_PLUGGABLE_YES]);
else
2016-10-27 15:06:11 +02:00
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
2014-12-01 23:55:08 +01:00
" FROM courses,tst_questions,tst_config"
2017-03-24 01:09:27 +01:00
" WHERE courses.DegCod=%ld"
2014-12-01 23:55:08 +01:00
" AND courses.CrsCod=tst_questions.CrsCod"
" AND tst_questions.AnsType='%s'"
" AND tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
Gbl.CurrentDeg.Deg.DegCod,
Tst_StrAnswerTypesDB[AnsType],
Tst_PluggableDB[Tst_PLUGGABLE_YES]);
break;
2015-02-01 20:17:24 +01:00
case Sco_SCOPE_CRS:
2014-12-01 23:55:08 +01:00
if (AnsType == Tst_ANS_ALL)
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM tst_questions,tst_config"
2017-03-24 01:09:27 +01:00
" WHERE tst_questions.CrsCod=%ld"
2014-12-01 23:55:08 +01:00
" AND tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
Gbl.CurrentCrs.Crs.CrsCod,
Tst_PluggableDB[Tst_PLUGGABLE_YES]);
else
sprintf (Query,"SELECT COUNT(DISTINCT tst_questions.CrsCod)"
" FROM tst_questions,tst_config"
2017-03-24 01:09:27 +01:00
" WHERE tst_questions.CrsCod=%ld"
2014-12-01 23:55:08 +01:00
" AND tst_questions.AnsType='%s'"
" AND tst_questions.CrsCod=tst_config.CrsCod"
" AND tst_config.pluggable='%s'",
Gbl.CurrentCrs.Crs.CrsCod,
Tst_StrAnswerTypesDB[AnsType],
Tst_PluggableDB[Tst_PLUGGABLE_YES]);
break;
default:
Lay_ShowErrorAndExit ("Wrong scope.");
break;
}
DB_QuerySELECT (Query,&mysql_res,"can not get number of courses with pluggable test questions");
/***** 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;
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/************ Select users and dates to show their test results **************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
void Tst_SelUsrsToSeeUsrsTestResults (void)
2014-12-01 23:55:08 +01:00
{
2016-11-25 09:41:06 +01:00
extern const char *Hlp_ASSESSMENT_Tests_test_results;
2015-10-02 12:28:52 +02:00
extern const char *The_ClassForm[The_NUM_THEMES];
2016-11-21 13:15:08 +01:00
extern const char *Txt_Test_results;
2015-04-11 23:46:21 +02:00
extern const char *Txt_Users;
2016-11-21 13:15:08 +01:00
extern const char *Txt_View_test_results;
2016-06-23 13:51:37 +02:00
unsigned NumTotalUsrs;
2014-12-01 23:55:08 +01:00
2015-10-19 21:49:45 +02:00
/***** Get and update type of list,
number of columns in class photo
2014-12-01 23:55:08 +01:00
and preference about view photos *****/
Usr_GetAndUpdatePrefsAboutUsrList ();
2016-11-25 03:21:02 +01:00
/***** Get groups to show ******/
Grp_GetParCodsSeveralGrpsToShowUsrs ();
2014-12-01 23:55:08 +01:00
/***** Get and order lists of users from this course *****/
2016-06-23 13:10:43 +02:00
Usr_GetListUsrs (Rol_TEACHER,Sco_SCOPE_CRS);
Usr_GetListUsrs (Rol_STUDENT,Sco_SCOPE_CRS);
2016-06-23 13:51:37 +02:00
NumTotalUsrs = Gbl.Usrs.LstUsrs[Rol_TEACHER].NumUsrs +
Gbl.Usrs.LstUsrs[Rol_STUDENT].NumUsrs;
2014-12-01 23:55:08 +01:00
2016-11-25 03:21:02 +01:00
/***** Start frame *****/
2016-11-25 09:41:06 +01:00
Lay_StartRoundFrame (NULL,Txt_Test_results,
NULL,Hlp_ASSESSMENT_Tests_test_results);
2016-11-25 03:21:02 +01:00
/***** Show form to select the groups *****/
Grp_ShowFormToSelectSeveralGroups (ActReqSeeUsrTstRes);
2016-06-23 13:51:37 +02:00
if (NumTotalUsrs)
2014-12-01 23:55:08 +01:00
{
2016-07-04 14:03:04 +02:00
if (Usr_GetIfShowBigList (NumTotalUsrs,NULL))
2014-12-01 23:55:08 +01:00
{
2015-10-02 01:04:28 +02:00
/***** Form to select type of list used for select several users *****/
2016-11-21 13:15:08 +01:00
Usr_ShowFormsToSelectUsrListType (ActReqSeeUsrTstRes);
2015-10-02 01:04:28 +02:00
2015-04-11 17:33:14 +02:00
/***** Start form *****/
2016-11-21 13:15:08 +01:00
Act_FormStart (ActSeeUsrTstRes);
2014-12-01 23:55:08 +01:00
Grp_PutParamsCodGrps ();
/***** Put list of users to select some of them *****/
2017-05-01 12:36:24 +02:00
Lay_StartTableCenter (2);
fprintf (Gbl.F.Out,"<tr>"
2015-10-02 12:28:52 +02:00
"<td class=\"%s RIGHT_TOP\">"
"%s:"
"</td>"
"<td colspan=\"2\" class=\"%s LEFT_TOP\">"
2016-04-01 18:37:16 +02:00
"<table class=\"CELLS_PAD_2\">",
2015-10-02 12:28:52 +02:00
The_ClassForm[Gbl.Prefs.Theme],Txt_Users,
The_ClassForm[Gbl.Prefs.Theme]);
2015-04-07 21:44:24 +02:00
Usr_ListUsersToSelect (Rol_TEACHER);
Usr_ListUsersToSelect (Rol_STUDENT);
2015-10-02 12:28:52 +02:00
fprintf (Gbl.F.Out,"</table>"
"</td>"
"</tr>");
2014-12-01 23:55:08 +01:00
/***** Starting and ending dates in the search *****/
2017-02-26 20:09:21 +01:00
Dat_PutFormStartEndClientLocalDateTimesWithYesterdayToday (false);
2015-10-02 12:28:52 +02:00
2017-05-01 12:36:24 +02:00
Lay_EndTable ();
2014-12-01 23:55:08 +01:00
2015-10-02 01:04:28 +02:00
/***** Send button *****/
2016-11-21 13:15:08 +01:00
Lay_PutConfirmButton (Txt_View_test_results);
2015-10-02 01:04:28 +02:00
/***** End form *****/
2015-03-13 00:16:02 +01:00
Act_FormEnd ();
2014-12-01 23:55:08 +01:00
}
}
else
2015-04-07 21:44:24 +02:00
Usr_ShowWarningNoUsersFound (Rol_UNKNOWN);
2014-12-01 23:55:08 +01:00
2016-11-25 03:21:02 +01:00
/***** End frame *****/
Lay_EndRoundFrame ();
2014-12-01 23:55:08 +01:00
/***** Free memory for users' list *****/
2016-06-23 13:10:43 +02:00
Usr_FreeUsrsList (Rol_TEACHER);
Usr_FreeUsrsList (Rol_STUDENT);
2014-12-01 23:55:08 +01:00
2016-07-04 14:03:04 +02:00
/***** Free memory used by list of selected users' codes *****/
Usr_FreeListsSelectedUsrsCods ();
2014-12-01 23:55:08 +01:00
/***** Free memory for list of selected groups *****/
Grp_FreeListCodSelectedGrps ();
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/******************* Select dates to show my test results ********************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
void Tst_SelDatesToSeeMyTestResults (void)
2014-12-01 23:55:08 +01:00
{
2016-11-25 09:41:06 +01:00
extern const char *Hlp_ASSESSMENT_Tests_test_results;
2016-11-21 13:15:08 +01:00
extern const char *Txt_Test_results;
extern const char *Txt_View_test_results;
2014-12-01 23:55:08 +01:00
2015-04-11 17:33:14 +02:00
/***** Start form *****/
2016-11-21 13:15:08 +01:00
Act_FormStart (ActSeeMyTstRes);
2014-12-01 23:55:08 +01:00
/***** Starting and ending dates in the search *****/
2016-11-25 09:41:06 +01:00
Lay_StartRoundFrameTable (NULL,Txt_Test_results,
NULL,Hlp_ASSESSMENT_Tests_test_results,2);
2017-02-26 20:09:21 +01:00
Dat_PutFormStartEndClientLocalDateTimesWithYesterdayToday (false);
2014-12-01 23:55:08 +01:00
2015-10-02 12:28:52 +02:00
/***** Send button and end frame *****/
2016-11-21 13:15:08 +01:00
Lay_EndRoundFrameTableWithButton (Lay_CONFIRM_BUTTON,Txt_View_test_results);
2015-10-02 12:28:52 +02:00
/***** End form *****/
2015-03-13 00:16:02 +01:00
Act_FormEnd ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/********************* Store test result in database *************************/
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static long Tst_CreateTestResultInDB (void)
2014-12-01 23:55:08 +01:00
{
char Query[256];
2016-11-21 13:15:08 +01:00
/***** Insert new test result into table *****/
sprintf (Query,"INSERT INTO tst_exams"
" (CrsCod,UsrCod,AllowTeachers,TstTime,NumQsts)"
2017-03-13 13:17:53 +01:00
" VALUES"
2017-03-24 01:09:27 +01:00
" (%ld,%ld,'%c',NOW(),%u)",
2014-12-01 23:55:08 +01:00
Gbl.CurrentCrs.Crs.CrsCod,
Gbl.Usrs.Me.UsrDat.UsrCod,
Gbl.Test.AllowTeachers ? 'Y' :
'N',
Gbl.Test.NumQsts);
2016-11-21 13:15:08 +01:00
return DB_QueryINSERTandReturnCode (Query,"can not create new test result");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/********************* Store test result in database *************************/
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_StoreScoreOfTestResultInDB (long TstCod,
2014-12-01 23:55:08 +01:00
unsigned NumQstsNotBlank,double Score)
{
char Query[256];
2016-11-21 13:15:08 +01:00
/***** Update score in test result *****/
2016-06-04 14:21:01 +02:00
Str_SetDecimalPointToUS (); // To print the floating point as a dot
2014-12-01 23:55:08 +01:00
sprintf (Query,"UPDATE tst_exams"
2017-03-24 01:09:27 +01:00
" SET NumQstsNotBlank=%u,Score='%lf'"
" WHERE TstCod=%ld",
2014-12-01 23:55:08 +01:00
NumQstsNotBlank,Score,
TstCod);
2016-06-04 14:21:01 +02:00
Str_SetDecimalPointToLocal (); // Return to local system
2016-11-21 13:15:08 +01:00
DB_QueryUPDATE (Query,"can not update result of test result");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/******************** Show test results for several users ********************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
void Tst_ShowUsrsTestResults (void)
2014-12-01 23:55:08 +01:00
{
2016-11-25 09:41:06 +01:00
extern const char *Hlp_ASSESSMENT_Tests_test_results;
2016-11-21 13:15:08 +01:00
extern const char *Txt_Test_results;
2014-12-01 23:55:08 +01:00
extern const char *Txt_You_must_select_one_ore_more_users;
const char *Ptr;
/***** Get list of the selected users's IDs *****/
2016-07-04 14:03:04 +02:00
Usr_GetListsSelectedUsrsCods ();
2014-12-01 23:55:08 +01:00
/***** Get starting and ending dates *****/
Dat_GetIniEndDatesFromForm ();
/***** Check the number of users whose tests results will be shown *****/
2016-11-14 10:05:41 +01:00
if (Usr_CountNumUsrsInListOfSelectedUsrs ()) // If some users are selected...
2014-12-01 23:55:08 +01:00
{
/***** Header of the table with the list of users *****/
2016-11-25 09:41:06 +01:00
Lay_StartRoundFrameTable (NULL,Txt_Test_results,
NULL,Hlp_ASSESSMENT_Tests_test_results,2);
2014-12-01 23:55:08 +01:00
Tst_ShowHeaderTestResults ();
/***** List the assignments and works of the selected users *****/
Ptr = Gbl.Usrs.Select.All;
while (*Ptr)
{
2017-03-13 14:22:36 +01:00
Par_GetNextStrUntilSeparParamMult (&Ptr,Gbl.Usrs.Other.UsrDat.EncryptedUsrCod,
Cry_BYTES_ENCRYPTED_STR_SHA256_BASE64);
2014-12-01 23:55:08 +01:00
Usr_GetUsrCodFromEncryptedUsrCod (&Gbl.Usrs.Other.UsrDat);
if (Usr_ChkUsrCodAndGetAllUsrDataFromUsrCod (&Gbl.Usrs.Other.UsrDat)) // Get of the database the data of the user
2015-09-17 11:21:49 +02:00
if (Usr_CheckIfUsrBelongsToCrs (Gbl.Usrs.Other.UsrDat.UsrCod,
Gbl.CurrentCrs.Crs.CrsCod,
false))
2016-11-21 13:15:08 +01:00
/***** Show test results *****/
Tst_ShowTestResults (&Gbl.Usrs.Other.UsrDat);
2014-12-01 23:55:08 +01:00
}
/***** End of the table *****/
2015-04-12 18:01:06 +02:00
Lay_EndRoundFrameTable ();
2014-12-01 23:55:08 +01:00
}
else // If no users are selected...
{
// ...write warning alert
Lay_ShowAlert (Lay_WARNING,Txt_You_must_select_one_ore_more_users);
// ...and show again the form
2016-11-21 13:15:08 +01:00
Tst_SelUsrsToSeeUsrsTestResults ();
2014-12-01 23:55:08 +01:00
}
2016-07-04 14:03:04 +02:00
/***** Free memory used by list of selected users' codes *****/
Usr_FreeListsSelectedUsrsCods ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/*********************** Show header of my test results **********************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
static void Tst_ShowHeaderTestResults (void)
{
2016-06-15 14:42:28 +02:00
extern const char *Txt_User[Usr_NUM_SEXS];
2014-12-01 23:55:08 +01:00
extern const char *Txt_Date;
extern const char *Txt_Questions;
extern const char *Txt_Non_blank_BR_questions;
extern const char *Txt_Total_BR_score;
extern const char *Txt_Average_BR_score_BR_per_question_BR_from_0_to_1;
extern const char *Txt_Score;
extern const char *Txt_out_of_PART_OF_A_SCORE;
fprintf (Gbl.F.Out,"<tr>"
2015-09-06 20:02:14 +02:00
"<th colspan=\"2\" class=\"CENTER_TOP\">"
2014-12-27 00:28:19 +01:00
"%s"
"</th>"
2015-09-06 20:02:14 +02:00
"<th class=\"RIGHT_TOP\">"
2014-12-27 00:28:19 +01:00
"%s"
"</th>"
2015-09-06 20:02:14 +02:00
"<th class=\"RIGHT_TOP\">"
2014-12-27 00:28:19 +01:00
"%s"
"</th>"
2015-09-06 20:02:14 +02:00
"<th class=\"RIGHT_TOP\">"
2014-12-27 00:28:19 +01:00
"%s"
"</th>"
2015-09-06 20:02:14 +02:00
"<th class=\"RIGHT_TOP\">"
2014-12-27 00:28:19 +01:00
"%s"
"</th>"
2015-09-06 20:02:14 +02:00
"<th class=\"RIGHT_TOP\">"
2014-12-27 00:28:19 +01:00
"%s"
"</th>"
2015-09-06 20:02:14 +02:00
"<th class=\"RIGHT_TOP\">"
2014-12-27 00:28:19 +01:00
"%s<br />%s<br />%u"
"</th>"
2014-12-01 23:55:08 +01:00
"<th></th>"
"</tr>",
2016-06-15 14:42:28 +02:00
Txt_User[Usr_SEX_UNKNOWN],
2014-12-01 23:55:08 +01:00
Txt_Date,
Txt_Questions,
Txt_Non_blank_BR_questions,
Txt_Total_BR_score,
Txt_Average_BR_score_BR_per_question_BR_from_0_to_1,
Txt_Score,Txt_out_of_PART_OF_A_SCORE,Tst_SCORE_MAX);
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/***************************** Show my test results **************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
void Tst_ShowMyTestResults (void)
2014-12-01 23:55:08 +01:00
{
2016-11-25 09:41:06 +01:00
extern const char *Hlp_ASSESSMENT_Tests_test_results;
2016-11-21 13:15:08 +01:00
extern const char *Txt_Test_results;
2015-04-12 18:01:06 +02:00
2014-12-01 23:55:08 +01:00
/***** Get starting and ending dates *****/
Dat_GetIniEndDatesFromForm ();
/***** Header of the table with the list of users *****/
2016-11-25 09:41:06 +01:00
Lay_StartRoundFrameTable (NULL,Txt_Test_results,
NULL,Hlp_ASSESSMENT_Tests_test_results,2);
2014-12-01 23:55:08 +01:00
Tst_ShowHeaderTestResults ();
2016-11-21 13:15:08 +01:00
/***** List my test results *****/
2014-12-01 23:55:08 +01:00
Tst_GetConfigTstFromDB (); // To get feedback type
2016-11-21 13:15:08 +01:00
Tst_ShowTestResults (&Gbl.Usrs.Me.UsrDat);
2014-12-01 23:55:08 +01:00
/***** End of the table *****/
2015-04-12 18:01:06 +02:00
Lay_EndRoundFrameTable ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/*********** Show the test results of a user in the current course ***********/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_ShowTestResults (struct UsrData *UsrDat)
2014-12-01 23:55:08 +01:00
{
2015-12-29 11:35:01 +01:00
extern const char *Txt_Today;
2016-11-21 13:15:08 +01:00
extern const char *Txt_Visible_tests;
extern const char *Txt_View_test;
2014-12-01 23:55:08 +01:00
char Query[512];
MYSQL_RES *mysql_res;
MYSQL_ROW row;
2016-11-21 13:15:08 +01:00
unsigned NumTestResults;
unsigned NumTest;
2015-11-09 16:23:39 +01:00
static unsigned UniqueId = 0;
2014-12-01 23:55:08 +01:00
long TstCod;
2016-11-21 13:15:08 +01:00
unsigned NumQstsInThisTest;
unsigned NumQstsNotBlankInThisTest;
2014-12-01 23:55:08 +01:00
unsigned NumTotalQsts = 0;
unsigned NumTotalQstsNotBlank = 0;
2016-11-21 13:15:08 +01:00
double ScoreInThisTest;
double TotalScoreOfAllTests = 0.0;
unsigned NumTestResultsVisibleByTchs = 0;
2014-12-01 23:55:08 +01:00
bool ItsMe = (UsrDat->UsrCod == Gbl.Usrs.Me.UsrDat.UsrCod);
2016-11-21 13:15:08 +01:00
bool ICanViewTest;
2014-12-01 23:55:08 +01:00
bool ICanViewScore;
2016-11-24 13:50:44 +01:00
bool ICanViewTotalScore;
2015-10-26 14:11:30 +01:00
time_t TimeUTC;
2014-12-01 23:55:08 +01:00
char *ClassDat;
/***** Make database query *****/
sprintf (Query,"SELECT TstCod,AllowTeachers,"
2015-11-09 14:22:25 +01:00
"UNIX_TIMESTAMP(TstTime),"
2014-12-01 23:55:08 +01:00
"NumQsts,NumQstsNotBlank,Score"
" FROM tst_exams"
2017-03-24 01:09:27 +01:00
" WHERE CrsCod=%ld AND UsrCod=%ld"
" AND TstTime>=FROM_UNIXTIME(%ld)"
" AND TstTime<=FROM_UNIXTIME(%ld)"
2014-12-01 23:55:08 +01:00
" ORDER BY TstCod",
Gbl.CurrentCrs.Crs.CrsCod,
UsrDat->UsrCod,
2015-10-27 19:00:21 +01:00
(long) Gbl.DateRange.TimeUTC[0],
(long) Gbl.DateRange.TimeUTC[1]);
2016-11-21 13:15:08 +01:00
NumTestResults = (unsigned) DB_QuerySELECT (Query,&mysql_res,"can not get test exams of a user");
2014-12-01 23:55:08 +01:00
/***** Show user's data *****/
fprintf (Gbl.F.Out,"<tr>");
2016-11-21 13:15:08 +01:00
Tst_ShowDataUsr (UsrDat,NumTestResults);
2014-12-01 23:55:08 +01:00
2016-11-21 13:15:08 +01:00
/***** Get and print test results *****/
if (NumTestResults)
2014-12-01 23:55:08 +01:00
{
2016-11-21 13:15:08 +01:00
for (NumTest = 0;
NumTest < NumTestResults;
NumTest++)
2014-12-01 23:55:08 +01:00
{
row = mysql_fetch_row (mysql_res);
/* Get test code (row[0]) */
if ((TstCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
2016-11-21 13:15:08 +01:00
Lay_ShowErrorAndExit ("Wrong code of test result.");
2014-12-01 23:55:08 +01:00
2016-11-21 13:15:08 +01:00
/* Get if teachers are allowed to see this test result (row[1]) */
2016-09-07 18:48:10 +02:00
Gbl.Test.AllowTeachers = (row[1][0] == 'Y');
2014-12-01 23:55:08 +01:00
ClassDat = Gbl.Test.AllowTeachers ? "DAT" :
"DAT_LIGHT";
2016-11-24 13:50:44 +01:00
switch (Gbl.Usrs.Me.LoggedRole)
{
case Rol_STUDENT:
ICanViewTest = ItsMe;
ICanViewScore = ItsMe &&
Gbl.Test.Config.FeedbackType != Tst_FEEDBACK_NOTHING;
break;
case Rol_TEACHER:
case Rol_DEG_ADM:
case Rol_CTR_ADM:
case Rol_INS_ADM:
ICanViewTest =
ICanViewScore = ItsMe ||
Gbl.Test.AllowTeachers;
break;
case Rol_SYS_ADM:
ICanViewTest =
ICanViewScore = true;
break;
default:
ICanViewTest =
ICanViewScore = false;
break;
}
2014-12-01 23:55:08 +01:00
2016-11-21 13:15:08 +01:00
if (NumTest)
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"<tr>");
2015-10-26 14:11:30 +01:00
/* Write date and time (row[2] holds UTC date-time) */
TimeUTC = Dat_GetUNIXTimeFromStr (row[2]);
2015-11-09 16:23:39 +01:00
UniqueId++;
2015-10-26 14:11:30 +01:00
fprintf (Gbl.F.Out,"<td id =\"tst_date_%u\" class=\"%s RIGHT_TOP COLOR%u\">"
"<script type=\"text/javascript\">"
2016-12-12 23:10:11 +01:00
"writeLocalDateHMSFromUTC('tst_date_%u',"
"%ld,',&nbsp;','%s',true,false,true);"
2015-10-26 14:11:30 +01:00
"</script>"
"</td>",
UniqueId,ClassDat,Gbl.RowEvenOdd,
2015-12-29 11:35:01 +01:00
UniqueId,(long) TimeUTC,Txt_Today);
2014-12-01 23:55:08 +01:00
/* Get number of questions (row[3]) */
2016-11-24 13:50:44 +01:00
if (sscanf (row[3],"%u",&NumQstsInThisTest) != 1)
2016-11-21 13:15:08 +01:00
NumQstsInThisTest = 0;
2016-11-24 13:50:44 +01:00
if (Gbl.Test.AllowTeachers)
NumTotalQsts += NumQstsInThisTest;
2014-12-01 23:55:08 +01:00
/* Get number of questions not blank (row[4]) */
2016-11-24 13:50:44 +01:00
if (sscanf (row[4],"%u",&NumQstsNotBlankInThisTest) != 1)
2016-11-21 13:15:08 +01:00
NumQstsNotBlankInThisTest = 0;
2016-11-24 13:50:44 +01:00
if (Gbl.Test.AllowTeachers)
NumTotalQstsNotBlank += NumQstsNotBlankInThisTest;
2014-12-01 23:55:08 +01:00
/* Get score (row[5]) */
2016-06-04 14:21:01 +02:00
Str_SetDecimalPointToUS (); // To get the decimal point as a dot
2016-11-24 13:50:44 +01:00
if (sscanf (row[5],"%lf",&ScoreInThisTest) != 1)
2016-11-21 13:15:08 +01:00
ScoreInThisTest = 0.0;
2016-06-04 14:21:01 +02:00
Str_SetDecimalPointToLocal (); // Return to local system
2016-11-24 13:50:44 +01:00
if (Gbl.Test.AllowTeachers)
TotalScoreOfAllTests += ScoreInThisTest;
2014-12-01 23:55:08 +01:00
/* Write number of questions */
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"%s RIGHT_TOP COLOR%u\">",
ClassDat,Gbl.RowEvenOdd);
2016-11-21 13:15:08 +01:00
if (ICanViewTest)
fprintf (Gbl.F.Out,"%u",NumQstsInThisTest);
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"</td>");
/* Write number of questions not blank */
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"%s RIGHT_TOP COLOR%u\">",
ClassDat,Gbl.RowEvenOdd);
2016-11-21 13:15:08 +01:00
if (ICanViewTest)
fprintf (Gbl.F.Out,"%u",NumQstsNotBlankInThisTest);
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"</td>");
/* Write score */
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"%s RIGHT_TOP COLOR%u\">",
ClassDat,Gbl.RowEvenOdd);
2014-12-01 23:55:08 +01:00
if (ICanViewScore)
2016-11-21 13:15:08 +01:00
fprintf (Gbl.F.Out,"%.2lf",ScoreInThisTest);
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"</td>");
/* Write average score per question */
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"%s RIGHT_TOP COLOR%u\">",
ClassDat,Gbl.RowEvenOdd);
2014-12-01 23:55:08 +01:00
if (ICanViewScore)
fprintf (Gbl.F.Out,"%.2lf",
2016-11-21 13:15:08 +01:00
NumQstsInThisTest ? ScoreInThisTest / (double) NumQstsInThisTest :
2014-12-01 23:55:08 +01:00
0.0);
fprintf (Gbl.F.Out,"</td>");
/* Write score over Tst_SCORE_MAX */
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"%s RIGHT_TOP COLOR%u\">",
ClassDat,Gbl.RowEvenOdd);
2014-12-01 23:55:08 +01:00
if (ICanViewScore)
fprintf (Gbl.F.Out,"%.2lf",
2016-11-21 13:15:08 +01:00
NumQstsInThisTest ? ScoreInThisTest * Tst_SCORE_MAX / (double) NumQstsInThisTest :
2014-12-01 23:55:08 +01:00
0.0);
fprintf (Gbl.F.Out,"</td>");
2016-11-21 13:15:08 +01:00
/* Link to show this result */
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"RIGHT_TOP COLOR%u\">",
Gbl.RowEvenOdd);
2016-11-21 13:15:08 +01:00
if (ICanViewTest)
2014-12-01 23:55:08 +01:00
{
2016-11-21 13:15:08 +01:00
Act_FormStart (Gbl.Action.Act == ActSeeMyTstRes ? ActSeeOneTstResMe :
ActSeeOneTstResOth);
2014-12-01 23:55:08 +01:00
Tst_PutParamTstCod (TstCod);
2016-11-21 01:20:19 +01:00
fprintf (Gbl.F.Out,"<input type=\"image\" src=\"%s/exam64x64.png\""
2014-12-30 19:09:25 +01:00
" alt=\"%s\" title=\"%s\""
2016-11-14 10:05:41 +01:00
" class=\"ICO20x20B\" />",
2014-12-01 23:55:08 +01:00
Gbl.Prefs.IconsURL,
2016-11-21 13:15:08 +01:00
Txt_View_test,
Txt_View_test);
2015-03-13 00:16:02 +01:00
Act_FormEnd ();
2014-12-01 23:55:08 +01:00
}
fprintf (Gbl.F.Out,"</td>"
"</tr>");
if (Gbl.Test.AllowTeachers)
2016-11-21 13:15:08 +01:00
NumTestResultsVisibleByTchs++;
2014-12-01 23:55:08 +01:00
}
/***** Write totals for this user *****/
2016-11-24 13:50:44 +01:00
switch (Gbl.Usrs.Me.LoggedRole)
{
case Rol_STUDENT:
ICanViewTotalScore = ItsMe &&
Gbl.Test.Config.FeedbackType != Tst_FEEDBACK_NOTHING;
break;
case Rol_TEACHER:
case Rol_DEG_ADM:
case Rol_CTR_ADM:
case Rol_INS_ADM:
ICanViewTotalScore = ItsMe ||
NumTestResultsVisibleByTchs;
break;
case Rol_SYS_ADM:
ICanViewTotalScore = true;
break;
default:
ICanViewTotalScore = false;
break;
}
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"<tr>"
2015-09-06 15:34:44 +02:00
"<td class=\"DAT_N_LINE_TOP RIGHT_MIDDLE COLOR%u\">"
2014-12-26 14:45:14 +01:00
"%s: %u"
2014-12-01 23:55:08 +01:00
"</td>",
2015-09-03 17:14:30 +02:00
Gbl.RowEvenOdd,
2016-11-21 13:15:08 +01:00
Txt_Visible_tests,NumTestResultsVisibleByTchs);
2014-12-01 23:55:08 +01:00
/* Write total number of questions */
2015-09-06 15:34:44 +02:00
fprintf (Gbl.F.Out,"<td class=\"DAT_N_LINE_TOP RIGHT_MIDDLE COLOR%u\">",
2015-09-03 17:14:30 +02:00
Gbl.RowEvenOdd);
2016-11-21 13:15:08 +01:00
if (NumTestResultsVisibleByTchs)
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"%u",NumTotalQsts);
fprintf (Gbl.F.Out,"</td>");
/* Write total number of questions not blank */
2015-09-06 15:34:44 +02:00
fprintf (Gbl.F.Out,"<td class=\"DAT_N_LINE_TOP RIGHT_MIDDLE COLOR%u\">",
2015-09-03 17:14:30 +02:00
Gbl.RowEvenOdd);
2016-11-21 13:15:08 +01:00
if (NumTestResultsVisibleByTchs)
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"%u",NumTotalQstsNotBlank);
fprintf (Gbl.F.Out,"</td>");
/* Write total score */
2015-09-06 15:34:44 +02:00
fprintf (Gbl.F.Out,"<td class=\"DAT_N_LINE_TOP RIGHT_MIDDLE COLOR%u\">",
2015-09-03 17:14:30 +02:00
Gbl.RowEvenOdd);
2016-11-24 13:50:44 +01:00
if (ICanViewTotalScore)
2016-11-21 13:15:08 +01:00
fprintf (Gbl.F.Out,"%.2lf",TotalScoreOfAllTests);
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"</td>");
/* Write average score per question */
2015-09-06 15:34:44 +02:00
fprintf (Gbl.F.Out,"<td class=\"DAT_N_LINE_TOP RIGHT_MIDDLE COLOR%u\">",
2015-09-03 17:14:30 +02:00
Gbl.RowEvenOdd);
2016-11-24 13:50:44 +01:00
if (ICanViewTotalScore)
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"%.2lf",
2016-11-21 13:15:08 +01:00
NumTotalQsts ? TotalScoreOfAllTests / (double) NumTotalQsts :
2014-12-01 23:55:08 +01:00
0.0);
fprintf (Gbl.F.Out,"</td>");
/* Write score over Tst_SCORE_MAX */
2015-09-06 15:34:44 +02:00
fprintf (Gbl.F.Out,"<td class=\"DAT_N_LINE_TOP RIGHT_MIDDLE COLOR%u\">",
2015-09-03 17:14:30 +02:00
Gbl.RowEvenOdd);
2016-11-24 13:50:44 +01:00
if (ICanViewTotalScore)
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"%.2lf",
2016-11-21 13:15:08 +01:00
NumTotalQsts ? TotalScoreOfAllTests * Tst_SCORE_MAX /
(double) NumTotalQsts :
2014-12-01 23:55:08 +01:00
0.0);
fprintf (Gbl.F.Out,"</td>");
/* Last cell */
2015-09-06 15:34:44 +02:00
fprintf (Gbl.F.Out,"<td class=\"DAT_N_LINE_TOP COLOR%u\"></td>"
2014-12-01 23:55:08 +01:00
"</tr>",
2015-09-03 17:14:30 +02:00
Gbl.RowEvenOdd);
2014-12-01 23:55:08 +01:00
}
else
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out,"<td class=\"COLOR%u\"></td>"
"<td class=\"COLOR%u\"></td>"
"<td class=\"COLOR%u\"></td>"
"<td class=\"COLOR%u\"></td>"
"<td class=\"COLOR%u\"></td>"
"<td class=\"COLOR%u\"></td>"
"<td class=\"COLOR%u\"></td>"
2014-12-01 23:55:08 +01:00
"</tr>",
2015-09-03 17:14:30 +02:00
Gbl.RowEvenOdd,
Gbl.RowEvenOdd,
Gbl.RowEvenOdd,
Gbl.RowEvenOdd,
Gbl.RowEvenOdd,
Gbl.RowEvenOdd,
Gbl.RowEvenOdd);
2014-12-01 23:55:08 +01:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
Gbl.RowEvenOdd = 1 - Gbl.RowEvenOdd;
}
/*****************************************************************************/
/******************** Show a row with the data of a user *********************/
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_ShowDataUsr (struct UsrData *UsrDat,unsigned NumTestResults)
2014-12-01 23:55:08 +01:00
{
bool ShowPhoto;
2017-01-28 15:58:46 +01:00
char PhotoURL[PATH_MAX + 1];
2014-12-01 23:55:08 +01:00
/***** Show user's photo and name *****/
fprintf (Gbl.F.Out,"<td ");
2016-11-21 13:15:08 +01:00
if (NumTestResults)
fprintf (Gbl.F.Out,"rowspan=\"%u\"",NumTestResults + 1);
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out," class=\"LEFT_TOP COLOR%u\">",
Gbl.RowEvenOdd);
2017-01-28 15:58:46 +01:00
ShowPhoto = Pho_ShowingUsrPhotoIsAllowed (UsrDat,PhotoURL);
2014-12-30 15:14:43 +01:00
Pho_ShowUsrPhoto (UsrDat,ShowPhoto ? PhotoURL :
NULL,
2016-01-14 10:31:09 +01:00
"PHOTO45x60",Pho_ZOOM,false);
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out,"</td>");
/***** Start form to go to user's record card *****/
fprintf (Gbl.F.Out,"<td ");
2016-11-21 13:15:08 +01:00
if (NumTestResults)
fprintf (Gbl.F.Out,"rowspan=\"%u\"",NumTestResults + 1);
2015-09-03 17:14:30 +02:00
fprintf (Gbl.F.Out," class=\"LEFT_TOP COLOR%u\">",
Gbl.RowEvenOdd);
2015-04-07 21:44:24 +02:00
Act_FormStart (UsrDat->RoleInCurrentCrsDB == Rol_STUDENT ? ActSeeRecOneStd :
2015-09-03 17:14:30 +02:00
ActSeeRecOneTch);
2015-04-02 18:39:49 +02:00
Usr_PutParamUsrCodEncrypted (UsrDat->EncryptedUsrCod);
2017-03-03 21:34:25 +01:00
Act_LinkFormSubmit (UsrDat->FullName,"AUTHOR_TXT",NULL);
2014-12-01 23:55:08 +01:00
/***** Show user's ID *****/
2016-04-23 13:23:09 +02:00
ID_WriteUsrIDs (UsrDat);
2014-12-01 23:55:08 +01:00
/***** Show user's name *****/
fprintf (Gbl.F.Out,"<br />%s",UsrDat->Surname1);
if (UsrDat->Surname2[0])
fprintf (Gbl.F.Out," %s",UsrDat->Surname2);
if (UsrDat->FirstName[0])
fprintf (Gbl.F.Out,",<br />%s",UsrDat->FirstName);
/***** End form *****/
2015-03-13 00:16:02 +01:00
Act_FormEnd ();
fprintf (Gbl.F.Out,"</td>");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/******************** Write parameter with code of test **********************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
static void Tst_PutParamTstCod (long TstCod)
{
Par_PutHiddenParamLong ("TstCod",TstCod);
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/********************* Get parameter with code of test ***********************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
static long Tst_GetParamTstCod (void)
{
2017-01-28 20:32:50 +01:00
/***** Get code of test *****/
return Par_GetParToLong ("TstCod");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/******************* Show one test result of another user ********************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
void Tst_ShowOneTestResult (void)
2014-12-01 23:55:08 +01:00
{
2016-11-25 09:41:06 +01:00
extern const char *Hlp_ASSESSMENT_Tests_test_results;
2016-03-21 01:28:16 +01:00
extern const char *Txt_Test_result;
2015-03-12 14:45:40 +01:00
extern const char *Txt_ROLES_SINGUL_Abc[Rol_NUM_ROLES][Usr_NUM_SEXS];
2014-12-01 23:55:08 +01:00
extern const char *Txt_Date;
2015-12-29 11:35:01 +01:00
extern const char *Txt_Today;
2014-12-01 23:55:08 +01:00
extern const char *Txt_Questions;
extern const char *Txt_non_blank_QUESTIONS;
extern const char *Txt_Score;
extern const char *Txt_out_of_PART_OF_A_SCORE;
extern const char *Txt_Tags;
long TstCod;
2016-11-21 13:15:08 +01:00
time_t TstTimeUTC = 0; // Test result UTC date-time, initialized to avoid warning
2014-12-01 23:55:08 +01:00
unsigned NumQstsNotBlank;
double TotalScore;
bool ShowPhoto;
2017-01-28 15:58:46 +01:00
char PhotoURL[PATH_MAX + 1];
2016-11-24 13:50:44 +01:00
bool ItsMe;
bool ICanViewTest;
2014-12-01 23:55:08 +01:00
bool ICanViewScore;
2016-11-21 13:15:08 +01:00
/***** Get the code of the test *****/
2014-12-01 23:55:08 +01:00
if ((TstCod = Tst_GetParamTstCod ()) == -1L)
2016-11-21 13:15:08 +01:00
Lay_ShowErrorAndExit ("Code of test is missing.");
2014-12-01 23:55:08 +01:00
2016-11-24 13:50:44 +01:00
/***** Get test result data *****/
2016-11-21 13:15:08 +01:00
Tst_GetTestResultDataByTstCod (TstCod,&TstTimeUTC,&Gbl.Test.NumQsts,&NumQstsNotBlank,&TotalScore);
2014-12-01 23:55:08 +01:00
Gbl.Test.Config.FeedbackType = Tst_FEEDBACK_FULL_FEEDBACK; // Initialize feedback to maximum
2016-11-24 13:50:44 +01:00
/***** Check if I can view this test result *****/
ItsMe = (Gbl.Usrs.Other.UsrDat.UsrCod == Gbl.Usrs.Me.UsrDat.UsrCod);
switch (Gbl.Usrs.Me.LoggedRole)
2014-12-01 23:55:08 +01:00
{
2016-11-24 13:50:44 +01:00
case Rol_STUDENT:
ICanViewTest = ItsMe;
if (ItsMe)
2014-12-01 23:55:08 +01:00
{
2016-11-24 13:50:44 +01:00
Tst_GetConfigTstFromDB (); // To get feedback type
ICanViewScore = Gbl.Test.Config.FeedbackType != Tst_FEEDBACK_NOTHING;
2014-12-01 23:55:08 +01:00
}
2016-11-24 13:50:44 +01:00
else
ICanViewScore = false;
break;
case Rol_TEACHER:
case Rol_DEG_ADM:
case Rol_CTR_ADM:
case Rol_INS_ADM:
switch (Gbl.Action.Act)
{
case ActSeeOneTstResMe:
ICanViewTest =
ICanViewScore = ItsMe;
break;
case ActSeeOneTstResOth:
ICanViewTest =
ICanViewScore = ItsMe ||
Gbl.Test.AllowTeachers;
break;
default:
ICanViewTest =
ICanViewScore = false;
break;
}
break;
case Rol_SYS_ADM:
ICanViewTest =
ICanViewScore = true;
2014-12-01 23:55:08 +01:00
break;
2016-11-24 13:50:44 +01:00
default:
ICanViewTest =
ICanViewScore = false;
2014-12-01 23:55:08 +01:00
break;
}
2016-11-24 13:50:44 +01:00
if (ICanViewTest) // I am allowed to view this test result
{
/***** Get questions and user's answers of the test result from database *****/
Tst_GetTestResultQuestionsFromDB (TstCod);
2014-12-01 23:55:08 +01:00
2016-11-24 13:50:44 +01:00
/***** Start frame *****/
2016-11-25 09:41:06 +01:00
Lay_StartRoundFrame (NULL,Txt_Test_result,
NULL,Hlp_ASSESSMENT_Tests_test_results);
2016-11-24 13:50:44 +01:00
Lay_WriteHeaderClassPhoto (false,false,
Gbl.CurrentIns.Ins.InsCod,
Gbl.CurrentDeg.Deg.DegCod,
Gbl.CurrentCrs.Crs.CrsCod);
2016-03-21 01:28:16 +01:00
2016-11-24 13:50:44 +01:00
/***** Start table *****/
2017-05-01 12:36:24 +02:00
Lay_StartTableWideMargin (10);
2014-12-01 23:55:08 +01:00
2016-11-24 13:50:44 +01:00
/***** Header row *****/
/* Get data of the user who made the test */
if (!Usr_ChkUsrCodAndGetAllUsrDataFromUsrCod (&Gbl.Usrs.Other.UsrDat))
Lay_ShowErrorAndExit ("User does not exists.");
2014-12-01 23:55:08 +01:00
2016-11-24 13:50:44 +01:00
/* User */
fprintf (Gbl.F.Out,"<tr>"
"<td class=\"DAT_N RIGHT_TOP\">"
"%s:"
"</td>"
"<td class=\"DAT LEFT_TOP\">",
Txt_ROLES_SINGUL_Abc[Gbl.Usrs.Other.UsrDat.RoleInCurrentCrsDB][Gbl.Usrs.Other.UsrDat.Sex]);
ID_WriteUsrIDs (&Gbl.Usrs.Other.UsrDat);
2014-12-01 23:55:08 +01:00
fprintf (Gbl.F.Out," %s",
2016-11-24 13:50:44 +01:00
Gbl.Usrs.Other.UsrDat.Surname1);
if (Gbl.Usrs.Other.UsrDat.Surname2[0])
fprintf (Gbl.F.Out," %s",
Gbl.Usrs.Other.UsrDat.Surname2);
if (Gbl.Usrs.Other.UsrDat.FirstName[0])
fprintf (Gbl.F.Out,", %s",
Gbl.Usrs.Other.UsrDat.FirstName);
fprintf (Gbl.F.Out,"<br />");
2017-01-28 15:58:46 +01:00
ShowPhoto = Pho_ShowingUsrPhotoIsAllowed (&Gbl.Usrs.Other.UsrDat,PhotoURL);
2016-11-24 13:50:44 +01:00
Pho_ShowUsrPhoto (&Gbl.Usrs.Other.UsrDat,ShowPhoto ? PhotoURL :
NULL,
"PHOTO45x60",Pho_ZOOM,false);
fprintf (Gbl.F.Out,"</td>"
"</tr>");
2014-12-01 23:55:08 +01:00
2016-11-24 13:50:44 +01:00
/* Test date */
fprintf (Gbl.F.Out,"<tr>"
"<td class=\"DAT_N RIGHT_TOP\">"
"%s:"
"</td>"
"<td id=\"test\" class=\"DAT LEFT_TOP\">"
"<script type=\"text/javascript\">"
2016-12-12 23:10:11 +01:00
"writeLocalDateHMSFromUTC('test',"
"%ld,',&nbsp;','%s',true,true,true);"
2016-11-24 13:50:44 +01:00
"</script>"
"</td>"
"</tr>",
Txt_Date,TstTimeUTC,Txt_Today);
2014-12-01 23:55:08 +01:00
2016-11-24 13:50:44 +01:00
/* Number of questions */
fprintf (Gbl.F.Out,"<tr>"
"<td class=\"DAT_N RIGHT_TOP\">"
"%s:"
"</td>"
"<td class=\"DAT LEFT_TOP\">"
"%u (%u %s)"
"</td>"
"</tr>",
Txt_Questions,
Gbl.Test.NumQsts,NumQstsNotBlank,Txt_non_blank_QUESTIONS);
2014-12-01 23:55:08 +01:00
2016-11-24 13:50:44 +01:00
/* Score */
fprintf (Gbl.F.Out,"<tr>"
"<td class=\"DAT_N RIGHT_TOP\">"
"%s:"
"</td>"
"<td class=\"DAT LEFT_TOP\">",
Txt_Score);
if (ICanViewScore)
fprintf (Gbl.F.Out,"%.2lf (%.2lf",
TotalScore,
Gbl.Test.NumQsts ? TotalScore * Tst_SCORE_MAX / (double) Gbl.Test.NumQsts :
0.0);
else
fprintf (Gbl.F.Out,"? (?"); // No feedback
fprintf (Gbl.F.Out," %s %u)</td>"
"</tr>",
Txt_out_of_PART_OF_A_SCORE,Tst_SCORE_MAX);
2014-12-01 23:55:08 +01:00
2016-11-24 13:50:44 +01:00
/* Tags present in this test */
fprintf (Gbl.F.Out,"<tr>"
"<td class=\"DAT_N RIGHT_TOP\">"
"%s:"
"</td>"
"<td class=\"DAT LEFT_TOP\">",
Txt_Tags);
Tst_ShowTstTagsPresentInATestResult (TstCod);
fprintf (Gbl.F.Out,"</td>"
"</tr>");
2014-12-01 23:55:08 +01:00
2016-11-24 13:50:44 +01:00
/***** Write answers and solutions *****/
Tst_ShowTestResult (TstTimeUTC);
2014-12-01 23:55:08 +01:00
2016-11-24 13:50:44 +01:00
/***** Write total mark of test *****/
if (ICanViewScore)
Tst_ShowTstTotalMark (TotalScore);
2014-12-01 23:55:08 +01:00
2016-11-24 13:50:44 +01:00
/***** End table *****/
2017-05-01 12:36:24 +02:00
Lay_EndTable ();
2016-03-21 01:28:16 +01:00
2016-11-24 13:50:44 +01:00
/***** End frame *****/
Lay_EndRoundFrame ();
}
else // I am not allowed to view this test result
Lay_ShowErrorAndExit ("You can not view this test result.");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/************************* Show the result of a test *************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_ShowTestResult (time_t TstTimeUTC)
2014-12-01 23:55:08 +01:00
{
extern const char *Txt_Question_modified;
extern const char *Txt_Question_removed;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumQst;
long QstCod;
double ScoreThisQst;
bool AnswerIsNotBlank;
bool ThisQuestionHasBeenEdited;
2015-10-26 14:11:30 +01:00
time_t EditTimeUTC;
2014-12-01 23:55:08 +01:00
for (NumQst = 0;
NumQst < Gbl.Test.NumQsts;
NumQst++)
{
Gbl.RowEvenOdd = NumQst % 2;
/***** Query database *****/
if (Tst_GetOneQuestionByCod (Gbl.Test.QstCodes[NumQst],&mysql_res)) // Question exists
{
/***** Get row of the result of the query *****/
row = mysql_fetch_row (mysql_res);
/*
2016-04-06 01:10:04 +02:00
row[ 0] QstCod
row[ 1] UNIX_TIMESTAMP(EditTime)
row[ 2] AnsType
row[ 3] Shuffle
row[ 4] Stem
row[ 5] Feedback
row[ 6] ImageName
row[ 7] ImageTitle
2016-04-15 02:33:16 +02:00
row[ 8] ImageURL
row[ 9] NumHits
row[10] NumHitsNotBlank
row[11] Score
2014-12-01 23:55:08 +01:00
*/
2016-11-21 13:15:08 +01:00
/***** If this question has been edited later than test time
==> don't show question ****/
2015-10-26 14:11:30 +01:00
EditTimeUTC = Dat_GetUNIXTimeFromStr (row[1]);
2014-12-01 23:55:08 +01:00
ThisQuestionHasBeenEdited = false;
2015-10-26 14:11:30 +01:00
// if (TstTimeUTC)
if (EditTimeUTC > TstTimeUTC)
2014-12-01 23:55:08 +01:00
ThisQuestionHasBeenEdited = true;
if (ThisQuestionHasBeenEdited)
/***** Question has been edited *****/
fprintf (Gbl.F.Out,"<tr>"
2016-04-01 19:15:03 +02:00
"<td class=\"TEST_NUM_QST RIGHT_TOP COLOR%u\">"
"%u"
2014-12-26 14:45:14 +01:00
"</td>"
2015-09-03 17:14:30 +02:00
"<td class=\"DAT_LIGHT LEFT_TOP COLOR%u\">"
2014-12-26 14:45:14 +01:00
"%s"
"</td>"
2014-12-01 23:55:08 +01:00
"</tr>",
2015-09-03 17:14:30 +02:00
Gbl.RowEvenOdd,NumQst + 1,
Gbl.RowEvenOdd,Txt_Question_modified);
2014-12-01 23:55:08 +01:00
else
{
/***** Get the code of question (row[0]) *****/
if ((QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
Lay_ShowErrorAndExit ("Wrong code of question.");
/***** Write questions and answers *****/
2016-11-21 13:15:08 +01:00
Tst_WriteQstAndAnsTest (NumQst,QstCod,row,
2014-12-01 23:55:08 +01:00
&ScoreThisQst, // Not used here
&AnswerIsNotBlank); // Not used here
}
}
else
/***** Question does not exists *****/
fprintf (Gbl.F.Out,"<tr>"
2016-04-01 19:15:03 +02:00
"<td class=\"TEST_NUM_QST RIGHT_TOP COLOR%u\">"
"%u"
2014-12-26 14:45:14 +01:00
"</td>"
2015-09-03 17:14:30 +02:00
"<td class=\"DAT_LIGHT LEFT_TOP COLOR%u\">"
2014-12-26 14:45:14 +01:00
"%s"
"</td>"
2014-12-01 23:55:08 +01:00
"</tr>",
2015-09-03 17:14:30 +02:00
Gbl.RowEvenOdd,NumQst + 1,
Gbl.RowEvenOdd,Txt_Question_removed);
2014-12-01 23:55:08 +01:00
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/********* Get data of a test result using its test result code **************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_GetTestResultDataByTstCod (long TstCod,time_t *TstTimeUTC,
unsigned *NumQsts,unsigned *NumQstsNotBlank,double *Score)
2014-12-01 23:55:08 +01:00
{
char Query[512];
MYSQL_RES *mysql_res;
MYSQL_ROW row;
/***** Make database query *****/
sprintf (Query,"SELECT UsrCod,AllowTeachers,"
2015-10-26 14:11:30 +01:00
"UNIX_TIMESTAMP(TstTime),"
2014-12-01 23:55:08 +01:00
"NumQsts,NumQstsNotBlank,Score"
" FROM tst_exams"
2017-03-24 01:09:27 +01:00
" WHERE TstCod=%ld AND CrsCod=%ld",
2014-12-01 23:55:08 +01:00
TstCod,
Gbl.CurrentCrs.Crs.CrsCod);
2016-11-21 13:15:08 +01:00
if (DB_QuerySELECT (Query,&mysql_res,"can not get data of a test result of a user") == 1)
2014-12-01 23:55:08 +01:00
{
row = mysql_fetch_row (mysql_res);
/* Get user code (row[0]) */
Gbl.Usrs.Other.UsrDat.UsrCod = Str_ConvertStrCodToLongCod (row[0]);
2016-11-21 13:15:08 +01:00
/* Get if teachers are allowed to see this test result (row[1]) */
2016-09-07 18:48:10 +02:00
Gbl.Test.AllowTeachers = (row[1][0] == 'Y');
2014-12-01 23:55:08 +01:00
2015-10-26 14:11:30 +01:00
/* Get date-time (row[2] holds UTC date-time) */
*TstTimeUTC = Dat_GetUNIXTimeFromStr (row[2]);
2014-12-01 23:55:08 +01:00
/* Get number of questions (row[3]) */
if (sscanf (row[3],"%u",NumQsts) != 1)
*NumQsts = 0;
/* Get number of questions not blank (row[4]) */
if (sscanf (row[4],"%u",NumQstsNotBlank) != 1)
*NumQstsNotBlank = 0;
/* Get score (row[5]) */
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[5],"%lf",Score) != 1)
*Score = 0.0;
2016-06-04 14:21:01 +02:00
Str_SetDecimalPointToLocal (); // Return to local system
2014-12-01 23:55:08 +01:00
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/************ Store user's answers of an test result into database ***********/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_StoreOneTestResultQstInDB (long TstCod,long QstCod,unsigned NumQst,double Score)
2014-12-01 23:55:08 +01:00
{
2017-03-13 19:02:15 +01:00
char Query[256 +
Tst_MAX_BYTES_INDEXES_ONE_QST +
Tst_MAX_BYTES_ANSWERS_ONE_QST];
char Indexes[Tst_MAX_BYTES_INDEXES_ONE_QST + 1];
char Answers[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1];
2014-12-01 23:55:08 +01:00
/***** Replace each separator of multiple parameters by a comma *****/
/* In database commas are used as separators instead of special chars */
Par_ReplaceSeparatorMultipleByComma (Gbl.Test.StrIndexesOneQst[NumQst],Indexes);
Par_ReplaceSeparatorMultipleByComma (Gbl.Test.StrAnswersOneQst[NumQst],Answers);
/***** Insert question and user's answers into database *****/
2016-06-04 14:21:01 +02:00
Str_SetDecimalPointToUS (); // To print the floating point as a dot
2014-12-01 23:55:08 +01:00
sprintf (Query,"INSERT INTO tst_exam_questions"
" (TstCod,QstCod,QstInd,Score,Indexes,Answers)"
2017-03-13 13:17:53 +01:00
" VALUES"
2017-03-24 01:09:27 +01:00
" (%ld,%ld,%u,'%lf','%s','%s')",
2014-12-01 23:55:08 +01:00
TstCod,QstCod,
NumQst, // 0, 1, 2, 3...
Score,
Indexes,
Answers);
2016-06-04 14:21:01 +02:00
Str_SetDecimalPointToLocal (); // Return to local system
2016-11-21 13:15:08 +01:00
DB_QueryINSERT (Query,"can not insert a question of a test result");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/************ Get the questions of a test result from database ***************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
static void Tst_GetTestResultQuestionsFromDB (long TstCod)
2014-12-01 23:55:08 +01:00
{
2017-03-13 19:02:15 +01:00
char Query[256];
2014-12-01 23:55:08 +01:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumQst;
2016-11-21 13:15:08 +01:00
/***** Get questions of a test result from database *****/
2017-03-13 19:02:15 +01:00
sprintf (Query,"SELECT QstCod,Indexes,Answers FROM tst_exam_questions"
2017-03-24 01:09:27 +01:00
" WHERE TstCod=%ld ORDER BY QstInd",
2014-12-01 23:55:08 +01:00
TstCod);
2016-11-21 13:15:08 +01:00
Gbl.Test.NumQsts = (unsigned) DB_QuerySELECT (Query,&mysql_res,"can not get questions of a test result");
2014-12-01 23:55:08 +01:00
/***** Get questions codes *****/
for (NumQst = 0;
NumQst < Gbl.Test.NumQsts;
NumQst++)
{
row = mysql_fetch_row (mysql_res);
/* Get question code */
if ((Gbl.Test.QstCodes[NumQst] = Str_ConvertStrCodToLongCod (row[0])) < 0)
Lay_ShowErrorAndExit ("Wrong code of question.");
/* Get indexes for this question (row[1]) */
2017-01-15 18:02:52 +01:00
Str_Copy (Gbl.Test.StrIndexesOneQst[NumQst],row[1],
2017-03-13 19:02:15 +01:00
Tst_MAX_BYTES_INDEXES_ONE_QST);
2014-12-01 23:55:08 +01:00
/* Get answers selected by user for this question (row[2]) */
2017-01-15 18:02:52 +01:00
Str_Copy (Gbl.Test.StrAnswersOneQst[NumQst],row[2],
2017-03-13 19:02:15 +01:00
Tst_MAX_BYTES_ANSWERS_ONE_QST);
2014-12-01 23:55:08 +01:00
/* Replace each comma by a separator of multiple parameters */
/* In database commas are used as separators instead of special chars */
Par_ReplaceCommaBySeparatorMultiple (Gbl.Test.StrIndexesOneQst[NumQst]);
Par_ReplaceCommaBySeparatorMultiple (Gbl.Test.StrAnswersOneQst[NumQst]);
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/********************** Remove test results made by a user ********************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
void Tst_RemoveTestResultsMadeByUsrInAllCrss (long UsrCod)
2014-12-01 23:55:08 +01:00
{
char Query[512];
2016-11-21 13:15:08 +01:00
/***** Remove test results made by the specified user *****/
2014-12-01 23:55:08 +01:00
sprintf (Query,"DELETE FROM tst_exam_questions"
" USING tst_exams,tst_exam_questions"
2017-03-24 01:09:27 +01:00
" WHERE tst_exams.UsrCod=%ld"
2014-12-01 23:55:08 +01:00
" AND tst_exams.TstCod=tst_exam_questions.TstCod",
UsrCod);
2016-11-21 13:15:08 +01:00
DB_QueryDELETE (Query,"can not remove test results made by a user");
2014-12-01 23:55:08 +01:00
sprintf (Query,"DELETE FROM tst_exams"
2017-03-24 01:09:27 +01:00
" WHERE UsrCod=%ld",
2014-12-01 23:55:08 +01:00
UsrCod);
2016-11-21 13:15:08 +01:00
DB_QueryDELETE (Query,"can not remove test results made by a user");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/************** Remove test results made by a user in a course ***************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
void Tst_RemoveTestResultsMadeByUsrInCrs (long UsrCod,long CrsCod)
2014-12-01 23:55:08 +01:00
{
char Query[512];
2016-11-21 13:15:08 +01:00
/***** Remove test results made by the specified user *****/
2014-12-01 23:55:08 +01:00
sprintf (Query,"DELETE FROM tst_exam_questions"
" USING tst_exams,tst_exam_questions"
2017-03-24 01:09:27 +01:00
" WHERE tst_exams.CrsCod=%ld AND tst_exams.UsrCod=%ld"
2014-12-01 23:55:08 +01:00
" AND tst_exams.TstCod=tst_exam_questions.TstCod",
CrsCod,UsrCod);
2016-11-21 13:15:08 +01:00
DB_QueryDELETE (Query,"can not remove test results made by a user in a course");
2014-12-01 23:55:08 +01:00
sprintf (Query,"DELETE FROM tst_exams"
2017-03-24 01:09:27 +01:00
" WHERE CrsCod=%ld AND UsrCod=%ld",
2014-12-01 23:55:08 +01:00
CrsCod,UsrCod);
2016-11-21 13:15:08 +01:00
DB_QueryDELETE (Query,"can not remove test results made by a user in a course");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
/****************** Remove all test results made in a course *****************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2016-11-21 13:15:08 +01:00
void Tst_RemoveCrsTestResults (long CrsCod)
2014-12-01 23:55:08 +01:00
{
char Query[512];
2016-11-21 13:15:08 +01:00
/***** Remove questions of test results made in the course *****/
2014-12-01 23:55:08 +01:00
sprintf (Query,"DELETE FROM tst_exam_questions"
" USING tst_exams,tst_exam_questions"
2017-03-24 01:09:27 +01:00
" WHERE tst_exams.CrsCod=%ld"
2014-12-01 23:55:08 +01:00
" AND tst_exams.TstCod=tst_exam_questions.TstCod",
CrsCod);
2016-11-21 13:15:08 +01:00
DB_QueryDELETE (Query,"can not remove test results made in a course");
2014-12-01 23:55:08 +01:00
2016-11-21 13:15:08 +01:00
/***** Remove test results made in the course *****/
2017-03-24 01:09:27 +01:00
sprintf (Query,"DELETE FROM tst_exams WHERE CrsCod=%ld",
2014-12-01 23:55:08 +01:00
CrsCod);
2016-11-21 13:15:08 +01:00
DB_QueryDELETE (Query,"can not remove test results made in a course");
2014-12-01 23:55:08 +01:00
}
2016-04-03 14:13:20 +02:00
/*****************************************************************************/
/******************* Remove all test exams made in a course ******************/
/*****************************************************************************/
void Tst_RemoveCrsTests (long CrsCod)
{
char Query[512];
/***** Remove tests status in the course *****/
2017-03-24 01:09:27 +01:00
sprintf (Query,"DELETE FROM tst_status WHERE CrsCod=%ld",CrsCod);
2016-04-03 14:13:20 +02:00
DB_QueryDELETE (Query,"can not remove status of tests of a course");
/***** Remove test configuration of the course *****/
2017-03-24 01:09:27 +01:00
sprintf (Query,"DELETE FROM tst_config WHERE CrsCod=%ld",
2016-04-03 14:13:20 +02:00
CrsCod);
DB_QueryDELETE (Query,"can not remove configuration of tests of a course");
/***** Remove associations between test questions
and test tags in the course *****/
sprintf (Query,"DELETE FROM tst_question_tags"
" USING tst_questions,tst_question_tags"
2017-03-24 01:09:27 +01:00
" WHERE tst_questions.CrsCod=%ld"
2016-04-03 14:13:20 +02:00
" AND tst_questions.QstCod=tst_question_tags.QstCod",
CrsCod);
DB_QueryDELETE (Query,"can not remove tags associated to questions of tests of a course");
/***** Remove test tags in the course *****/
2017-03-24 01:09:27 +01:00
sprintf (Query,"DELETE FROM tst_tags WHERE CrsCod=%ld",
2016-04-03 14:13:20 +02:00
CrsCod);
DB_QueryDELETE (Query,"can not remove tags of test of a course");
/***** Remove test answers in the course *****/
sprintf (Query,"DELETE FROM tst_answers USING tst_questions,tst_answers"
2017-03-24 01:09:27 +01:00
" WHERE tst_questions.CrsCod=%ld"
2016-04-03 14:13:20 +02:00
" AND tst_questions.QstCod=tst_answers.QstCod",
CrsCod);
DB_QueryDELETE (Query,"can not remove answers of tests of a course");
/***** Remove files with images associated
to test questions in the course *****/
2016-04-07 12:36:58 +02:00
Tst_RemoveAllImgFilesFromAnsOfAllQstsInCrs (CrsCod);
Tst_RemoveAllImgFilesFromStemOfAllQstsInCrs (CrsCod);
2016-04-03 14:13:20 +02:00
/***** Remove test questions in the course *****/
2017-03-24 01:09:27 +01:00
sprintf (Query,"DELETE FROM tst_questions WHERE CrsCod=%ld",
2016-04-03 14:13:20 +02:00
CrsCod);
DB_QueryDELETE (Query,"can not remove test questions of a course");
}