Version19.226

This commit is contained in:
acanas 2020-05-13 00:28:32 +02:00
parent d9c02b0523
commit 1a0d5b4471
16 changed files with 775 additions and 290 deletions

View File

@ -551,13 +551,6 @@ CREATE TABLE IF NOT EXISTS exa_prints (
UNIQUE INDEX(PrnCod),
UNIQUE INDEX(EvtCod,UsrCod));
--
-- Table exa_questions: stores the questions in the set of questions for exams
--
CREATE TABLE IF NOT EXISTS exa_questions (
SetCod INT NOT NULL,
QstCod INT NOT NULL,
UNIQUE INDEX(SetCod,QstCod));
--
-- Table exa_results: stores exam results
--
CREATE TABLE IF NOT EXISTS exa_results (
@ -570,6 +563,32 @@ CREATE TABLE IF NOT EXISTS exa_results (
Score DOUBLE PRECISION NOT NULL DEFAULT 0,
UNIQUE INDEX(EvtCod,UsrCod));
--
-- Table exa_set_answers: stores the answers of questions in exam sets
--
CREATE TABLE IF NOT EXISTS exa_set_answers (
QstCod INT NOT NULL,
AnsInd TINYINT NOT NULL,
Answer TEXT NOT NULL,
Feedback TEXT NOT NULL,
MedCod INT NOT NULL DEFAULT -1,
Correct ENUM('N','Y') NOT NULL,
UNIQUE INDEX(QstCod,AnsInd),
INDEX(MedCod));
--
-- Table exa_set_questions: stores the questions in exam sets
--
CREATE TABLE IF NOT EXISTS exa_set_questions (
QstCod INT NOT NULL AUTO_INCREMENT,
SetCod INT NOT NULL,
AnsType ENUM ('int','float','true_false','unique_choice','multiple_choice','text') NOT NULL,
Shuffle ENUM('N','Y') NOT NULL,
Stem TEXT NOT NULL,
Feedback TEXT NOT NULL,
MedCod INT NOT NULL DEFAULT -1,
UNIQUE INDEX(QstCod),
UNIQUE INDEX(SetCod,QstCod),
INDEX(MedCod));
--
-- Table exa_sets: stores the question sets in the exams
--
CREATE TABLE IF NOT EXISTS exa_sets (

View File

@ -548,10 +548,33 @@ enscript -2 --landscape --color --file-align=2 --highlight --line-numbers -o - *
En OpenSWAD:
ps2pdf source.ps destination.pdf
*/
#define Log_PLATFORM_VERSION "SWAD 19.225.1 (2020-05-12)"
#define Log_PLATFORM_VERSION "SWAD 19.226 (2020-05-12)"
#define CSS_FILE "swad19.217.css"
#define JS_FILE "swad19.223.js"
/*
// TODO: Public link to images on exams should be cached during the current session. When session is closed ==> remove public link
Version 19.226: May 12, 2020 Questions and answer are cloned from test bank to exams. (303468 lines)
18 changes necessary in database:
CREATE TABLE IF NOT EXISTS exa_set_answers (QstCod INT NOT NULL,AnsInd TINYINT NOT NULL,Answer TEXT NOT NULL,Feedback TEXT NOT NULL,MedCod INT NOT NULL DEFAULT -1,Correct ENUM('N','Y') NOT NULL,UNIQUE INDEX(QstCod,AnsInd),INDEX(MedCod));
CREATE TABLE IF NOT EXISTS exa_set_questions (QstCod INT NOT NULL AUTO_INCREMENT,SetCod INT NOT NULL,AnsType ENUM ('int','float','true_false','unique_choice','multiple_choice','text') NOT NULL,Shuffle ENUM('N','Y') NOT NULL,Stem TEXT NOT NULL,Feedback TEXT NOT NULL,MedCod INT NOT NULL DEFAULT -1,UNIQUE INDEX(QstCod),UNIQUE INDEX(SetCod,QstCod),INDEX(MedCod));
DELETE FROM exa_answers;
DELETE FROM exa_events;
DELETE FROM exa_exams;
DELETE FROM exa_groups;
DELETE FROM exa_happening;
DELETE FROM exa_indexes;
DELETE FROM exa_participants;
DELETE FROM exa_print_questions;
DELETE FROM exa_prints;
DELETE FROM exa_questions;
DELETE FROM exa_results;
DELETE FROM exa_set_answers;
DELETE FROM exa_set_questions;
DELETE FROM exa_sets;
DELETE FROM exa_times;
DROP TABLE exa_questions;
Version 19.225.1: May 12, 2020 Code refactoring in exam prints.
Unckeck radio button in exam prints. (303057 lines)
Version 19.225: May 12, 2020 Stored unique/multiple choice questions in exam print.

View File

@ -1216,22 +1216,6 @@ mysql> DESCRIBE exa_prints;
"UNIQUE INDEX(PrnCod),"
"UNIQUE INDEX(EvtCod,UsrCod))");
/***** Table exa_questions *****/
/*
mysql> DESCRIBE exa_questions;
+--------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+-------+
| SetCod | int(11) | NO | PRI | NULL | |
| QstCod | int(11) | NO | PRI | NULL | |
+--------+---------+------+-----+---------+-------+
2 rows in set (0.00 sec)
*/
DB_CreateTable ("CREATE TABLE IF NOT EXISTS exa_questions ("
"SetCod INT NOT NULL,"
"QstCod INT NOT NULL,"
"UNIQUE INDEX(SetCod,QstCod))");
/***** Table exa_results *****/
/*
mysql> DESCRIBE exa_results;
@ -1258,6 +1242,59 @@ mysql> DESCRIBE exa_results;
"Score DOUBLE PRECISION NOT NULL DEFAULT 0,"
"UNIQUE INDEX(EvtCod,UsrCod))");
/***** Table exa_set_answers *****/
/*
mysql> DESCRIBE exa_set_answers;
+----------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+---------------+------+-----+---------+-------+
| QstCod | int(11) | NO | PRI | NULL | |
| AnsInd | tinyint(4) | NO | PRI | NULL | |
| Answer | text | NO | | NULL | |
| Feedback | text | NO | | NULL | |
| MedCod | int(11) | NO | MUL | -1 | |
| Correct | enum('N','Y') | NO | | NULL | |
+----------+---------------+------+-----+---------+-------+
6 rows in set (0.00 sec)
*/
DB_CreateTable ("CREATE TABLE IF NOT EXISTS exa_set_answers ("
"QstCod INT NOT NULL,"
"AnsInd TINYINT NOT NULL,"
"Answer TEXT NOT NULL," // Tst_MAX_BYTES_ANSWER_OR_FEEDBACK
"Feedback TEXT NOT NULL," // Tst_MAX_BYTES_ANSWER_OR_FEEDBACK
"MedCod INT NOT NULL DEFAULT -1,"
"Correct ENUM('N','Y') NOT NULL,"
"UNIQUE INDEX(QstCod,AnsInd),"
"INDEX(MedCod))");
/***** Table exa_set_questions *****/
/*
mysql> DESCRIBE exa_set_questions;
+----------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+---------------+------+-----+---------+-------+
| QstCod | int(11) | NO | PRI | NULL | |
| AnsInd | tinyint(4) | NO | PRI | NULL | |
| Answer | text | NO | | NULL | |
| Feedback | text | NO | | NULL | |
| MedCod | int(11) | NO | MUL | -1 | |
| Correct | enum('N','Y') | NO | | NULL | |
+----------+---------------+------+-----+---------+-------+
6 rows in set (0.00 sec)
*/
DB_CreateTable ("CREATE TABLE IF NOT EXISTS exa_set_questions ("
"QstCod INT NOT NULL AUTO_INCREMENT,"
"SetCod INT NOT NULL,"
"AnsType ENUM ('int','float','true_false','unique_choice','multiple_choice','text') NOT NULL,"
"Shuffle ENUM('N','Y') NOT NULL,"
"Stem TEXT NOT NULL," // Cns_MAX_BYTES_TEXT
"Feedback TEXT NOT NULL," // Cns_MAX_BYTES_TEXT
"MedCod INT NOT NULL DEFAULT -1,"
"UNIQUE INDEX(QstCod),"
"UNIQUE INDEX(SetCod,QstCod),"
"INDEX(MedCod))");
/***** Table exa_sets *****/
/*
mysql> DESCRIBE exa_sets;

View File

@ -1181,9 +1181,18 @@ static void Exa_RemoveExamFromAllTables (long ExaCod)
/***** Remove all events in this exam *****/
ExaEvt_RemoveEventsInExamFromAllTables (ExaCod);
/***** Remove exam question *****/
/***** Remove exam questions *****/
DB_QueryDELETE ("can not remove exam questions",
"DELETE FROM exa_questions WHERE ExaCod=%ld",
"DELETE FROM exa_set_questions"
" USING exa_sets,exa_set_questions"
" WHERE exa_sets.ExaCod=%ld"
" AND exa_sets.SetCod=exa_set_questions.SetCod",
ExaCod);
/***** Remove exam sets *****/
DB_QueryDELETE ("can not remove exam sets",
"DELETE FROM exa_sets"
" WHERE ExaCod=%ld",
ExaCod);
/***** Remove exam *****/
@ -1203,10 +1212,19 @@ void Exa_RemoveExamsCrs (long CrsCod)
/***** Remove the questions in exams *****/
DB_QueryDELETE ("can not remove questions in course exams",
"DELETE FROM exa_questions"
" USING exa_exams,exa_questions"
"DELETE FROM exa_set_questions"
" USING exa_exams,exa_sets,exa_set_questions"
" WHERE exa_exams.CrsCod=%ld"
" AND exa_exams.ExaCod=exa_questions.ExaCod",
" AND exa_exams.ExaCod=exa_sets.ExaCod",
" AND exa_sets.SetCod=exa_set_questions.SetCod",
CrsCod);
/***** Remove the sets in exams *****/
DB_QueryDELETE ("can not remove sets in course exams",
"DELETE FROM exa_sets"
" USING exa_exams,exa_sets"
" WHERE exa_exams.CrsCod=%ld"
" AND exa_exams.ExaCod=exa_sets.ExaCod",
CrsCod);
/***** Remove the exams *****/
@ -1678,7 +1696,7 @@ long Exa_GetQstCodFromQstInd (long ExaCod,unsigned QstInd)
/***** Get question code of the question to be moved up *****/
if (!DB_QuerySELECT (&mysql_res,"can not get question code",
"SELECT QstCod FROM exa_questions"
"SELECT QstCod FROM exa_set_questions"
" WHERE ExaCod=%ld AND QstInd=%u",
ExaCod,QstInd))
Lay_ShowErrorAndExit ("Error: wrong question index.");
@ -1710,7 +1728,7 @@ unsigned Exa_GetPrevQuestionIndexInExam (long ExaCod,unsigned QstInd)
// Although indexes are always continuous...
// ...this implementation works even with non continuous indexes
if (!DB_QuerySELECT (&mysql_res,"can not get previous question index",
"SELECT MAX(QstInd) FROM exa_questions"
"SELECT MAX(QstInd) FROM exa_set_questions"
" WHERE ExaCod=%ld AND QstInd<%u",
ExaCod,QstInd))
Lay_ShowErrorAndExit ("Error: previous question index not found.");
@ -1744,7 +1762,7 @@ unsigned Exa_GetNextQuestionIndexInExam (long ExaCod,unsigned QstInd)
// Although indexes are always continuous...
// ...this implementation works even with non continuous indexes
if (!DB_QuerySELECT (&mysql_res,"can not get next question index",
"SELECT MIN(QstInd) FROM exa_questions"
"SELECT MIN(QstInd) FROM exa_set_questions"
" WHERE ExaCod=%ld AND QstInd>%u",
ExaCod,QstInd))
Lay_ShowErrorAndExit ("Error: next question index not found.");
@ -1964,69 +1982,69 @@ double Exa_GetNumQstsPerCrsExam (Hie_Level_t Scope)
case Hie_SYS:
DB_QuerySELECT (&mysql_res,"can not get number of questions per exam",
"SELECT AVG(NumQsts) FROM"
" (SELECT COUNT(exa_questions.QstCod) AS NumQsts"
" FROM exa_exams,exa_questions"
" WHERE exa_exams.ExaCod=exa_questions.ExaCod"
" GROUP BY exa_questions.ExaCod) AS NumQstsTable");
" (SELECT COUNT(exa_set_questions.QstCod) AS NumQsts"
" FROM exa_exams,exa_set_questions"
" WHERE exa_exams.ExaCod=exa_set_questions.ExaCod"
" GROUP BY exa_set_questions.ExaCod) AS NumQstsTable");
break;
case Hie_CTY:
DB_QuerySELECT (&mysql_res,"can not get number of questions per exam",
"SELECT AVG(NumQsts) FROM"
" (SELECT COUNT(exa_questions.QstCod) AS NumQsts"
" FROM institutions,centres,degrees,courses,exa_exams,exa_questions"
" (SELECT COUNT(exa_set_questions.QstCod) AS NumQsts"
" FROM institutions,centres,degrees,courses,exa_exams,exa_set_questions"
" WHERE institutions.CtyCod=%ld"
" AND institutions.InsCod=centres.InsCod"
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=exa_exams.CrsCod"
" AND exa_exams.ExaCod=exa_questions.ExaCod"
" GROUP BY exa_questions.ExaCod) AS NumQstsTable",
" AND exa_exams.ExaCod=exa_set_questions.ExaCod"
" GROUP BY exa_set_questions.ExaCod) AS NumQstsTable",
Gbl.Hierarchy.Cty.CtyCod);
break;
case Hie_INS:
DB_QuerySELECT (&mysql_res,"can not get number of questions per exam",
"SELECT AVG(NumQsts) FROM"
" (SELECT COUNT(exa_questions.QstCod) AS NumQsts"
" FROM centres,degrees,courses,exa_exams,exa_questions"
" (SELECT COUNT(exa_set_questions.QstCod) AS NumQsts"
" FROM centres,degrees,courses,exa_exams,exa_set_questions"
" WHERE centres.InsCod=%ld"
" AND centres.CtrCod=degrees.CtrCod"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=exa_exams.CrsCod"
" AND exa_exams.ExaCod=exa_questions.ExaCod"
" GROUP BY exa_questions.ExaCod) AS NumQstsTable",
" AND exa_exams.ExaCod=exa_set_questions.ExaCod"
" GROUP BY exa_set_questions.ExaCod) AS NumQstsTable",
Gbl.Hierarchy.Ins.InsCod);
break;
case Hie_CTR:
DB_QuerySELECT (&mysql_res,"can not get number of questions per exam",
"SELECT AVG(NumQsts) FROM"
" (SELECT COUNT(exa_questions.QstCod) AS NumQsts"
" FROM degrees,courses,exa_exams,exa_questions"
" (SELECT COUNT(exa_set_questions.QstCod) AS NumQsts"
" FROM degrees,courses,exa_exams,exa_set_questions"
" WHERE degrees.CtrCod=%ld"
" AND degrees.DegCod=courses.DegCod"
" AND courses.CrsCod=exa_exams.CrsCod"
" AND exa_exams.ExaCod=exa_questions.ExaCod"
" GROUP BY exa_questions.ExaCod) AS NumQstsTable",
" AND exa_exams.ExaCod=exa_set_questions.ExaCod"
" GROUP BY exa_set_questions.ExaCod) AS NumQstsTable",
Gbl.Hierarchy.Ctr.CtrCod);
break;
case Hie_DEG:
DB_QuerySELECT (&mysql_res,"can not get number of questions per exam",
"SELECT AVG(NumQsts) FROM"
" (SELECT COUNT(exa_questions.QstCod) AS NumQsts"
" FROM courses,exa_exams,exa_questions"
" (SELECT COUNT(exa_set_questions.QstCod) AS NumQsts"
" FROM courses,exa_exams,exa_set_questions"
" WHERE courses.DegCod=%ld"
" AND courses.CrsCod=exa_exams.CrsCod"
" AND exa_exams.ExaCod=exa_questions.ExaCod"
" GROUP BY exa_questions.ExaCod) AS NumQstsTable",
" AND exa_exams.ExaCod=exa_set_questions.ExaCod"
" GROUP BY exa_set_questions.ExaCod) AS NumQstsTable",
Gbl.Hierarchy.Deg.DegCod);
break;
case Hie_CRS:
DB_QuerySELECT (&mysql_res,"can not get number of questions per exam",
"SELECT AVG(NumQsts) FROM"
" (SELECT COUNT(exa_questions.QstCod) AS NumQsts"
" FROM exa_exams,exa_questions"
" (SELECT COUNT(exa_set_questions.QstCod) AS NumQsts"
" FROM exa_exams,exa_set_questions"
" WHERE exa_exams.Cod=%ld"
" AND exa_exams.ExaCod=exa_questions.ExaCod"
" GROUP BY exa_questions.ExaCod) AS NumQstsTable",
" AND exa_exams.ExaCod=exa_set_questions.ExaCod"
" GROUP BY exa_set_questions.ExaCod) AS NumQstsTable",
Gbl.Hierarchy.Crs.CrsCod);
break;
default:
@ -2060,9 +2078,9 @@ void Exa_ShowTstTagsPresentInAnExam (long ExaCod)
"SELECT tst_tags.TagTxt" // row[0]
" FROM"
" (SELECT DISTINCT(tst_question_tags.TagCod)"
" FROM tst_question_tags,exa_questions"
" WHERE exa_questions.ExaCod=%ld"
" AND exa_questions.QstCod=tst_question_tags.QstCod)"
" FROM tst_question_tags,exa_set_questions"
" WHERE exa_set_questions.ExaCod=%ld"
" AND exa_set_questions.QstCod=tst_question_tags.QstCod)"
" AS TagsCods,tst_tags"
" WHERE TagsCods.TagCod=tst_tags.TagCod"
" ORDER BY tst_tags.TagTxt",
@ -2089,9 +2107,9 @@ void Exa_GetScoreRange (long ExaCod,double *MinScore,double *MaxScore)
NumRows = (unsigned)
DB_QuerySELECT (&mysql_res,"can not get data of a question",
"SELECT COUNT(tst_answers.AnsInd) AS N"
" FROM tst_answers,exa_questions"
" WHERE exa_questions.ExaCod=%ld"
" AND exa_questions.QstCod=tst_answers.QstCod"
" FROM tst_answers,exa_set_questions"
" WHERE exa_set_questions.ExaCod=%ld"
" AND exa_set_questions.QstCod=tst_answers.QstCod"
" GROUP BY tst_answers.QstCod",
ExaCod);
for (NumRow = 0, *MinScore = *MaxScore = 0.0;

View File

@ -1847,146 +1847,6 @@ static void ExaEvt_UpdateEvent (struct ExaEvt_Event *Event)
ExaEvt_CreateGrps (Event->EvtCod); // Associate new groups
}
/*****************************************************************************/
/******************** Create indexes for an exam event ***********************/
/*****************************************************************************/
/* Everytime a new exam event is created,
the answers of each shufflable question are shuffled.
The shuffling is stored in a table of indexes
that will be read when showing an exam event */
/*
static void ExaEvt_CreateIndexes (long ExaCod,long EvtCod)
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumQsts;
unsigned NumQst;
struct Tst_Question Question;
long LongNum;
unsigned QstInd;
***** Get questions of the exam *****
NumQsts = (unsigned)
DB_QuerySELECT (&mysql_res,"can not get questions of an exam",
"SELECT exa_questions.QstCod," // row[0]
"exa_questions.QstInd," // row[1]
"tst_questions.AnsType," // row[2]
"tst_questions.Shuffle" // row[3]
" FROM exa_questions,tst_questions"
" WHERE exa_questions.ExaCod=%ld"
" AND exa_questions.QstCod=tst_questions.QstCod"
" ORDER BY exa_questions.QstInd",
ExaCod);
***** For each question in exam... *****
for (NumQst = 0;
NumQst < NumQsts;
NumQst++)
{
***** Create test question *****
Tst_QstConstructor (&Question);
***** Get question data *****
row = mysql_fetch_row (mysql_res);
*
exa_questions.QstCod row[0]
exa_questions.QstInd row[1]
tst_questions.AnsType row[2]
tst_questions.Shuffle row[3]
*
* Get question code (row[0]) *
if ((Question.QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
Lay_ShowErrorAndExit ("Wrong code of question.");
* Get question index (row[1]) *
if ((LongNum = Str_ConvertStrCodToLongCod (row[1])) < 0)
Lay_ShowErrorAndExit ("Wrong question index.");
QstInd = (unsigned) LongNum;
* Get answer type (row[2]) *
Question.Answer.Type = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[2]);
if (Question.Answer.Type != Tst_ANS_UNIQUE_CHOICE)
Lay_ShowErrorAndExit ("Wrong answer type.");
* Get shuffle (row[3]) *
Question.Answer.Shuffle = (row[3][0] == 'Y');
***** Reorder answer *****
ExaEvt_ReorderAnswer (EvtCod,QstInd,&Question);
***** Destroy test question *****
Tst_QstDestructor (&Question);
}
***** Free structure that stores the query result *****
DB_FreeMySQLResult (&mysql_res);
}
*/
/*****************************************************************************/
/**************** Reorder answers of an exam event question ******************/
/*****************************************************************************/
/*
static void ExaEvt_ReorderAnswer (long EvtCod,unsigned QstInd,
const struct Tst_Question *Question)
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned NumAnss;
unsigned NumAns;
long LongNum;
unsigned AnsInd;
char StrOneAnswer[Cns_MAX_DECIMAL_DIGITS_UINT + 1];
char StrAnswersOneQst[Tst_MAX_BYTES_ANSWERS_ONE_QST + 1];
***** Initialize list of answers to empty string *****
StrAnswersOneQst[0] = '\0';
***** Get questions of the exam *****
NumAnss = (unsigned)
DB_QuerySELECT (&mysql_res,"can not get questions of an exam",
"SELECT AnsInd" // row[0]
" FROM tst_answers"
" WHERE QstCod=%ld"
" ORDER BY %s",
Question->QstCod,
Question->Answer.Shuffle ? "RAND()" : // Use RAND() because is really random; RAND(NOW()) repeats order
"AnsInd");
***** For each answer in question... *****
for (NumAns = 0;
NumAns < NumAnss;
NumAns++)
{
row = mysql_fetch_row (mysql_res);
* Get answer index (row[0]) *
if ((LongNum = Str_ConvertStrCodToLongCod (row[0])) < 0)
Lay_ShowErrorAndExit ("Wrong answer index.");
AnsInd = (unsigned) LongNum;
snprintf (StrOneAnswer,sizeof (StrOneAnswer),
"%u",AnsInd);
* Concatenate answer index to list of answers *
if (NumAns)
Str_Concat (StrAnswersOneQst,",",
Tst_MAX_BYTES_ANSWERS_ONE_QST);
Str_Concat (StrAnswersOneQst,StrOneAnswer,
Tst_MAX_BYTES_ANSWERS_ONE_QST);
}
***** Free structure that stores the query result *****
DB_FreeMySQLResult (&mysql_res);
***** Create entry for this question in table of exam event indexes *****
DB_QueryINSERT ("can not create exam event indexes",
"INSERT INTO exa_indexes"
" (EvtCod,QstInd,Indexes)"
" VALUES"
" (%ld,%u,'%s')",
EvtCod,QstInd,StrAnswersOneQst);
}
*/
/*****************************************************************************/
/***************** Get indexes for a question from database ******************/
/*****************************************************************************/

View File

@ -93,6 +93,8 @@ static void ExaPrn_GetQuestionsForNewPrintFromDB (struct ExaPrn_Print *Print);
static unsigned ExaPrn_GetSomeQstsFromSetToPrint (struct ExaPrn_Print *Print,
struct ExaSet_Set *Set,
unsigned *NumQstInPrint);
static void ExaPrn_GenerateChoiceIndexes (struct TstPrn_PrintedQuestion *PrintedQuestion,
bool Shuffle);
static void ExaPrn_CreatePrintInDB (struct ExaPrn_Print *Print);
static void ExaPrn_GetPrintQuestionsFromDB (struct ExaPrn_Print *Print);
@ -363,12 +365,11 @@ static unsigned ExaPrn_GetSomeQstsFromSetToPrint (struct ExaPrn_Print *Print,
/***** Get questions from database *****/
NumQstsInSet = (unsigned)
DB_QuerySELECT (&mysql_res,"can not get questions from set",
"SELECT tst_questions.QstCod," // row[0]
"tst_questions.AnsType," // row[1]
"tst_questions.Shuffle" // row[2]
" FROM exa_questions,tst_questions"
" WHERE exa_questions.setCod=%ld"
" AND exa_questions.QstCod=tst_questions.QstCod"
"SELECT QstCod," // row[0]
"AnsType," // row[1]
"Shuffle" // row[2]
" FROM exa_set_questions"
" WHERE SetCod=%ld"
" ORDER BY RAND()" // Don't use RAND(NOW()) because the same ordering will be repeated across sets
" LIMIT %u",
Set->SetCod,
@ -414,7 +415,7 @@ static unsigned ExaPrn_GetSomeQstsFromSetToPrint (struct ExaPrn_Print *Print,
case Tst_ANS_MULTIPLE_CHOICE:
/* If answer type is unique or multiple option,
generate indexes of answers depending on shuffle */
Tst_GenerateChoiceIndexesDependingOnShuffle (&Print->PrintedQuestions[*NumQstInPrint],Shuffle);
ExaPrn_GenerateChoiceIndexes (&Print->PrintedQuestions[*NumQstInPrint],Shuffle);
break;
default:
break;
@ -430,6 +431,71 @@ static unsigned ExaPrn_GetSomeQstsFromSetToPrint (struct ExaPrn_Print *Print,
return NumQstsInSet;
}
/*****************************************************************************/
/*************** Generate choice indexes depending on shuffle ****************/
/*****************************************************************************/
static void ExaPrn_GenerateChoiceIndexes (struct TstPrn_PrintedQuestion *PrintedQuestion,
bool Shuffle)
{
struct Tst_Question Question;
unsigned NumOpt;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
unsigned Index;
bool ErrorInIndex;
char StrInd[1 + Cns_MAX_DECIMAL_DIGITS_UINT + 1];
/***** Create test question *****/
Tst_QstConstructor (&Question);
Question.QstCod = PrintedQuestion->QstCod;
/***** Get answers of question from database *****/
ExaSet_GetAnswersQst (&Question,&mysql_res,Shuffle);
/*
row[0] AnsInd
row[1] Answer
row[2] Feedback
row[3] MedCod
row[4] Correct
*/
for (NumOpt = 0;
NumOpt < Question.Answer.NumOptions;
NumOpt++)
{
/***** Get next answer *****/
row = mysql_fetch_row (mysql_res);
/***** Assign index (row[0]).
Index is 0,1,2,3... if no shuffle
or 1,3,0,2... (example) if shuffle *****/
ErrorInIndex = false;
if (sscanf (row[0],"%u",&Index) == 1)
{
if (Index >= Tst_MAX_OPTIONS_PER_QUESTION)
ErrorInIndex = true;
}
else
ErrorInIndex = true;
if (ErrorInIndex)
Lay_ShowErrorAndExit ("Wrong index of answer.");
if (NumOpt == 0)
snprintf (StrInd,sizeof (StrInd),"%u",Index);
else
snprintf (StrInd,sizeof (StrInd),",%u",Index);
Str_Concat (PrintedQuestion->StrIndexes,StrInd,
Tst_MAX_BYTES_INDEXES_ONE_QST);
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
/***** Destroy test question *****/
Tst_QstDestructor (&Question);
}
/*****************************************************************************/
/***************** Create new blank exam print in database *******************/
/*****************************************************************************/
@ -571,8 +637,7 @@ static void ExaPrn_ShowTableWithQstsToFill (struct ExaPrn_Print *Print)
Question.QstCod = Print->PrintedQuestions[NumQst].QstCod;
/* Show question */
if (!Tst_GetQstDataFromDB (&Question)) // Question exists
Lay_ShowErrorAndExit ("Wrong question.");
ExaSet_GetQstDataFromDB (&Question,Print->ExaCod);
/* Write question and answers */
ExaPrn_WriteQstAndAnsToFill (Print,NumQst,&Question);
@ -981,7 +1046,7 @@ static void ExaPrn_ComputeScoreAndStoreQuestionOfPrint (struct ExaPrn_Print *Pri
/***** Compute question score *****/
Tst_QstConstructor (&Question);
Question.QstCod = Print->PrintedQuestions[NumQst].QstCod;
Question.Answer.Type = Tst_GetQstAnswerType (Question.QstCod);
Question.Answer.Type = ExaSet_GetQstAnswerTypeFromDB (Question.QstCod);
TstPrn_ComputeAnswerScore (&Print->PrintedQuestions[NumQst],&Question);
Tst_QstDestructor (&Question);
@ -1000,7 +1065,6 @@ static void ExaPrn_ComputeScoreAndStoreQuestionOfPrint (struct ExaPrn_Print *Pri
NumQst); // 0, 1, 2, 3...
}
/*****************************************************************************/
/************* Get the questions of an exam print from database **************/
/*****************************************************************************/

View File

@ -1326,15 +1326,15 @@ void ExaRes_GetExamResultQuestionsFromDB (long EvtCod,long UsrCod,
Print->NumQsts = (unsigned)
DB_QuerySELECT (&mysql_res,"can not get questions and answers"
" of a event result",
"SELECT exa_questions.QstCod," // row[0]
"exa_questions.QstInd," // row[1]
"SELECT exa_set_questions.QstCod," // row[0]
"exa_set_questions.QstInd," // row[1]
"exa_indexes.Indexes" // row[2]
" FROM exa_events,exa_questions,exa_indexes"
" FROM exa_events,exa_set_questions,exa_indexes"
" WHERE exa_events.EvtCod=%ld"
" AND exa_events.ExaCod=exa_questions.ExaCod"
" AND exa_events.ExaCod=exa_set_questions.ExaCod"
" AND exa_events.EvtCod=exa_indexes.EvtCod"
" AND exa_questions.QstInd=exa_indexes.QstInd"
" ORDER BY exa_questions.QstInd",
" AND exa_set_questions.QstInd=exa_indexes.QstInd"
" ORDER BY exa_set_questions.QstInd",
EvtCod);
for (NumQst = 0, Print->NumQstsNotBlank = 0;
NumQst < Print->NumQsts;

View File

@ -123,10 +123,17 @@ static void ExaSet_ListOneOrMoreQuestionsForEdition (struct Exa_Exams *Exams,
unsigned NumQsts,
MYSQL_RES *mysql_res,
bool ICanEditQuestions);
static void ExaSet_ListQuestionForEdition (const struct Tst_Question *Question,
unsigned QstInd,const char *Anchor);
static void ExaSet_AllocateListSelectedQuestions (struct Exa_Exams *Exams);
static void ExaSet_FreeListsSelectedQuestions (struct Exa_Exams *Exams);
static void ExaSet_CopyQstFromBankToExamSet (struct ExaSet_Set *Set,long QstCod);
static long ExaSet_GetParamQstCod (void);
static void ExaSet_PutParamQstCod (void *QstCod); // Should be a pointer to long
static void ExaSet_ExchangeSets (long ExaCod,
unsigned SetIndTop,unsigned SetIndBottom);
@ -153,7 +160,7 @@ void ExaSet_PutParamsOneSet (void *Exams)
static void ExaSet_PutParamsOneQst (void *Exams)
{
ExaSet_PutParamsOneSet (Exams);
Tst_PutParamQstCod (&(((struct Exa_Exams *) Exams)->QstCod));
ExaSet_PutParamQstCod (&(((struct Exa_Exams *) Exams)->QstCod));
}
/*****************************************************************************/
@ -175,7 +182,7 @@ static unsigned ExaSet_GetNumQstsInSet (long SetCod)
/***** Get number of questions in set from database *****/
return
(unsigned) DB_QueryCOUNT ("can not get number of questions in a set",
"SELECT COUNT(*) FROM exa_questions"
"SELECT COUNT(*) FROM exa_set_questions"
" WHERE SetCod=%ld",
SetCod);
}
@ -1042,11 +1049,10 @@ static void ExaSet_ListSetQuestions (struct Exa_Exams *Exams,
/***** Get data of questions from database *****/
NumQsts = (unsigned)
DB_QuerySELECT (&mysql_res,"can not get exam questions",
"SELECT exa_questions.QstCod" // row[0]
" FROM exa_questions LEFT JOIN tst_questions" // LEFT JOIN because the question could be removed in table of test questions
" ON (exa_questions.QstCod=tst_questions.QstCod)"
" WHERE exa_questions.SetCod=%ld"
" ORDER BY tst_questions.Stem",
"SELECT QstCod" // row[0]
" FROM exa_set_questions"
" WHERE SetCod=%ld"
" ORDER BY Stem",
Set->SetCod);
/***** Begin box *****/
@ -1298,12 +1304,10 @@ static void ExaSet_ListOneOrMoreQuestionsForEdition (struct Exa_Exams *Exams,
extern const char *Txt_Questions;
extern const char *Txt_No_INDEX;
extern const char *Txt_Code;
extern const char *Txt_Tags;
extern const char *Txt_Question;
unsigned NumQst;
MYSQL_ROW row;
struct Tst_Question Question;
bool QuestionExists;
char *Anchor;
/***** Build anchor string *****/
@ -1317,7 +1321,6 @@ static void ExaSet_ListOneOrMoreQuestionsForEdition (struct Exa_Exams *Exams,
HTM_TH (1,1,"CT",Txt_No_INDEX);
HTM_TH (1,1,"CT",Txt_Code);
HTM_TH (1,1,"CT",Txt_Tags);
HTM_TH (1,1,"CT",Txt_Question);
HTM_TR_End ();
@ -1360,13 +1363,13 @@ static void ExaSet_ListOneOrMoreQuestionsForEdition (struct Exa_Exams *Exams,
/* Put icon to edit the question */
if (ICanEditQuestions)
Ico_PutContextualIconToEdit (ActEdiOneTstQst,NULL,
Tst_PutParamQstCod,&Question.QstCod);
ExaSet_PutParamQstCod,&Question.QstCod);
HTM_TD_End ();
/***** Question *****/
QuestionExists = Tst_GetQstDataFromDB (&Question);
Tst_ListQuestionForEdition (&Question,NumQst + 1,QuestionExists,Anchor);
ExaSet_GetQstDataFromDB (&Question,Exams->ExaCod);
ExaSet_ListQuestionForEdition (&Question,NumQst + 1,Anchor);
/***** End row *****/
HTM_TR_End ();
@ -1382,6 +1385,233 @@ static void ExaSet_ListOneOrMoreQuestionsForEdition (struct Exa_Exams *Exams,
Frm_FreeAnchorStr (Anchor);
}
/*****************************************************************************/
/*************** Get answer type of a question from database *****************/
/*****************************************************************************/
Tst_AnswerType_t ExaSet_GetQstAnswerTypeFromDB (long QstCod)
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
Tst_AnswerType_t AnswerType;
/***** Get type of answer from database *****/
if (!DB_QuerySELECT (&mysql_res,"can not get the type of a question",
"SELECT AnsType" // row[0]
" FROM exa_set_questions"
" WHERE QstCod=%ld",
QstCod))
Lay_ShowErrorAndExit ("Question does not exist.");
/* Get type of answer */
row = mysql_fetch_row (mysql_res);
AnswerType = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[0]);
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
return AnswerType;
}
/*****************************************************************************/
/*************** Get data of a question in a set from database ***************/
/*****************************************************************************/
void ExaSet_GetQstDataFromDB (struct Tst_Question *Question,long ExaCod)
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
bool QuestionExists;
unsigned NumOpt;
/***** Get question data from database *****/
QuestionExists = (DB_QuerySELECT (&mysql_res,"can not get a question",
"SELECT AnsType," // row[0]
"Shuffle," // row[1]
"Stem," // row[2]
"Feedback," // row[3]
"MedCod" // row[4]
" FROM exa_set_questions"
" WHERE QstCod=%ld"
" AND SetCod IN"
" (SELECT SetCod FROM exa_sets WHERE ExaCod=%ld)", // Extra check
Question->QstCod,
ExaCod) != 0);
if (QuestionExists)
{
row = mysql_fetch_row (mysql_res);
/* Get the type of answer (row[0]) */
Question->Answer.Type = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[0]);
/* Get shuffle (row[1]) */
Question->Answer.Shuffle = (row[1][0] == 'Y');
/* Get the stem (row[2]) */
Question->Stem[0] = '\0';
if (row[2])
if (row[2][0])
Str_Copy (Question->Stem,row[2],
Cns_MAX_BYTES_TEXT);
/* Get the feedback (row[3]) */
Question->Feedback[0] = '\0';
if (row[3])
if (row[3][0])
Str_Copy (Question->Feedback,row[3],
Cns_MAX_BYTES_TEXT);
/* Get media (row[4]) */
Question->Media.MedCod = Str_ConvertStrCodToLongCod (row[4]);
Med_GetMediaDataByCod (&Question->Media);
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
/***** Get the answers from the database *****/
ExaSet_GetAnswersQst (Question,&mysql_res,
false); // Don't shuffle
/*
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 Tst_ANS_INT:
Tst_CheckIfNumberOfAnswersIsOne (Question);
Question->Answer.Integer = Tst_GetIntAnsFromStr (row[1]);
break;
case Tst_ANS_FLOAT:
if (Question->Answer.NumOptions != 2)
Lay_ShowErrorAndExit ("Wrong answer.");
Question->Answer.FloatingPoint[NumOpt] = Str_GetDoubleFromStr (row[1]);
break;
case Tst_ANS_TRUE_FALSE:
Tst_CheckIfNumberOfAnswersIsOne (Question);
Question->Answer.TF = row[1][0];
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
case Tst_ANS_TEXT:
/* Check number of options */
if (Question->Answer.NumOptions > Tst_MAX_OPTIONS_PER_QUESTION)
Lay_ShowErrorAndExit ("Wrong answer.");
/* Allocate space for text and feedback */
if (!Tst_AllocateTextChoiceAnswer (Question,NumOpt))
/* Abort on error */
Ale_ShowAlertsAndExit ();
/* Get text (row[1]) */
Question->Answer.Options[NumOpt].Text[0] = '\0';
if (row[1])
if (row[1][0])
Str_Copy (Question->Answer.Options[NumOpt].Text,row[1],
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
/* Get feedback (row[2]) */
Question->Answer.Options[NumOpt].Feedback[0] = '\0';
if (row[2])
if (row[2][0])
Str_Copy (Question->Answer.Options[NumOpt].Feedback,row[2],
Tst_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);
if (!QuestionExists)
Lay_ShowErrorAndExit ("Wrong question.");
}
/*****************************************************************************/
/*************** Get answers of a test question from database ****************/
/*****************************************************************************/
void ExaSet_GetAnswersQst (struct Tst_Question *Question,MYSQL_RES **mysql_res,
bool Shuffle)
{
/***** Get answers of a question from database *****/
Question->Answer.NumOptions = (unsigned)
DB_QuerySELECT (mysql_res,"can not get answers of a question",
"SELECT AnsInd," // row[0]
"Answer," // row[1]
"Feedback," // row[2]
"MedCod," // row[3]
"Correct" // row[4]
" FROM exa_set_answers"
" WHERE QstCod=%ld"
" ORDER BY %s",
Question->QstCod,
Shuffle ? "RAND()" :
"AnsInd");
if (!Question->Answer.NumOptions)
Ale_ShowAlert (Ale_ERROR,"Error when getting answers of a question.");
}
/*****************************************************************************/
/********************* List question in set for edition **********************/
/*****************************************************************************/
static void ExaSet_ListQuestionForEdition (const struct Tst_Question *Question,
unsigned QstInd,const char *Anchor)
{
/***** Number of question and answer type (row[1]) *****/
HTM_TD_Begin ("class=\"RT COLOR%u\"",Gbl.RowEvenOdd);
Tst_WriteNumQst (QstInd);
Tst_WriteAnswerType (Question->Answer.Type);
HTM_TD_End ();
/***** Write question code *****/
HTM_TD_Begin ("class=\"DAT_SMALL CT COLOR%u\"",Gbl.RowEvenOdd);
HTM_TxtF ("%ld&nbsp;",Question->QstCod);
HTM_TD_End ();
/***** Write stem (row[3]) and media *****/
HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd);
HTM_ARTICLE_Begin (Anchor);
/* Write stem */
Tst_WriteQstStem (Question->Stem,"TEST_EDI",
true); // Visible
/* Show media */
Med_ShowMedia (&Question->Media,
"TEST_MED_EDIT_LIST_STEM_CONTAINER",
"TEST_MED_EDIT_LIST_STEM");
/* Show feedback */
Tst_WriteQstFeedback (Question->Feedback,"TEST_EDI_LIGHT");
/* Show answers */
Tst_WriteAnswersListing (Question);
HTM_ARTICLE_End ();
HTM_TD_End ();
}
/*****************************************************************************/
/************* Add selected test questions to set of questions ***************/
/*****************************************************************************/
@ -1448,13 +1678,7 @@ void ExaSet_AddQstsToSet (void)
if (sscanf (LongStr,"%ld",&QstCod) != 1)
Lay_ShowErrorAndExit ("Wrong question code.");
/* Insert question in the table of questions */
DB_QueryINSERT ("can not add question to set",
"INSERT INTO exa_questions"
" (SetCod,QstCod)"
" VALUES"
" (%ld,%ld)",
Set.SetCod,QstCod);
ExaSet_CopyQstFromBankToExamSet (&Set,QstCod);
}
}
else
@ -1495,6 +1719,93 @@ static void ExaSet_FreeListsSelectedQuestions (struct Exa_Exams *Exams)
}
}
/*****************************************************************************/
/******* Copy question and answers from back of questions to exam set ********/
/*****************************************************************************/
static void ExaSet_CopyQstFromBankToExamSet (struct ExaSet_Set *Set,long QstCod)
{
extern const char *Tst_StrAnswerTypesDB[Tst_NUM_ANS_TYPES];
extern const char *Txt_Question_removed;
struct Tst_Question Question;
long CloneMedCod;
long QstCodInSet;
unsigned NumOpt;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
/***** Create test question *****/
Tst_QstConstructor (&Question);
Question.QstCod = QstCod;
/***** Get data of question from database *****/
if (Tst_GetQstDataFromDB (&Question))
{
/***** Clone media *****/
CloneMedCod = Med_CloneMedia (&Question.Media);
Ale_ShowAlert (Ale_INFO,"DEBUG: CloneMedCod = %ld",CloneMedCod);
/***** Insert question in table of questions *****/
QstCodInSet = DB_QueryINSERTandReturnCode ("can not add question to set",
"INSERT INTO exa_set_questions"
" (SetCod,AnsType,Shuffle,Stem,Feedback,MedCod)"
" VALUES"
" (%ld,'%s','%c','%s','%s',%ld)",
Set->SetCod,
Tst_StrAnswerTypesDB[Question.Answer.Type],
Question.Answer.Shuffle ? 'Y' :
'N',
Question.Stem,
Question.Feedback,
CloneMedCod);
/***** Get the answers from the database *****/
Tst_GetAnswersQst (&Question,&mysql_res,
false); // Don't shuffle
/*
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);
/* Get media (row[3]) */
Question.Answer.Options[NumOpt].Media.MedCod = Str_ConvertStrCodToLongCod (row[3]);
Med_GetMediaDataByCod (&Question.Answer.Options[NumOpt].Media);
/* Clone media */
CloneMedCod = Med_CloneMedia (&Question.Answer.Options[NumOpt].Media);
/* Copy answer option to exam set */
DB_QueryINSERT ("can not add answer to set",
"INSERT INTO exa_set_answers"
" (QstCod,AnsInd,Answer,Feedback,MedCod,Correct)"
" VALUES"
" (%ld,%u,'%s','%s',%ld,'%s')",
QstCodInSet, // Question code in set
NumOpt, // Answer index (number of option)
row[1], // Copy of text
row[2], // Copy of feedback
CloneMedCod, // Media code of the new cloned media
row[4]); // Copy of correct
}
/* Free structure that stores the query result */
DB_FreeMySQLResult (&mysql_res);
}
else
Ale_ShowAlert (Ale_WARNING,Txt_Question_removed);
/***** Destroy test question *****/
Tst_QstDestructor (&Question);
}
/*****************************************************************************/
/***************** Request the removal of a set of questions *****************/
/*****************************************************************************/
@ -1581,10 +1892,10 @@ void ExaSet_RemoveSet (void)
/***** Remove the set from all the tables *****/
/* Remove questions associated to set */
DB_QueryDELETE ("can not remove questions associated to set",
"DELETE FROM exa_questions"
" USING exa_questions,exa_sets"
" WHERE exa_questions.SetCod=%ld"
" AND exa_questions.SetCod=exa_sets.SetCod"
"DELETE FROM exa_set_questions"
" USING exa_set_questions,exa_sets"
" WHERE exa_set_questions.SetCod=%ld"
" AND exa_set_questions.SetCod=exa_sets.SetCod"
" AND exa_sets.ExaCod=%ld", // Extra check
Set.SetCod,Set.ExaCod);
@ -1771,7 +2082,7 @@ void ExaSet_RequestRemoveQstFromSet (void)
Exams.SetCod = Set.SetCod;
/***** Get question index *****/
Exams.QstCod = Tst_GetParamQstCod ();
Exams.QstCod = ExaSet_GetParamQstCod ();
/***** Build anchor string *****/
Frm_SetAnchorStr (Set.SetCod,&Anchor);
@ -1828,14 +2139,15 @@ void ExaSet_RemoveQstFromSet (void)
Exams.SetCod = Set.SetCod;
/***** Get question index *****/
QstCod = Tst_GetParamQstCod ();
QstCod = ExaSet_GetParamQstCod ();
/***** Remove the question from set *****/
/* Remove the question itself */
DB_QueryDELETE ("can not remove a question from a set",
"DELETE FROM exa_questions"
" WHERE SetCod=%ld AND QstCod=%ld",
Set.SetCod,QstCod);
"DELETE FROM exa_set_questions"
" WHERE QstCod=%ld"
" AND SetCod=%ld", // Extra check
QstCod,Set.SetCod);
if (!mysql_affected_rows (&Gbl.mysql))
Lay_ShowErrorAndExit ("The question to be removed does not exist.");
@ -1847,6 +2159,27 @@ void ExaSet_RemoveQstFromSet (void)
false); // It's not a new exam
}
/*****************************************************************************/
/************ Get the parameter with the code of a test question *************/
/*****************************************************************************/
static long ExaSet_GetParamQstCod (void)
{
/***** Get code of test question *****/
return Par_GetParToLong ("QstCod");
}
/*****************************************************************************/
/************ Put parameter with question code to edit, remove... ************/
/*****************************************************************************/
static void ExaSet_PutParamQstCod (void *QstCod) // Should be a pointer to long
{
if (QstCod)
if (*((long *) QstCod) > 0) // If question exists
Par_PutHiddenParamLong (NULL,"QstCod",*((long *) QstCod));
}
/*****************************************************************************/
/*********** Exchange the order of two consecutive sets in an exam ***********/
/*****************************************************************************/

View File

@ -28,10 +28,8 @@
/********************************* Headers ***********************************/
/*****************************************************************************/
// #include "swad_date.h"
// #include "swad_exam_event.h"
#include "swad_exam_type.h"
// #include "swad_scope.h"
#include "swad_test_type.h"
/*****************************************************************************/
/************************** Public types and constants ***********************/
@ -63,6 +61,11 @@ void ExaSet_ListExamSets (struct Exa_Exams *Exams,
void ExaSet_ResetSet (struct ExaSet_Set *Set);
Tst_AnswerType_t ExaSet_GetQstAnswerTypeFromDB (long QstCod);
void ExaSet_GetQstDataFromDB (struct Tst_Question *Question,long ExaCod);
void ExaSet_GetAnswersQst (struct Tst_Question *Question,MYSQL_RES **mysql_res,
bool Shuffle);
void ExaSet_AddQstsToSet (void);
void ExaSet_RequestRemoveSet (void);

View File

@ -307,7 +307,7 @@ void Med_GetMediaDataByCod (struct Media *Media)
Length = Cns_MAX_BYTES_WWW;
if ((Media->URL = (char *) malloc (Length + 1)) == NULL)
Lay_ShowErrorAndExit ("Error allocating memory for image URL.");
Lay_ShowErrorAndExit ("Error allocating memory for media URL.");
Str_Copy (Media->URL,row[2],
Length);
}
@ -324,7 +324,7 @@ void Med_GetMediaDataByCod (struct Media *Media)
Length = Med_MAX_BYTES_TITLE;
if ((Media->Title = (char *) malloc (Length + 1)) == NULL)
Lay_ShowErrorAndExit ("Error allocating memory for image title.");
Lay_ShowErrorAndExit ("Error allocating memory for media title.");
Str_Copy (Media->Title,row[3],
Length);
}
@ -1405,13 +1405,6 @@ static bool Med_MoveTmpFileToDefDir (struct Media *Media,
void Med_StoreMediaInDB (struct Media *Media)
{
/***** Trivial case *****/
if (Media->Status != Med_MOVED)
{
Med_ResetMedia (Media); // No media inserted in database
return;
}
/***** Insert media into database *****/
Media->MedCod = DB_QueryINSERTandReturnCode ("can not create media",
"INSERT INTO media"
@ -1436,9 +1429,8 @@ void Med_ShowMedia (const struct Media *Media,
char PathMedPriv[PATH_MAX + 1];
/***** If no media to show ==> nothing to do *****/
if (Media->Status != Med_STORED_IN_DB)
return;
if (Media->Type == Med_TYPE_NONE)
if (Media->Status != Med_STORED_IN_DB ||
Media->Type == Med_TYPE_NONE)
return;
/***** Start media container *****/
@ -1808,6 +1800,140 @@ static void Med_AlertThirdPartyCookies (void)
Btn_NO_BUTTON,NULL);
}
/*****************************************************************************/
/************************* Create duplicate of media *************************/
/*****************************************************************************/
#define Med_NUM_MEDIA 2
#define Med_SRC 0
#define Med_DST 1
long Med_CloneMedia (const struct Media *MediaSrc)
{
long MedCod = -1L;
struct Media MediaDst;
struct
{
char Path[PATH_MAX + 1];
char FullPath[PATH_MAX + 1 + NAME_MAX + 1];
} MediaPriv[Med_NUM_MEDIA];
size_t Length;
/***** If no media ==> nothing to do *****/
if (MediaSrc->Type == Med_TYPE_NONE)
return MedCod;
/***** If no media name ==> nothing to do *****/
if (!MediaSrc->Name)
return MedCod;
if (!MediaSrc->Name[0])
return MedCod;
/***** Initialize media *****/
Med_MediaConstructor (&MediaDst);
/***** Copy type *****/
MediaDst.Type = MediaSrc->Type;
/***** Assign a unique name for the destination media *****/
Cry_CreateUniqueNameEncrypted (MediaDst.Name);
/***** Copy media URL *****/
Med_FreeMediaURL (&MediaDst);
if (MediaSrc->URL)
{
/* Get and limit length of the URL */
Length = strlen (MediaSrc->URL);
if (Length > Cns_MAX_BYTES_WWW)
Length = Cns_MAX_BYTES_WWW;
if ((MediaDst.URL = (char *) malloc (Length + 1)) == NULL)
Lay_ShowErrorAndExit ("Error allocating memory for media URL.");
Str_Copy (MediaDst.URL,MediaSrc->URL,
Length);
}
/***** Copy media title *****/
Med_FreeMediaTitle (&MediaDst);
if (MediaSrc->Title)
{
/* Get and limit length of the title */
Length = strlen (MediaSrc->Title);
if (Length > Cns_MAX_BYTES_WWW)
Length = Cns_MAX_BYTES_WWW;
if ((MediaDst.Title = (char *) malloc (Length + 1)) == NULL)
Lay_ShowErrorAndExit ("Error allocating memory for media title.");
Str_Copy (MediaDst.Title,MediaSrc->Title,
Length);
}
/***** Create duplicate of files *****/
switch (MediaSrc->Type)
{
case Med_JPG:
case Med_GIF:
case Med_MP4:
case Med_WEBM:
case Med_OGG:
/***** Create private directories if not exist *****/
/* Create private directory for images/videos if it does not exist */
Fil_CreateDirIfNotExists (Cfg_PATH_MEDIA_PRIVATE);
/* Build paths to private directories */
snprintf (MediaPriv[Med_SRC].Path,sizeof (MediaPriv[Med_SRC].Path),
"%s/%c%c",
Cfg_PATH_MEDIA_PRIVATE,MediaSrc->Name[0],MediaSrc->Name[1]);
snprintf (MediaPriv[Med_DST].Path,sizeof (MediaPriv[Med_DST].Path),
"%s/%c%c",
Cfg_PATH_MEDIA_PRIVATE,MediaDst.Name[0],MediaDst.Name[1]);
Fil_CreateDirIfNotExists (MediaPriv[Med_DST].Path);
/* Build paths to private files */
snprintf (MediaPriv[Med_SRC].FullPath,sizeof (MediaPriv[Med_SRC].FullPath),
"%s/%s.%s",
MediaPriv[Med_SRC].Path,MediaSrc->Name,Med_Extensions[MediaSrc->Type]);
snprintf (MediaPriv[Med_DST].FullPath,sizeof (MediaPriv[Med_DST].FullPath),
"%s/%s.%s",
MediaPriv[Med_DST].Path,MediaDst.Name,Med_Extensions[MediaSrc->Type]);
/* Copy file */
Ale_ShowAlert (Ale_INFO,"DEBUG<br />"
"MediaPriv[Med_SRC].FullPath = &quot;%s&quot<br />"
"MediaPriv[Med_DST].FullPath = &quot;%s&quot",
MediaPriv[Med_SRC].FullPath,
MediaPriv[Med_DST].FullPath);
Fil_FastCopyOfFiles (MediaPriv[Med_SRC].FullPath,
MediaPriv[Med_DST].FullPath);
if (MediaSrc->Type == Med_GIF)
{
/* Build private paths to PNG */
snprintf (MediaPriv[Med_SRC].FullPath,sizeof (MediaPriv[Med_SRC].FullPath),
"%s/%s.png",
MediaPriv[Med_SRC].Path,MediaSrc->Name);
snprintf (MediaPriv[Med_DST].FullPath,sizeof (MediaPriv[Med_DST].FullPath),
"%s/%s.png",
MediaPriv[Med_DST].Path,MediaDst.Name);
/* Copy PNG file */
Fil_FastCopyOfFiles (MediaPriv[Med_SRC].FullPath,
MediaPriv[Med_DST].FullPath);
}
break;
default:
break;
}
/***** Code to return *****/
Med_StoreMediaInDB (&MediaDst);
MedCod = MediaDst.MedCod;
/***** Free media *****/
Med_MediaDestructor (&MediaDst);
return MedCod;
}
/*****************************************************************************/
/********** Remove several media files and entries in database ***************/
/*****************************************************************************/

View File

@ -155,6 +155,8 @@ void Med_StoreMediaInDB (struct Media *Media);
void Med_ShowMedia (const struct Media *Media,
const char *ClassContainer,const char *ClassMedia);
long Med_CloneMedia (const struct Media *MediaSrc);
void Med_RemoveMediaFromAllRows (unsigned NumMedia,MYSQL_RES *mysql_res);
void Med_RemoveMedia (long MedCod);

View File

@ -3197,13 +3197,13 @@ static void Msg_ShowASentOrReceivedMessage (struct Msg_Messages *Messages,
HTM_TxtColonNBSP (Txt_MSG_Content);
HTM_TD_End ();
/***** Initialize image *****/
/***** Initialize media *****/
Med_MediaConstructor (&Media);
/***** Get message content and optional image *****/
/***** Get message content and optional media *****/
Msg_GetMsgContent (MsgCod,Content,&Media);
/***** Show content and image *****/
/***** Show content and media *****/
HTM_TD_Begin ("colspan=\"2\" class=\"MSG_TXT LT\"");
if (Content[0])
Msg_WriteMsgContent (Content,Cns_MAX_BYTES_LONG_TEXT,true,false);
@ -3212,7 +3212,7 @@ static void Msg_ShowASentOrReceivedMessage (struct Msg_Messages *Messages,
HTM_TR_End ();
/***** Free image *****/
/***** Free media *****/
Med_MediaDestructor (&Media);
}

View File

@ -74,13 +74,7 @@ const char *Tst_StrAnswerTypesXML[Tst_NUM_ANS_TYPES] =
[Tst_ANS_TEXT ] = "text",
};
/*****************************************************************************/
/**************************** Private constants ******************************/
/*****************************************************************************/
#define Tst_MAX_BYTES_TAGS_LIST (16 * 1024)
static const char *Tst_StrAnswerTypesDB[Tst_NUM_ANS_TYPES] =
const char *Tst_StrAnswerTypesDB[Tst_NUM_ANS_TYPES] =
{
[Tst_ANS_INT ] = "int",
[Tst_ANS_FLOAT ] = "float",
@ -90,6 +84,12 @@ static const char *Tst_StrAnswerTypesDB[Tst_NUM_ANS_TYPES] =
[Tst_ANS_TEXT ] = "text",
};
/*****************************************************************************/
/**************************** Private constants ******************************/
/*****************************************************************************/
#define Tst_MAX_BYTES_TAGS_LIST (16 * 1024)
// Test images will be saved with:
// - maximum width of Tst_IMAGE_SAVED_MAX_HEIGHT
// - maximum height of Tst_IMAGE_SAVED_MAX_HEIGHT
@ -936,7 +936,7 @@ static void TstPrn_WriteQstAndAnsToFill (struct TstPrn_PrintedQuestion *PrintedQ
}
/*****************************************************************************/
/******************* List exam/game question for edition *********************/
/********************* List game question for edition ************************/
/*****************************************************************************/
void Tst_ListQuestionForEdition (const struct Tst_Question *Question,
@ -2662,7 +2662,7 @@ static void Tst_GetQuestionsForNewTestFromDB (struct Tst_Test *Test,
case Tst_ANS_MULTIPLE_CHOICE:
/* If answer type is unique or multiple option,
generate indexes of answers depending on shuffle */
Tst_GenerateChoiceIndexesDependingOnShuffle (&Print->PrintedQuestions[NumQst],Shuffle);
Tst_GenerateChoiceIndexes (&Print->PrintedQuestions[NumQst],Shuffle);
break;
default:
break;
@ -2680,13 +2680,12 @@ static void Tst_GetQuestionsForNewTestFromDB (struct Tst_Test *Test,
}
/*****************************************************************************/
/********* Get single or multiple choice answer when seeing a test ***********/
/*************** Generate choice indexes depending on shuffle ****************/
/*****************************************************************************/
void Tst_GenerateChoiceIndexesDependingOnShuffle (struct TstPrn_PrintedQuestion *PrintedQuestion,
bool Shuffle)
void Tst_GenerateChoiceIndexes (struct TstPrn_PrintedQuestion *PrintedQuestion,
bool Shuffle)
{
// extern const char *Par_SEPARATOR_PARAM_MULTIPLE;
struct Tst_Question Question;
unsigned NumOpt;
MYSQL_RES *mysql_res;
@ -2733,7 +2732,6 @@ void Tst_GenerateChoiceIndexesDependingOnShuffle (struct TstPrn_PrintedQuestion
if (NumOpt == 0)
snprintf (StrInd,sizeof (StrInd),"%u",Index);
else
// snprintf (StrInd,sizeof (StrInd),"%s%u",Par_SEPARATOR_PARAM_MULTIPLE,Index);
snprintf (StrInd,sizeof (StrInd),",%u",Index);
Str_Concat (PrintedQuestion->StrIndexes,StrInd,
Tst_MAX_BYTES_INDEXES_ONE_QST);
@ -4699,19 +4697,18 @@ static void Tst_FreeMediaOfQuestion (struct Tst_Question *Question)
/*************** Get answer type of a question from database *****************/
/*****************************************************************************/
Tst_AnswerType_t Tst_GetQstAnswerType (long QstCod)
Tst_AnswerType_t Tst_GetQstAnswerTypeFromDB (long QstCod)
{
MYSQL_RES *mysql_res;
MYSQL_ROW row;
Tst_AnswerType_t AnswerType;
/***** Get type of answer from database *****/
if (!DB_QuerySELECT (&mysql_res,"can not get a question",
if (!DB_QuerySELECT (&mysql_res,"can not get the type of a question",
"SELECT AnsType" // row[0]
" FROM tst_questions"
" WHERE QstCod=%ld"
" AND CrsCod=%ld", // Extra check
QstCod,Gbl.Hierarchy.Crs.CrsCod))
" WHERE QstCod=%ld",
QstCod))
Lay_ShowErrorAndExit ("Question does not exist.");
/* Get type of answer */

View File

@ -122,8 +122,8 @@ void Tst_ListQuestionsToEdit (void);
void Tst_ListQuestionsToSelectForSet (struct Exa_Exams *Exams);
void Tst_ListQuestionsToSelectForGame (struct Gam_Games *Games);
void Tst_GenerateChoiceIndexesDependingOnShuffle (struct TstPrn_PrintedQuestion *PrintedQuestion,
bool Shuffle);
void Tst_GenerateChoiceIndexes (struct TstPrn_PrintedQuestion *PrintedQuestion,
bool Shuffle);
void Tst_WriteParamEditQst (const struct Tst_Test *Test);
@ -158,7 +158,7 @@ void Tst_QstDestructor (struct Tst_Question *Question);
bool Tst_AllocateTextChoiceAnswer (struct Tst_Question *Question,unsigned NumOpt);
Tst_AnswerType_t Tst_GetQstAnswerType (long QstCod);
Tst_AnswerType_t Tst_GetQstAnswerTypeFromDB (long QstCod);
bool Tst_GetQstDataFromDB (struct Tst_Question *Question);
Tst_AnswerType_t Tst_ConvertFromStrAnsTypDBToAnsTyp (const char *StrAnsTypeBD);
void Tst_ReceiveQst (void);

View File

@ -374,7 +374,7 @@ void TstPrn_ComputeScoresAndStoreQuestionsOfPrint (struct TstPrn_Print *Print,
/* Compute question score */
Tst_QstConstructor (&Question);
Question.QstCod = Print->PrintedQuestions[NumQst].QstCod;
Question.Answer.Type = Tst_GetQstAnswerType (Question.QstCod);
Question.Answer.Type = Tst_GetQstAnswerTypeFromDB (Question.QstCod);
TstPrn_ComputeAnswerScore (&Print->PrintedQuestions[NumQst],&Question);
Tst_QstDestructor (&Question);

View File

@ -26,6 +26,9 @@
/*****************************************************************************/
/********************************* Headers ***********************************/
/*****************************************************************************/
#include "swad_media.h"
/*****************************************************************************/
/***************************** Public constants ******************************/
/*****************************************************************************/