2021-10-25 10:24:43 +02:00
// swad_question.c: test/exam/game questions
/*
SWAD ( Shared Workspace At a Distance ) ,
is a web platform developed at the University of Granada ( Spain ) ,
and used to support university teaching .
This file is part of SWAD core .
Copyright ( C ) 1999 - 2021 Antonio Ca <EFBFBD> as Vargas
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation , either version 3 of the
License , or ( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU Affero General Public License for more details .
You should have received a copy of the GNU Affero General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
/*****************************************************************************/
/*********************************** Headers *********************************/
/*****************************************************************************/
2021-10-25 22:19:03 +02:00
# define _GNU_SOURCE // For asprintf
# include <stdio.h> // For asprintf
2021-11-24 21:20:25 +01:00
# include <stdlib.h> // For free
2021-10-25 13:52:17 +02:00
# include <string.h> // For string functions
2021-10-25 10:24:43 +02:00
2021-10-25 13:52:17 +02:00
# include "swad_database.h"
# include "swad_error.h"
2021-10-25 22:19:03 +02:00
# include "swad_exam_set.h"
# include "swad_figure.h"
# include "swad_form.h"
2021-10-25 13:52:17 +02:00
# include "swad_global.h"
2021-10-25 10:24:43 +02:00
# include "swad_question.h"
2021-10-27 21:40:19 +02:00
# include "swad_question_database.h"
2021-10-25 13:52:17 +02:00
# include "swad_question_import.h"
2021-10-25 22:19:03 +02:00
# include "swad_tag_database.h"
2021-10-25 13:52:17 +02:00
# include "swad_test.h"
2021-10-25 10:24:43 +02:00
/*****************************************************************************/
/***************************** Public constants ******************************/
/*****************************************************************************/
2021-10-25 13:52:17 +02:00
// strings are limited to Qst_MAX_BYTES_ANSWER_TYPE characters
const char * Qst_StrAnswerTypesXML [ Qst_NUM_ANS_TYPES ] =
{
[ Qst_ANS_INT ] = " int " ,
[ Qst_ANS_FLOAT ] = " float " ,
[ Qst_ANS_TRUE_FALSE ] = " TF " ,
[ Qst_ANS_UNIQUE_CHOICE ] = " uniqueChoice " ,
[ Qst_ANS_MULTIPLE_CHOICE ] = " multipleChoice " ,
[ Qst_ANS_TEXT ] = " text " ,
} ;
2021-10-25 10:24:43 +02:00
/*****************************************************************************/
/**************************** Private constants ******************************/
/*****************************************************************************/
2021-10-25 22:19:03 +02:00
// Test images will be saved with:
// - maximum width of Tst_IMAGE_SAVED_MAX_HEIGHT
// - maximum height of Tst_IMAGE_SAVED_MAX_HEIGHT
// - maintaining the original aspect ratio (aspect ratio recommended: 3:2)
# define Qst_IMAGE_SAVED_MAX_WIDTH 768
# define Qst_IMAGE_SAVED_MAX_HEIGHT 768
# define Qst_IMAGE_SAVED_QUALITY 90 // 1 to 100
2021-10-25 10:24:43 +02:00
/*****************************************************************************/
/************** External global variables from others modules ****************/
/*****************************************************************************/
extern struct Globals Gbl ;
2021-10-25 22:19:03 +02:00
/*****************************************************************************/
/***************************** Test constructor ******************************/
/*****************************************************************************/
void Qst_Constructor ( struct Qst_Questions * Questions )
{
/***** Reset tags *****/
Tag_ResetTags ( & Questions - > Tags ) ;
/***** Reset answer types *****/
Questions - > AnswerTypes . All = false ;
Questions - > AnswerTypes . List [ 0 ] = ' \0 ' ;
/***** Reset selected order *****/
Questions - > SelectedOrder = Qst_DEFAULT_ORDER ;
/***** Question constructor *****/
Qst_QstConstructor ( & Questions - > Question ) ;
}
/*****************************************************************************/
/****************************** Test destructor ******************************/
/*****************************************************************************/
void Qst_Destructor ( struct Qst_Questions * Questions )
{
/***** Question destructor *****/
Qst_QstDestructor ( & Questions - > Question ) ;
/***** Free tag list *****/
Tag_FreeTagsList ( & Questions - > Tags ) ;
}
/*****************************************************************************/
/*********************** Request the edition of tests ************************/
/*****************************************************************************/
void Qst_RequestEditQsts ( void )
{
struct Qst_Questions Questions ;
/***** Create test *****/
Qst_Constructor ( & Questions ) ;
/***** Show form to generate a self-assessment test *****/
Qst_ShowFormRequestEditQsts ( & Questions ) ;
/***** Destroy test *****/
Qst_Destructor ( & Questions ) ;
}
/*****************************************************************************/
/******* Select tags and dates for edition of the self-assessment test *******/
/*****************************************************************************/
void Qst_ShowFormRequestEditQsts ( struct Qst_Questions * Questions )
{
extern const char * Hlp_ASSESSMENT_Questions_editing_questions ;
extern const char * Txt_No_test_questions ;
extern const char * Txt_Question_bank ;
extern const char * Txt_Show_questions ;
MYSQL_RES * mysql_res ;
static const Dat_SetHMS SetHMS [ Dat_NUM_START_END_TIME ] =
{
[ Dat_STR_TIME ] = Dat_HMS_DO_NOT_SET ,
[ Dat_END_TIME ] = Dat_HMS_DO_NOT_SET
} ;
/***** Begin box *****/
Box_BoxBegin ( NULL , Txt_Question_bank ,
Qst_PutIconsRequestBankQsts , NULL ,
Hlp_ASSESSMENT_Questions_editing_questions , Box_NOT_CLOSABLE ) ;
/***** Get tags already present in the table of questions *****/
if ( ( Questions - > Tags . Num = Tag_DB_GetAllTagsFromCurrentCrs ( & mysql_res ) ) )
{
Frm_BeginForm ( ActLstTstQst ) ;
2021-12-01 01:43:13 +01:00
Par_PutHiddenParamUnsigned ( NULL , " Order " , ( unsigned ) Qst_DEFAULT_ORDER ) ;
2021-10-25 22:19:03 +02:00
HTM_TABLE_BeginPadding ( 2 ) ;
/***** Selection of tags *****/
Tag_ShowFormSelTags ( & Questions - > Tags , mysql_res , false ) ;
/***** Selection of types of answers *****/
Qst_ShowFormAnswerTypes ( & Questions - > AnswerTypes ) ;
/***** Starting and ending dates in the search *****/
Dat_PutFormStartEndClientLocalDateTimesWithYesterdayToday ( SetHMS ) ;
HTM_TABLE_End ( ) ;
/***** Send button *****/
Btn_PutConfirmButton ( Txt_Show_questions ) ;
Frm_EndForm ( ) ;
}
else // No test questions
{
/***** Warning message *****/
Ale_ShowAlert ( Ale_INFO , Txt_No_test_questions ) ;
/***** Button to create a new question *****/
Qst_PutButtonToAddQuestion ( ) ;
}
/***** End box *****/
Box_BoxEnd ( ) ;
/* Free structure that stores the query result */
DB_FreeMySQLResult ( & mysql_res ) ;
}
/*****************************************************************************/
/***************** Show form for select the types of answers *****************/
/*****************************************************************************/
void Qst_ShowFormAnswerTypes ( const struct Qst_AnswerTypes * AnswerTypes )
{
extern const char * The_ClassFormInBox [ The_NUM_THEMES ] ;
extern const char * Txt_Types_of_answers ;
extern const char * Txt_All_types_of_answers ;
extern const char * Txt_TST_STR_ANSWER_TYPES [ Qst_NUM_ANS_TYPES ] ;
Qst_AnswerType_t AnsType ;
bool Checked ;
char UnsignedStr [ Cns_MAX_DECIMAL_DIGITS_UINT + 1 ] ;
const char * Ptr ;
HTM_TR_Begin ( NULL ) ;
/***** Label *****/
HTM_TD_Begin ( " class= \" RT %s \" " , The_ClassFormInBox [ Gbl . Prefs . Theme ] ) ;
HTM_TxtColon ( Txt_Types_of_answers ) ;
HTM_TD_End ( ) ;
/***** Select all types of answers *****/
HTM_TD_Begin ( " class= \" LT \" " ) ;
HTM_TABLE_BeginPadding ( 2 ) ;
HTM_TR_Begin ( NULL ) ;
HTM_TD_Begin ( " class= \" LM \" " ) ;
HTM_LABEL_Begin ( " class= \" %s \" " , The_ClassFormInBox [ Gbl . Prefs . Theme ] ) ;
HTM_INPUT_CHECKBOX ( " AllAnsTypes " , HTM_DONT_SUBMIT_ON_CHANGE ,
" value= \" Y \" %s onclick= \" togglecheckChildren(this,'AnswerType'); \" " ,
AnswerTypes - > All ? " checked= \" checked \" " :
" " ) ;
HTM_TxtF ( " %s " , Txt_All_types_of_answers ) ;
HTM_LABEL_End ( ) ;
HTM_TD_End ( ) ;
HTM_TR_End ( ) ;
/***** Type of answer *****/
for ( AnsType = ( Qst_AnswerType_t ) 0 ;
AnsType < = ( Qst_AnswerType_t ) ( Qst_NUM_ANS_TYPES - 1 ) ;
AnsType + + )
{
HTM_TR_Begin ( NULL ) ;
Checked = false ;
Ptr = AnswerTypes - > List ;
while ( * Ptr )
{
Par_GetNextStrUntilSeparParamMult ( & Ptr , UnsignedStr , Cns_MAX_DECIMAL_DIGITS_UINT ) ;
if ( Qst_ConvertFromUnsignedStrToAnsTyp ( UnsignedStr ) = = AnsType )
{
Checked = true ;
break ;
}
}
HTM_TD_Begin ( " class= \" LM \" " ) ;
HTM_LABEL_Begin ( " class= \" DAT \" " ) ;
HTM_INPUT_CHECKBOX ( " AnswerType " , HTM_DONT_SUBMIT_ON_CHANGE ,
" value= \" %u \" %s onclick= \" checkParent(this,'AllAnsTypes'); \" " ,
( unsigned ) AnsType ,
Checked ? " checked= \" checked \" " :
" " ) ;
HTM_TxtF ( " %s " , Txt_TST_STR_ANSWER_TYPES [ AnsType ] ) ;
HTM_LABEL_End ( ) ;
HTM_TD_End ( ) ;
HTM_TR_End ( ) ;
}
HTM_TABLE_End ( ) ;
HTM_TD_End ( ) ;
HTM_TR_End ( ) ;
}
/*****************************************************************************/
/******************* Select test questions for a game ************************/
/*****************************************************************************/
void Qst_RequestSelectQstsForExamSet ( struct Exa_Exams * Exams )
{
struct Qst_Questions Questions ;
/***** Create test *****/
Qst_Constructor ( & Questions ) ;
/***** Show form to select test for exam *****/
Qst_ShowFormRequestSelectQstsForExamSet ( Exams , & Questions ) ; // No tags selected
/***** Destroy test *****/
Qst_Destructor ( & Questions ) ;
}
/*****************************************************************************/
/******************* Select test questions for a game ************************/
/*****************************************************************************/
void Qst_RequestSelectQstsForGame ( struct Gam_Games * Games )
{
struct Qst_Questions Questions ;
/***** Create test *****/
Qst_Constructor ( & Questions ) ;
/***** Show form to select test for game *****/
Qst_ShowFormRequestSelectQstsForGame ( Games , & Questions ) ; // No tags selected
/***** Destroy test *****/
Qst_Destructor ( & Questions ) ;
}
/*****************************************************************************/
/************** Show form to select test questions for a exam ****************/
/*****************************************************************************/
void Qst_ShowFormRequestSelectQstsForExamSet ( struct Exa_Exams * Exams ,
struct Qst_Questions * Questions )
{
extern const char * Hlp_ASSESSMENT_Exams_questions ;
extern const char * Txt_No_test_questions ;
extern const char * Txt_Select_questions ;
extern const char * Txt_Show_questions ;
MYSQL_RES * mysql_res ;
static const Dat_SetHMS SetHMS [ Dat_NUM_START_END_TIME ] =
{
[ Dat_STR_TIME ] = Dat_HMS_DO_NOT_SET ,
[ Dat_END_TIME ] = Dat_HMS_DO_NOT_SET
} ;
/***** Begin box *****/
Box_BoxBegin ( NULL , Txt_Select_questions ,
NULL , NULL ,
Hlp_ASSESSMENT_Exams_questions , Box_NOT_CLOSABLE ) ;
/***** Get tags already present in the table of questions *****/
if ( ( Questions - > Tags . Num = Tag_DB_GetAllTagsFromCurrentCrs ( & mysql_res ) ) )
{
Frm_BeginForm ( ActLstTstQstForSet ) ;
2021-12-01 01:43:13 +01:00
ExaSet_PutParamsOneSet ( Exams ) ;
2021-10-25 22:19:03 +02:00
HTM_TABLE_BeginPadding ( 2 ) ;
/***** Selection of tags *****/
Tag_ShowFormSelTags ( & Questions - > Tags , mysql_res , false ) ;
/***** Selection of types of answers *****/
Qst_ShowFormAnswerTypes ( & Questions - > AnswerTypes ) ;
/***** Starting and ending dates in the search *****/
Dat_PutFormStartEndClientLocalDateTimesWithYesterdayToday ( SetHMS ) ;
HTM_TABLE_End ( ) ;
/***** Send button *****/
Btn_PutConfirmButton ( Txt_Show_questions ) ;
Frm_EndForm ( ) ;
}
else // No test questions
{
/***** Warning message *****/
Ale_ShowAlert ( Ale_INFO , Txt_No_test_questions ) ;
/***** Button to create a new question *****/
Qst_PutButtonToAddQuestion ( ) ;
}
/***** End box *****/
Box_BoxEnd ( ) ;
/* Free structure that stores the query result */
DB_FreeMySQLResult ( & mysql_res ) ;
}
/*****************************************************************************/
/************** Show form to select test questions for a game ****************/
/*****************************************************************************/
void Qst_ShowFormRequestSelectQstsForGame ( struct Gam_Games * Games ,
struct Qst_Questions * Questions )
{
extern const char * Hlp_ASSESSMENT_Games_questions ;
extern const char * Txt_No_test_questions ;
extern const char * Txt_Select_questions ;
extern const char * Txt_Show_questions ;
MYSQL_RES * mysql_res ;
static const Dat_SetHMS SetHMS [ Dat_NUM_START_END_TIME ] =
{
[ Dat_STR_TIME ] = Dat_HMS_DO_NOT_SET ,
[ Dat_END_TIME ] = Dat_HMS_DO_NOT_SET
} ;
/***** Begin box *****/
Box_BoxBegin ( NULL , Txt_Select_questions ,
NULL , NULL ,
Hlp_ASSESSMENT_Games_questions , Box_NOT_CLOSABLE ) ;
/***** Get tags already present in the table of questions *****/
if ( ( Questions - > Tags . Num = Tag_DB_GetAllTagsFromCurrentCrs ( & mysql_res ) ) )
{
Frm_BeginForm ( ActGamLstTstQst ) ;
2021-12-01 01:43:13 +01:00
Gam_PutParams ( Games ) ;
2021-10-25 22:19:03 +02:00
HTM_TABLE_BeginPadding ( 2 ) ;
/***** Selection of tags *****/
Tag_ShowFormSelTags ( & Questions - > Tags , mysql_res , false ) ;
/***** Starting and ending dates in the search *****/
Dat_PutFormStartEndClientLocalDateTimesWithYesterdayToday ( SetHMS ) ;
HTM_TABLE_End ( ) ;
/***** Send button *****/
Btn_PutConfirmButton ( Txt_Show_questions ) ;
Frm_EndForm ( ) ;
}
else // No test questions
{
/***** Warning message *****/
Ale_ShowAlert ( Ale_INFO , Txt_No_test_questions ) ;
/***** Button to create a new question *****/
Qst_PutButtonToAddQuestion ( ) ;
}
/***** End box *****/
Box_BoxEnd ( ) ;
/* Free structure that stores the query result */
DB_FreeMySQLResult ( & mysql_res ) ;
}
/*****************************************************************************/
/*********************** Check if I can edit questions ***********************/
/*****************************************************************************/
bool Qst_CheckIfICanEditQsts ( void )
{
2021-11-12 01:12:15 +01:00
return Gbl . Usrs . Me . Role . Logged = = Rol_TCH | |
Gbl . Usrs . Me . Role . Logged = = Rol_SYS_ADM ;
2021-10-25 22:19:03 +02:00
}
/*****************************************************************************/
/********************* Put contextual icons in tests *************************/
/*****************************************************************************/
void Qst_PutIconsRequestBankQsts ( __attribute__ ( ( unused ) ) void * Args )
{
extern const char * Txt_New_question ;
/***** Put icon to create a new test question *****/
Ico_PutContextualIconToAdd ( ActEdiOneTstQst , NULL ,
NULL , NULL ,
Txt_New_question ) ;
/***** Put icon to edit tags *****/
Tag_PutIconToEditTags ( ) ;
/***** Put icon to import questions *****/
QstImp_PutIconToImportQuestions ( ) ;
/***** Put icon to show a figure *****/
Fig_PutIconToShowFigure ( Fig_TESTS ) ;
}
/*****************************************************************************/
/********************* Put contextual icons in tests *************************/
/*****************************************************************************/
void Qst_PutIconsEditBankQsts ( void * Questions )
{
extern const char * Txt_New_question ;
/***** Put form to remove selected test questions *****/
switch ( Gbl . Action . Act )
{
case ActLstTstQst : // List selected test questions for edition
case ActReqRemSevTstQst : // Request removal of selected questions
case ActReqRemOneTstQst : // Request removal of a question
case ActRemOneTstQst : // Remove a question
case ActChgShfTstQst : // Change shuffle of a question
Ico_PutContextualIconToRemove ( ActReqRemSevTstQst , NULL ,
Qst_PutParamsEditQst , Questions ) ;
break ;
default :
break ;
}
if ( Gbl . Action . Act ! = ActEdiOneTstQst )
/***** Put form to create a new test question *****/
Ico_PutContextualIconToAdd ( ActEdiOneTstQst , NULL ,
NULL , NULL ,
Txt_New_question ) ;
/***** Put icon to edit tags *****/
Tag_PutIconToEditTags ( ) ;
/***** Put icon to export questions *****/
QstImp_PutIconToExportQuestions ( Questions ) ;
/***** Put icon to show a figure *****/
Fig_PutIconToShowFigure ( Fig_TESTS ) ;
}
/*****************************************************************************/
/**************** Put button to create a new test question *******************/
/*****************************************************************************/
void Qst_PutButtonToAddQuestion ( void )
{
extern const char * Txt_New_question ;
Frm_BeginForm ( ActEdiOneTstQst ) ;
Btn_PutConfirmButton ( Txt_New_question ) ;
Frm_EndForm ( ) ;
}
/*****************************************************************************/
/********************* List game question for edition ************************/
/*****************************************************************************/
void Qst_ListQuestionForEdition ( struct Qst_Question * Question ,
unsigned QstInd , bool QuestionExists ,
const char * Anchor )
{
extern const char * Txt_Question_removed ;
/***** Number of question and answer type (row[1]) *****/
HTM_TD_Begin ( " class= \" RT COLOR%u \" " , Gbl . RowEvenOdd ) ;
Qst_WriteNumQst ( QstInd , " BIG_INDEX " ) ;
if ( QuestionExists )
Qst_WriteAnswerType ( Question - > Answer . Type , " DAT_SMALL " ) ;
HTM_TD_End ( ) ;
/***** Write question code *****/
HTM_TD_Begin ( " class= \" DAT_SMALL CT COLOR%u \" " , Gbl . RowEvenOdd ) ;
HTM_TxtF ( " %ld " , Question - > QstCod ) ;
HTM_TD_End ( ) ;
/***** Write the question tags *****/
HTM_TD_Begin ( " class= \" LT COLOR%u \" " , Gbl . RowEvenOdd ) ;
if ( QuestionExists )
Tag_GetAndWriteTagsQst ( Question - > QstCod ) ;
HTM_TD_End ( ) ;
/***** Write stem (row[3]) and media *****/
HTM_TD_Begin ( " class= \" LT COLOR%u \" " , Gbl . RowEvenOdd ) ;
HTM_ARTICLE_Begin ( Anchor ) ;
if ( QuestionExists )
{
/* Write stem */
Qst_WriteQstStem ( Question - > Stem , " TEST_TXT " ,
true ) ; // Visible
/* Show media */
Med_ShowMedia ( & Question - > Media ,
" TEST_MED_EDIT_LIST_CONT " ,
" TEST_MED_EDIT_LIST " ) ;
/* Show feedback */
Qst_WriteQstFeedback ( Question - > Feedback , " TEST_TXT_LIGHT " ) ;
/* Show answers */
Qst_WriteAnswersBank ( Question , " TEST_TXT " , " TEST_TXT_LIGHT " ) ;
}
else
{
HTM_SPAN_Begin ( " class= \" DAT_LIGHT \" " ) ;
HTM_Txt ( Txt_Question_removed ) ;
HTM_SPAN_End ( ) ;
}
HTM_ARTICLE_End ( ) ;
HTM_TD_End ( ) ;
}
/*****************************************************************************/
/********************* Write the number of a test question *******************/
/*****************************************************************************/
// Number of question should be 1, 2, 3...
void Qst_WriteNumQst ( unsigned NumQst , const char * Class )
{
HTM_DIV_Begin ( " class= \" %s \" " , Class ) ;
HTM_Unsigned ( NumQst ) ;
HTM_DIV_End ( ) ;
}
/*****************************************************************************/
/************************** Write the type of answer *************************/
/*****************************************************************************/
void Qst_WriteAnswerType ( Qst_AnswerType_t AnswerType , const char * Class )
{
extern const char * Txt_TST_STR_ANSWER_TYPES [ Qst_NUM_ANS_TYPES ] ;
HTM_DIV_Begin ( " class= \" %s \" " , Class ) ;
HTM_Txt ( Txt_TST_STR_ANSWER_TYPES [ AnswerType ] ) ;
HTM_DIV_End ( ) ;
}
/*****************************************************************************/
/********************* Write the stem of a test question *********************/
/*****************************************************************************/
void Qst_WriteQstStem ( const char * Stem , const char * ClassStem , bool Visible )
{
unsigned long StemLength ;
char * StemRigorousHTML ;
/***** DIV begin *****/
HTM_DIV_Begin ( " class= \" %s \" " , ClassStem ) ;
/***** Write stem *****/
if ( Stem & & Visible )
{
if ( Stem [ 0 ] )
{
/* Convert the stem, that is in HTML, to rigorous HTML */
StemLength = strlen ( Stem ) * Str_MAX_BYTES_PER_CHAR ;
if ( ( StemRigorousHTML = malloc ( StemLength + 1 ) ) = = NULL )
Err_NotEnoughMemoryExit ( ) ;
Str_Copy ( StemRigorousHTML , Stem , StemLength ) ;
Str_ChangeFormat ( Str_FROM_HTML , Str_TO_RIGOROUS_HTML ,
StemRigorousHTML , StemLength , false ) ;
/* Write stem text */
HTM_Txt ( StemRigorousHTML ) ;
/* Free memory allocated for the stem */
free ( StemRigorousHTML ) ;
}
}
else
Ico_PutIconNotVisible ( ) ;
/***** DIV end *****/
HTM_DIV_End ( ) ;
}
/*****************************************************************************/
/************* Put form to upload a new image for a test question ************/
/*****************************************************************************/
void Qst_PutFormToEditQstMedia ( const struct Med_Media * Media , int NumMedia ,
bool OptionsDisabled )
{
extern const char * The_ClassFormInBox [ The_NUM_THEMES ] ;
extern const char * Txt_No_image_video ;
extern const char * Txt_Current_image_video ;
extern const char * Txt_Change_image_video ;
static unsigned UniqueId = 0 ;
struct ParamUploadMedia ParamUploadMedia ;
if ( Media - > Name [ 0 ] )
{
/***** Set names of parameters depending on number of image in form *****/
Med_SetParamNames ( & ParamUploadMedia , NumMedia ) ;
/***** Begin container *****/
HTM_DIV_Begin ( " class= \" TEST_MED_EDIT_FORM \" " ) ;
/***** Choice 1: No media *****/
HTM_LABEL_Begin ( " class= \" %s \" " , The_ClassFormInBox [ Gbl . Prefs . Theme ] ) ;
HTM_INPUT_RADIO ( ParamUploadMedia . Action , false ,
" value= \" %u \" %s " ,
( unsigned ) Med_ACTION_NO_MEDIA ,
OptionsDisabled ? " disabled= \" disabled \" " : " " ) ;
HTM_Txt ( Txt_No_image_video ) ;
HTM_LABEL_End ( ) ;
HTM_BR ( ) ;
/***** Choice 2: Current media *****/
HTM_LABEL_Begin ( " class= \" %s \" " , The_ClassFormInBox [ Gbl . Prefs . Theme ] ) ;
HTM_INPUT_RADIO ( ParamUploadMedia . Action , false ,
" value= \" %u \" %s checked= \" checked \" " ,
( unsigned ) Med_ACTION_KEEP_MEDIA ,
OptionsDisabled ? " disabled= \" disabled \" " : " " ) ;
HTM_Txt ( Txt_Current_image_video ) ;
HTM_LABEL_End ( ) ;
Med_ShowMedia ( Media ,
" TEST_MED_EDIT_ONE_CONT " ,
" TEST_MED_EDIT_ONE " ) ;
/***** Choice 3: Change media *****/
UniqueId + + ;
HTM_LABEL_Begin ( " class= \" %s \" " , The_ClassFormInBox [ Gbl . Prefs . Theme ] ) ;
HTM_INPUT_RADIO ( ParamUploadMedia . Action , false ,
" id= \" chg_img_%u \" value= \" %u \" %s " ,
UniqueId ,
( unsigned ) Med_ACTION_NEW_MEDIA ,
OptionsDisabled ? " disabled= \" disabled \" " : " " ) ;
HTM_TxtColonNBSP ( Txt_Change_image_video ) ;
HTM_LABEL_End ( ) ;
Med_PutMediaUploader ( NumMedia , " TEST_MED_INPUT " ) ;
/***** End container *****/
HTM_DIV_End ( ) ;
}
else // No current image
/***** Attached media *****/
Med_PutMediaUploader ( NumMedia , " TEST_MED_INPUT " ) ;
}
/*****************************************************************************/
/******************* Write the feedback of a test question *******************/
/*****************************************************************************/
void Qst_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 *****/
FeedbackLength = strlen ( Feedback ) * Str_MAX_BYTES_PER_CHAR ;
if ( ( FeedbackRigorousHTML = malloc ( FeedbackLength + 1 ) ) = = NULL )
Err_NotEnoughMemoryExit ( ) ;
Str_Copy ( FeedbackRigorousHTML , Feedback , FeedbackLength ) ;
Str_ChangeFormat ( Str_FROM_HTML , Str_TO_RIGOROUS_HTML ,
FeedbackRigorousHTML , FeedbackLength , false ) ;
/***** Write the feedback *****/
HTM_DIV_Begin ( " class= \" %s \" " , ClassFeedback ) ;
HTM_Txt ( FeedbackRigorousHTML ) ;
HTM_DIV_End ( ) ;
/***** Free memory allocated for the feedback *****/
free ( FeedbackRigorousHTML ) ;
}
}
2021-10-25 13:52:17 +02:00
/*****************************************************************************/
/***************** List several test questions for edition *******************/
/*****************************************************************************/
void Qst_ListQuestionsToEdit ( void )
{
struct Qst_Questions Questions ;
MYSQL_RES * mysql_res ;
/***** Create test *****/
Qst_Constructor ( & Questions ) ;
/***** Get parameters, query the database and list the questions *****/
if ( Tst_GetParamsTst ( & Questions , Tst_EDIT_QUESTIONS ) ) // Get parameters from the form
{
/***** Get question codes from database *****/
2021-10-29 00:32:19 +02:00
if ( ( Questions . NumQsts = Qst_DB_GetQsts ( & mysql_res , & Questions ) ) )
2021-10-25 13:52:17 +02:00
{
/* Contextual menu */
if ( QstImp_GetCreateXMLParamFromForm ( ) )
{
Mnu_ContextMenuBegin ( ) ;
2021-10-29 00:32:19 +02:00
QstImp_CreateXML ( Questions . NumQsts , mysql_res ) ; // Create XML file with exported questions...
2021-10-28 18:48:21 +02:00
// ...and put a link to download it
2021-10-25 13:52:17 +02:00
Mnu_ContextMenuEnd ( ) ;
}
/* Show the table with the questions */
Qst_ListOneOrMoreQstsForEdition ( & Questions , mysql_res ) ;
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult ( & mysql_res ) ;
}
else
/* Show the form again */
Qst_ShowFormRequestEditQsts ( & Questions ) ;
/***** Destroy test *****/
Qst_Destructor ( & Questions ) ;
}
/*****************************************************************************/
/************ List several test questions for selection for exam *************/
/*****************************************************************************/
void Qst_ListQuestionsToSelectForExamSet ( struct Exa_Exams * Exams )
{
struct Qst_Questions Questions ;
MYSQL_RES * mysql_res ;
/***** Create test *****/
Qst_Constructor ( & Questions ) ;
/***** Get parameters, query the database and list the questions *****/
if ( Tst_GetParamsTst ( & Questions , Tst_SELECT_QUESTIONS_FOR_EXAM ) ) // Get parameters from the form
{
2021-10-29 00:32:19 +02:00
if ( ( Questions . NumQsts = Qst_DB_GetQsts ( & mysql_res , & Questions ) ) )
2021-10-25 13:52:17 +02:00
/* Show the table with the questions */
Qst_ListOneOrMoreQstsForSelectionForExamSet ( Exams , Questions . NumQsts , mysql_res ) ;
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult ( & mysql_res ) ;
}
else
/* Show the form again */
Qst_ShowFormRequestSelectQstsForExamSet ( Exams , & Questions ) ;
/***** Destroy test *****/
Qst_Destructor ( & Questions ) ;
}
/*****************************************************************************/
/************ List several test questions for selection for game *************/
/*****************************************************************************/
void Qst_ListQuestionsToSelectForGame ( struct Gam_Games * Games )
{
struct Qst_Questions Questions ;
MYSQL_RES * mysql_res ;
/***** Create test *****/
Qst_Constructor ( & Questions ) ;
/***** Get parameters, query the database and list the questions *****/
if ( Tst_GetParamsTst ( & Questions , Tst_SELECT_QUESTIONS_FOR_GAME ) ) // Get parameters from the form
{
2021-10-29 00:32:19 +02:00
if ( ( Questions . NumQsts = Qst_DB_GetQsts ( & mysql_res , & Questions ) ) )
2021-10-25 13:52:17 +02:00
/* Show the table with the questions */
Qst_ListOneOrMoreQstsForSelectionForGame ( Games , Questions . NumQsts , mysql_res ) ;
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult ( & mysql_res ) ;
}
else
/* Show the form again */
Qst_ShowFormRequestSelectQstsForGame ( Games , & Questions ) ;
/***** Destroy test *****/
Qst_Destructor ( & Questions ) ;
}
/*****************************************************************************/
2021-10-25 22:19:03 +02:00
/****************** List for edition one or more test questions **************/
2021-10-25 13:52:17 +02:00
/*****************************************************************************/
2021-10-25 22:19:03 +02:00
void Qst_ListOneOrMoreQstsForEdition ( struct Qst_Questions * Questions ,
MYSQL_RES * mysql_res )
2021-10-25 13:52:17 +02:00
{
2021-10-25 22:19:03 +02:00
extern const char * Hlp_ASSESSMENT_Tests ;
extern const char * Txt_Questions ;
unsigned QstInd ;
MYSQL_ROW row ;
/***** Begin box *****/
Box_BoxBegin ( NULL , Txt_Questions ,
Qst_PutIconsEditBankQsts , Questions ,
Hlp_ASSESSMENT_Tests , Box_NOT_CLOSABLE ) ;
/***** Begin table *****/
HTM_TABLE_BeginWideMarginPadding ( 5 ) ;
/***** Write the heading *****/
Qst_WriteHeadingRowQuestionsForEdition ( Questions ) ;
/***** Write rows *****/
for ( QstInd = 0 ;
QstInd < Questions - > NumQsts ;
QstInd + + )
{
Gbl . RowEvenOdd = QstInd % 2 ;
/***** Create test question *****/
Qst_QstConstructor ( & Questions - > Question ) ;
/***** Get question code (row[0]) *****/
row = mysql_fetch_row ( mysql_res ) ;
if ( ( Questions - > Question . QstCod = Str_ConvertStrCodToLongCod ( row [ 0 ] ) ) < = 0 )
Err_WrongQuestionExit ( ) ;
/***** Write question row *****/
Qst_WriteQuestionListing ( Questions , QstInd ) ;
/***** Destroy test question *****/
Qst_QstDestructor ( & Questions - > Question ) ;
}
/***** End table *****/
HTM_TABLE_End ( ) ;
/***** Button to add a new question *****/
Qst_PutButtonToAddQuestion ( ) ;
/***** End box *****/
Box_BoxEnd ( ) ;
}
/*****************************************************************************/
/*********** Write heading row in listing of questions for edition ***********/
/*****************************************************************************/
void Qst_WriteHeadingRowQuestionsForEdition ( struct Qst_Questions * Questions )
{
extern const char * Txt_No_INDEX ;
extern const char * Txt_Code ;
extern const char * Txt_Date ;
extern const char * Txt_Tags ;
extern const char * Txt_Shuffle ;
extern const char * Txt_TST_STR_ORDER_FULL [ Qst_NUM_TYPES_ORDER_QST ] ;
extern const char * Txt_TST_STR_ORDER_SHORT [ Qst_NUM_TYPES_ORDER_QST ] ;
Qst_QuestionsOrder_t Order ;
/***** Begin row *****/
HTM_TR_Begin ( NULL ) ;
/***** First columns *****/
HTM_TH_Empty ( 1 ) ;
HTM_TH ( 1 , 1 , " CT " , Txt_No_INDEX ) ;
HTM_TH ( 1 , 1 , " CT " , Txt_Code ) ;
HTM_TH ( 1 , 1 , " CT " , Txt_Date ) ;
HTM_TH ( 1 , 1 , " CT " , Txt_Tags ) ;
HTM_TH ( 1 , 1 , " CT " , Txt_Shuffle ) ;
/***** Columns which data can be ordered *****/
/* Stem and answers of question */
/* Number of times that the question has been answered */
/* Average score */
for ( Order = ( Qst_QuestionsOrder_t ) 0 ;
Order < = ( Qst_QuestionsOrder_t ) ( Qst_NUM_TYPES_ORDER_QST - 1 ) ;
Order + + )
{
HTM_TH_Begin ( 1 , 1 , " LT " ) ;
if ( Questions - > NumQsts > 1 )
{
Frm_BeginForm ( ActLstTstQst ) ;
2021-12-01 01:43:13 +01:00
Qst_PutParamsEditQst ( Questions ) ;
Par_PutHiddenParamUnsigned ( NULL , " Order " , ( unsigned ) Order ) ;
2021-10-25 22:19:03 +02:00
HTM_BUTTON_SUBMIT_Begin ( Txt_TST_STR_ORDER_FULL [ Order ] , " BT_LINK TIT_TBL " , NULL ) ;
if ( Order = = Questions - > SelectedOrder )
HTM_U_Begin ( ) ;
}
HTM_Txt ( Txt_TST_STR_ORDER_SHORT [ Order ] ) ;
if ( Questions - > NumQsts > 1 )
{
if ( Order = = Questions - > SelectedOrder )
HTM_U_End ( ) ;
HTM_BUTTON_End ( ) ;
Frm_EndForm ( ) ;
}
HTM_TH_End ( ) ;
}
/***** End row *****/
HTM_TR_End ( ) ;
}
/*****************************************************************************/
/********** Write question row in listing of questions for edition ***********/
/*****************************************************************************/
void Qst_WriteQuestionListing ( struct Qst_Questions * Questions , unsigned QstInd )
{
static unsigned UniqueId = 0 ;
char * Id ;
/***** Get and show question data *****/
if ( Qst_GetQstDataFromDB ( & Questions - > Question ) )
{
/***** Begin table row *****/
HTM_TR_Begin ( NULL ) ;
/***** Icons *****/
HTM_TD_Begin ( " class= \" BT%u \" " , Gbl . RowEvenOdd ) ;
/* Write icon to remove the question */
Ico_PutContextualIconToRemove ( ActReqRemOneTstQst , NULL ,
Qst_PutParamsEditQst , Questions ) ;
/* Write icon to edit the question */
Ico_PutContextualIconToEdit ( ActEdiOneTstQst , NULL ,
Qst_PutParamQstCod , & Questions - > Question . QstCod ) ;
HTM_TD_End ( ) ;
/* Number of question and answer type */
HTM_TD_Begin ( " class= \" RT COLOR%u \" " , Gbl . RowEvenOdd ) ;
Qst_WriteNumQst ( QstInd + 1 , " BIG_INDEX " ) ;
Qst_WriteAnswerType ( Questions - > Question . Answer . Type , " DAT_SMALL " ) ;
HTM_TD_End ( ) ;
/* Question code */
HTM_TD_Begin ( " class= \" DAT_SMALL CT COLOR%u \" " , Gbl . RowEvenOdd ) ;
HTM_TxtF ( " %ld " , Questions - > Question . QstCod ) ;
HTM_TD_End ( ) ;
/* Date (row[0] has the UTC date-time) */
if ( asprintf ( & Id , " tst_date_%u " , + + UniqueId ) < 0 )
Err_NotEnoughMemoryExit ( ) ;
HTM_TD_Begin ( " id= \" %s \" class= \" DAT_SMALL CT COLOR%u \" " ,
Id , Gbl . RowEvenOdd ) ;
Dat_WriteLocalDateHMSFromUTC ( Id , Questions - > Question . EditTime ,
Gbl . Prefs . DateFormat , Dat_SEPARATOR_BREAK ,
true , true , false , 0x7 ) ;
HTM_TD_End ( ) ;
free ( Id ) ;
/* Question tags */
HTM_TD_Begin ( " class= \" LT COLOR%u \" " , Gbl . RowEvenOdd ) ;
Tag_GetAndWriteTagsQst ( Questions - > Question . QstCod ) ;
HTM_TD_End ( ) ;
/* Shuffle (row[2]) */
HTM_TD_Begin ( " class= \" DAT_SMALL CT COLOR%u \" " , Gbl . RowEvenOdd ) ;
if ( Questions - > Question . Answer . Type = = Qst_ANS_UNIQUE_CHOICE | |
Questions - > Question . Answer . Type = = Qst_ANS_MULTIPLE_CHOICE )
{
Frm_BeginForm ( ActChgShfTstQst ) ;
2021-12-01 01:43:13 +01:00
Qst_PutParamsEditQst ( Questions ) ;
Par_PutHiddenParamUnsigned ( NULL , " Order " , ( unsigned ) Questions - > SelectedOrder ) ;
2021-10-25 22:19:03 +02:00
HTM_INPUT_CHECKBOX ( " Shuffle " , HTM_SUBMIT_ON_CHANGE ,
" value= \" Y \" %s " ,
Questions - > Question . Answer . Shuffle ? " checked= \" checked \" " :
" " ) ;
Frm_EndForm ( ) ;
}
HTM_TD_End ( ) ;
/* Stem (row[3]) */
HTM_TD_Begin ( " class= \" LT COLOR%u \" " , Gbl . RowEvenOdd ) ;
Qst_WriteQstStem ( Questions - > Question . Stem , " TEST_TXT " ,
true ) ; // Visible
/***** Get and show media (row[5]) *****/
Med_ShowMedia ( & Questions - > Question . Media ,
" TEST_MED_EDIT_LIST_CONT " ,
" TEST_MED_EDIT_LIST " ) ;
/* Feedback (row[4]) and answers */
Qst_WriteQstFeedback ( Questions - > Question . Feedback , " TEST_TXT_LIGHT " ) ;
Qst_WriteAnswersBank ( & Questions - > Question , " TEST_TXT " , " TEST_TXT_LIGHT " ) ;
HTM_TD_End ( ) ;
/* Number of times this question has been answered */
HTM_TD_Begin ( " class= \" DAT_SMALL CT COLOR%u \" " , Gbl . RowEvenOdd ) ;
HTM_UnsignedLong ( Questions - > Question . NumHits ) ;
HTM_TD_End ( ) ;
/* Average score */
HTM_TD_Begin ( " class= \" DAT_SMALL CT COLOR%u \" " , Gbl . RowEvenOdd ) ;
if ( Questions - > Question . NumHits )
HTM_Double2Decimals ( Questions - > Question . Score /
( double ) Questions - > Question . NumHits ) ;
else
HTM_Txt ( " N.A. " ) ;
HTM_TD_End ( ) ;
/* Number of times this question has been answered (not blank) */
HTM_TD_Begin ( " class= \" DAT_SMALL CT COLOR%u \" " , Gbl . RowEvenOdd ) ;
HTM_UnsignedLong ( Questions - > Question . NumHitsNotBlank ) ;
HTM_TD_End ( ) ;
/* Average score (not blank) */
HTM_TD_Begin ( " class= \" DAT_SMALL CT COLOR%u \" " , Gbl . RowEvenOdd ) ;
if ( Questions - > Question . NumHitsNotBlank )
HTM_Double2Decimals ( Questions - > Question . Score /
( double ) Questions - > Question . NumHitsNotBlank ) ;
else
HTM_Txt ( " N.A. " ) ;
HTM_TD_End ( ) ;
/***** End table row *****/
HTM_TR_End ( ) ;
}
}
/*****************************************************************************/
/*************** List for selection one or more test questions ***************/
/*****************************************************************************/
void Qst_ListOneOrMoreQstsForSelectionForExamSet ( struct Exa_Exams * Exams ,
unsigned NumQsts ,
MYSQL_RES * mysql_res )
{
extern const char * Hlp_ASSESSMENT_Exams_questions ;
extern const char * Txt_Questions ;
extern const char * Txt_No_INDEX ;
extern const char * Txt_Code ;
extern const char * Txt_Date ;
extern const char * Txt_Tags ;
extern const char * Txt_Type ;
extern const char * Txt_Shuffle ;
extern const char * Txt_Question ;
extern const char * Txt_Add_questions ;
unsigned QstInd ;
struct Qst_Question Question ;
MYSQL_ROW row ;
/***** Begin box *****/
Box_BoxBegin ( NULL , Txt_Questions ,
NULL , NULL ,
Hlp_ASSESSMENT_Exams_questions , Box_NOT_CLOSABLE ) ;
/***** Begin form *****/
Frm_BeginForm ( ActAddQstToExa ) ;
2021-12-01 01:43:13 +01:00
ExaSet_PutParamsOneSet ( Exams ) ;
2021-10-25 22:19:03 +02:00
/***** Select all questions *****/
Qst_PutCheckboxToSelectAllQuestions ( ) ;
/***** Begin table *****/
HTM_TABLE_BeginWideMarginPadding ( 5 ) ;
/***** Write the heading *****/
HTM_TR_Begin ( NULL ) ;
HTM_TH_Empty ( 1 ) ;
HTM_TH ( 1 , 1 , " CT " , Txt_No_INDEX ) ;
HTM_TH ( 1 , 1 , " CT " , Txt_Code ) ;
HTM_TH ( 1 , 1 , " CT " , Txt_Date ) ;
HTM_TH ( 1 , 1 , " LT " , Txt_Tags ) ;
HTM_TH ( 1 , 1 , " CT " , Txt_Type ) ;
HTM_TH ( 1 , 1 , " CT " , Txt_Shuffle ) ;
HTM_TH ( 1 , 1 , " CT " , Txt_Question ) ;
HTM_TR_End ( ) ;
/***** Write rows *****/
for ( QstInd = 0 ;
QstInd < NumQsts ;
QstInd + + )
{
Gbl . RowEvenOdd = QstInd % 2 ;
/* Create test question */
Qst_QstConstructor ( & Question ) ;
/* Get question code (row[0]) */
row = mysql_fetch_row ( mysql_res ) ;
if ( ( Question . QstCod = Str_ConvertStrCodToLongCod ( row [ 0 ] ) ) < = 0 )
Err_WrongQuestionExit ( ) ;
/* Write question row */
Qst_WriteQuestionRowForSelection ( QstInd , & Question ) ;
/* Destroy test question */
Qst_QstDestructor ( & Question ) ;
}
/***** End table *****/
HTM_TABLE_End ( ) ;
/***** Button to add questions *****/
Btn_PutCreateButton ( Txt_Add_questions ) ;
/***** End form *****/
Frm_EndForm ( ) ;
/***** End box *****/
Box_BoxEnd ( ) ;
}
/*****************************************************************************/
/*************** List for selection one or more test questions ***************/
/*****************************************************************************/
void Qst_ListOneOrMoreQstsForSelectionForGame ( struct Gam_Games * Games ,
unsigned NumQsts ,
MYSQL_RES * mysql_res )
{
extern const char * Hlp_ASSESSMENT_Games_questions ;
extern const char * Txt_Questions ;
extern const char * Txt_No_INDEX ;
extern const char * Txt_Code ;
extern const char * Txt_Date ;
extern const char * Txt_Tags ;
extern const char * Txt_Type ;
extern const char * Txt_Shuffle ;
extern const char * Txt_Question ;
extern const char * Txt_Add_questions ;
unsigned QstInd ;
struct Qst_Question Question ;
MYSQL_ROW row ;
/***** Begin box *****/
Box_BoxBegin ( NULL , Txt_Questions ,
NULL , NULL ,
Hlp_ASSESSMENT_Games_questions , Box_NOT_CLOSABLE ) ;
/***** Begin form *****/
Frm_BeginForm ( ActAddTstQstToGam ) ;
2021-12-01 01:43:13 +01:00
Gam_PutParams ( Games ) ;
2021-10-25 22:19:03 +02:00
/***** Select all questions *****/
Qst_PutCheckboxToSelectAllQuestions ( ) ;
/***** Begin table *****/
HTM_TABLE_BeginWideMarginPadding ( 5 ) ;
/***** Write the heading *****/
HTM_TR_Begin ( NULL ) ;
HTM_TH_Empty ( 1 ) ;
HTM_TH ( 1 , 1 , " CT " , Txt_No_INDEX ) ;
HTM_TH ( 1 , 1 , " CT " , Txt_Code ) ;
HTM_TH ( 1 , 1 , " CT " , Txt_Date ) ;
HTM_TH ( 1 , 1 , " LT " , Txt_Tags ) ;
HTM_TH ( 1 , 1 , " CT " , Txt_Type ) ;
HTM_TH ( 1 , 1 , " CT " , Txt_Shuffle ) ;
HTM_TH ( 1 , 1 , " CT " , Txt_Question ) ;
HTM_TR_End ( ) ;
/***** Write rows *****/
for ( QstInd = 0 ;
QstInd < NumQsts ;
QstInd + + )
{
Gbl . RowEvenOdd = QstInd % 2 ;
/* Create test question */
Qst_QstConstructor ( & Question ) ;
/* Get question code (row[0]) */
row = mysql_fetch_row ( mysql_res ) ;
if ( ( Question . QstCod = Str_ConvertStrCodToLongCod ( row [ 0 ] ) ) < = 0 )
Err_WrongQuestionExit ( ) ;
/* Write question row */
Qst_WriteQuestionRowForSelection ( QstInd , & Question ) ;
/* Destroy test question */
Qst_QstDestructor ( & Question ) ;
}
/***** End table *****/
HTM_TABLE_End ( ) ;
/***** Button to add questions *****/
Btn_PutCreateButton ( Txt_Add_questions ) ;
/***** End form *****/
Frm_EndForm ( ) ;
/***** End box *****/
Box_BoxEnd ( ) ;
}
/*****************************************************************************/
/************** Select all questions to add them to set/game *****************/
/*****************************************************************************/
void Qst_PutCheckboxToSelectAllQuestions ( void )
{
extern const char * The_ClassFormInBox [ The_NUM_THEMES ] ;
extern const char * Txt_All_questions ;
/***** Checkbox to select all listed questions *****/
HTM_LABEL_Begin ( " class= \" %s \" " , The_ClassFormInBox [ Gbl . Prefs . Theme ] ) ;
HTM_INPUT_CHECKBOX ( " AllQsts " , HTM_DONT_SUBMIT_ON_CHANGE ,
" value= \" Y \" onclick= \" togglecheckChildren(this,'QstCods'); \" " ) ;
HTM_TxtF ( " %s " , Txt_All_questions ) ;
HTM_LABEL_End ( ) ;
}
/*****************************************************************************/
/********************** Write question row for selection *********************/
/*****************************************************************************/
void Qst_WriteQuestionRowForSelection ( unsigned QstInd ,
struct Qst_Question * Question )
{
extern const char * Txt_TST_STR_ANSWER_TYPES [ Qst_NUM_ANS_TYPES ] ;
static unsigned UniqueId = 0 ;
char * Id ;
/***** Get and show questvoidion data *****/
if ( Qst_GetQstDataFromDB ( Question ) )
{
/***** Begin table row *****/
HTM_TR_Begin ( NULL ) ;
/* Write checkbox to select the question */
HTM_TD_Begin ( " class= \" BT%u \" " , Gbl . RowEvenOdd ) ;
HTM_INPUT_CHECKBOX ( " QstCods " , HTM_DONT_SUBMIT_ON_CHANGE ,
" value= \" %ld \" onclick= \" checkParent(this,'AllQsts'); \" " ,
Question - > QstCod ) ;
HTM_TD_End ( ) ;
/* Write number of question */
HTM_TD_Begin ( " class= \" DAT_SMALL CT COLOR%u \" " , Gbl . RowEvenOdd ) ;
HTM_TxtF ( " %u " , QstInd + 1 ) ;
HTM_TD_End ( ) ;
/* Write question code */
HTM_TD_Begin ( " class= \" DAT_SMALL CT COLOR%u \" " , Gbl . RowEvenOdd ) ;
HTM_TxtF ( " %ld " , Question - > QstCod ) ;
HTM_TD_End ( ) ;
/* Write the date (row[0] has the UTC date-time) */
if ( asprintf ( & Id , " tst_date_%u " , + + UniqueId ) < 0 )
Err_NotEnoughMemoryExit ( ) ;
HTM_TD_Begin ( " id= \" %s \" class= \" DAT_SMALL CT COLOR%u \" > " ,
Id , Gbl . RowEvenOdd ) ;
Dat_WriteLocalDateHMSFromUTC ( Id , Question - > EditTime ,
Gbl . Prefs . DateFormat , Dat_SEPARATOR_BREAK ,
true , true , false , 0x7 ) ;
HTM_TD_End ( ) ;
free ( Id ) ;
/* Write the question tags */
HTM_TD_Begin ( " class= \" LT COLOR%u \" " , Gbl . RowEvenOdd ) ;
Tag_GetAndWriteTagsQst ( Question - > QstCod ) ;
HTM_TD_End ( ) ;
/* Write the question type */
HTM_TD_Begin ( " class= \" DAT_SMALL CT COLOR%u \" " , Gbl . RowEvenOdd ) ;
HTM_TxtF ( " %s " , Txt_TST_STR_ANSWER_TYPES [ Question - > Answer . Type ] ) ;
HTM_TD_End ( ) ;
/* Write if shuffle is enabled */
HTM_TD_Begin ( " class= \" DAT_SMALL CT COLOR%u \" " , Gbl . RowEvenOdd ) ;
HTM_INPUT_CHECKBOX ( " Shuffle " , HTM_DONT_SUBMIT_ON_CHANGE ,
" value= \" Y \" %s disabled= \" disabled \" " ,
Question - > Answer . Shuffle ? " checked= \" checked \" " :
" " ) ;
HTM_TD_End ( ) ;
/* Write stem */
HTM_TD_Begin ( " class= \" LT COLOR%u \" " , Gbl . RowEvenOdd ) ;
Qst_WriteQstStem ( Question - > Stem , " TEST_TXT " ,
true ) ; // Visible
/***** Get and show media *****/
Med_ShowMedia ( & Question - > Media ,
" TEST_MED_EDIT_LIST_CONT " ,
" TEST_MED_EDIT_LIST " ) ;
/* Write feedback */
Qst_WriteQstFeedback ( Question - > Feedback , " TEST_TXT_LIGHT " ) ;
/* Write answers */
Qst_WriteAnswersBank ( Question , " TEST_TXT " , " TEST_TXT_LIGHT " ) ;
HTM_TD_End ( ) ;
/***** End table row *****/
HTM_TR_End ( ) ;
}
}
/*****************************************************************************/
/************ Put hidden parameters for edition of test questions ************/
/*****************************************************************************/
void Qst_PutParamsEditQst ( void * Questions )
{
if ( Questions )
{
Par_PutHiddenParamChar ( " AllTags " , ( ( struct Qst_Questions * ) Questions ) - > Tags . All ? ' Y ' :
' N ' ) ;
Par_PutHiddenParamString ( NULL , " ChkTag " , ( ( struct Qst_Questions * ) Questions ) - > Tags . List ? ( ( struct Qst_Questions * ) Questions ) - > Tags . List :
" " ) ;
Par_PutHiddenParamChar ( " AllAnsTypes " , ( ( struct Qst_Questions * ) Questions ) - > AnswerTypes . All ? ' Y ' :
' N ' ) ;
Par_PutHiddenParamString ( NULL , " AnswerType " , ( ( struct Qst_Questions * ) Questions ) - > AnswerTypes . List ) ;
Qst_PutParamQstCod ( & ( ( struct Qst_Questions * ) Questions ) - > Question . QstCod ) ;
// if (Test->NumQsts == 1)
// Par_PutHiddenParamChar ("OnlyThisQst",'Y'); // If there are only one row, don't list again after removing
Dat_WriteParamsIniEndDates ( ) ;
}
}
/*****************************************************************************/
/**************** Get and write the answers of a test question ***************/
/*****************************************************************************/
void Qst_WriteAnswersBank ( struct Qst_Question * Question ,
const char * ClassTxt ,
const char * ClassFeedback )
{
void ( * Tst_WriteAnsBank [ Qst_NUM_ANS_TYPES ] ) ( struct Qst_Question * Question ,
const char * ClassTxt ,
const char * ClassFeedback ) =
{
[ Qst_ANS_INT ] = Qst_WriteIntAnsBank ,
[ Qst_ANS_FLOAT ] = Qst_WriteFltAnsBank ,
[ Qst_ANS_TRUE_FALSE ] = Qst_WriteTF_AnsBank ,
[ Qst_ANS_UNIQUE_CHOICE ] = Qst_WriteChoAnsBank ,
[ Qst_ANS_MULTIPLE_CHOICE ] = Qst_WriteChoAnsBank ,
[ Qst_ANS_TEXT ] = Qst_WriteChoAnsBank ,
} ;
/***** Write answers *****/
Tst_WriteAnsBank [ Question - > Answer . Type ] ( Question , ClassTxt , ClassFeedback ) ;
}
/*****************************************************************************/
/*********************** List a test question for edition ********************/
/*****************************************************************************/
void Qst_ListOneQstToEdit ( struct Qst_Questions * Questions )
{
extern const char * Hlp_ASSESSMENT_Tests ;
extern const char * Txt_Questions ;
/***** List only one question *****/
Questions - > NumQsts = 1 ;
/***** Begin box *****/
Box_BoxBegin ( NULL , Txt_Questions ,
Qst_PutIconsEditBankQsts , Questions ,
Hlp_ASSESSMENT_Tests , Box_NOT_CLOSABLE ) ;
/***** Begin table *****/
HTM_TABLE_BeginWideMarginPadding ( 5 ) ;
/***** Write the heading *****/
Qst_WriteHeadingRowQuestionsForEdition ( Questions ) ;
/***** Write question row *****/
Qst_WriteQuestionListing ( Questions , 0 ) ;
/***** End table *****/
HTM_TABLE_End ( ) ;
/***** Button to add a new question *****/
Qst_PutButtonToAddQuestion ( ) ;
/***** End box *****/
Box_BoxEnd ( ) ;
}
/*****************************************************************************/
/****************** Write integer answer when editing a test *****************/
/*****************************************************************************/
void Qst_WriteIntAnsBank ( struct Qst_Question * Question ,
const char * ClassTxt ,
__attribute__ ( ( unused ) ) const char * ClassFeedback )
{
HTM_SPAN_Begin ( " class= \" %s \" " , ClassTxt ) ;
HTM_TxtF ( " (%ld) " , Question - > Answer . Integer ) ;
HTM_SPAN_End ( ) ;
}
/*****************************************************************************/
/****************** Write float answer when editing a test *******************/
/*****************************************************************************/
void Qst_WriteFltAnsBank ( struct Qst_Question * Question ,
const char * ClassTxt ,
__attribute__ ( ( unused ) ) const char * ClassFeedback )
{
HTM_SPAN_Begin ( " class= \" %s \" " , ClassTxt ) ;
HTM_Txt ( " ([ " ) ;
HTM_Double ( Question - > Answer . FloatingPoint [ 0 ] ) ;
HTM_Txt ( " ; " ) ;
HTM_Double ( Question - > Answer . FloatingPoint [ 1 ] ) ;
HTM_Txt ( " ]) " ) ;
HTM_SPAN_End ( ) ;
}
/*****************************************************************************/
/*********** Write false / true answer when listing test questions ***********/
/*****************************************************************************/
void Qst_WriteTF_AnsBank ( struct Qst_Question * Question ,
const char * ClassTxt ,
__attribute__ ( ( unused ) ) const char * ClassFeedback )
{
/***** Write answer *****/
HTM_SPAN_Begin ( " class= \" %s \" " , ClassTxt ) ;
HTM_Txt ( " ( " ) ;
Qst_WriteAnsTF ( Question - > Answer . TF ) ;
HTM_Txt ( " ) " ) ;
HTM_SPAN_End ( ) ;
}
/*****************************************************************************/
/**** Write single or multiple choice answer when listing test questions *****/
/*****************************************************************************/
void Qst_WriteChoAnsBank ( struct Qst_Question * Question ,
const char * ClassTxt ,
const char * ClassFeedback )
{
extern const char * Txt_TST_Answer_given_by_the_teachers ;
unsigned NumOpt ;
/***** Change format of answers text *****/
Qst_ChangeFormatAnswersText ( Question ) ;
/***** Change format of answers feedback *****/
Qst_ChangeFormatAnswersFeedback ( Question ) ;
HTM_TABLE_BeginPadding ( 2 ) ;
for ( NumOpt = 0 ;
NumOpt < Question - > Answer . NumOptions ;
NumOpt + + )
{
HTM_TR_Begin ( NULL ) ;
/* Put an icon that indicates whether the answer is correct or wrong */
HTM_TD_Begin ( " class= \" BT%u \" " , Gbl . RowEvenOdd ) ;
if ( Question - > Answer . Options [ NumOpt ] . Correct )
Ico_PutIcon ( " check.svg " , Txt_TST_Answer_given_by_the_teachers , " CONTEXT_ICO_16x16 " ) ;
HTM_TD_End ( ) ;
/* Write the number of option */
HTM_TD_Begin ( " class= \" %s LT \" " , ClassTxt ) ;
HTM_TxtF ( " %c) " , ' a ' + ( char ) NumOpt ) ;
HTM_TD_End ( ) ;
HTM_TD_Begin ( " class= \" LT \" " ) ;
/* Write the text of the answer and the media */
HTM_DIV_Begin ( " class= \" %s \" " , ClassTxt ) ;
HTM_Txt ( Question - > Answer . Options [ NumOpt ] . Text ) ;
Med_ShowMedia ( & Question - > Answer . Options [ NumOpt ] . Media ,
" TEST_MED_EDIT_LIST_CONT " ,
" TEST_MED_EDIT_LIST " ) ;
HTM_DIV_End ( ) ;
/* Write the text of the feedback */
HTM_DIV_Begin ( " class= \" %s \" " , ClassFeedback ) ;
HTM_Txt ( Question - > Answer . Options [ NumOpt ] . Feedback ) ;
HTM_DIV_End ( ) ;
HTM_TD_End ( ) ;
HTM_TR_End ( ) ;
}
HTM_TABLE_End ( ) ;
}
2021-10-27 00:58:37 +02:00
/*****************************************************************************/
/**************** Get correct answer for each type of answer *****************/
/*****************************************************************************/
void Qst_GetCorrectIntAnswerFromDB ( struct Qst_Question * Question )
{
MYSQL_RES * mysql_res ;
MYSQL_ROW row ;
/***** Query database *****/
2021-10-28 18:48:21 +02:00
Question - > Answer . NumOptions = Qst_DB_GetTextOfAnswers ( & mysql_res , Question - > QstCod ) ;
2021-10-27 00:58:37 +02:00
/***** Check if number of rows is correct *****/
Qst_CheckIfNumberOfAnswersIsOne ( Question ) ;
/***** Get correct answer *****/
row = mysql_fetch_row ( mysql_res ) ;
if ( sscanf ( row [ 0 ] , " %ld " , & Question - > Answer . Integer ) ! = 1 )
Err_WrongAnswerExit ( ) ;
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult ( & mysql_res ) ;
}
void Qst_GetCorrectFltAnswerFromDB ( struct Qst_Question * Question )
{
MYSQL_RES * mysql_res ;
MYSQL_ROW row ;
unsigned NumOpt ;
double Tmp ;
/***** Query database *****/
2021-10-28 18:48:21 +02:00
Question - > Answer . NumOptions = Qst_DB_GetTextOfAnswers ( & mysql_res , Question - > QstCod ) ;
2021-10-27 00:58:37 +02:00
/***** Check if number of rows is correct *****/
if ( Question - > Answer . NumOptions ! = 2 )
Err_WrongAnswerExit ( ) ;
/***** Get float range *****/
for ( NumOpt = 0 ;
NumOpt < 2 ;
NumOpt + + )
{
row = mysql_fetch_row ( mysql_res ) ;
Question - > Answer . FloatingPoint [ NumOpt ] = Str_GetDoubleFromStr ( row [ 0 ] ) ;
}
if ( Question - > Answer . FloatingPoint [ 0 ] >
Question - > Answer . FloatingPoint [ 1 ] ) // The maximum and the minimum are swapped
{
/* Swap maximum and minimum */
Tmp = Question - > Answer . FloatingPoint [ 0 ] ;
Question - > Answer . FloatingPoint [ 0 ] = Question - > Answer . FloatingPoint [ 1 ] ;
Question - > Answer . FloatingPoint [ 1 ] = Tmp ;
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult ( & mysql_res ) ;
}
void Qst_GetCorrectTF_AnswerFromDB ( struct Qst_Question * Question )
{
MYSQL_RES * mysql_res ;
MYSQL_ROW row ;
/***** Query database *****/
2021-10-28 18:48:21 +02:00
Question - > Answer . NumOptions = Qst_DB_GetTextOfAnswers ( & mysql_res , Question - > QstCod ) ;
2021-10-27 00:58:37 +02:00
/***** Check if number of rows is correct *****/
Qst_CheckIfNumberOfAnswersIsOne ( Question ) ;
/***** Get answer *****/
row = mysql_fetch_row ( mysql_res ) ;
Question - > Answer . TF = row [ 0 ] [ 0 ] ;
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult ( & mysql_res ) ;
}
void Qst_GetCorrectChoAnswerFromDB ( struct Qst_Question * Question )
{
MYSQL_RES * mysql_res ;
MYSQL_ROW row ;
unsigned NumOpt ;
/***** Query database *****/
2021-11-25 15:39:32 +01:00
// Question->Answer.NumOptions = Qst_DB_GetTextOfAnswers (&mysql_res,Question->QstCod); // TODO: Esta l<> nea llev<65> al error del 25 de noviembre de 2021 Remove this line!!!!!!!
Question - > Answer . NumOptions = Qst_DB_GetQstAnswersCorr ( & mysql_res , Question - > QstCod ) ;
2021-10-28 18:48:21 +02:00
/***** Get options *****/
2021-10-27 00:58:37 +02:00
for ( NumOpt = 0 ;
NumOpt < Question - > Answer . NumOptions ;
NumOpt + + )
{
/* Get next answer */
row = mysql_fetch_row ( mysql_res ) ;
/* Assign correctness (row[0]) of this answer (this option) */
Question - > Answer . Options [ NumOpt ] . Correct = ( row [ 0 ] [ 0 ] = = ' Y ' ) ;
}
/* Free structure that stores the query result */
DB_FreeMySQLResult ( & mysql_res ) ;
}
void Qst_GetCorrectTxtAnswerFromDB ( struct Qst_Question * Question )
{
MYSQL_RES * mysql_res ;
MYSQL_ROW row ;
unsigned NumOpt ;
/***** Query database *****/
2021-10-28 18:48:21 +02:00
Question - > Answer . NumOptions = Qst_DB_GetTextOfAnswers ( & mysql_res , Question - > QstCod ) ;
/***** Get text and correctness of answers for this question
from database ( one row per answer ) * * * * */
2021-10-27 00:58:37 +02:00
for ( NumOpt = 0 ;
NumOpt < Question - > Answer . NumOptions ;
NumOpt + + )
{
/***** Get next answer *****/
row = mysql_fetch_row ( mysql_res ) ;
/***** Allocate memory for text in this choice answer *****/
if ( ! Qst_AllocateTextChoiceAnswer ( Question , NumOpt ) )
/* Abort on error */
Ale_ShowAlertsAndExit ( ) ;
/***** Copy answer text (row[0]) ******/
Str_Copy ( Question - > Answer . Options [ NumOpt ] . Text , row [ 0 ] ,
Qst_MAX_BYTES_ANSWER_OR_FEEDBACK ) ;
}
/***** Change format of answers text *****/
Qst_ChangeFormatAnswersText ( Question ) ;
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult ( & mysql_res ) ;
}
2021-10-25 22:19:03 +02:00
/*****************************************************************************/
/************** Write false / true answer when seeing a test *****************/
/*****************************************************************************/
void Qst_WriteAnsTF ( char AnsTF )
{
extern const char * Txt_TF_QST [ 2 ] ;
switch ( AnsTF )
{
case ' T ' : // true
HTM_Txt ( Txt_TF_QST [ 0 ] ) ;
break ;
case ' F ' : // false
HTM_Txt ( Txt_TF_QST [ 1 ] ) ;
break ;
default : // no answer
HTM_NBSP ( ) ;
break ;
}
}
/*****************************************************************************/
/*************** Write parameter with the code of a question *****************/
/*****************************************************************************/
void Qst_WriteParamQstCod ( unsigned QstInd , long QstCod )
{
char StrAns [ 3 + Cns_MAX_DECIMAL_DIGITS_UINT + 1 ] ; // "Ansxx...x"
snprintf ( StrAns , sizeof ( StrAns ) , " Qst%010u " , QstInd ) ;
Par_PutHiddenParamLong ( NULL , StrAns , QstCod ) ;
}
/*****************************************************************************/
/********************* Check if number of answers is one *********************/
/*****************************************************************************/
void Qst_CheckIfNumberOfAnswersIsOne ( const struct Qst_Question * Question )
{
if ( Question - > Answer . NumOptions ! = 1 )
Err_WrongAnswerExit ( ) ;
}
2021-10-25 10:24:43 +02:00
/*****************************************************************************/
/***************** Change format of answers text / feedback ******************/
/*****************************************************************************/
void Qst_ChangeFormatAnswersText ( struct Qst_Question * Question )
{
unsigned NumOpt ;
/***** Change format of answers text *****/
for ( NumOpt = 0 ;
NumOpt < Question - > Answer . NumOptions ;
NumOpt + + )
/* Convert answer text, that is in HTML, to rigorous HTML */
if ( Question - > Answer . Options [ NumOpt ] . Text [ 0 ] )
Str_ChangeFormat ( Str_FROM_HTML , Str_TO_RIGOROUS_HTML ,
Question - > Answer . Options [ NumOpt ] . Text ,
Qst_MAX_BYTES_ANSWER_OR_FEEDBACK , false ) ;
}
void Qst_ChangeFormatAnswersFeedback ( struct Qst_Question * Question )
{
unsigned NumOpt ;
/***** Change format of answers text and feedback *****/
for ( NumOpt = 0 ;
NumOpt < Question - > Answer . NumOptions ;
NumOpt + + )
/* Convert answer feedback, that is in HTML, to rigorous HTML */
if ( Question - > Answer . Options [ NumOpt ] . Feedback )
if ( Question - > Answer . Options [ NumOpt ] . Feedback [ 0 ] )
Str_ChangeFormat ( Str_FROM_HTML , Str_TO_RIGOROUS_HTML ,
Question - > Answer . Options [ NumOpt ] . Feedback ,
Qst_MAX_BYTES_ANSWER_OR_FEEDBACK , false ) ;
}
2021-10-25 13:52:17 +02:00
/*****************************************************************************/
/** Convert a string with the type of answer in database to type of answer ***/
/*****************************************************************************/
Qst_AnswerType_t Qst_ConvertFromStrAnsTypDBToAnsTyp ( const char * StrAnsTypeDB )
{
2021-10-27 21:40:19 +02:00
extern const char * Qst_DB_StrAnswerTypes [ Qst_NUM_ANS_TYPES ] ;
2021-10-25 13:52:17 +02:00
Qst_AnswerType_t AnsType ;
if ( StrAnsTypeDB ! = NULL )
if ( StrAnsTypeDB [ 0 ] )
for ( AnsType = ( Qst_AnswerType_t ) 0 ;
AnsType < = ( Qst_AnswerType_t ) ( Qst_NUM_ANS_TYPES - 1 ) ;
AnsType + + )
if ( ! strcmp ( StrAnsTypeDB , Qst_DB_StrAnswerTypes [ AnsType ] ) )
return AnsType ;
return Qst_ANS_UNKNOWN ;
}
/*****************************************************************************/
/************ Convert a string with an unsigned to answer type ***************/
/*****************************************************************************/
Qst_AnswerType_t Qst_ConvertFromUnsignedStrToAnsTyp ( const char * UnsignedStr )
{
unsigned AnsType ;
if ( sscanf ( UnsignedStr , " %u " , & AnsType ) ! = 1 )
Err_WrongAnswerExit ( ) ;
if ( AnsType > = Qst_NUM_ANS_TYPES )
Err_WrongAnswerExit ( ) ;
return ( Qst_AnswerType_t ) AnsType ;
}
2021-10-25 22:19:03 +02:00
/*****************************************************************************/
/******************** Show form to edit one test question ********************/
/*****************************************************************************/
void Qst_ShowFormEditOneQst ( void )
{
extern const char * Txt_Question_removed ;
struct Qst_Question Question ;
bool PutFormToEditQuestion ;
/***** Create test question *****/
Qst_QstConstructor ( & Question ) ;
/***** Get question data *****/
Question . QstCod = Qst_GetParamQstCod ( ) ;
if ( Question . QstCod > 0 ) // Question already exists in the database
PutFormToEditQuestion = Qst_GetQstDataFromDB ( & Question ) ;
else // New question
PutFormToEditQuestion = true ;
/***** Put form to edit question *****/
if ( PutFormToEditQuestion )
Qst_PutFormEditOneQst ( & Question ) ;
else
Ale_ShowAlert ( Ale_WARNING , Txt_Question_removed ) ;
/***** Destroy test question *****/
Qst_QstDestructor ( & Question ) ;
}
/*****************************************************************************/
/******************** Show form to edit one test question ********************/
/*****************************************************************************/
// This function may be called from three places:
// 1. By clicking "New question" icon
// 2. By clicking "Edit" icon in a listing of existing questions
// 3. From the action associated to reception of a question, on error in the parameters received from the form
void Qst_PutFormEditOneQst ( struct Qst_Question * Question )
{
extern const char * Hlp_ASSESSMENT_Questions_writing_a_question ;
extern const char * The_ClassFormInBox [ The_NUM_THEMES ] ;
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_Wording ;
extern const char * Txt_Feedback ;
extern const char * Txt_optional ;
extern const char * Txt_Type ;
extern const char * Txt_TST_STR_ANSWER_TYPES [ Qst_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 ;
extern const char * Txt_Expand ;
extern const char * Txt_Contract ;
extern const char * Txt_Save_changes ;
extern const char * Txt_Create_question ;
MYSQL_RES * mysql_res ;
MYSQL_ROW row ;
unsigned NumTags ;
unsigned IndTag ;
unsigned NumTag ;
unsigned NumOpt ;
Qst_AnswerType_t AnsType ;
bool IsThisTag ;
bool TagFound ;
bool OptionsDisabled ;
bool AnswerHasContent ;
bool DisplayRightColumn ;
char StrTagTxt [ 6 + Cns_MAX_DECIMAL_DIGITS_UINT + 1 ] ;
char StrInteger [ Cns_MAX_DECIMAL_DIGITS_UINT + 1 ] ;
char * Title ;
/***** Begin box *****/
if ( Question - > QstCod > 0 ) // The question already has assigned a code
{
Box_BoxBegin ( NULL , Str_BuildStringLong ( Txt_Question_code_X , Question - > QstCod ) ,
Qst_PutIconToRemoveOneQst , & Question - > QstCod ,
Hlp_ASSESSMENT_Questions_writing_a_question , Box_NOT_CLOSABLE ) ;
Str_FreeString ( ) ;
}
else
Box_BoxBegin ( NULL , Txt_New_question ,
NULL , NULL ,
Hlp_ASSESSMENT_Questions_writing_a_question , Box_NOT_CLOSABLE ) ;
/***** Begin form *****/
Frm_BeginForm ( ActRcvTstQst ) ;
2021-12-01 01:43:13 +01:00
Qst_PutParamQstCod ( & Question - > QstCod ) ;
2021-10-25 22:19:03 +02:00
/***** Begin table *****/
HTM_TABLE_BeginPadding ( 2 ) ; // Table for this question
/***** Help for text editor *****/
HTM_TR_Begin ( NULL ) ;
HTM_TD_Begin ( " colspan= \" 2 \" " ) ;
Lay_HelpPlainEditor ( ) ;
HTM_TD_End ( ) ;
HTM_TR_End ( ) ;
/***** Get tags already existing for questions in current course *****/
NumTags = Tag_DB_GetAllTagsFromCurrentCrs ( & mysql_res ) ;
/***** Write the tags *****/
HTM_TR_Begin ( NULL ) ;
HTM_TD_Begin ( " class= \" RT %s \" " , The_ClassFormInBox [ Gbl . Prefs . Theme ] ) ;
HTM_TxtColon ( Txt_Tags ) ;
HTM_TD_End ( ) ;
HTM_TD_Begin ( " class= \" LT \" " ) ;
HTM_TABLE_BeginPadding ( 2 ) ; // Table for tags
for ( IndTag = 0 ;
IndTag < Tag_MAX_TAGS_PER_QUESTION ;
IndTag + + )
{
HTM_TR_Begin ( NULL ) ;
/***** Write the tags already existing in a selector *****/
HTM_TD_Begin ( " class= \" LM \" " ) ;
HTM_SELECT_Begin ( HTM_DONT_SUBMIT_ON_CHANGE ,
" id= \" SelTag%u \" name= \" SelTag%u \" "
" class= \" TAG_SEL \" onchange= \" changeTxtTag('%u') \" " ,
IndTag , IndTag , IndTag ) ;
HTM_OPTION ( HTM_Type_STRING , " " , false , false , " " ) ;
mysql_data_seek ( mysql_res , 0 ) ;
TagFound = false ;
for ( NumTag = 1 ;
NumTag < = NumTags ;
NumTag + + )
{
row = mysql_fetch_row ( mysql_res ) ;
/*
row [ 0 ] TagCod
row [ 1 ] TagTxt
row [ 2 ] TagHidden
*/
IsThisTag = false ;
if ( ! strcasecmp ( Question - > Tags . Txt [ IndTag ] , row [ 1 ] ) )
{
HTM_Txt ( " selected= \" selected \" " ) ;
IsThisTag = true ;
TagFound = true ;
}
HTM_OPTION ( HTM_Type_STRING , row [ 1 ] ,
IsThisTag , false ,
" %s " , row [ 1 ] ) ;
}
/* If it's a new tag received from the form */
if ( ! TagFound & & Question - > Tags . Txt [ IndTag ] [ 0 ] )
HTM_OPTION ( HTM_Type_STRING , Question - > Tags . Txt [ IndTag ] ,
true , false ,
" %s " , Question - > Tags . Txt [ IndTag ] ) ;
HTM_OPTION ( HTM_Type_STRING , " " ,
false , false ,
" [%s] " , Txt_new_tag ) ;
HTM_SELECT_End ( ) ;
HTM_TD_End ( ) ;
/***** Input of a new tag *****/
HTM_TD_Begin ( " class= \" RM \" " ) ;
snprintf ( StrTagTxt , sizeof ( StrTagTxt ) , " TagTxt%u " , IndTag ) ;
HTM_INPUT_TEXT ( StrTagTxt , Tag_MAX_CHARS_TAG , Question - > Tags . Txt [ IndTag ] ,
HTM_DONT_SUBMIT_ON_CHANGE ,
" id= \" %s \" class= \" TAG_TXT \" onchange= \" changeSelTag('%u') \" " ,
StrTagTxt , IndTag ) ;
HTM_TD_End ( ) ;
HTM_TR_End ( ) ;
}
HTM_TABLE_End ( ) ; // Table for tags
HTM_TD_End ( ) ;
HTM_TR_End ( ) ;
/* Free structure that stores the query result */
DB_FreeMySQLResult ( & mysql_res ) ;
/***** Stem and image *****/
HTM_TR_Begin ( NULL ) ;
/* Label */
Frm_LabelColumn ( " RT " , " Stem " , Txt_Wording ) ;
/* Data */
HTM_TD_Begin ( " class= \" LT \" " ) ;
HTM_TEXTAREA_Begin ( " id= \" Stem \" name= \" Stem \" class= \" STEM_TEXTAREA \" "
" rows= \" 5 \" required= \" required \" " ) ;
HTM_Txt ( Question - > Stem ) ;
HTM_TEXTAREA_End ( ) ;
HTM_BR ( ) ;
Qst_PutFormToEditQstMedia ( & Question - > Media , - 1 ,
false ) ;
/***** Feedback *****/
HTM_LABEL_Begin ( " class= \" %s \" " , The_ClassFormInBox [ Gbl . Prefs . Theme ] ) ;
HTM_TxtF ( " %s (%s): " , Txt_Feedback , Txt_optional ) ;
HTM_BR ( ) ;
HTM_TEXTAREA_Begin ( " name= \" Feedback \" class= \" STEM_TEXTAREA \" rows= \" 2 \" " ) ;
if ( Question - > Feedback [ 0 ] )
HTM_Txt ( Question - > Feedback ) ;
HTM_TEXTAREA_End ( ) ;
HTM_LABEL_End ( ) ;
HTM_TD_End ( ) ;
HTM_TR_End ( ) ;
/***** Type of answer *****/
HTM_TR_Begin ( NULL ) ;
HTM_TD_Begin ( " class= \" RT %s \" " , The_ClassFormInBox [ Gbl . Prefs . Theme ] ) ;
HTM_TxtColon ( Txt_Type ) ;
HTM_TD_End ( ) ;
HTM_TD_Begin ( " class= \" %s LT \" " , The_ClassFormInBox [ Gbl . Prefs . Theme ] ) ;
for ( AnsType = ( Qst_AnswerType_t ) 0 ;
AnsType < = ( Qst_AnswerType_t ) ( Qst_NUM_ANS_TYPES - 1 ) ;
AnsType + + )
{
HTM_LABEL_Begin ( NULL ) ;
HTM_INPUT_RADIO ( " AnswerType " , false ,
" value= \" %u \" %s onclick= \" enableDisableAns(this.form); \" " ,
( unsigned ) AnsType ,
AnsType = = Question - > Answer . Type ? " checked= \" checked \" " :
" " ) ;
HTM_TxtF ( " %s " , Txt_TST_STR_ANSWER_TYPES [ AnsType ] ) ;
HTM_LABEL_End ( ) ;
HTM_BR ( ) ;
}
HTM_TD_End ( ) ;
HTM_TR_End ( ) ;
/***** Answers *****/
/* Integer answer */
HTM_TR_Begin ( NULL ) ;
HTM_TD_Begin ( " class= \" RT %s \" " , The_ClassFormInBox [ Gbl . Prefs . Theme ] ) ;
HTM_TxtColon ( Txt_Answers ) ;
HTM_TD_End ( ) ;
HTM_TD_Begin ( " class= \" LT \" " ) ;
HTM_LABEL_Begin ( " class= \" %s \" " , The_ClassFormInBox [ Gbl . Prefs . Theme ] ) ;
HTM_TxtColonNBSP ( Txt_Integer_number ) ;
snprintf ( StrInteger , sizeof ( StrInteger ) , " %ld " , Question - > Answer . Integer ) ;
HTM_INPUT_TEXT ( " AnsInt " , Cns_MAX_DECIMAL_DIGITS_LONG , StrInteger ,
HTM_DONT_SUBMIT_ON_CHANGE ,
" size= \" 11 \" required= \" required \" %s " ,
Question - > Answer . Type = = Qst_ANS_INT ? " " :
" disabled= \" disabled \" " ) ;
HTM_LABEL_End ( ) ;
HTM_TD_End ( ) ;
HTM_TR_End ( ) ;
/* Floating point answer */
HTM_TR_Begin ( NULL ) ;
HTM_TD_Empty ( 1 ) ;
HTM_TD_Begin ( " class= \" LT \" " ) ;
Qst_PutFloatInputField ( Txt_Real_number_between_A_and_B_1 , " AnsFloatMin " ,
Question , 0 ) ;
Qst_PutFloatInputField ( Txt_Real_number_between_A_and_B_2 , " AnsFloatMax " ,
Question , 1 ) ;
HTM_TD_End ( ) ;
HTM_TR_End ( ) ;
/* T/F answer */
HTM_TR_Begin ( NULL ) ;
HTM_TD_Empty ( 1 ) ;
HTM_TD_Begin ( " class= \" LT \" " ) ;
Qst_PutTFInputField ( Question , Txt_TF_QST [ 0 ] , ' T ' ) ;
Qst_PutTFInputField ( Question , Txt_TF_QST [ 1 ] , ' F ' ) ;
HTM_TD_End ( ) ;
HTM_TR_End ( ) ;
/* Questions can be shuffled? */
HTM_TR_Begin ( NULL ) ;
HTM_TD_Empty ( 1 ) ;
HTM_TD_Begin ( " class= \" LT \" " ) ;
HTM_LABEL_Begin ( " class= \" %s \" " , The_ClassFormInBox [ Gbl . Prefs . Theme ] ) ;
HTM_INPUT_CHECKBOX ( " Shuffle " , HTM_DONT_SUBMIT_ON_CHANGE ,
" value= \" Y \" %s%s " ,
Question - > Answer . Shuffle ? " checked= \" checked \" " :
" " ,
Question - > Answer . Type ! = Qst_ANS_UNIQUE_CHOICE & &
Question - > Answer . Type ! = Qst_ANS_MULTIPLE_CHOICE ? " disabled= \" disabled \" " :
" " ) ;
HTM_Txt ( Txt_Shuffle ) ;
HTM_LABEL_End ( ) ;
HTM_TD_End ( ) ;
HTM_TR_End ( ) ;
/* Simple or multiple choice answers */
HTM_TR_Begin ( NULL ) ;
HTM_TD_Empty ( 1 ) ;
HTM_TD_Begin ( " class= \" LT \" " ) ;
HTM_TABLE_BeginPadding ( 2 ) ; // Table with choice answers
OptionsDisabled = Question - > Answer . Type ! = Qst_ANS_UNIQUE_CHOICE & &
Question - > Answer . Type ! = Qst_ANS_MULTIPLE_CHOICE & &
Question - > Answer . Type ! = Qst_ANS_TEXT ;
for ( NumOpt = 0 ;
NumOpt < Qst_MAX_OPTIONS_PER_QUESTION ;
NumOpt + + )
{
Gbl . RowEvenOdd = NumOpt % 2 ;
AnswerHasContent = false ;
if ( Question - > Answer . Options [ NumOpt ] . Text )
if ( Question - > Answer . Options [ NumOpt ] . Text [ 0 ] | | // Text
Question - > Answer . Options [ NumOpt ] . Media . Type ! = Med_TYPE_NONE ) // or media
AnswerHasContent = true ;
DisplayRightColumn = NumOpt < 2 | | // Display at least the two first options
AnswerHasContent ;
HTM_TR_Begin ( NULL ) ;
/***** Left column: selectors *****/
HTM_TD_Begin ( " class= \" TEST_EDI_ANS_LEFT_COL COLOR%u \" " , Gbl . RowEvenOdd ) ;
/* Radio selector for unique choice answers */
HTM_INPUT_RADIO ( " AnsUni " , false ,
" value= \" %u \" %s%s%s onclick= \" enableDisableAns(this.form); \" " ,
NumOpt ,
Question - > Answer . Options [ NumOpt ] . Correct ? " checked= \" checked \" " :
" " ,
NumOpt < 2 ? " required= \" required \" " : // First or second options required
" " ,
Question - > Answer . Type = = Qst_ANS_UNIQUE_CHOICE ? " " :
" disabled= \" disabled \" " ) ;
/* Checkbox for multiple choice answers */
HTM_INPUT_CHECKBOX ( " AnsMulti " , HTM_DONT_SUBMIT_ON_CHANGE ,
" value= \" %u \" %s%s " ,
NumOpt ,
Question - > Answer . Options [ NumOpt ] . Correct ? " checked= \" checked \" " :
" " ,
Question - > Answer . Type = = Qst_ANS_MULTIPLE_CHOICE ? " " :
" disabled= \" disabled \" " ) ;
HTM_TD_End ( ) ;
/***** Center column: letter of the answer and expand / contract icon *****/
HTM_TD_Begin ( " class= \" %s TEST_EDI_ANS_CENTER_COL COLOR%u \" " ,
The_ClassFormInBox [ Gbl . Prefs . Theme ] , Gbl . RowEvenOdd ) ;
HTM_TxtF ( " %c) " , ' a ' + ( char ) NumOpt ) ;
/* Icon to expand (show the answer) */
HTM_A_Begin ( " href= \" \" id= \" exp_%u \" %s "
" onclick= \" toggleAnswer('%u');return false; \" " ,
NumOpt ,
DisplayRightColumn ? " style= \" display:none; \" " : // Answer does have content ==> Hide icon
" " ,
NumOpt ) ;
if ( asprintf ( & Title , " %s %c) " , Txt_Expand , ' a ' + ( char ) NumOpt ) < 0 )
Err_NotEnoughMemoryExit ( ) ;
Ico_PutIcon ( " caret-right.svg " , Title , " ICO16x16 " ) ;
free ( Title ) ;
HTM_A_End ( ) ;
/* Icon to contract (hide the answer) */
HTM_A_Begin ( " href= \" \" id= \" con_%u \" %s "
" onclick= \" toggleAnswer(%u);return false; \" " ,
NumOpt ,
DisplayRightColumn ? " " :
" style= \" display:none; \" " , // Answer does not have content ==> Hide icon
NumOpt ) ;
if ( asprintf ( & Title , " %s %c) " , Txt_Contract , ' a ' + ( char ) NumOpt ) < 0 )
Err_NotEnoughMemoryExit ( ) ;
Ico_PutIcon ( " caret-down.svg " , Title , " ICO16x16 " ) ;
free ( Title ) ;
HTM_A_End ( ) ;
HTM_TD_End ( ) ;
/***** Right column: content of the answer *****/
HTM_TD_Begin ( " class= \" TEST_EDI_ANS_RIGHT_COL COLOR%u \" " , Gbl . RowEvenOdd ) ;
HTM_DIV_Begin ( " id= \" ans_%u \" %s " ,
NumOpt ,
DisplayRightColumn ? " " :
" style= \" display:none; \" " ) ; // Answer does not have content ==> Hide column
/* Answer text */
HTM_TEXTAREA_Begin ( " name= \" AnsStr%u \" class= \" ANSWER_TEXTAREA \" rows= \" 5 \" %s " ,
NumOpt , OptionsDisabled ? " disabled= \" disabled \" " :
" " ) ;
if ( AnswerHasContent )
HTM_Txt ( Question - > Answer . Options [ NumOpt ] . Text ) ;
HTM_TEXTAREA_End ( ) ;
/* Media */
Qst_PutFormToEditQstMedia ( & Question - > Answer . Options [ NumOpt ] . Media ,
( int ) NumOpt ,
OptionsDisabled ) ;
/* Feedback */
HTM_LABEL_Begin ( " class= \" %s \" " , The_ClassFormInBox [ Gbl . Prefs . Theme ] ) ;
HTM_TxtF ( " %s (%s): " , Txt_Feedback , Txt_optional ) ;
HTM_BR ( ) ;
HTM_TEXTAREA_Begin ( " name= \" FbStr%u \" class= \" ANSWER_TEXTAREA \" rows= \" 2 \" %s " ,
NumOpt , OptionsDisabled ? " disabled= \" disabled \" " :
" " ) ;
if ( Question - > Answer . Options [ NumOpt ] . Feedback )
if ( Question - > Answer . Options [ NumOpt ] . Feedback [ 0 ] )
HTM_Txt ( Question - > Answer . Options [ NumOpt ] . Feedback ) ;
HTM_TEXTAREA_End ( ) ;
HTM_LABEL_End ( ) ;
/* End of right column */
HTM_DIV_End ( ) ;
HTM_TD_End ( ) ;
HTM_TR_End ( ) ;
}
HTM_TABLE_End ( ) ; // Table with choice answers
HTM_TD_End ( ) ;
HTM_TR_End ( ) ;
/***** End table *****/
HTM_TABLE_End ( ) ; // Table for this question
/***** Send button *****/
if ( Question - > QstCod > 0 ) // The question already has assigned a code
Btn_PutConfirmButton ( Txt_Save_changes ) ;
else
Btn_PutCreateButton ( Txt_Create_question ) ;
/***** End form *****/
Frm_EndForm ( ) ;
/***** End box *****/
Box_BoxEnd ( ) ;
}
/*****************************************************************************/
/********************* Put input field for floating answer *******************/
/*****************************************************************************/
void Qst_PutFloatInputField ( const char * Label , const char * Field ,
const struct Qst_Question * Question ,
unsigned Index )
{
extern const char * The_ClassFormInBox [ The_NUM_THEMES ] ;
char StrDouble [ 32 ] ;
HTM_LABEL_Begin ( " class= \" %s \" " , The_ClassFormInBox [ Gbl . Prefs . Theme ] ) ;
HTM_TxtF ( " %s " , Label ) ;
snprintf ( StrDouble , sizeof ( StrDouble ) , " %.15lg " ,
Question - > Answer . FloatingPoint [ Index ] ) ;
HTM_INPUT_TEXT ( Field , Qst_MAX_BYTES_FLOAT_ANSWER , StrDouble ,
HTM_DONT_SUBMIT_ON_CHANGE ,
" size= \" 11 \" required= \" required \" %s " ,
Question - > Answer . Type = = Qst_ANS_FLOAT ? " " :
" disabled= \" disabled \" " ) ;
HTM_LABEL_End ( ) ;
}
/*****************************************************************************/
/*********************** Put input field for T/F answer **********************/
/*****************************************************************************/
void Qst_PutTFInputField ( const struct Qst_Question * Question ,
const char * Label , char Value )
{
extern const char * The_ClassFormInBox [ The_NUM_THEMES ] ;
HTM_LABEL_Begin ( " class= \" %s \" " , The_ClassFormInBox [ Gbl . Prefs . Theme ] ) ;
HTM_INPUT_RADIO ( " AnsTF " , false ,
" value= \" %c \" %s%s required= \" required \" " ,
Value ,
Question - > Answer . TF = = Value ? " checked= \" checked \" " :
" " ,
Question - > Answer . Type = = Qst_ANS_TRUE_FALSE ? " " :
" disabled= \" disabled \" " ) ;
HTM_Txt ( Label ) ;
HTM_LABEL_End ( ) ;
}
/*****************************************************************************/
/********************* Initialize a new question to zero *********************/
/*****************************************************************************/
void Qst_QstConstructor ( struct Qst_Question * Question )
{
unsigned NumOpt ;
/***** Reset question tags *****/
Tag_ResetTags ( & Question - > Tags ) ;
/***** Reset edition time *****/
Question - > EditTime = ( time_t ) 0 ;
/***** Allocate memory for stem and feedback *****/
if ( ( Question - > Stem = malloc ( Cns_MAX_BYTES_TEXT + 1 ) ) = = NULL )
Err_NotEnoughMemoryExit ( ) ;
Question - > Stem [ 0 ] = ' \0 ' ;
if ( ( Question - > Feedback = malloc ( Cns_MAX_BYTES_TEXT + 1 ) ) = = NULL )
Err_NotEnoughMemoryExit ( ) ;
Question - > Feedback [ 0 ] = ' \0 ' ;
/***** Initialize answers *****/
Question - > Answer . Type = Qst_ANS_UNIQUE_CHOICE ;
Question - > Answer . NumOptions = 0 ;
Question - > Answer . Shuffle = false ;
Question - > Answer . TF = ' ' ;
/* Initialize image attached to stem */
Med_MediaConstructor ( & Question - > Media ) ;
/* Initialize options */
for ( NumOpt = 0 ;
NumOpt < Qst_MAX_OPTIONS_PER_QUESTION ;
NumOpt + + )
{
Question - > Answer . Options [ NumOpt ] . Correct = false ;
Question - > Answer . Options [ NumOpt ] . Text = NULL ;
Question - > Answer . Options [ NumOpt ] . Feedback = NULL ;
/* Initialize image attached to option */
Med_MediaConstructor ( & Question - > Answer . Options [ NumOpt ] . Media ) ;
}
Question - > Answer . Integer = 0 ;
Question - > Answer . FloatingPoint [ 0 ] =
Question - > Answer . FloatingPoint [ 1 ] = 0.0 ;
/***** Initialize stats *****/
Question - > NumHits =
Question - > NumHitsNotBlank = 0 ;
Question - > Score = 0.0 ;
/***** Mark question as valid *****/
Question - > Validity = Qst_VALID_QUESTION ;
}
/*****************************************************************************/
/***************** Free memory allocated for test question *******************/
/*****************************************************************************/
void Qst_QstDestructor ( struct Qst_Question * Question )
{
Qst_FreeTextChoiceAnswers ( Question ) ;
Qst_FreeMediaOfQuestion ( Question ) ;
if ( Question - > Feedback )
{
free ( Question - > Feedback ) ;
Question - > Feedback = NULL ;
}
if ( Question - > Stem )
{
free ( Question - > Stem ) ;
Question - > Stem = NULL ;
}
}
/*****************************************************************************/
/******************* Allocate memory for a choice answer *********************/
/*****************************************************************************/
// Return false on error
bool Qst_AllocateTextChoiceAnswer ( struct Qst_Question * Question , unsigned NumOpt )
{
if ( ( Question - > Answer . Options [ NumOpt ] . Text =
malloc ( Qst_MAX_BYTES_ANSWER_OR_FEEDBACK + 1 ) ) = = NULL )
{
Ale_CreateAlert ( Ale_ERROR , NULL ,
" Not enough memory to store answer. " ) ;
return false ;
}
if ( ( Question - > Answer . Options [ NumOpt ] . Feedback =
malloc ( Qst_MAX_BYTES_ANSWER_OR_FEEDBACK + 1 ) ) = = NULL )
{
Ale_CreateAlert ( Ale_ERROR , NULL ,
" Not enough memory to store feedback. " ) ;
return false ;
}
Question - > Answer . Options [ NumOpt ] . Text [ 0 ] =
Question - > Answer . Options [ NumOpt ] . Feedback [ 0 ] = ' \0 ' ;
return true ;
}
/*****************************************************************************/
/******************** Free memory of all choice answers **********************/
/*****************************************************************************/
void Qst_FreeTextChoiceAnswers ( struct Qst_Question * Question )
{
unsigned NumOpt ;
for ( NumOpt = 0 ;
NumOpt < Qst_MAX_OPTIONS_PER_QUESTION ;
NumOpt + + )
Qst_FreeTextChoiceAnswer ( Question , NumOpt ) ;
}
/*****************************************************************************/
/********************** Free memory of a choice answer ***********************/
/*****************************************************************************/
void Qst_FreeTextChoiceAnswer ( struct Qst_Question * Question , unsigned NumOpt )
{
if ( Question - > Answer . Options [ NumOpt ] . Text )
{
free ( Question - > Answer . Options [ NumOpt ] . Text ) ;
Question - > Answer . Options [ NumOpt ] . Text = NULL ;
}
if ( Question - > Answer . Options [ NumOpt ] . Feedback )
{
free ( Question - > Answer . Options [ NumOpt ] . Feedback ) ;
Question - > Answer . Options [ NumOpt ] . Feedback = NULL ;
}
}
/*****************************************************************************/
/***************** Initialize images of a question to zero *******************/
/*****************************************************************************/
void Qst_ResetMediaOfQuestion ( struct Qst_Question * Question )
{
unsigned NumOpt ;
/***** Reset media for stem *****/
Med_ResetMedia ( & Question - > Media ) ;
/***** Reset media for every answer option *****/
for ( NumOpt = 0 ;
NumOpt < Qst_MAX_OPTIONS_PER_QUESTION ;
NumOpt + + )
Med_ResetMedia ( & Question - > Answer . Options [ NumOpt ] . Media ) ;
}
/*****************************************************************************/
/*********************** Free images of a question ***************************/
/*****************************************************************************/
void Qst_FreeMediaOfQuestion ( struct Qst_Question * Question )
{
unsigned NumOpt ;
Med_MediaDestructor ( & Question - > Media ) ;
for ( NumOpt = 0 ;
NumOpt < Qst_MAX_OPTIONS_PER_QUESTION ;
NumOpt + + )
Med_MediaDestructor ( & Question - > Answer . Options [ NumOpt ] . Media ) ;
}
/*****************************************************************************/
/****************** Get data of a question from database *********************/
/*****************************************************************************/
bool Qst_GetQstDataFromDB ( struct Qst_Question * Question )
{
MYSQL_RES * mysql_res ;
MYSQL_ROW row ;
bool QuestionExists ;
unsigned NumTags ;
unsigned NumTag ;
unsigned NumOpt ;
/***** Get question data from database *****/
2021-10-29 00:32:19 +02:00
if ( ( QuestionExists = ( Qst_DB_GetQstData ( & mysql_res , Question - > QstCod ) ! = 0 ) ) )
2021-10-25 22:19:03 +02:00
{
row = mysql_fetch_row ( mysql_res ) ;
/* Get edition time (row[0] holds the start UTC time) */
Question - > EditTime = Dat_GetUNIXTimeFromStr ( row [ 0 ] ) ;
/* Get the type of answer (row[1]) */
Question - > Answer . Type = Qst_ConvertFromStrAnsTypDBToAnsTyp ( row [ 1 ] ) ;
/* Get shuffle (row[2]) */
Question - > Answer . Shuffle = ( row [ 2 ] [ 0 ] = = ' Y ' ) ;
/* Get the stem (row[3]) and the feedback (row[4]) */
Question - > Stem [ 0 ] = ' \0 ' ;
2021-10-29 00:32:19 +02:00
Question - > Feedback [ 0 ] = ' \0 ' ;
2021-10-25 22:19:03 +02:00
if ( row [ 3 ] )
if ( row [ 3 ] [ 0 ] )
Str_Copy ( Question - > Stem , row [ 3 ] , Cns_MAX_BYTES_TEXT ) ;
if ( row [ 4 ] )
if ( row [ 4 ] [ 0 ] )
Str_Copy ( Question - > Feedback , row [ 4 ] , Cns_MAX_BYTES_TEXT ) ;
/* Get media (row[5]) */
Question - > Media . MedCod = Str_ConvertStrCodToLongCod ( row [ 5 ] ) ;
Med_GetMediaDataByCod ( & Question - > Media ) ;
/* Get number of hits
( number of times that the question has been answered ,
2021-10-29 00:32:19 +02:00
including blank answers ) ( row [ 6 ] )
and number of hits not blank
2021-10-25 22:19:03 +02:00
( number of times that the question has been answered
with a not blank answer ) ( row [ 7 ] ) */
2021-10-29 00:32:19 +02:00
if ( sscanf ( row [ 6 ] , " %lu " , & Question - > NumHits ) ! = 1 )
Question - > NumHits = 0 ;
2021-10-25 22:19:03 +02:00
if ( sscanf ( row [ 7 ] , " %lu " , & Question - > NumHitsNotBlank ) ! = 1 )
Question - > NumHitsNotBlank = 0 ;
/* Get the acumulated score of the question (row[8]) */
Str_SetDecimalPointToUS ( ) ; // To get the decimal point as a dot
if ( sscanf ( row [ 8 ] , " %lf " , & Question - > Score ) ! = 1 )
Question - > Score = 0.0 ;
Str_SetDecimalPointToLocal ( ) ; // Return to local system
/* Free structure that stores the query result */
DB_FreeMySQLResult ( & mysql_res ) ;
/***** Get the tags from the database *****/
NumTags = Tag_DB_GetTagsQst ( & mysql_res , Question - > QstCod ) ;
for ( NumTag = 0 ;
NumTag < NumTags ;
NumTag + + )
{
row = mysql_fetch_row ( mysql_res ) ;
Str_Copy ( Question - > Tags . Txt [ NumTag ] , row [ 0 ] ,
sizeof ( Question - > Tags . Txt [ NumTag ] ) - 1 ) ;
}
/* Free structure that stores the query result */
DB_FreeMySQLResult ( & mysql_res ) ;
/***** Get the answers from the database *****/
2021-10-28 18:48:21 +02:00
Question - > Answer . NumOptions = Qst_DB_GetDataOfAnswers ( & mysql_res , Question - > QstCod ,
false ) ; // Don't shuffle
2021-10-25 22:19:03 +02:00
/*
row [ 0 ] AnsInd
row [ 1 ] Answer
row [ 2 ] Feedback
row [ 3 ] MedCod
row [ 4 ] Correct
*/
for ( NumOpt = 0 ;
NumOpt < Question - > Answer . NumOptions ;
NumOpt + + )
{
row = mysql_fetch_row ( mysql_res ) ;
switch ( Question - > Answer . Type )
{
case Qst_ANS_INT :
Qst_CheckIfNumberOfAnswersIsOne ( Question ) ;
Question - > Answer . Integer = Qst_GetIntAnsFromStr ( row [ 1 ] ) ;
break ;
case Qst_ANS_FLOAT :
if ( Question - > Answer . NumOptions ! = 2 )
Err_WrongAnswerExit ( ) ;
Question - > Answer . FloatingPoint [ NumOpt ] = Str_GetDoubleFromStr ( row [ 1 ] ) ;
break ;
case Qst_ANS_TRUE_FALSE :
Qst_CheckIfNumberOfAnswersIsOne ( Question ) ;
Question - > Answer . TF = row [ 1 ] [ 0 ] ;
break ;
case Qst_ANS_UNIQUE_CHOICE :
case Qst_ANS_MULTIPLE_CHOICE :
case Qst_ANS_TEXT :
/* Check number of options */
if ( Question - > Answer . NumOptions > Qst_MAX_OPTIONS_PER_QUESTION )
Err_WrongAnswerExit ( ) ;
/* Allocate space for text and feedback */
if ( ! Qst_AllocateTextChoiceAnswer ( Question , NumOpt ) )
/* Abort on error */
Ale_ShowAlertsAndExit ( ) ;
/* Get text (row[1]) and feedback (row[2])*/
Question - > Answer . Options [ NumOpt ] . Text [ 0 ] = ' \0 ' ;
if ( row [ 1 ] )
if ( row [ 1 ] [ 0 ] )
Str_Copy ( Question - > Answer . Options [ NumOpt ] . Text , row [ 1 ] ,
Qst_MAX_BYTES_ANSWER_OR_FEEDBACK ) ;
Question - > Answer . Options [ NumOpt ] . Feedback [ 0 ] = ' \0 ' ;
if ( row [ 2 ] )
if ( row [ 2 ] [ 0 ] )
Str_Copy ( Question - > Answer . Options [ NumOpt ] . Feedback , row [ 2 ] ,
Qst_MAX_BYTES_ANSWER_OR_FEEDBACK ) ;
/* Get media (row[3]) */
Question - > Answer . Options [ NumOpt ] . Media . MedCod = Str_ConvertStrCodToLongCod ( row [ 3 ] ) ;
Med_GetMediaDataByCod ( & Question - > Answer . Options [ NumOpt ] . Media ) ;
/* Get if this option is correct (row[4]) */
Question - > Answer . Options [ NumOpt ] . Correct = ( row [ 4 ] [ 0 ] = = ' Y ' ) ;
break ;
default :
break ;
}
}
}
/* Free structure that stores the query result */
DB_FreeMySQLResult ( & mysql_res ) ;
return QuestionExists ;
}
/*****************************************************************************/
/******* Get media code associated with a test question from database ********/
/*****************************************************************************/
// NumOpt < 0 ==> media associated to stem
// NumOpt >= 0 ==> media associated to answer
long Qst_GetMedCodFromDB ( long CrsCod , long QstCod , int NumOpt )
{
/***** Trivial check: question code should be > 0 *****/
if ( QstCod < = 0 )
return - 1L ;
/***** Query depending on NumOpt *****/
if ( NumOpt < 0 )
// Get media associated to stem
2021-10-29 00:32:19 +02:00
return Qst_DB_GetQstMedCod ( CrsCod , QstCod ) ;
2021-10-25 22:19:03 +02:00
else
// Get media associated to answer
2021-10-29 19:48:36 +02:00
return Qst_DB_GetMedCodFromAnsOfQst ( QstCod , ( unsigned ) NumOpt ) ;
2021-10-25 22:19:03 +02:00
}
/*****************************************************************************/
/***** Get possible media associated with a test question from database ******/
/*****************************************************************************/
// NumOpt < 0 ==> media associated to stem
// NumOpt >= 0 ==> media associated to an answer option
void Qst_GetMediaFromDB ( long CrsCod , long QstCod , int NumOpt ,
struct Med_Media * Media )
{
/***** Get media *****/
Media - > MedCod = Qst_GetMedCodFromDB ( CrsCod , QstCod , NumOpt ) ;
Med_GetMediaDataByCod ( Media ) ;
}
/*****************************************************************************/
/***************************** Receive a question ****************************/
/*****************************************************************************/
void Qst_ReceiveQst ( void )
{
struct Qst_Questions Questions ;
/***** Create test *****/
Qst_Constructor ( & Questions ) ;
/***** Get parameters of the question from form *****/
Qst_GetQstFromForm ( & Questions . Question ) ;
/***** Make sure that tags, text and answer are not empty *****/
if ( Qst_CheckIfQstFormatIsCorrectAndCountNumOptions ( & Questions . Question ) )
{
/***** Move images to definitive directories *****/
Qst_MoveMediaToDefinitiveDirectories ( & Questions . Question ) ;
/***** Insert or update question, tags and answer in the database *****/
Qst_InsertOrUpdateQstTagsAnsIntoDB ( & Questions . Question ) ;
/***** Show the question just inserted in the database *****/
snprintf ( Questions . AnswerTypes . List , sizeof ( Questions . AnswerTypes . List ) , " %u " ,
( unsigned ) Questions . Question . Answer . Type ) ;
Qst_ListOneQstToEdit ( & Questions ) ;
}
else // Question is wrong
{
/***** Whether images has been received or not, reset images *****/
Qst_ResetMediaOfQuestion ( & Questions . Question ) ;
/***** Put form to edit question again *****/
Qst_PutFormEditOneQst ( & Questions . Question ) ;
}
/***** Destroy test *****/
Qst_Destructor ( & Questions ) ;
}
/*****************************************************************************/
/**************** Get parameters of a test question from form ****************/
/*****************************************************************************/
void Qst_GetQstFromForm ( struct Qst_Question * Question )
{
unsigned NumTag ;
unsigned NumTagRead ;
unsigned NumOpt ;
char UnsignedStr [ Cns_MAX_DECIMAL_DIGITS_UINT + 1 ] ;
char TagStr [ 6 + Cns_MAX_DECIMAL_DIGITS_UINT + 1 ] ;
char AnsStr [ 6 + Cns_MAX_DECIMAL_DIGITS_UINT + 1 ] ;
char FbStr [ 5 + Cns_MAX_DECIMAL_DIGITS_UINT + 1 ] ;
char StrMultiAns [ Qst_MAX_BYTES_ANSWERS_ONE_QST + 1 ] ;
char TF [ 1 + 1 ] ; // (T)rue or (F)alse
const char * Ptr ;
unsigned NumCorrectAns ;
/***** Get question code *****/
Question - > QstCod = Qst_GetParamQstCod ( ) ;
/***** Get answer type *****/
Question - > Answer . Type = ( Qst_AnswerType_t )
Par_GetParToUnsignedLong ( " AnswerType " ,
0 ,
Qst_NUM_ANS_TYPES - 1 ,
( unsigned long ) Qst_ANS_UNKNOWN ) ;
if ( Question - > Answer . Type = = Qst_ANS_UNKNOWN )
Err_WrongAnswerExit ( ) ;
/***** Get question tags *****/
for ( NumTag = 0 ;
NumTag < Tag_MAX_TAGS_PER_QUESTION ;
NumTag + + )
{
snprintf ( TagStr , sizeof ( TagStr ) , " TagTxt%u " , NumTag ) ;
Par_GetParToText ( TagStr , Question - > Tags . Txt [ NumTag ] , Tag_MAX_BYTES_TAG ) ;
if ( Question - > Tags . Txt [ NumTag ] [ 0 ] )
{
Str_ChangeFormat ( Str_FROM_FORM , Str_TO_TEXT ,
Question - > Tags . Txt [ NumTag ] , Tag_MAX_BYTES_TAG , true ) ;
/* Check if not repeated */
for ( NumTagRead = 0 ;
NumTagRead < NumTag ;
NumTagRead + + )
if ( ! strcmp ( Question - > Tags . Txt [ NumTagRead ] , Question - > Tags . Txt [ NumTag ] ) )
{
Question - > Tags . Txt [ NumTag ] [ 0 ] = ' \0 ' ;
break ;
}
}
}
/***** Get question stem *****/
Par_GetParToHTML ( " Stem " , Question - > Stem , Cns_MAX_BYTES_TEXT ) ;
/***** Get question feedback *****/
Par_GetParToHTML ( " Feedback " , Question - > Feedback , Cns_MAX_BYTES_TEXT ) ;
/***** Get media associated to the stem (action, file and title) *****/
Question - > Media . Width = Qst_IMAGE_SAVED_MAX_WIDTH ;
Question - > Media . Height = Qst_IMAGE_SAVED_MAX_HEIGHT ;
Question - > Media . Quality = Qst_IMAGE_SAVED_QUALITY ;
Med_GetMediaFromForm ( Gbl . Hierarchy . Crs . CrsCod , Question - > QstCod ,
- 1 , // < 0 ==> the image associated to the stem
& Question - > Media ,
Qst_GetMediaFromDB ,
NULL ) ;
Ale_ShowAlerts ( NULL ) ;
/***** Get answers *****/
Question - > Answer . Shuffle = false ;
switch ( Question - > Answer . Type )
{
case Qst_ANS_INT :
if ( ! Qst_AllocateTextChoiceAnswer ( Question , 0 ) )
/* Abort on error */
Ale_ShowAlertsAndExit ( ) ;
Par_GetParToText ( " AnsInt " , Question - > Answer . Options [ 0 ] . Text ,
Cns_MAX_DECIMAL_DIGITS_LONG ) ;
break ;
case Qst_ANS_FLOAT :
if ( ! Qst_AllocateTextChoiceAnswer ( Question , 0 ) )
/* Abort on error */
Ale_ShowAlertsAndExit ( ) ;
Par_GetParToText ( " AnsFloatMin " , Question - > Answer . Options [ 0 ] . Text ,
Qst_MAX_BYTES_FLOAT_ANSWER ) ;
if ( ! Qst_AllocateTextChoiceAnswer ( Question , 1 ) )
/* Abort on error */
Ale_ShowAlertsAndExit ( ) ;
Par_GetParToText ( " AnsFloatMax " , Question - > Answer . Options [ 1 ] . Text ,
Qst_MAX_BYTES_FLOAT_ANSWER ) ;
break ;
case Qst_ANS_TRUE_FALSE :
Par_GetParToText ( " AnsTF " , TF , 1 ) ;
Question - > Answer . TF = TF [ 0 ] ;
break ;
case Qst_ANS_UNIQUE_CHOICE :
case Qst_ANS_MULTIPLE_CHOICE :
/* Get shuffle */
Question - > Answer . Shuffle = Par_GetParToBool ( " Shuffle " ) ;
/* falls through */
/* no break */
case Qst_ANS_TEXT :
/* Get the texts of the answers */
for ( NumOpt = 0 ;
NumOpt < Qst_MAX_OPTIONS_PER_QUESTION ;
NumOpt + + )
{
if ( ! Qst_AllocateTextChoiceAnswer ( Question , NumOpt ) )
/* Abort on error */
Ale_ShowAlertsAndExit ( ) ;
/* Get answer */
snprintf ( AnsStr , sizeof ( AnsStr ) , " AnsStr%u " , NumOpt ) ;
Par_GetParToHTML ( AnsStr , Question - > Answer . Options [ NumOpt ] . Text ,
Qst_MAX_BYTES_ANSWER_OR_FEEDBACK ) ;
if ( Question - > Answer . Type = = Qst_ANS_TEXT )
/* In order to compare student answer to stored answer,
the text answers are stored avoiding two or more consecurive spaces */
Str_ReplaceSeveralSpacesForOne ( Question - > Answer . Options [ NumOpt ] . Text ) ;
/* Get feedback */
snprintf ( FbStr , sizeof ( FbStr ) , " FbStr%u " , NumOpt ) ;
Par_GetParToHTML ( FbStr , Question - > Answer . Options [ NumOpt ] . Feedback ,
Qst_MAX_BYTES_ANSWER_OR_FEEDBACK ) ;
/* Get media associated to the answer (action, file and title) */
if ( Question - > Answer . Type = = Qst_ANS_UNIQUE_CHOICE | |
Question - > Answer . Type = = Qst_ANS_MULTIPLE_CHOICE )
{
Question - > Answer . Options [ NumOpt ] . Media . Width = Qst_IMAGE_SAVED_MAX_WIDTH ;
Question - > Answer . Options [ NumOpt ] . Media . Height = Qst_IMAGE_SAVED_MAX_HEIGHT ;
Question - > Answer . Options [ NumOpt ] . Media . Quality = Qst_IMAGE_SAVED_QUALITY ;
Med_GetMediaFromForm ( Gbl . Hierarchy . Crs . CrsCod , Question - > QstCod ,
( int ) NumOpt , // >= 0 ==> the image associated to an answer
& Question - > Answer . Options [ NumOpt ] . Media ,
Qst_GetMediaFromDB ,
NULL ) ;
Ale_ShowAlerts ( NULL ) ;
}
}
/* Get the numbers of correct answers */
if ( Question - > Answer . Type = = Qst_ANS_UNIQUE_CHOICE )
{
NumCorrectAns = ( unsigned ) Par_GetParToUnsignedLong ( " AnsUni " ,
0 ,
Qst_MAX_OPTIONS_PER_QUESTION - 1 ,
0 ) ;
Question - > Answer . Options [ NumCorrectAns ] . Correct = true ;
}
else if ( Question - > Answer . Type = = Qst_ANS_MULTIPLE_CHOICE )
{
Par_GetParMultiToText ( " AnsMulti " , StrMultiAns , Qst_MAX_BYTES_ANSWERS_ONE_QST ) ;
Ptr = StrMultiAns ;
while ( * Ptr )
{
Par_GetNextStrUntilSeparParamMult ( & Ptr , UnsignedStr , Cns_MAX_DECIMAL_DIGITS_UINT ) ;
if ( sscanf ( UnsignedStr , " %u " , & NumCorrectAns ) ! = 1 )
Err_WrongAnswerExit ( ) ;
if ( NumCorrectAns > = Qst_MAX_OPTIONS_PER_QUESTION )
Err_WrongAnswerExit ( ) ;
Question - > Answer . Options [ NumCorrectAns ] . Correct = true ;
}
}
else // Tst_ANS_TEXT
for ( NumOpt = 0 ;
NumOpt < Qst_MAX_OPTIONS_PER_QUESTION ;
NumOpt + + )
if ( Question - > Answer . Options [ NumOpt ] . Text [ 0 ] )
Question - > Answer . Options [ NumOpt ] . Correct = true ; // All the answers are correct
break ;
default :
break ;
}
/***** Adjust variables related to this test question *****/
for ( NumTag = 0 , Question - > Tags . Num = 0 ;
NumTag < Tag_MAX_TAGS_PER_QUESTION ;
NumTag + + )
if ( Question - > Tags . Txt [ NumTag ] [ 0 ] )
Question - > Tags . Num + + ;
}
/*****************************************************************************/
/*********************** Check if a question is correct **********************/
/*****************************************************************************/
// Returns false if question format is wrong
// Counts Question->Answer.NumOptions
// Computes Question->Answer.Integer and Question->Answer.FloatingPoint[0..1]
bool Qst_CheckIfQstFormatIsCorrectAndCountNumOptions ( struct Qst_Question * Question )
{
extern const char * Txt_You_must_type_at_least_one_tag_for_the_question ;
extern const char * Txt_You_must_type_the_stem_of_the_question ;
extern const char * Txt_You_must_select_a_T_F_answer ;
extern const char * Txt_You_can_not_leave_empty_intermediate_answers ;
extern const char * Txt_You_must_type_at_least_the_first_two_answers ;
extern const char * Txt_You_must_mark_an_answer_as_correct ;
extern const char * Txt_You_must_type_at_least_the_first_answer ;
extern const char * Txt_You_must_enter_an_integer_value_as_the_correct_answer ;
extern const char * Txt_You_must_enter_the_range_of_floating_point_values_allowed_as_answer ;
extern const char * Txt_The_lower_limit_of_correct_answers_must_be_less_than_or_equal_to_the_upper_limit ;
unsigned NumOpt ;
unsigned NumLastOpt ;
bool ThereIsEndOfAnswers ;
unsigned i ;
/***** This function also counts the number of options. Initialize this number to 0. *****/
Question - > Answer . NumOptions = 0 ;
/***** A question must have at least one tag *****/
if ( ! Question - > Tags . Num ) // There are no tags with text
{
Ale_ShowAlert ( Ale_WARNING , Txt_You_must_type_at_least_one_tag_for_the_question ) ;
return false ;
}
/***** A question must have a stem *****/
if ( ! Question - > Stem [ 0 ] )
{
Ale_ShowAlert ( Ale_WARNING , Txt_You_must_type_the_stem_of_the_question ) ;
return false ;
}
/***** Check answer *****/
switch ( Question - > Answer . Type )
{
case Qst_ANS_INT :
/* First option should be filled */
if ( ! Question - > Answer . Options [ 0 ] . Text )
{
Ale_ShowAlert ( Ale_WARNING , Txt_You_must_enter_an_integer_value_as_the_correct_answer ) ;
return false ;
}
if ( ! Question - > Answer . Options [ 0 ] . Text [ 0 ] )
{
Ale_ShowAlert ( Ale_WARNING , Txt_You_must_enter_an_integer_value_as_the_correct_answer ) ;
return false ;
}
Question - > Answer . Integer = Qst_GetIntAnsFromStr ( Question - > Answer . Options [ 0 ] . Text ) ;
Question - > Answer . NumOptions = 1 ;
break ;
case Qst_ANS_FLOAT :
/* First two options should be filled */
if ( ! Question - > Answer . Options [ 0 ] . Text | |
! Question - > Answer . Options [ 1 ] . Text )
{
Ale_ShowAlert ( Ale_WARNING , Txt_You_must_enter_the_range_of_floating_point_values_allowed_as_answer ) ;
return false ;
}
if ( ! Question - > Answer . Options [ 0 ] . Text [ 0 ] | |
! Question - > Answer . Options [ 1 ] . Text [ 0 ] )
{
Ale_ShowAlert ( Ale_WARNING , Txt_You_must_enter_the_range_of_floating_point_values_allowed_as_answer ) ;
return false ;
}
/* Lower limit should be <= upper limit */
for ( i = 0 ;
i < 2 ;
i + + )
Question - > Answer . FloatingPoint [ i ] = Str_GetDoubleFromStr ( Question - > Answer . Options [ i ] . Text ) ;
if ( Question - > Answer . FloatingPoint [ 0 ] >
Question - > Answer . FloatingPoint [ 1 ] )
{
Ale_ShowAlert ( Ale_WARNING , Txt_The_lower_limit_of_correct_answers_must_be_less_than_or_equal_to_the_upper_limit ) ;
return false ;
}
Question - > Answer . NumOptions = 2 ;
break ;
case Qst_ANS_TRUE_FALSE :
/* Answer should be 'T' or 'F' */
if ( Question - > Answer . TF ! = ' T ' & &
Question - > Answer . TF ! = ' F ' )
{
Ale_ShowAlert ( Ale_WARNING , Txt_You_must_select_a_T_F_answer ) ;
return false ;
}
Question - > Answer . NumOptions = 1 ;
break ;
case Qst_ANS_UNIQUE_CHOICE :
case Qst_ANS_MULTIPLE_CHOICE :
/* No option should be empty before a non-empty option */
for ( NumOpt = 0 , NumLastOpt = 0 , ThereIsEndOfAnswers = false ;
NumOpt < Qst_MAX_OPTIONS_PER_QUESTION ;
NumOpt + + )
if ( Question - > Answer . Options [ NumOpt ] . Text )
{
if ( Question - > Answer . Options [ NumOpt ] . Text [ 0 ] | | // Text
Question - > Answer . Options [ NumOpt ] . Media . Type ! = Med_TYPE_NONE ) // or media
{
if ( ThereIsEndOfAnswers )
{
Ale_ShowAlert ( Ale_WARNING , Txt_You_can_not_leave_empty_intermediate_answers ) ;
return false ;
}
NumLastOpt = NumOpt ;
Question - > Answer . NumOptions + + ;
}
else
ThereIsEndOfAnswers = true ;
}
else
ThereIsEndOfAnswers = true ;
/* The two first options must be filled */
if ( NumLastOpt < 1 )
{
Ale_ShowAlert ( Ale_WARNING , Txt_You_must_type_at_least_the_first_two_answers ) ;
return false ;
}
/* Its mandatory to mark at least one option as correct */
for ( NumOpt = 0 ;
NumOpt < = NumLastOpt ;
NumOpt + + )
if ( Question - > Answer . Options [ NumOpt ] . Correct )
break ;
if ( NumOpt > NumLastOpt )
{
Ale_ShowAlert ( Ale_WARNING , Txt_You_must_mark_an_answer_as_correct ) ;
return false ;
}
break ;
case Qst_ANS_TEXT :
/* First option should be filled */
if ( ! Question - > Answer . Options [ 0 ] . Text ) // If the first answer is empty
{
Ale_ShowAlert ( Ale_WARNING , Txt_You_must_type_at_least_the_first_answer ) ;
return false ;
}
if ( ! Question - > Answer . Options [ 0 ] . Text [ 0 ] ) // If the first answer is empty
{
Ale_ShowAlert ( Ale_WARNING , Txt_You_must_type_at_least_the_first_answer ) ;
return false ;
}
/* No option should be empty before a non-empty option */
for ( NumOpt = 0 , ThereIsEndOfAnswers = false ;
NumOpt < Qst_MAX_OPTIONS_PER_QUESTION ;
NumOpt + + )
if ( Question - > Answer . Options [ NumOpt ] . Text )
{
if ( Question - > Answer . Options [ NumOpt ] . Text [ 0 ] )
{
if ( ThereIsEndOfAnswers )
{
Ale_ShowAlert ( Ale_WARNING , Txt_You_can_not_leave_empty_intermediate_answers ) ;
return false ;
}
Question - > Answer . NumOptions + + ;
}
else
ThereIsEndOfAnswers = true ;
}
else
ThereIsEndOfAnswers = true ;
break ;
default :
break ;
}
return true ; // Question format without errors
}
/*****************************************************************************/
/*********** Check if a test question already exists in database *************/
/*****************************************************************************/
bool Qst_CheckIfQuestionExistsInDB ( struct Qst_Question * Question )
{
extern const char * Qst_DB_StrAnswerTypes [ Qst_NUM_ANS_TYPES ] ;
MYSQL_RES * mysql_res_qst ;
MYSQL_RES * mysql_res_ans ;
MYSQL_ROW row ;
bool IdenticalQuestionFound = false ;
bool IdenticalAnswers ;
unsigned NumQst ;
unsigned NumQstsWithThisStem ;
unsigned NumOpt ;
unsigned NumOptsExistingQstInDB ;
unsigned i ;
2021-10-30 20:50:29 +02:00
/***** Check if there are existing questions in database
with the same stem that the one of this question * * * * */
if ( ( NumQstsWithThisStem = Qst_DB_GetQstCodFromTypeAnsStem ( & mysql_res_qst , Question ) ) )
2021-10-25 22:19:03 +02:00
{
/***** Check if the answer exists in any of the questions with the same stem *****/
/* For each question with the same stem */
for ( NumQst = 0 ;
! IdenticalQuestionFound & & NumQst < NumQstsWithThisStem ;
NumQst + + )
{
/* Get question code */
if ( ( Question - > QstCod = DB_GetNextCode ( mysql_res_qst ) ) < 0 )
Err_WrongQuestionExit ( ) ;
/* Get answers from this question */
2021-10-29 00:32:19 +02:00
NumOptsExistingQstInDB = Qst_DB_GetTextOfAnswers ( & mysql_res_ans , Question - > QstCod ) ;
2021-10-25 22:19:03 +02:00
switch ( Question - > Answer . Type )
{
case Qst_ANS_INT :
row = mysql_fetch_row ( mysql_res_ans ) ;
IdenticalQuestionFound = ( Qst_GetIntAnsFromStr ( row [ 0 ] ) = = Question - > Answer . Integer ) ;
break ;
case Qst_ANS_FLOAT :
for ( IdenticalAnswers = true , i = 0 ;
IdenticalAnswers & & i < 2 ;
i + + )
{
row = mysql_fetch_row ( mysql_res_ans ) ;
IdenticalAnswers = ( Str_GetDoubleFromStr ( row [ 0 ] ) = = Question - > Answer . FloatingPoint [ i ] ) ;
}
IdenticalQuestionFound = IdenticalAnswers ;
break ;
case Qst_ANS_TRUE_FALSE :
row = mysql_fetch_row ( mysql_res_ans ) ;
IdenticalQuestionFound = ( Str_ConvertToUpperLetter ( row [ 0 ] [ 0 ] ) = = Question - > Answer . TF ) ;
break ;
case Qst_ANS_UNIQUE_CHOICE :
case Qst_ANS_MULTIPLE_CHOICE :
case Qst_ANS_TEXT :
if ( NumOptsExistingQstInDB = = Question - > Answer . NumOptions )
{
for ( IdenticalAnswers = true , NumOpt = 0 ;
IdenticalAnswers & & NumOpt < NumOptsExistingQstInDB ;
NumOpt + + )
{
row = mysql_fetch_row ( mysql_res_ans ) ;
if ( strcasecmp ( row [ 0 ] , Question - > Answer . Options [ NumOpt ] . Text ) )
IdenticalAnswers = false ;
}
}
else // Different number of answers (options)
IdenticalAnswers = false ;
IdenticalQuestionFound = IdenticalAnswers ;
break ;
default :
break ;
}
/* Free structure that stores the query result for answers */
DB_FreeMySQLResult ( & mysql_res_ans ) ;
}
}
else // Stem does not exist
IdenticalQuestionFound = false ;
/* Free structure that stores the query result for questions */
DB_FreeMySQLResult ( & mysql_res_qst ) ;
return IdenticalQuestionFound ;
}
/*****************************************************************************/
/* Move images associates to a test question to their definitive directories */
/*****************************************************************************/
void Qst_MoveMediaToDefinitiveDirectories ( struct Qst_Question * Question )
{
unsigned NumOpt ;
long CurrentMedCodInDB ;
/***** Media associated to question stem *****/
CurrentMedCodInDB = Qst_GetMedCodFromDB ( Gbl . Hierarchy . Crs . CrsCod , Question - > QstCod ,
2021-10-29 00:32:19 +02:00
- 1 ) ; // Get current media code associated to stem
2021-10-25 22:19:03 +02:00
Med_RemoveKeepOrStoreMedia ( CurrentMedCodInDB , & Question - > Media ) ;
/****** Move media associated to answers *****/
switch ( Question - > Answer . Type )
{
case Qst_ANS_UNIQUE_CHOICE :
case Qst_ANS_MULTIPLE_CHOICE :
for ( NumOpt = 0 ;
NumOpt < Question - > Answer . NumOptions ;
NumOpt + + )
{
CurrentMedCodInDB = Qst_GetMedCodFromDB ( Gbl . Hierarchy . Crs . CrsCod , Question - > QstCod ,
2021-10-29 00:32:19 +02:00
( int ) NumOpt ) ; // Get current media code associated to this option
2021-10-25 22:19:03 +02:00
Med_RemoveKeepOrStoreMedia ( CurrentMedCodInDB , & Question - > Answer . Options [ NumOpt ] . Media ) ;
}
break ;
default :
break ;
}
}
/*****************************************************************************/
/******************** Get a integer number from a string *********************/
/*****************************************************************************/
long Qst_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 ;
}
/*****************************************************************************/
/***************** Request the removal of selected questions *****************/
/*****************************************************************************/
void Qst_RequestRemoveSelectedQsts ( void )
{
extern const char * Txt_Do_you_really_want_to_remove_the_selected_questions ;
extern const char * Txt_Remove_questions ;
struct Qst_Questions Questions ;
/***** Create test *****/
Qst_Constructor ( & Questions ) ;
/***** Get parameters *****/
if ( Tst_GetParamsTst ( & Questions , Tst_EDIT_QUESTIONS ) ) // Get parameters from the form
{
/***** Show question and button to remove question *****/
Ale_ShowAlertAndButton ( ActRemSevTstQst , NULL , NULL ,
Qst_PutParamsEditQst , & Questions ,
Btn_REMOVE_BUTTON , Txt_Remove_questions ,
Ale_QUESTION , Txt_Do_you_really_want_to_remove_the_selected_questions ) ;
}
else
Ale_ShowAlert ( Ale_ERROR , " Wrong parameters. " ) ;
/***** Continue editing questions *****/
Qst_ListQuestionsToEdit ( ) ;
/***** Destroy test *****/
Qst_Destructor ( & Questions ) ;
}
/*****************************************************************************/
/************************** Remove several questions *************************/
/*****************************************************************************/
void Qst_RemoveSelectedQsts ( void )
{
extern const char * Txt_Questions_removed_X ;
struct Qst_Questions Questions ;
MYSQL_RES * mysql_res ;
MYSQL_ROW row ;
unsigned NumQst ;
long QstCod ;
/***** Create test *****/
Qst_Constructor ( & Questions ) ;
/***** Get parameters *****/
if ( Tst_GetParamsTst ( & Questions , Tst_EDIT_QUESTIONS ) ) // Get parameters
{
/***** Get question codes *****/
2021-10-29 00:32:19 +02:00
Questions . NumQsts = Qst_DB_GetQsts ( & mysql_res , & Questions ) ;
2021-10-25 22:19:03 +02:00
/***** Remove questions one by one *****/
for ( NumQst = 0 ;
NumQst < Questions . NumQsts ;
NumQst + + )
{
/* Get question code (row[0]) */
row = mysql_fetch_row ( mysql_res ) ;
if ( ( QstCod = Str_ConvertStrCodToLongCod ( row [ 0 ] ) ) < = 0 )
Err_WrongQuestionExit ( ) ;
/* Remove test question from database */
Qst_RemoveOneQstFromDB ( Gbl . Hierarchy . Crs . CrsCod , QstCod ) ;
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult ( & mysql_res ) ;
/***** Write message *****/
Ale_ShowAlert ( Ale_SUCCESS , Txt_Questions_removed_X , Questions . NumQsts ) ;
}
/***** Destroy test *****/
Qst_Destructor ( & Questions ) ;
}
/*****************************************************************************/
/********************* Put icon to remove one question ***********************/
/*****************************************************************************/
void Qst_PutIconToRemoveOneQst ( void * QstCod )
{
Ico_PutContextualIconToRemove ( ActReqRemOneTstQst , NULL ,
Qst_PutParamsRemoveOnlyThisQst , QstCod ) ;
}
/*****************************************************************************/
/******************** Request the removal of a question **********************/
/*****************************************************************************/
void Qst_RequestRemoveOneQst ( void )
{
extern const char * Txt_Do_you_really_want_to_remove_the_question_X ;
extern const char * Txt_Remove_question ;
bool EditingOnlyThisQst ;
struct Qst_Questions Questions ;
/***** Create test *****/
Qst_Constructor ( & Questions ) ;
/***** Get main parameters from form *****/
/* Get the question code */
Questions . Question . QstCod = Qst_GetParamQstCod ( ) ;
if ( Questions . Question . QstCod < = 0 )
Err_WrongQuestionExit ( ) ;
/* Get a parameter that indicates whether it's necessary
to continue listing the rest of questions */
EditingOnlyThisQst = Par_GetParToBool ( " OnlyThisQst " ) ;
/* Get other parameters */
if ( ! EditingOnlyThisQst )
if ( ! Tst_GetParamsTst ( & Questions , Tst_EDIT_QUESTIONS ) )
Err_ShowErrorAndExit ( " Wrong test parameters. " ) ;
/***** Show question and button to remove question *****/
if ( EditingOnlyThisQst )
Ale_ShowAlertAndButton ( ActRemOneTstQst , NULL , NULL ,
Qst_PutParamsRemoveOnlyThisQst , & Questions . Question . QstCod ,
Btn_REMOVE_BUTTON , Txt_Remove_question ,
Ale_QUESTION , Txt_Do_you_really_want_to_remove_the_question_X ,
Questions . Question . QstCod ) ;
else
Ale_ShowAlertAndButton ( ActRemOneTstQst , NULL , NULL ,
Qst_PutParamsEditQst , & Questions ,
Btn_REMOVE_BUTTON , Txt_Remove_question ,
Ale_QUESTION , Txt_Do_you_really_want_to_remove_the_question_X ,
Questions . Question . QstCod ) ;
/***** Continue editing questions *****/
if ( EditingOnlyThisQst )
Qst_ListOneQstToEdit ( & Questions ) ;
else
Qst_ListQuestionsToEdit ( ) ;
/***** Destroy test *****/
Qst_Destructor ( & Questions ) ;
}
/*****************************************************************************/
/***** Put parameters to remove question when editing only one question ******/
/*****************************************************************************/
void Qst_PutParamsRemoveOnlyThisQst ( void * QstCod )
{
if ( QstCod )
{
Qst_PutParamQstCod ( QstCod ) ;
Par_PutHiddenParamChar ( " OnlyThisQst " , ' Y ' ) ;
}
}
/*****************************************************************************/
/***************************** Remove a question *****************************/
/*****************************************************************************/
void Qst_RemoveOneQst ( void )
{
extern const char * Txt_Question_removed ;
long QstCod ;
bool EditingOnlyThisQst ;
/***** Get the question code *****/
QstCod = Qst_GetParamQstCod ( ) ;
if ( QstCod < = 0 )
Err_WrongQuestionExit ( ) ;
/***** Get a parameter that indicates whether it's necessary
to continue listing the rest of questions * * * * * */
EditingOnlyThisQst = Par_GetParToBool ( " OnlyThisQst " ) ;
/***** Remove test question from database *****/
Qst_RemoveOneQstFromDB ( Gbl . Hierarchy . Crs . CrsCod , QstCod ) ;
/***** Write message *****/
Ale_ShowAlert ( Ale_SUCCESS , Txt_Question_removed ) ;
/***** Continue editing questions *****/
if ( ! EditingOnlyThisQst )
Qst_ListQuestionsToEdit ( ) ;
}
/*****************************************************************************/
/********************** Remove a question from database **********************/
/*****************************************************************************/
void Qst_RemoveOneQstFromDB ( long CrsCod , long QstCod )
{
/***** Remove media associated to question *****/
Qst_RemoveMediaFromStemOfQst ( CrsCod , QstCod ) ;
Qst_RemoveMediaFromAllAnsOfQst ( CrsCod , QstCod ) ;
2021-10-30 13:28:48 +02:00
/***** Remove the question from all tables *****/
2021-10-25 22:19:03 +02:00
/* Remove answers and tags from this test question */
2021-10-25 22:35:24 +02:00
Qst_DB_RemAnsFromQst ( QstCod ) ;
2021-10-25 22:19:03 +02:00
Tag_DB_RemTagsFromQst ( QstCod ) ;
Tag_DB_RemoveUnusedTagsFromCrs ( CrsCod ) ;
/* Remove the question itself */
2021-10-29 00:32:19 +02:00
Qst_DB_RemoveQst ( CrsCod , QstCod ) ;
2021-10-25 22:19:03 +02:00
}
/*****************************************************************************/
/*********************** Change the shuffle of a question ********************/
/*****************************************************************************/
void Qst_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 ;
struct Qst_Questions Questions ;
bool EditingOnlyThisQst ;
bool Shuffle ;
/***** Create test *****/
Qst_Constructor ( & Questions ) ;
/***** Get the question code *****/
Questions . Question . QstCod = Qst_GetParamQstCod ( ) ;
if ( Questions . Question . QstCod < = 0 )
Err_WrongQuestionExit ( ) ;
/***** Get a parameter that indicates whether it's necessary to continue listing the rest of questions ******/
EditingOnlyThisQst = Par_GetParToBool ( " OnlyThisQst " ) ;
/***** Get a parameter that indicates whether it's possible to shuffle the answers of this question ******/
Shuffle = Par_GetParToBool ( " Shuffle " ) ;
2021-10-30 20:50:29 +02:00
/***** Update the question changing the current shuffle *****/
Qst_DB_UpdateQstShuffle ( Questions . Question . QstCod , Shuffle ) ;
2021-10-25 22:19:03 +02:00
/***** Write message *****/
Ale_ShowAlert ( Ale_SUCCESS , Shuffle ? Txt_The_answers_of_the_question_with_code_X_will_appear_shuffled :
Txt_The_answers_of_the_question_with_code_X_will_appear_without_shuffling ,
Questions . Question . QstCod ) ;
/***** Continue editing questions *****/
if ( EditingOnlyThisQst )
Qst_ListOneQstToEdit ( & Questions ) ;
else
Qst_ListQuestionsToEdit ( ) ;
/***** Destroy test *****/
Qst_Destructor ( & Questions ) ;
}
/*****************************************************************************/
/************ Get the parameter with the code of a test question *************/
/*****************************************************************************/
long Qst_GetParamQstCod ( void )
{
/***** Get code of test question *****/
return Par_GetParToLong ( " QstCod " ) ;
}
/*****************************************************************************/
/************ Put parameter with question code to edit, remove... ************/
/*****************************************************************************/
void Qst_PutParamQstCod ( void * QstCod ) // Should be a pointer to long
{
if ( QstCod )
if ( * ( ( long * ) QstCod ) > 0 ) // If question exists
Par_PutHiddenParamLong ( NULL , " QstCod " , * ( ( long * ) QstCod ) ) ;
}
/*****************************************************************************/
/******** Insert or update question, tags and answer in the database *********/
/*****************************************************************************/
void Qst_InsertOrUpdateQstTagsAnsIntoDB ( struct Qst_Question * Question )
{
/***** Insert or update question in the table of questions *****/
Qst_InsertOrUpdateQstIntoDB ( Question ) ;
if ( Question - > QstCod > 0 )
{
/***** Insert tags in the tags table *****/
Tag_InsertTagsIntoDB ( Question - > QstCod , & Question - > Tags ) ;
/***** Remove unused tags in current course *****/
Tag_DB_RemoveUnusedTagsFromCrs ( Gbl . Hierarchy . Crs . CrsCod ) ;
/***** Insert answers in the answers table *****/
Qst_InsertAnswersIntoDB ( Question ) ;
}
}
/*****************************************************************************/
/*********** Insert or update question in the table of questions *************/
/*****************************************************************************/
void Qst_InsertOrUpdateQstIntoDB ( struct Qst_Question * Question )
{
extern const char * Qst_DB_StrAnswerTypes [ Qst_NUM_ANS_TYPES ] ;
if ( Question - > QstCod < 0 ) // It's a new question
/***** Insert question in the table of questions *****/
2021-10-30 20:50:29 +02:00
Question - > QstCod = Qst_DB_CreateQst ( Question ) ;
2021-10-25 22:19:03 +02:00
else // It's an existing question
{
/***** Update existing question *****/
2021-10-30 20:50:29 +02:00
Qst_DB_UpdateQst ( Question ) ;
/***** Remove answers and tags from this test question *****/
2021-10-25 22:35:24 +02:00
Qst_DB_RemAnsFromQst ( Question - > QstCod ) ;
2021-10-25 22:19:03 +02:00
Tag_DB_RemTagsFromQst ( Question - > QstCod ) ;
}
}
/*****************************************************************************/
/******************* Insert answers in the answers table *********************/
/*****************************************************************************/
void Qst_InsertAnswersIntoDB ( struct Qst_Question * Question )
{
2021-10-30 20:50:29 +02:00
void ( * Qst_DB_CreateAnswer [ Qst_NUM_ANS_TYPES ] ) ( struct Qst_Question * Question ) =
{
[ Qst_ANS_INT ] = Qst_DB_CreateIntAnswer ,
[ Qst_ANS_FLOAT ] = Qst_DB_CreateFltAnswer ,
[ Qst_ANS_TRUE_FALSE ] = Qst_DB_CreateTF_Answer ,
[ Qst_ANS_UNIQUE_CHOICE ] = Qst_DB_CreateChoAnswer ,
[ Qst_ANS_MULTIPLE_CHOICE ] = Qst_DB_CreateChoAnswer ,
[ Qst_ANS_TEXT ] = Qst_DB_CreateChoAnswer ,
} ;
2021-10-25 22:19:03 +02:00
2021-10-30 20:50:29 +02:00
/***** Create answer *****/
Qst_DB_CreateAnswer [ Question - > Answer . Type ] ( Question ) ;
2021-10-25 22:19:03 +02:00
}
2021-10-26 17:33:20 +02:00
/*****************************************************************************/
/**** Count the number of types of answers in the list of types of answers ***/
/*****************************************************************************/
unsigned Qst_CountNumAnswerTypesInList ( const struct Qst_AnswerTypes * AnswerTypes )
{
const char * Ptr ;
unsigned NumAnsTypes = 0 ;
char UnsignedStr [ Cns_MAX_DECIMAL_DIGITS_UINT + 1 ] ;
/***** Go over the list of answer types counting the number of types of answer *****/
Ptr = AnswerTypes - > List ;
while ( * Ptr )
{
Par_GetNextStrUntilSeparParamMult ( & Ptr , UnsignedStr , Cns_MAX_DECIMAL_DIGITS_UINT ) ;
Qst_ConvertFromUnsignedStrToAnsTyp ( UnsignedStr ) ;
NumAnsTypes + + ;
}
return NumAnsTypes ;
}
/*****************************************************************************/
/**** Count the number of questions in the list of selected question codes ***/
/*****************************************************************************/
unsigned Qst_CountNumQuestionsInList ( const char * ListQuestions )
{
const char * Ptr ;
unsigned NumQuestions = 0 ;
char LongStr [ Cns_MAX_DECIMAL_DIGITS_LONG + 1 ] ;
long QstCod ;
/***** Go over list of questions counting the number of questions *****/
Ptr = ListQuestions ;
while ( * Ptr )
{
Par_GetNextStrUntilSeparParamMult ( & Ptr , LongStr , Cns_MAX_DECIMAL_DIGITS_LONG ) ;
if ( sscanf ( LongStr , " %ld " , & QstCod ) ! = 1 )
Err_WrongQuestionExit ( ) ;
NumQuestions + + ;
}
return NumQuestions ;
}
2021-10-25 22:19:03 +02:00
/*****************************************************************************/
/********************* Remove all questions in a course **********************/
/*****************************************************************************/
void Qst_RemoveCrsQsts ( long CrsCod )
{
2021-10-29 19:48:36 +02:00
/***** Remove associations between questions and tags in the course *****/
Tag_DB_RemTagsInQstsInCrs ( CrsCod ) ;
2021-10-25 22:19:03 +02:00
/***** Remove test tags in the course *****/
2021-10-29 19:48:36 +02:00
Tag_DB_RemTagsInCrs ( CrsCod ) ;
2021-10-25 22:19:03 +02:00
/***** Remove media associated to test questions in the course *****/
Qst_RemoveAllMedFilesFromStemOfAllQstsInCrs ( CrsCod ) ;
Qst_RemoveAllMedFilesFromAnsOfAllQstsInCrs ( CrsCod ) ;
/***** Remove test answers in the course *****/
2021-10-29 19:48:36 +02:00
Qst_DB_RemAnssFromQstsInCrs ( CrsCod ) ;
2021-10-25 22:19:03 +02:00
/***** Remove test questions in the course *****/
2021-10-29 19:48:36 +02:00
Qst_DB_RemoveQstsInCrs ( CrsCod ) ;
2021-10-25 22:19:03 +02:00
}
/*****************************************************************************/
/************ Remove media associated to stem of a test question *************/
/*****************************************************************************/
void Qst_RemoveMediaFromStemOfQst ( long CrsCod , long QstCod )
{
MYSQL_RES * mysql_res ;
unsigned NumMedia ;
/***** Get media code associated to stem of test question from database *****/
2021-10-29 19:48:36 +02:00
NumMedia = Qst_DB_GetMedCodFromStemOfQst ( & mysql_res , CrsCod , QstCod ) ;
2021-10-25 22:19:03 +02:00
/***** Go over result removing media *****/
Med_RemoveMediaFromAllRows ( NumMedia , mysql_res ) ;
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult ( & mysql_res ) ;
}
/*****************************************************************************/
/******* Remove all media associated to all answers of a test question *******/
/*****************************************************************************/
void Qst_RemoveMediaFromAllAnsOfQst ( long CrsCod , long QstCod )
{
MYSQL_RES * mysql_res ;
unsigned NumMedia ;
/***** Get media codes associated to answers of test questions from database *****/
2021-10-29 19:48:36 +02:00
NumMedia = Qst_DB_GetMedCodsFromAnssOfQst ( & mysql_res , CrsCod , QstCod ) ;
2021-10-25 22:19:03 +02:00
/***** Go over result removing media *****/
Med_RemoveMediaFromAllRows ( NumMedia , mysql_res ) ;
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult ( & mysql_res ) ;
}
/*****************************************************************************/
/** Remove all media associated to stems of all test questions in a course ***/
/*****************************************************************************/
void Qst_RemoveAllMedFilesFromStemOfAllQstsInCrs ( long CrsCod )
{
MYSQL_RES * mysql_res ;
unsigned NumMedia ;
/***** Get media codes associated to stems of test questions from database *****/
2021-10-29 19:48:36 +02:00
NumMedia = Qst_DB_GetMedCodsFromStemsOfQstsInCrs ( & mysql_res , CrsCod ) ;
2021-10-25 22:19:03 +02:00
/***** Go over result removing media files *****/
Med_RemoveMediaFromAllRows ( NumMedia , mysql_res ) ;
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult ( & mysql_res ) ;
}
/*****************************************************************************/
/* Remove media associated to all answers of all test questions in a course **/
/*****************************************************************************/
void Qst_RemoveAllMedFilesFromAnsOfAllQstsInCrs ( long CrsCod )
{
MYSQL_RES * mysql_res ;
unsigned NumMedia ;
/***** Get names of media files associated to answers of test questions from database *****/
2021-10-29 19:48:36 +02:00
NumMedia = Qst_DB_GetMedCodsFromAnssOfQstsInCrs ( & mysql_res , CrsCod ) ;
2021-10-25 22:19:03 +02:00
/***** Go over result removing media files *****/
Med_RemoveMediaFromAllRows ( NumMedia , mysql_res ) ;
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult ( & mysql_res ) ;
}
/*****************************************************************************/
/*********************** Get number of test questions ************************/
/*****************************************************************************/
// Returns the number of test questions
// in this location (all the platform, current degree or current course)
2021-10-27 21:40:19 +02:00
unsigned Qst_GetNumQuestions ( HieLvl_Level_t Scope , Qst_AnswerType_t AnsType ,
struct Qst_Stats * Stats )
2021-10-25 22:19:03 +02:00
{
extern const char * Qst_DB_StrAnswerTypes [ Qst_NUM_ANS_TYPES ] ;
MYSQL_RES * mysql_res ;
MYSQL_ROW row ;
2021-10-27 21:40:19 +02:00
/***** Reset default stats *****/
Stats - > NumQsts = 0 ;
Stats - > NumHits = 0L ;
Stats - > TotalScore = 0.0 ;
2021-10-25 22:19:03 +02:00
2021-10-27 21:40:19 +02:00
/***** Get number of questions from database *****/
if ( Qst_DB_GetNumQsts ( & mysql_res , Scope , AnsType ) )
2021-10-25 22:19:03 +02:00
{
2021-10-27 21:40:19 +02:00
/***** Get number of questions *****/
row = mysql_fetch_row ( mysql_res ) ;
if ( sscanf ( row [ 0 ] , " %u " , & ( Stats - > NumQsts ) ) ! = 1 )
Err_ShowErrorAndExit ( " Error when getting number of test questions. " ) ;
2021-10-25 22:19:03 +02:00
2021-10-27 21:40:19 +02:00
if ( Stats - > NumQsts )
{
if ( sscanf ( row [ 1 ] , " %lu " , & ( Stats - > NumHits ) ) ! = 1 )
Err_ShowErrorAndExit ( " Error when getting total number of hits in test questions. " ) ;
Str_SetDecimalPointToUS ( ) ; // To get the decimal point as a dot
if ( sscanf ( row [ 2 ] , " %lf " , & ( Stats - > TotalScore ) ) ! = 1 )
Err_ShowErrorAndExit ( " Error when getting total score in test questions. " ) ;
Str_SetDecimalPointToLocal ( ) ; // Return to local system
}
2021-10-25 22:19:03 +02:00
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult ( & mysql_res ) ;
return Stats - > NumQsts ;
}
2021-10-25 22:35:24 +02:00
/*****************************************************************************/
/*********************** Get stats about test questions **********************/
/*****************************************************************************/
void Qst_GetTestStats ( Qst_AnswerType_t AnsType , struct Qst_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 ( Qst_GetNumQuestions ( Gbl . Scope . Current , AnsType , Stats ) )
{
2021-10-27 21:40:19 +02:00
if ( ( Stats - > NumCoursesWithQuestions = Qst_DB_GetNumCrssWithQsts ( Gbl . Scope . Current , AnsType ) ) ! = 0 )
2021-10-25 22:35:24 +02:00
{
2021-10-27 21:40:19 +02:00
Stats - > NumCoursesWithPluggableQuestions = Qst_DB_GetNumCrssWithPluggableQsts ( Gbl . Scope . Current , AnsType ) ;
2021-10-25 22:35:24 +02:00
Stats - > AvgQstsPerCourse = ( double ) Stats - > NumQsts / ( double ) Stats - > NumCoursesWithQuestions ;
Stats - > AvgHitsPerCourse = ( double ) Stats - > NumHits / ( double ) Stats - > NumCoursesWithQuestions ;
}
Stats - > AvgHitsPerQuestion = ( double ) Stats - > NumHits / ( double ) Stats - > NumQsts ;
if ( Stats - > NumHits )
Stats - > AvgScorePerQuestion = Stats - > TotalScore / ( double ) Stats - > NumHits ;
}
}