diff --git a/sql/swad.sql b/sql/swad.sql index 6bacf22ee..d6bb1e9a4 100644 --- a/sql/swad.sql +++ b/sql/swad.sql @@ -1087,6 +1087,7 @@ CREATE TABLE IF NOT EXISTS tst_answers ( AnsInd TINYINT NOT NULL, Answer TEXT NOT NULL, Feedback TEXT NOT NULL, + Image VARCHAR(43) NOT NULL, Correct ENUM('N','Y') NOT NULL, INDEX(QstCod)); -- @@ -1143,8 +1144,8 @@ CREATE TABLE IF NOT EXISTS tst_questions ( AnsType ENUM ('int','float','true_false','unique_choice','multiple_choice','text') NOT NULL, Shuffle ENUM('N','Y') NOT NULL, Stem TEXT NOT NULL, - Image CHAR(43) NOT NULL, Feedback TEXT NOT NULL, + Image VARCHAR(43) NOT NULL, NumHits INT NOT NULL DEFAULT 0, NumHitsNotBlank INT NOT NULL DEFAULT 0, Score DOUBLE PRECISION NOT NULL DEFAULT 0, diff --git a/swad_changelog.h b/swad_changelog.h index 2ebc59370..7ac432d5c 100644 --- a/swad_changelog.h +++ b/swad_changelog.h @@ -108,7 +108,6 @@ // TODO: When page is refreshed in course works, prevent users to be duplicated // TODO: Reply to all // TODO: Hour in exam announcement should start at six a.m. - // TODO: Forum SWAD should be always named "SWAD"? // TODO: Enable chat for guests? // TODO: Go to forum post (or at least to forum thread) from social timeline and notifications? @@ -118,34 +117,38 @@ // TODO: FIX BUG: In results of search of students, no mark of confirmation is shown even if the student really has confirmed his/her registration in the course // TODO: Insert "http://" to WWW when WWW does not start with "*://" // TODO: Change links from external degrees to internal degrees in STATS > Degrees - // TODO: Icon to the left in list of forums is not correct when scope is system // TODO: Move info about number of files to bottom of file browsers - // TODO: To avoid wrong email addresses, when a user fills his/her email address, check if the domain is in the white list of allowed domains. If not, ask for confirmation. // TODO: Filtering email addresses --> an email address can not finish in "." - // TODO: Upload an image in social posts, in test questions, in forum posts, in private messages, etc. - // TODO: Important!!!! E-mail should not be visible for not logged users // TODO: Do not show e-mails of administrators and teachers in lists openly // TODO: Fix bug in marks reported by Francisco Ocaņa - // TODO: In Statistics > Degrees, show only degrees with students +// TODO: Change layout of confirm / reject incription. Use green and red buttons -// TODO: Ask for confirmation when removing a test question? +// TODO: Ask for confirmation when removing a test question /*****************************************************************************/ /****************************** Public constants *****************************/ /*****************************************************************************/ -#define Log_PLATFORM_VERSION "SWAD 15.177 (2016-04-04)" +#define Log_PLATFORM_VERSION "SWAD 15.178 (2016-04-04)" #define CSS_FILE "swad15.175.10.css" #define JS_FILE "swad15.131.3.js" // Number of lines (includes comments but not blank lines) has been got with the following command: // nl swad*.c swad*.h css/swad*.css py/swad*.py js/swad*.js soap/swad*.h sql/swad*.sql | tail -1 /* + Version 15.178: Apr 04, 2016 Code refactoring related to images in test questions. (198244 lines) + 5 changes necessary in database: +ALTER TABLE tst_questions CHANGE COLUMN Image ImageOld CHAR(43) NOT NULL; +ALTER TABLE tst_questions ADD COLUMN Image VARCHAR(43) NOT NULL AFTER Feedback; +UPDATE tst_questions SET Image=ImageOld; +ALTER TABLE tst_questions DROP COLUMN ImageOld; +ALTER TABLE tst_answers ADD COLUMN Image VARCHAR(43) NOT NULL AFTER Feedback; + Version 15.177: Apr 04, 2016 Code refactoring related to images. (198083 lines) Version 15.176: Apr 04, 2016 Code refactoring related to images. (198019 lines) Version 15.175.11:Apr 04, 2016 Code refactoring related to image associated to a test question. diff --git a/swad_database.c b/swad_database.c index 5d187730d..542581b65 100644 --- a/swad_database.c +++ b/swad_database.c @@ -2297,15 +2297,17 @@ mysql> DESCRIBE tst_answers; | AnsInd | tinyint(4) | NO | | NULL | | | Answer | text | NO | | NULL | | | Feedback | text | NO | | NULL | | +| Image | varchar(43) | NO | | NULL | | | Correct | enum('N','Y') | NO | | NULL | | +----------+---------------+------+-----+---------+-------+ -5 rows in set (0.00 sec) +6 rows in set (0.00 sec) */ DB_CreateTable ("CREATE TABLE IF NOT EXISTS tst_answers (" "QstCod INT NOT NULL," "AnsInd TINYINT NOT NULL," "Answer TEXT NOT NULL," "Feedback TEXT NOT NULL," + "Image VARCHAR(43) NOT NULL," "Correct ENUM('N','Y') NOT NULL," "INDEX(QstCod))"); @@ -2418,13 +2420,13 @@ mysql> DESCRIBE tst_questions; | AnsType | enum('int','float','true_false','unique_choice','multiple_choice','text') | NO | | NULL | | | Shuffle | enum('N','Y') | NO | | NULL | | | Stem | text | NO | | NULL | | -| Image | char(43) | NO | | NULL | | | Feedback | text | NO | | NULL | | +| Image | varchar(43) | NO | | NULL | | | NumHits | int(11) | NO | | 0 | | | NumHitsNotBlank | int(11) | NO | | 0 | | | Score | double | NO | | 0 | | +-----------------+---------------------------------------------------------------------------+------+-----+---------+----------------+ -11 rows in set (0.01 sec) +11 rows in set (0.00 sec) */ DB_CreateTable ("CREATE TABLE IF NOT EXISTS tst_questions (" "QstCod INT NOT NULL AUTO_INCREMENT," @@ -2433,8 +2435,8 @@ mysql> DESCRIBE tst_questions; "AnsType ENUM ('int','float','true_false','unique_choice','multiple_choice','text') NOT NULL," "Shuffle ENUM('N','Y') NOT NULL," "Stem TEXT NOT NULL," - "Image CHAR(43) NOT NULL," "Feedback TEXT NOT NULL," + "Image VARCHAR(43) NOT NULL," "NumHits INT NOT NULL DEFAULT 0," "NumHitsNotBlank INT NOT NULL DEFAULT 0," "Score DOUBLE PRECISION NOT NULL DEFAULT 0," diff --git a/swad_image.c b/swad_image.c index d25763f88..9deae8826 100644 --- a/swad_image.c +++ b/swad_image.c @@ -321,8 +321,9 @@ void Img_ShowImage (struct Image *Image,const char *ClassImg) FileNameImgPriv); /***** Show image *****/ - fprintf (Gbl.F.Out,"\"\"" - "
", + fprintf (Gbl.F.Out,"
" + "\"\"" + "
", URL,ClassImg); } diff --git a/swad_test.c b/swad_test.c index a1fab49c9..6f9a96b18 100644 --- a/swad_test.c +++ b/swad_test.c @@ -216,11 +216,15 @@ static int Tst_CountNumTagsInList (void); static int Tst_CountNumAnswerTypesInList (void); static void Tst_PutFormEditOneQst (char *Stem,char *Feedback); +static void Tst_InitImagesOfQuestion (void); + static void Tst_GetQstDataFromDB (char *Stem,char *Feedback); static void Tst_GetImageNameFromDB (unsigned NumOpt,char *ImageName); static Tst_AnswerType_t Tst_ConvertFromUnsignedStrToAnsTyp (const char *UnsignedStr); static void Tst_GetQstFromForm (char *Stem,char *Feedback); +static void Tst_MoveImagesToDefinitiveDirectories (void); + static long Tst_GetTagCodFromTagTxt (const char *TagTxt); static long Tst_CreateNewTag (long CrsCod,const char *TagTxt); static void Tst_EnableOrDisableTag (long TagCod,bool TagHidden); @@ -233,6 +237,10 @@ static void Tst_InsertAnswersIntoDB (void); static void Tst_RemAnsFromQst (void); static void Tst_RemTagsFromQst (void); static void Tst_RemoveUnusedTagsFromCurrentCrs (void); + +static void Tst_RemoveImgFilesFromStemOfQsts (long CrsCod,long QstCod); +static void Tst_RemoveImgFilesFromAnsOfQsts (long CrsCod,long QstCod,unsigned AnsInd); + static void Tst_FreeTextChoiceAnswer (unsigned NumOpt); static unsigned Tst_GetNumTstQuestions (Sco_Scope_t Scope,Tst_AnswerType_t AnsType,struct Tst_Stats *Stats); @@ -2728,7 +2736,7 @@ static void Tst_ListOneOrMoreQuestionsToEdit (unsigned long NumRows,MYSQL_RES *m } fprintf (Gbl.F.Out,""); - /* Write the stem (row[4]), the feedback (row[6]) and the answers */ + /* Write the stem (row[4]), the image (row[5],vthe feedback (row[6]) and the answers */ fprintf (Gbl.F.Out,"", Gbl.RowEvenOdd); Tst_WriteQstStem (row[4],"TEST_EDI"); @@ -2834,7 +2842,7 @@ unsigned Tst_GetAnswersQst (long QstCod,MYSQL_RES **mysql_res,bool Shuffle) unsigned long NumRows; /***** Get answers of a question from database *****/ - sprintf (Query,"SELECT AnsInd,Answer,Correct,Feedback" + sprintf (Query,"SELECT AnsInd,Answer,Correct,Feedback,Image" " FROM tst_answers" " WHERE QstCod='%ld' ORDER BY %s", QstCod, @@ -2929,9 +2937,18 @@ static void Tst_WriteAnswersOfAQstEdit (long QstCod) Feedback,LengthFeedback,false); } + /* Copy image */ + if (row[4][0]) + { + Gbl.Test.Answer.Options[NumOpt].Image.Status = Img_NAME_STORED_IN_DB; + strncpy (Gbl.Test.Answer.Options[NumOpt].Image.Name,row[4],Cry_LENGTH_ENCRYPTED_STR_SHA256_BASE64); + Gbl.Test.Answer.Options[NumOpt].Image.Name[Cry_LENGTH_ENCRYPTED_STR_SHA256_BASE64] = '\0'; + } + /* Put an icon that indicates whether the answer is correct or wrong */ fprintf (Gbl.F.Out,"" - "",Gbl.RowEvenOdd); + "", + Gbl.RowEvenOdd); if (Str_ConvertToUpperLetter (row[2][0]) == 'Y') fprintf (Gbl.F.Out,"\"%s\"", 'a' + (char) NumOpt); - /* Write the text of the answer */ - fprintf (Gbl.F.Out,"" - "%s" - "", + /* Write the text of the answer and the image (row[4]) */ + fprintf (Gbl.F.Out,"" + "
" + "%s", Answer); + if (Gbl.Test.Answer.Options[NumOpt].Image.Name[0]) + Img_ShowImage (&Gbl.Test.Answer.Options[NumOpt].Image,"TEST_IMG_EDIT_LIST"); + fprintf (Gbl.F.Out,"
"); /* Write the text of the feedback */ - fprintf (Gbl.F.Out,""); + fprintf (Gbl.F.Out,"
"); if (LengthFeedback) fprintf (Gbl.F.Out,"%s",Feedback); - fprintf (Gbl.F.Out,"" + fprintf (Gbl.F.Out,"
" + "" ""); /* Free memory allocated for the answer and the feedback */ @@ -3212,6 +3233,14 @@ static void Tst_WriteChoiceAnsSeeExam (unsigned NumQst,long QstCod,bool Shuffle) Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, Gbl.Test.Answer.Options[NumOpt].Text,Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false); + /***** Copy image *****/ + if (row[4][0]) + { + Gbl.Test.Answer.Options[NumOpt].Image.Status = Img_NAME_STORED_IN_DB; + strncpy (Gbl.Test.Answer.Options[NumOpt].Image.Name,row[4],Cry_LENGTH_ENCRYPTED_STR_SHA256_BASE64); + Gbl.Test.Answer.Options[NumOpt].Image.Name[Cry_LENGTH_ENCRYPTED_STR_SHA256_BASE64] = '\0'; + } + /***** Write selectors and letter of this option *****/ fprintf (Gbl.F.Out,"" ""); @@ -3231,10 +3260,12 @@ static void Tst_WriteChoiceAnsSeeExam (unsigned NumQst,long QstCod,bool Shuffle) /***** Write the option text *****/ fprintf (Gbl.F.Out,"" - "%s" - "" - "", + "%s", Gbl.Test.Answer.Options[NumOpt].Text); + if (Gbl.Test.Answer.Options[NumOpt].Image.Name[0]) + Img_ShowImage (&Gbl.Test.Answer.Options[NumOpt].Image,"TEST_IMG_SHOW"); + fprintf (Gbl.F.Out,"" + ""); } /***** End of table *****/ @@ -3275,6 +3306,7 @@ static void Tst_WriteChoiceAnsAssessExam (unsigned NumQst,MYSQL_RES *mysql_res, row[1] Answer row[2] Correct row[3] Feedback + row[4] Image */ for (NumOpt = 0; NumOpt < Gbl.Test.Answer.NumOptions; @@ -3306,6 +3338,14 @@ static void Tst_WriteChoiceAnsAssessExam (unsigned NumQst,MYSQL_RES *mysql_res, Gbl.Test.Answer.Options[NumOpt].Feedback,Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,false); } + /***** Copy image *****/ + if (row[4][0]) + { + Gbl.Test.Answer.Options[NumOpt].Image.Status = Img_NAME_STORED_IN_DB; + strncpy (Gbl.Test.Answer.Options[NumOpt].Image.Name,row[4],Cry_LENGTH_ENCRYPTED_STR_SHA256_BASE64); + Gbl.Test.Answer.Options[NumOpt].Image.Name[Cry_LENGTH_ENCRYPTED_STR_SHA256_BASE64] = '\0'; + } + /***** Assign correctness (row[2]) of this answer (this option) *****/ Gbl.Test.Answer.Options[NumOpt].Correct = (Str_ConvertToUpperLetter (row[2][0]) == 'Y'); } @@ -3394,9 +3434,11 @@ static void Tst_WriteChoiceAnsAssessExam (unsigned NumQst,MYSQL_RES *mysql_res, /* Answer text and feedback */ fprintf (Gbl.F.Out,"" "
" - "%s" - "
", + "%s", Gbl.Test.Answer.Options[Indexes[NumOpt]].Text); + if (Gbl.Test.Answer.Options[Indexes[NumOpt]].Image.Name[0]) + Img_ShowImage (&Gbl.Test.Answer.Options[Indexes[NumOpt]].Image,"TEST_IMG_SHOW"); + fprintf (Gbl.F.Out,""); if (Gbl.Test.Config.FeedbackType == Tst_FEEDBACK_FULL_FEEDBACK) if (Gbl.Test.Answer.Options[Indexes[NumOpt]].Feedback) if (Gbl.Test.Answer.Options[Indexes[NumOpt]].Feedback[0]) @@ -4601,9 +4643,6 @@ void Tst_InitQst (void) Gbl.Test.Stem.Length = 0; Gbl.Test.Feedback.Text = NULL; Gbl.Test.Feedback.Length = 0; - Gbl.Test.Image.Action = Img_ACTION_NO_IMAGE; - Gbl.Test.Image.Status = Img_FILE_NONE; - Gbl.Test.Image.Name[0] = '\0'; Gbl.Test.Shuffle = false; Gbl.Test.AnswerType = Tst_ANS_UNIQUE_CHOICE; Gbl.Test.Answer.NumOptions = 0; @@ -4615,13 +4654,33 @@ void Tst_InitQst (void) Gbl.Test.Answer.Options[NumOpt].Correct = false; Gbl.Test.Answer.Options[NumOpt].Text = NULL; Gbl.Test.Answer.Options[NumOpt].Feedback = NULL; - Gbl.Test.Answer.Options[NumOpt].Image.Action = Img_ACTION_NO_IMAGE; - Gbl.Test.Answer.Options[NumOpt].Image.Status = Img_FILE_NONE; - Gbl.Test.Answer.Options[NumOpt].Image.Name[0] = '\0'; } Gbl.Test.Answer.Integer = 0; Gbl.Test.Answer.FloatingPoint[0] = Gbl.Test.Answer.FloatingPoint[1] = 0.0; + + Tst_InitImagesOfQuestion (); + } + +/*****************************************************************************/ +/***************** Initialize images of a question to zero *******************/ +/*****************************************************************************/ + +static void Tst_InitImagesOfQuestion (void) + { + unsigned NumOpt; + + Gbl.Test.Image.Action = Img_ACTION_NO_IMAGE; + Gbl.Test.Image.Status = Img_FILE_NONE; + Gbl.Test.Image.Name[0] = '\0'; + for (NumOpt = 0; + NumOpt < Tst_MAX_OPTIONS_PER_QUESTION; + NumOpt++) + { + Gbl.Test.Answer.Options[NumOpt].Image.Action = Img_ACTION_NO_IMAGE; + Gbl.Test.Answer.Options[NumOpt].Image.Status = Img_FILE_NONE; + Gbl.Test.Answer.Options[NumOpt].Image.Name[0] = '\0'; + } } /*****************************************************************************/ @@ -4736,6 +4795,14 @@ static void Tst_GetQstDataFromDB (char *Stem,char *Feedback) Gbl.Test.Answer.Options[NumOpt].Feedback[Tst_MAX_BYTES_ANSWER_OR_FEEDBACK] = '\0'; } + /* Copy image */ + if (row[4][0]) + { + Gbl.Test.Answer.Options[NumOpt].Image.Status = Img_NAME_STORED_IN_DB; + strncpy (Gbl.Test.Answer.Options[NumOpt].Image.Name,row[4],Cry_LENGTH_ENCRYPTED_STR_SHA256_BASE64); + Gbl.Test.Answer.Options[NumOpt].Image.Name[Cry_LENGTH_ENCRYPTED_STR_SHA256_BASE64] = '\0'; + } + Gbl.Test.Answer.Options[NumOpt].Correct = (Str_ConvertToUpperLetter (row[2][0]) == 'Y'); break; default: @@ -4840,17 +4907,8 @@ void Tst_ReceiveQst (void) /***** Make sure that tags, text and answer are not empty *****/ if (Tst_CheckIfQstFormatIsCorrectAndCountNumOptions ()) { - if (Gbl.Test.Image.Action != Img_ACTION_KEEP_IMAGE) // Don't keep the current image - /* Remove possible file with the old image - (the new image file is already processed - and moved to the definitive directory) */ - Tst_RemoveImageFilesFromQstsInCrs (Gbl.CurrentCrs.Crs.CrsCod,Gbl.Test.QstCod); - - if ((Gbl.Test.Image.Action == Img_ACTION_NEW_IMAGE || // Upload new image - Gbl.Test.Image.Action == Img_ACTION_CHANGE_IMAGE) && // Replace existing image by new image - Gbl.Test.Image.Status == Img_FILE_PROCESSED) // The new image received has been processed - /* Move processed image to definitive directory */ - Img_MoveImageToDefinitiveDirectory (&Gbl.Test.Image); + /***** Move images to definitive directories *****/ + Tst_MoveImagesToDefinitiveDirectories (); /***** Insert or update question, tags and answer in the database *****/ Tst_InsertOrUpdateQstTagsAnsIntoDB (); @@ -4860,11 +4918,8 @@ void Tst_ReceiveQst (void) } else // Question is wrong { - /***** Whether an image has been received or not, - reset status and image *****/ - Gbl.Test.Image.Action = Img_ACTION_NO_IMAGE; - Gbl.Test.Image.Status = Img_FILE_NONE; - Gbl.Test.Image.Name[0] = '\0'; + /***** Whether images has been received or not, reset images *****/ + Tst_InitImagesOfQuestion (); /***** Put form to edit question again *****/ Tst_PutFormEditOneQst (Stem,Feedback); @@ -5242,6 +5297,51 @@ bool Tst_CheckIfQstFormatIsCorrectAndCountNumOptions (void) return true; // Question format without errors } +/*****************************************************************************/ +/* Move images associates to a test question to their definitive directories */ +/*****************************************************************************/ + +static void Tst_MoveImagesToDefinitiveDirectories (void) + { + unsigned NumOpt; + + /****** Move image associated to question stem *****/ + if (Gbl.Test.Image.Action != Img_ACTION_KEEP_IMAGE) // Don't keep the current image + /* Remove possible file with the old image + (the new image file is already processed + and moved to the definitive directory) */ + Tst_RemoveImgFilesFromStemOfQsts (Gbl.CurrentCrs.Crs.CrsCod, + Gbl.Test.QstCod); + + if ((Gbl.Test.Image.Action == Img_ACTION_NEW_IMAGE || // Upload new image + Gbl.Test.Image.Action == Img_ACTION_CHANGE_IMAGE) && // Replace existing image by new image + Gbl.Test.Image.Status == Img_FILE_PROCESSED) // The new image received has been processed + /* Move processed image to definitive directory */ + Img_MoveImageToDefinitiveDirectory (&Gbl.Test.Image); + + /****** Move images associated to answers *****/ + if (Gbl.Test.AnswerType == Tst_ANS_UNIQUE_CHOICE || + Gbl.Test.AnswerType == Tst_ANS_MULTIPLE_CHOICE) + for (NumOpt = 0; + NumOpt < Gbl.Test.Answer.NumOptions; + NumOpt++) + { + if (Gbl.Test.Answer.Options[NumOpt].Image.Action != Img_ACTION_KEEP_IMAGE) // Don't keep the current image + /* Remove possible file with the old image + (the new image file is already processed + and moved to the definitive directory) */ + Tst_RemoveImgFilesFromAnsOfQsts (Gbl.CurrentCrs.Crs.CrsCod, + Gbl.Test.QstCod, + NumOpt); + + if ((Gbl.Test.Answer.Options[NumOpt].Image.Action == Img_ACTION_NEW_IMAGE || // Upload new image + Gbl.Test.Answer.Options[NumOpt].Image.Action == Img_ACTION_CHANGE_IMAGE) && // Replace existing image by new image + Gbl.Test.Answer.Options[NumOpt].Image.Status == Img_FILE_PROCESSED) // The new image received has been processed + /* Move processed image to definitive directory */ + Img_MoveImageToDefinitiveDirectory (&Gbl.Test.Answer.Options[NumOpt].Image); + } + } + /*****************************************************************************/ /******************** Get a integer number from a string *********************/ /*****************************************************************************/ @@ -5384,8 +5484,12 @@ void Tst_RemoveQst (void) Par_GetParToText ("OnlyThisQst",YN,1); EditingOnlyThisQst = (Str_ConvertToUpperLetter (YN[0]) == 'Y'); - /***** Remove image associated to question *****/ - Tst_RemoveImageFilesFromQstsInCrs (Gbl.CurrentCrs.Crs.CrsCod,Gbl.Test.QstCod); + /***** Remove images associated to question *****/ + Tst_RemoveImgFilesFromAnsOfQsts (Gbl.CurrentCrs.Crs.CrsCod, + Gbl.Test.QstCod, + Tst_MAX_OPTIONS_PER_QUESTION); // All answers + Tst_RemoveImgFilesFromStemOfQsts (Gbl.CurrentCrs.Crs.CrsCod, + Gbl.Test.QstCod); /***** Remove the question from all the tables *****/ /* Remove answers and tags from this test question */ @@ -5539,11 +5643,11 @@ static void Tst_InsertOrUpdateQstIntoDB (void) Gbl.Test.Image.Name, Gbl.Test.Feedback.Text ? Gbl.Test.Feedback.Text : "", Gbl.Test.QstCod,Gbl.CurrentCrs.Crs.CrsCod); + DB_QueryUPDATE (Query,"can not update question"); /* Update image status */ if (Gbl.Test.Image.Name[0]) Gbl.Test.Image.Status = Img_NAME_STORED_IN_DB; - DB_QueryUPDATE (Query,"can not update question"); /* Remove answers and tags from this test question */ Tst_RemAnsFromQst (); @@ -5554,47 +5658,6 @@ static void Tst_InsertOrUpdateQstIntoDB (void) free ((void *) Query); } -/*****************************************************************************/ -/******** Remove one or more image files associated to test questions ********/ -/*****************************************************************************/ -// Use question code <= 0 to remove all images associated to questions in course - -void Tst_RemoveImageFilesFromQstsInCrs (long CrsCod, - long QstCod) // <= 0 ==> all questions in course - { - char Query[256]; - MYSQL_RES *mysql_res; - MYSQL_ROW row; - unsigned NumImages; - unsigned NumImg; - - /***** Get names of images associated to test questions from database *****/ - if (QstCod > 0) // Only one question - sprintf (Query,"SELECT Image FROM tst_questions" - " WHERE QstCod='%ld' AND CrsCod='%ld'", - QstCod,CrsCod); - else // All questions in the course - sprintf (Query,"SELECT Image FROM tst_questions" - " WHERE CrsCod='%ld'", - CrsCod); - NumImages = (unsigned) DB_QuerySELECT (Query,&mysql_res,"can not get test images"); - - /***** Go over result removing image files *****/ - for (NumImg = 0; - NumImg < NumImages; - NumImg++) - { - /***** Get image name (row[0]) *****/ - row = mysql_fetch_row (mysql_res); - - /***** Remove image file *****/ - Img_RemoveImageFile (row[0]); - } - - /***** Free structure that stores the query result *****/ - DB_FreeMySQLResult (&mysql_res); - } - /*****************************************************************************/ /*********************** Insert tags in the tags table ***********************/ /*****************************************************************************/ @@ -5680,14 +5743,20 @@ static void Tst_InsertAnswersIntoDB (void) NumOpt++) if (Gbl.Test.Answer.Options[NumOpt].Text[0]) { - sprintf (Query,"INSERT INTO tst_answers (QstCod,AnsInd,Answer,Feedback,Correct)" - " VALUES (%ld,%u,'%s','%s','%c')", + sprintf (Query,"INSERT INTO tst_answers" + " (QstCod,AnsInd,Answer,Feedback,Image,Correct)" + " VALUES (%ld,%u,'%s','%s','%s','%c')", Gbl.Test.QstCod,NumOpt, Gbl.Test.Answer.Options[NumOpt].Text, Gbl.Test.Answer.Options[NumOpt].Feedback, + Gbl.Test.Answer.Options[NumOpt].Image.Name, Gbl.Test.Answer.Options[NumOpt].Correct ? 'Y' : 'N'); DB_QueryINSERT (Query,"can not create answer"); + + /* Update image status */ + if (Gbl.Test.Answer.Options[NumOpt].Image.Name[0]) + Gbl.Test.Answer.Options[NumOpt].Image.Status = Img_NAME_STORED_IN_DB; } break; default: @@ -5742,6 +5811,108 @@ static void Tst_RemoveUnusedTagsFromCurrentCrs (void) DB_QueryDELETE (Query,"can not remove unused tags"); } +/*****************************************************************************/ +/**** Remove one or more image files associated to stems of test questions ***/ +/*****************************************************************************/ +// Use question code <= 0 to remove all images associated to stems of questions in course + +static void Tst_RemoveImgFilesFromStemOfQsts (long CrsCod, + long QstCod) // <= 0 ==> all questions in course + { + char Query[256]; + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NumImages; + unsigned NumImg; + + /***** Get names of images associated to stems of test questions from database *****/ + if (QstCod > 0) // Only one question + sprintf (Query,"SELECT Image FROM tst_questions" + " WHERE QstCod='%ld' AND CrsCod='%ld'", + QstCod,CrsCod); + else // All questions in the course + sprintf (Query,"SELECT Image FROM tst_questions" + " WHERE CrsCod='%ld'", + CrsCod); + NumImages = (unsigned) DB_QuerySELECT (Query,&mysql_res,"can not get test images"); + + /***** Go over result removing image files *****/ + for (NumImg = 0; + NumImg < NumImages; + NumImg++) + { + /***** Get image name (row[0]) *****/ + row = mysql_fetch_row (mysql_res); + + /***** Remove image file *****/ + Img_RemoveImageFile (row[0]); + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/** Remove one or more image files associated to answers of test questions ***/ +/*****************************************************************************/ +// Use question code <= 0 to remove all images associated to answers of questions in course +// Use AnsInd == Tst_MAX_OPTIONS_PER_QUESTION to remove all images associated to answers of a question + +static void Tst_RemoveImgFilesFromAnsOfQsts (long CrsCod, + long QstCod, // <= 0 ==> all questions in course + unsigned AnsInd) // == Tst_MAX_OPTIONS_PER_QUESTION ==> all answers of a question + { + char Query[512]; + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NumImages; + unsigned NumImg; + + /***** Get names of images associated to answers of test questions from database *****/ + if (QstCod > 0) // Only one question + { + if (AnsInd < Tst_MAX_OPTIONS_PER_QUESTION) // Only one answer + sprintf (Query,"SELECT tst_answers.Image" + " FROM tst_questions,tst_answers" + " WHERE tst_questions.CrsCod='%ld'" // Extra check + " AND tst_questions.QstCod='%ld'" // Extra check + " AND tst_questions.QstCod=tst_answers.QstCod" + " AND tst_answers.QstCod='%ld'" + " AND tst_answers.AnsInd='%u'", + CrsCod,QstCod,QstCod,AnsInd); + else // All answers of a question + sprintf (Query,"SELECT tst_answers.Image" + " FROM tst_questions,tst_answers" + " WHERE tst_questions.CrsCod='%ld'" // Extra check + " AND tst_questions.QstCod='%ld'" // Extra check + " AND tst_questions.QstCod=tst_answers.QstCod" + " AND tst_answers.QstCod='%ld'", + CrsCod,QstCod,QstCod); + } + else // All answers of all questions in the course + sprintf (Query,"SELECT tst_answers.Image" + " FROM tst_questions,tst_answers" + " WHERE tst_questions.CrsCod='%ld'" + " AND tst_questions.QstCod=tst_answers.QstCod", + CrsCod); + NumImages = (unsigned) DB_QuerySELECT (Query,&mysql_res,"can not get test images"); + + /***** Go over result removing image files *****/ + for (NumImg = 0; + NumImg < NumImages; + NumImg++) + { + /***** Get image name (row[0]) *****/ + row = mysql_fetch_row (mysql_res); + + /***** Remove image file *****/ + Img_RemoveImageFile (row[0]); + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + /*****************************************************************************/ /******************* Allocate memory for a choice answer *********************/ /*****************************************************************************/ @@ -7361,8 +7532,11 @@ void Tst_RemoveCrsTests (long CrsCod) /***** Remove files with images associated to test questions in the course *****/ - Tst_RemoveImageFilesFromQstsInCrs (CrsCod, - -1L); // All questions in the course + Tst_RemoveImgFilesFromAnsOfQsts (CrsCod, + -1L, // All answers of questions in the course + Tst_MAX_OPTIONS_PER_QUESTION); // does not matter + Tst_RemoveImgFilesFromStemOfQsts (CrsCod, + -1L); // All questions in the course /***** Remove test questions in the course *****/ sprintf (Query,"DELETE FROM tst_questions WHERE CrsCod='%ld'", diff --git a/swad_test.h b/swad_test.h index 1d2cb1472..f2911ce9d 100644 --- a/swad_test.h +++ b/swad_test.h @@ -151,8 +151,6 @@ void Tst_RemoveQst (void); void Tst_ChangeShuffleQst (void); void Tst_InsertOrUpdateQstTagsAnsIntoDB (void); -void Tst_RemoveImageFilesFromQstsInCrs (long CrsCod,long QstCod); - int Tst_AllocateTextChoiceAnswer (unsigned NumOpt); void Tst_FreeTextChoiceAnswers (void); void Tst_FreeTagsList (void);