diff --git a/sql/swad.sql b/sql/swad.sql
index 33a3522b..7c93668c 100644
--- a/sql/swad.sql
+++ b/sql/swad.sql
@@ -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 (
diff --git a/swad_changelog.h b/swad_changelog.h
index 14599082..064c7099 100644
--- a/swad_changelog.h
+++ b/swad_changelog.h
@@ -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.
diff --git a/swad_database.c b/swad_database.c
index 7e89b57e..913e939b 100644
--- a/swad_database.c
+++ b/swad_database.c
@@ -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;
diff --git a/swad_exam.c b/swad_exam.c
index 6f9c6730..3eb216ee 100644
--- a/swad_exam.c
+++ b/swad_exam.c
@@ -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;
diff --git a/swad_exam_event.c b/swad_exam_event.c
index e1f55289..460c4810 100644
--- a/swad_exam_event.c
+++ b/swad_exam_event.c
@@ -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 ******************/
/*****************************************************************************/
diff --git a/swad_exam_print.c b/swad_exam_print.c
index 22918a4b..af58992c 100644
--- a/swad_exam_print.c
+++ b/swad_exam_print.c
@@ -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 **************/
/*****************************************************************************/
diff --git a/swad_exam_result.c b/swad_exam_result.c
index 092078d9..bc760b2d 100644
--- a/swad_exam_result.c
+++ b/swad_exam_result.c
@@ -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;
diff --git a/swad_exam_set.c b/swad_exam_set.c
index 7254418f..831531e5 100644
--- a/swad_exam_set.c
+++ b/swad_exam_set.c
@@ -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 ",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 ***********/
/*****************************************************************************/
diff --git a/swad_exam_set.h b/swad_exam_set.h
index 27018191..c20c0bd1 100644
--- a/swad_exam_set.h
+++ b/swad_exam_set.h
@@ -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);
diff --git a/swad_media.c b/swad_media.c
index 553f2856..a9ea0dbc 100644
--- a/swad_media.c
+++ b/swad_media.c
@@ -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
"
+ "MediaPriv[Med_SRC].FullPath = "%s"
"
+ "MediaPriv[Med_DST].FullPath = "%s"",
+ 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 ***************/
/*****************************************************************************/
diff --git a/swad_media.h b/swad_media.h
index e44272c5..095a6594 100644
--- a/swad_media.h
+++ b/swad_media.h
@@ -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);
diff --git a/swad_message.c b/swad_message.c
index 54066b11..02e8f391 100644
--- a/swad_message.c
+++ b/swad_message.c
@@ -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);
}
diff --git a/swad_test.c b/swad_test.c
index e90c410e..75049914 100644
--- a/swad_test.c
+++ b/swad_test.c
@@ -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 */
diff --git a/swad_test.h b/swad_test.h
index 83a3bb6a..71da8647 100644
--- a/swad_test.h
+++ b/swad_test.h
@@ -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);
diff --git a/swad_test_print.c b/swad_test_print.c
index 926031a5..f2394a0e 100644
--- a/swad_test_print.c
+++ b/swad_test_print.c
@@ -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);
diff --git a/swad_test_type.h b/swad_test_type.h
index 8ab03d89..a8f083fc 100644
--- a/swad_test_type.h
+++ b/swad_test_type.h
@@ -26,6 +26,9 @@
/*****************************************************************************/
/********************************* Headers ***********************************/
/*****************************************************************************/
+
+#include "swad_media.h"
+
/*****************************************************************************/
/***************************** Public constants ******************************/
/*****************************************************************************/