From 7a73b7361823b08a01d87ee1947a31b85e060e30 Mon Sep 17 00:00:00 2001 From: acanas Date: Sun, 17 May 2020 17:11:04 +0200 Subject: [PATCH] Version19.235 --- Makefile | 6 +- swad_API.c | 4 +- swad_action.c | 1 + swad_changelog.h | 3 +- swad_database.c | 2 +- swad_tag.c | 658 ++++++++++++++++++++++++++++++++++++++++++++ swad_tag.h | 80 ++++++ swad_test.c | 673 +++------------------------------------------ swad_test.h | 7 +- swad_test_import.c | 4 +- swad_test_type.h | 16 +- 11 files changed, 785 insertions(+), 669 deletions(-) create mode 100644 swad_tag.c create mode 100644 swad_tag.h diff --git a/Makefile b/Makefile index b016ec41b..ced82a0f9 100644 --- a/Makefile +++ b/Makefile @@ -59,9 +59,9 @@ OBJS = swad_account.o swad_action.o swad_agenda.o swad_alert.o \ swad_scope.o swad_search.o swad_session.o swad_setting.o \ swad_statistic.o swad_string.o swad_survey.o swad_syllabus.o \ swad_system_config.o \ - swad_tab.o swad_test.o swad_test_config.o swad_test_import.o \ - swad_test_print.o swad_test_visibility.o swad_theme.o swad_timeline.o \ - swad_timetable.o \ + swad_tab.o swad_tag.o swad_test.o swad_test_config.o \ + swad_test_import.o swad_test_print.o swad_test_visibility.o \ + swad_theme.o swad_timeline.o swad_timetable.o \ swad_user.o \ swad_xml.o \ swad_zip.o diff --git a/swad_API.c b/swad_API.c index 746c50784..5a8bef1dd 100644 --- a/swad_API.c +++ b/swad_API.c @@ -4248,9 +4248,9 @@ static int API_GetTstTags (struct soap *soap, /* Get tag text (row[1]) */ getTestsOut->tagsArray.__ptr[NumRow].tagText = - (char *) soap_malloc (soap,Tst_MAX_BYTES_TAG + 1); + (char *) soap_malloc (soap,Tag_MAX_BYTES_TAG + 1); Str_Copy (getTestsOut->tagsArray.__ptr[NumRow].tagText,row[1], - Tst_MAX_BYTES_TAG); + Tag_MAX_BYTES_TAG); } } diff --git a/swad_action.c b/swad_action.c index 3c66b7208..f6851e59e 100644 --- a/swad_action.c +++ b/swad_action.c @@ -92,6 +92,7 @@ #include "swad_survey.h" #include "swad_system_config.h" #include "swad_tab.h" +#include "swad_tag.h" #include "swad_test_import.h" #include "swad_timeline.h" #include "swad_timetable.h" diff --git a/swad_changelog.h b/swad_changelog.h index 62c5e981d..90c07615c 100644 --- a/swad_changelog.h +++ b/swad_changelog.h @@ -557,7 +557,7 @@ enscript -2 --landscape --color --file-align=2 --highlight --line-numbers -o - * En OpenSWAD: ps2pdf source.ps destination.pdf */ -#define Log_PLATFORM_VERSION "SWAD 19.234 (2020-05-17)" +#define Log_PLATFORM_VERSION "SWAD 19.235 (2020-05-17)" #define CSS_FILE "swad19.230.1.css" #define JS_FILE "swad19.230.3.js" /* @@ -565,6 +565,7 @@ TODO: Comprobar si el directorio p // Si no existe, hay que crear un nuevo directorio y meterlo en cache TODO: ¿Mover importar y exportar a icono en la esquina? + Version 19.235: May 17, 2020 New module swad_tag for question tags. (301147 lines) Version 19.234: May 17, 2020 Option to edit tags in bank of questions. (301061 lines) Copy the following 3 icons to icon public directory: sudo cp icon/tag.svg /var/www/html/swad/icon/ diff --git a/swad_database.c b/swad_database.c index aa0d71fdc..931b7e42f 100644 --- a/swad_database.c +++ b/swad_database.c @@ -3281,7 +3281,7 @@ mysql> DESCRIBE tst_tags; "TagCod INT NOT NULL AUTO_INCREMENT," "CrsCod INT NOT NULL DEFAULT -1," "ChangeTime DATETIME NOT NULL," - "TagTxt VARCHAR(2047) NOT NULL," // Tst_MAX_BYTES_TAG + "TagTxt VARCHAR(2047) NOT NULL," // Tag_MAX_BYTES_TAG "TagHidden ENUM('N','Y') NOT NULL," "UNIQUE INDEX(TagCod)," "INDEX(CrsCod,ChangeTime))"); diff --git a/swad_tag.c b/swad_tag.c new file mode 100644 index 000000000..90185e11d --- /dev/null +++ b/swad_tag.c @@ -0,0 +1,658 @@ +// swad_tag.c: tags for questions + +/* + SWAD (Shared Workspace At a Distance), + is a web platform developed at the University of Granada (Spain), + and used to support university teaching. + + This file is part of SWAD core. + Copyright (C) 1999-2020 Antonio Cañas Vargas + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +/*****************************************************************************/ +/*********************************** Headers *********************************/ +/*****************************************************************************/ + +#include // To access MySQL databases +#include // For boolean type +#include // For string functions + +#include "swad_action.h" +#include "swad_database.h" +#include "swad_form.h" +#include "swad_global.h" +#include "swad_tag.h" +#include "swad_theme.h" + +/*****************************************************************************/ +/***************************** Public constants ******************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/**************************** Private constants ******************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/******************************* Private types *******************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/************** External global variables from others modules ****************/ +/*****************************************************************************/ + +extern struct Globals Gbl; + +/*****************************************************************************/ +/************************* Private global variables **************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/***************************** Private prototypes ****************************/ +/*****************************************************************************/ + +static void Tag_EnableOrDisableTag (long TagCod,bool TagHidden); + +static long Tag_GetParamTagCode (void); + +static long Tag_GetTagCodFromTagTxt (const char *TagTxt); +static long Tag_CreateNewTag (long CrsCod,const char *TagTxt); + +static void Tag_PutIconEnable (long TagCod,const char *TagTxt); +static void Tag_PutIconDisable (long TagCod,const char *TagTxt); + +/*****************************************************************************/ +/********************************* Reset tags ********************************/ +/*****************************************************************************/ + +void Tag_ResetTags (struct Tag_Tags *Tags) + { + unsigned IndTag; + + Tags->Num = 0; + Tags->All = false; + Tags->List = NULL; + + /***** Initialize all tags in question to empty string *****/ + for (IndTag = 0; + IndTag < Tag_MAX_TAGS_PER_QUESTION; + IndTag++) + Tags->Txt[IndTag][0] = '\0'; + } + +/*****************************************************************************/ +/**************** Free memory allocated for the list of tags *****************/ +/*****************************************************************************/ + +void Tag_FreeTagsList (struct Tag_Tags *Tags) + { + if (Tags->List) + { + free (Tags->List); + Tag_ResetTags (Tags); + } + } + +/*****************************************************************************/ +/******************* Check if current course has test tags *******************/ +/*****************************************************************************/ +// Return the number of rows of the result + +bool Tag_CheckIfCurrentCrsHasTestTags (void) + { + /***** Get available tags from database *****/ + return (DB_QueryCOUNT ("can not check if course has tags", + "SELECT COUNT(*) FROM tst_tags" + " WHERE CrsCod=%ld", + Gbl.Hierarchy.Crs.CrsCod) != 0); + } + +/*****************************************************************************/ +/********* Get all (enabled or disabled) test tags for this course ***********/ +/*****************************************************************************/ +// Return the number of rows of the result + +unsigned Tag_GetAllTagsFromCurrentCrs (MYSQL_RES **mysql_res) + { + /***** Get available tags from database *****/ + return (unsigned) DB_QuerySELECT (mysql_res,"can not get available tags", + "SELECT TagCod," // row[0] + "TagTxt," // row[1] + "TagHidden" // row[2] + " FROM tst_tags" + " WHERE CrsCod=%ld" + " ORDER BY TagTxt", + Gbl.Hierarchy.Crs.CrsCod); + } + +/*****************************************************************************/ +/********************** Get enabled test tags for this course ****************/ +/*****************************************************************************/ +// Return the number of rows of the result + +unsigned Tag_GetEnabledTagsFromThisCrs (MYSQL_RES **mysql_res) + { + /***** Get available not hidden tags from database *****/ + return (unsigned) DB_QuerySELECT (mysql_res,"can not get available enabled tags", + "SELECT TagCod," // row[0] + "TagTxt" // row[1] + " FROM tst_tags" + " WHERE CrsCod=%ld AND TagHidden='N'" + " ORDER BY TagTxt", + Gbl.Hierarchy.Crs.CrsCod); + } + +/*****************************************************************************/ +/******************************* Enable a test tag ***************************/ +/*****************************************************************************/ + +void Tag_EnableTag (void) + { + long TagCod = Tag_GetParamTagCode (); + + /***** Change tag status to enabled *****/ + Tag_EnableOrDisableTag (TagCod,false); + + /***** Show again the form to edit tags *****/ + Tag_ShowFormEditTags (); + } + +/*****************************************************************************/ +/****************************** Disable a test tag ***************************/ +/*****************************************************************************/ + +void Tag_DisableTag (void) + { + long TagCod = Tag_GetParamTagCode (); + + /***** Change tag status to disabled *****/ + Tag_EnableOrDisableTag (TagCod,true); + + /***** Show again the form to edit tags *****/ + Tag_ShowFormEditTags (); + } + +/*****************************************************************************/ +/********** Change visibility of an existing tag into tst_tags table *********/ +/*****************************************************************************/ + +static void Tag_EnableOrDisableTag (long TagCod,bool TagHidden) + { + /***** Insert new tag into tst_tags table *****/ + DB_QueryUPDATE ("can not update the visibility of a tag", + "UPDATE tst_tags SET TagHidden='%c',ChangeTime=NOW()" + " WHERE TagCod=%ld AND CrsCod=%ld", + TagHidden ? 'Y' : + 'N', + TagCod,Gbl.Hierarchy.Crs.CrsCod); + } + +/*****************************************************************************/ +/************************* Get parameter with tag code ***********************/ +/*****************************************************************************/ + +static long Tag_GetParamTagCode (void) + { + long TagCod; + + /***** Get tag code *****/ + if ((TagCod = Par_GetParToLong ("TagCod")) <= 0) + Lay_ShowErrorAndExit ("Code of tag is missing."); + + return TagCod; + } + +/*****************************************************************************/ +/************************ Rename a tag of test questions *********************/ +/*****************************************************************************/ + +void Tag_RenameTag (void) + { + extern const char *Txt_The_tag_X_has_been_renamed_as_Y; + extern const char *Txt_The_tag_X_has_not_changed; + char OldTagTxt[Tag_MAX_BYTES_TAG + 1]; + char NewTagTxt[Tag_MAX_BYTES_TAG + 1]; + long ExistingTagCod; + long OldTagCod; + bool ComplexRenaming; + + /***** Get old and new tags from the form *****/ + Par_GetParToText ("OldTagTxt",OldTagTxt,Tag_MAX_BYTES_TAG); + Par_GetParToText ("NewTagTxt",NewTagTxt,Tag_MAX_BYTES_TAG); + + /***** Check that the new tag is not empty *****/ + if (NewTagTxt[0]) // New tag not empty + { + /***** Check if the old tag is equal to the new one *****/ + if (!strcmp (OldTagTxt,NewTagTxt)) // The old and the new tag + // are exactly the same (case sensitively). + // This happens when user press INTRO + // without changing anything in the form. + Ale_ShowAlert (Ale_INFO,Txt_The_tag_X_has_not_changed, + NewTagTxt); + else // The old and the new tag + // are not exactly the same (case sensitively). + { + /***** Check if renaming is complex or easy *****/ + ComplexRenaming = false; + if (strcasecmp (OldTagTxt,NewTagTxt)) // The old and the new tag + // are not the same (case insensitively) + /* Check if the new tag text is equal to any of the tags + already present in the database */ + if ((ExistingTagCod = Tag_GetTagCodFromTagTxt (NewTagTxt)) > 0) + // The new tag was already in database + ComplexRenaming = true; + + if (ComplexRenaming) // Renaming is not easy + { + /***** Complex update made to not repeat tags: + - If the new tag existed for a question ==> + delete old tag from tst_question_tags; + the new tag will remain + - If the new tag did not exist for a question ==> + change old tag to new tag in tst_question_tags *****/ + /* Get tag code of the old tag */ + if ((OldTagCod = Tag_GetTagCodFromTagTxt (OldTagTxt)) < 0) + Lay_ShowErrorAndExit ("Tag does not exists."); + + /* Create a temporary table with all the question codes + that had the new tag as one of their tags */ + DB_Query ("can not remove temporary table", + "DROP TEMPORARY TABLE IF EXISTS tst_question_tags_tmp"); + + DB_Query ("can not create temporary table", + "CREATE TEMPORARY TABLE tst_question_tags_tmp" + " ENGINE=MEMORY" + " SELECT QstCod FROM tst_question_tags" + " WHERE TagCod=%ld", + ExistingTagCod); + + /* Remove old tag in questions where it would be repeated */ + // New tag existed for a question ==> delete old tag + DB_QueryDELETE ("can not remove a tag from some questions", + "DELETE FROM tst_question_tags" + " WHERE TagCod=%ld" + " AND QstCod IN" + " (SELECT QstCod FROM tst_question_tags_tmp)", + OldTagCod); + + /* Change old tag to new tag in questions where it would not be repeated */ + // New tag did not exist for a question ==> change old tag to new tag + DB_QueryUPDATE ("can not update a tag in some questions", + "UPDATE tst_question_tags" + " SET TagCod=%ld" + " WHERE TagCod=%ld" + " AND QstCod NOT IN" + " (SELECT QstCod FROM tst_question_tags_tmp)", + ExistingTagCod, + OldTagCod); + + /* Drop temporary table, no longer necessary */ + DB_Query ("can not remove temporary table", + "DROP TEMPORARY TABLE IF EXISTS tst_question_tags_tmp"); + + /***** Delete old tag from tst_tags + because it is not longer used *****/ + DB_QueryDELETE ("can not remove old tag", + "DELETE FROM tst_tags WHERE TagCod=%ld", + OldTagCod); + } + else // Renaming is easy + { + /***** Simple update replacing each instance of the old tag by the new tag *****/ + DB_QueryUPDATE ("can not update tag", + "UPDATE tst_tags SET TagTxt='%s',ChangeTime=NOW()" + " WHERE tst_tags.CrsCod=%ld" + " AND tst_tags.TagTxt='%s'", + NewTagTxt,Gbl.Hierarchy.Crs.CrsCod,OldTagTxt); + } + + /***** Write message to show the change made *****/ + Ale_ShowAlert (Ale_SUCCESS,Txt_The_tag_X_has_been_renamed_as_Y, + OldTagTxt,NewTagTxt); + } + } + else // New tag empty + Ale_ShowAlertYouCanNotLeaveFieldEmpty (); + + /***** Show again the form to edit tags *****/ + Tag_ShowFormEditTags (); + } + +/*****************************************************************************/ +/***************** Check if this tag exists for current course ***************/ +/*****************************************************************************/ + +static long Tag_GetTagCodFromTagTxt (const char *TagTxt) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned long NumRows; + long TagCod = -1L; // -1 means that the tag does not exist in database + + /***** Get tag code from database *****/ + NumRows = DB_QuerySELECT (&mysql_res,"can not get tag", + "SELECT TagCod FROM tst_tags" + " WHERE CrsCod=%ld AND TagTxt='%s'", + Gbl.Hierarchy.Crs.CrsCod,TagTxt); + if (NumRows == 1) + { + /***** Get tag code *****/ + row = mysql_fetch_row (mysql_res); + if ((TagCod = Str_ConvertStrCodToLongCod (row[0])) < 0) + Ale_CreateAlert (Ale_ERROR,NULL, + "Wrong code of tag."); + } + else if (NumRows > 1) + Ale_CreateAlert (Ale_ERROR,NULL, + "Duplicated tag."); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + /***** Abort on error *****/ + if (Ale_GetTypeOfLastAlert () == Ale_ERROR) + Ale_ShowAlertsAndExit (); + + return TagCod; + } + +/*****************************************************************************/ +/*********************** Insert tags in the tags table ***********************/ +/*****************************************************************************/ + +void Tag_InsertTagsIntoDB (long QstCod,const struct Tag_Tags *Tags) + { + unsigned NumTag; + unsigned TagIdx; + long TagCod; + + /***** For each tag... *****/ + for (NumTag = 0, TagIdx = 0; + TagIdx < Tags->Num; + NumTag++) + if (Tags->Txt[NumTag][0]) + { + /***** Check if this tag exists for current course *****/ + if ((TagCod = Tag_GetTagCodFromTagTxt (Tags->Txt[NumTag])) < 0) + /* This tag is new for current course. Add it to tags table */ + TagCod = Tag_CreateNewTag (Gbl.Hierarchy.Crs.CrsCod,Tags->Txt[NumTag]); + + /***** Insert tag in tst_question_tags *****/ + DB_QueryINSERT ("can not create tag", + "INSERT INTO tst_question_tags" + " (QstCod,TagCod,TagInd)" + " VALUES" + " (%ld,%ld,%u)", + QstCod,TagCod,TagIdx); + + TagIdx++; + } + } + +/*****************************************************************************/ +/********************* Insert new tag into tst_tags table ********************/ +/*****************************************************************************/ + +static long Tag_CreateNewTag (long CrsCod,const char *TagTxt) + { + /***** Insert new tag into tst_tags table *****/ + return + DB_QueryINSERTandReturnCode ("can not create new tag", + "INSERT INTO tst_tags" + " (CrsCod,ChangeTime,TagTxt,TagHidden)" + " VALUES" + " (%ld,NOW(),'%s','Y')", // Hidden by default + CrsCod,TagTxt); + } + +/*****************************************************************************/ +/********************* Show a form to select test tags ***********************/ +/*****************************************************************************/ + +void Tag_ShowFormSelTags (const struct Tag_Tags *Tags, + MYSQL_RES *mysql_res, + bool ShowOnlyEnabledTags) + { + extern const char *The_ClassFormInBox[The_NUM_THEMES]; + extern const char *Txt_Tags; + extern const char *Txt_All_tags; + extern const char *Txt_Tag_not_allowed; + extern const char *Txt_Tag_allowed; + unsigned NumTag; + MYSQL_ROW row; + bool TagHidden = false; + bool Checked; + const char *Ptr; + char TagText[Tag_MAX_BYTES_TAG + 1]; + /* + row[0] TagCod + row[1] TagTxt + row[2] TagHidden + */ + HTM_TR_Begin (NULL); + + /***** Label *****/ + HTM_TD_Begin ("class=\"RT %s\"",The_ClassFormInBox[Gbl.Prefs.Theme]); + HTM_TxtF ("%s:",Txt_Tags); + HTM_TD_End (); + + /***** Select all tags *****/ + HTM_TD_Begin ("class=\"LT\""); + + HTM_TABLE_BeginPadding (2); + HTM_TR_Begin (NULL); + if (!ShowOnlyEnabledTags) + HTM_TD_Empty (1); + + HTM_TD_Begin ("class=\"LM\""); + HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]); + HTM_INPUT_CHECKBOX ("AllTags",HTM_DONT_SUBMIT_ON_CHANGE, + "value=\"Y\"%s onclick=\"togglecheckChildren(this,'ChkTag');\"", + Tags->All ? " checked=\"checked\"" : + ""); + HTM_TxtF (" %s",Txt_All_tags); + HTM_LABEL_End (); + HTM_TD_End (); + + HTM_TR_End (); + + /***** Select tags one by one *****/ + for (NumTag = 1; + NumTag <= Tags->Num; + NumTag++) + { + row = mysql_fetch_row (mysql_res); + HTM_TR_Begin (NULL); + + if (!ShowOnlyEnabledTags) + { + TagHidden = (row[2][0] == 'Y'); + HTM_TD_Begin ("class=\"LM\""); + Ico_PutIconOff (TagHidden ? "eye-slash-red.svg" : + "eye-green.svg", + TagHidden ? Txt_Tag_not_allowed : + Txt_Tag_allowed); + HTM_TD_End (); + } + + Checked = false; + if (Tags->List) + { + Ptr = Tags->List; + while (*Ptr) + { + Par_GetNextStrUntilSeparParamMult (&Ptr,TagText,Tag_MAX_BYTES_TAG); + if (!strcmp (row[1],TagText)) + { + Checked = true; + break; + } + } + } + + HTM_TD_Begin ("class=\"LM\""); + HTM_LABEL_Begin ("class=\"DAT\""); + HTM_INPUT_CHECKBOX ("ChkTag",HTM_DONT_SUBMIT_ON_CHANGE, + "value=\"%s\"%s onclick=\"checkParent(this,'AllTags');\"", + row[1], + Checked ? " checked=\"checked\"" : + ""); + HTM_TxtF (" %s",row[1]); + HTM_LABEL_End (); + HTM_TD_End (); + + HTM_TR_End (); + } + + HTM_TABLE_End (); + HTM_TD_End (); + HTM_TR_End (); + } + +/*****************************************************************************/ +/************* Show a form to enable/disable and rename test tags ************/ +/*****************************************************************************/ + +void Tag_ShowFormEditTags (void) + { + extern const char *Hlp_ASSESSMENT_Tests_configuring_tests; + extern const char *Txt_No_test_questions; + extern const char *Txt_Tags; + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NumTags; + unsigned NumTag; + long TagCod; + + /***** Get current tags in current course *****/ + if ((NumTags = Tag_GetAllTagsFromCurrentCrs (&mysql_res))) + { + /***** Begin box and table *****/ + Box_BoxTableBegin (NULL,Txt_Tags, + NULL,NULL, + Hlp_ASSESSMENT_Tests_configuring_tests,Box_NOT_CLOSABLE,2); + + /***** Show tags *****/ + for (NumTag = 0; + NumTag < NumTags; + NumTag++) + { + row = mysql_fetch_row (mysql_res); + /* + row[0] TagCod + row[1] TagTxt + row[2] TagHidden + */ + if ((TagCod = Str_ConvertStrCodToLongCod (row[0])) < 0) + Lay_ShowErrorAndExit ("Wrong code of tag."); + + HTM_TR_Begin (NULL); + + /* Form to enable / disable this tag */ + if (row[2][0] == 'Y') // Tag disabled + Tag_PutIconEnable (TagCod,row[1]); + else + Tag_PutIconDisable (TagCod,row[1]); + + /* Form to rename this tag */ + HTM_TD_Begin ("class=\"LM\""); + Frm_StartForm (ActRenTag); + Par_PutHiddenParamString (NULL,"OldTagTxt",row[1]); + HTM_INPUT_TEXT ("NewTagTxt",Tag_MAX_CHARS_TAG,row[1], + HTM_SUBMIT_ON_CHANGE, + "size=\"36\" required=\"required\""); + Frm_EndForm (); + HTM_TD_End (); + + HTM_TR_End (); + } + + /***** End table and box *****/ + Box_BoxTableEnd (); + } + else + Ale_ShowAlert (Ale_INFO,Txt_No_test_questions); + + /* Free structure that stores the query result */ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/******************* Put a link and an icon to enable a tag ******************/ +/*****************************************************************************/ + +static void Tag_PutIconEnable (long TagCod,const char *TagTxt) + { + extern const char *Txt_Tag_X_not_allowed_Click_to_allow_it; + + HTM_TD_Begin ("class=\"BM\""); + Frm_StartForm (ActEnaTag); + Par_PutHiddenParamLong (NULL,"TagCod",TagCod); + Ico_PutIconLink ("eye-slash-red.svg", + Str_BuildStringStr (Txt_Tag_X_not_allowed_Click_to_allow_it, + TagTxt)); + Str_FreeString (); + Frm_EndForm (); + HTM_TD_End (); + } + +/*****************************************************************************/ +/****************** Put a link and an icon to disable a tag ******************/ +/*****************************************************************************/ + +static void Tag_PutIconDisable (long TagCod,const char *TagTxt) + { + extern const char *Txt_Tag_X_allowed_Click_to_disable_it; + + HTM_TD_Begin ("class=\"BM\""); + Frm_StartForm (ActDisTag); + Par_PutHiddenParamLong (NULL,"TagCod",TagCod); + Ico_PutIconLink ("eye-green.svg", + Str_BuildStringStr (Txt_Tag_X_allowed_Click_to_disable_it, + TagTxt)); + Str_FreeString (); + Frm_EndForm (); + HTM_TD_End (); + } + +/*****************************************************************************/ +/************************** Remove tags from a question **********************/ +/*****************************************************************************/ + +void Tag_RemTagsFromQst (long QstCod) + { + /***** Remove tags *****/ + DB_QueryDELETE ("can not remove the tags of a question", + "DELETE FROM tst_question_tags WHERE QstCod=%ld", + QstCod); + } + +/*****************************************************************************/ +/********************** Remove unused tags in a course ***********************/ +/*****************************************************************************/ + +void Tag_RemoveUnusedTagsFromCrs (long CrsCod) + { + /***** Remove unused tags from tst_tags *****/ + DB_QueryDELETE ("can not remove unused tags", + "DELETE FROM tst_tags" + " WHERE CrsCod=%ld AND TagCod NOT IN" + " (SELECT DISTINCT tst_question_tags.TagCod" + " FROM tst_questions,tst_question_tags" + " WHERE tst_questions.CrsCod=%ld" + " AND tst_questions.QstCod=tst_question_tags.QstCod)", + CrsCod, + CrsCod); + } diff --git a/swad_tag.h b/swad_tag.h new file mode 100644 index 000000000..af2f1bf1a --- /dev/null +++ b/swad_tag.h @@ -0,0 +1,80 @@ +// swad_tag.h: tags for questions + +#ifndef _SWAD_TAG +#define _SWAD_TAG +/* + SWAD (Shared Workspace At a Distance in Spanish), + is a web platform developed at the University of Granada (Spain), + and used to support university teaching. + + This file is part of SWAD core. + Copyright (C) 1999-2020 Antonio Cañas Vargas + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +/*****************************************************************************/ +/********************************* Headers ***********************************/ +/*****************************************************************************/ + +#include "swad_string.h" + +/*****************************************************************************/ +/***************************** Public constants ******************************/ +/*****************************************************************************/ + +#define Tag_MAX_TAGS_PER_QUESTION 5 + +#define Tag_MAX_CHARS_TAG (128 - 1) // 127 +#define Tag_MAX_BYTES_TAG ((Tag_MAX_CHARS_TAG + 1) * Str_MAX_BYTES_PER_CHAR - 1) // 2047 + +#define Tag_MAX_BYTES_TAGS_LIST (16 * 1024) + +/*****************************************************************************/ +/******************************* Public types ********************************/ +/*****************************************************************************/ + +struct Tag_Tags + { + unsigned Num; + bool All; + char *List; + char Txt[Tag_MAX_TAGS_PER_QUESTION][Tag_MAX_BYTES_TAG + 1]; + }; + +/*****************************************************************************/ +/***************************** Public prototypes *****************************/ +/*****************************************************************************/ + +void Tag_ResetTags (struct Tag_Tags *Tags); +void Tag_FreeTagsList (struct Tag_Tags *Tags); + +bool Tag_CheckIfCurrentCrsHasTestTags (void); +unsigned Tag_GetAllTagsFromCurrentCrs (MYSQL_RES **mysql_res); +unsigned Tag_GetEnabledTagsFromThisCrs (MYSQL_RES **mysql_res); + +void Tag_EnableTag (void); +void Tag_DisableTag (void); +void Tag_RenameTag (void); + +void Tag_InsertTagsIntoDB (long QstCod,const struct Tag_Tags *Tags); + +void Tag_ShowFormSelTags (const struct Tag_Tags *Tags, + MYSQL_RES *mysql_res, + bool ShowOnlyEnabledTags); +void Tag_ShowFormEditTags (void); + +void Tag_RemTagsFromQst (long QstCod); +void Tag_RemoveUnusedTagsFromCrs (long CrsCod); + +#endif diff --git a/swad_test.c b/swad_test.c index 7817cc31a..b0e46e8ee 100644 --- a/swad_test.c +++ b/swad_test.c @@ -88,8 +88,6 @@ const char *Tst_StrAnswerTypesDB[Tst_NUM_ANS_TYPES] = /**************************** 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 @@ -125,8 +123,6 @@ extern struct Globals Gbl; static void Tst_TstConstructor (struct Tst_Test *Test); static void Tst_TstDestructor (struct Tst_Test *Test); -static void Tst_ResetTags (struct Tst_Tags *Tags); -static void Tst_FreeTagsList (struct Tst_Tags *Tags); static void Tst_ShowFormRequestTest (struct Tst_Test *Test); @@ -158,17 +154,6 @@ static void Tst_PutIconsBankQsts (void *Test); static void Tst_PutIconsTests (__attribute__((unused)) void *Args); static void Tst_PutButtonToAddQuestion (void); -static long Tag_GetParamTagCode (void); -static bool Tst_CheckIfCurrentCrsHasTestTags (void); -static unsigned Tst_GetAllTagsFromCurrentCrs (MYSQL_RES **mysql_res); -static unsigned Tst_GetEnabledTagsFromThisCrs (MYSQL_RES **mysql_res); - -static void Tag_ShowFormSelTags (const struct Tst_Tags *Tags, - MYSQL_RES *mysql_res, - bool ShowOnlyEnabledTags); -static void Tag_PutIconEnable (long TagCod,const char *TagTxt); -static void Tag_PutIconDisable (long TagCod,const char *TagTxt); - static void Tst_ShowFormConfigTst (void); static void Tst_PutInputFieldNumQst (const char *Field,const char *Label, @@ -221,7 +206,7 @@ static bool Tst_GetParamsTst (struct Tst_Test *Test, Tst_ActionToDoWithQuestions_t ActionToDoWithQuestions); static unsigned Tst_GetParamNumTst (void); static unsigned Tst_GetParamNumQsts (void); -static unsigned Tst_CountNumTagsInList (const struct Tst_Tags *Tags); +static unsigned Tst_CountNumTagsInList (const struct Tag_Tags *Tags); static int Tst_CountNumAnswerTypesInList (const struct Tst_AnswerTypes *AnswerTypes); static void Tst_PutFormEditOneQst (struct Tst_Question *Question); @@ -245,10 +230,6 @@ static Tst_AnswerType_t Tst_ConvertFromUnsignedStrToAnsTyp (const char *Unsigned static void Tst_GetQstFromForm (struct Tst_Question *Question); static void Tst_MoveMediaToDefinitiveDirectories (struct Tst_Question *Question); -static long Tst_GetTagCodFromTagTxt (const char *TagTxt); -static long Tst_CreateNewTag (long CrsCod,const char *TagTxt); -static void Tst_EnableOrDisableTag (long TagCod,bool TagHidden); - static void Tst_PutParamsRemoveSelectedQsts (void *Test); static void Tst_PutIconToRemoveOneQst (void *QstCod); static void Tst_PutParamsRemoveOnlyThisQst (void *QstCod); @@ -256,12 +237,9 @@ static void Tst_PutParamsRemoveOneQstWhileEditing (void *Test); static void Tst_RemoveOneQstFromDB (long CrsCod,long QstCod); static void Tst_InsertOrUpdateQstIntoDB (struct Tst_Question *Question); -static void Tst_InsertTagsIntoDB (const struct Tst_Question *Question); static void Tst_InsertAnswersIntoDB (struct Tst_Question *Question); static void Tst_RemAnsFromQst (long QstCod); -static void Tst_RemTagsFromQst (long QstCod); -static void Tst_RemoveUnusedTagsFromCrs (long CrsCod); static void Tst_RemoveAllMedFilesFromStemOfAllQstsInCrs (long CrsCod); static void Tst_RemoveMediaFromAllAnsOfQst (long CrsCod,long QstCod); @@ -296,7 +274,7 @@ void Tst_RequestTest (void) static void Tst_TstConstructor (struct Tst_Test *Test) { /***** Reset tags *****/ - Tst_ResetTags (&Test->Tags); + Tag_ResetTags (&Test->Tags); /***** Reset answer types *****/ Test->AnswerTypes.All = false; @@ -319,39 +297,7 @@ static void Tst_TstDestructor (struct Tst_Test *Test) Tst_QstDestructor (&Test->Question); /***** Free tag list *****/ - Tst_FreeTagsList (&Test->Tags); - } - -/*****************************************************************************/ -/********************************* Reset tags ********************************/ -/*****************************************************************************/ - -static void Tst_ResetTags (struct Tst_Tags *Tags) - { - unsigned IndTag; - - Tags->Num = 0; - Tags->All = false; - Tags->List = NULL; - - /***** Initialize all tags in question to empty string *****/ - for (IndTag = 0; - IndTag < Tst_MAX_TAGS_PER_QUESTION; - IndTag++) - Tags->Txt[IndTag][0] = '\0'; - } - -/*****************************************************************************/ -/**************** Free memory allocated for the list of tags *****************/ -/*****************************************************************************/ - -static void Tst_FreeTagsList (struct Tst_Tags *Tags) - { - if (Tags->List) - { - free (Tags->List); - Tst_ResetTags (Tags); - } + Tag_FreeTagsList (&Test->Tags); } /*****************************************************************************/ @@ -376,7 +322,7 @@ static void Tst_ShowFormRequestTest (struct Tst_Test *Test) Hlp_ASSESSMENT_Tests,Box_NOT_CLOSABLE); /***** Get tags *****/ - if ((Test->Tags.Num = Tst_GetEnabledTagsFromThisCrs (&mysql_res)) != 0) + if ((Test->Tags.Num = Tag_GetEnabledTagsFromThisCrs (&mysql_res)) != 0) { /***** Check if minimum date-time of next access to test is older than now *****/ if (Tst_CheckIfNextTstAllowed ()) @@ -1235,7 +1181,7 @@ static void Tst_ShowFormRequestEditTests (struct Tst_Test *Test) Hlp_ASSESSMENT_Tests_editing_questions,Box_NOT_CLOSABLE); /***** Get tags already present in the table of questions *****/ - if ((Test->Tags.Num = Tst_GetAllTagsFromCurrentCrs (&mysql_res))) + if ((Test->Tags.Num = Tag_GetAllTagsFromCurrentCrs (&mysql_res))) { Frm_StartForm (ActLstTstQst); Par_PutHiddenParamUnsigned (NULL,"Order",(unsigned) Tst_DEFAULT_ORDER); @@ -1333,7 +1279,7 @@ static void Tst_ShowFormRequestSelectTestsForSet (struct Exa_Exams *Exams, Hlp_ASSESSMENT_Exams_questions,Box_NOT_CLOSABLE); /***** Get tags already present in the table of questions *****/ - if ((Test->Tags.Num = Tst_GetAllTagsFromCurrentCrs (&mysql_res))) + if ((Test->Tags.Num = Tag_GetAllTagsFromCurrentCrs (&mysql_res))) { Frm_StartForm (ActLstTstQstForSet); ExaSet_PutParamsOneSet (Exams); @@ -1395,7 +1341,7 @@ static void Tst_ShowFormRequestSelectTestsForGame (struct Gam_Games *Games, Hlp_ASSESSMENT_Games_questions,Box_NOT_CLOSABLE); /***** Get tags already present in the table of questions *****/ - if ((Test->Tags.Num = Tst_GetAllTagsFromCurrentCrs (&mysql_res))) + if ((Test->Tags.Num = Tag_GetAllTagsFromCurrentCrs (&mysql_res))) { Frm_StartForm (ActGamLstTstQst); Gam_PutParams (Games); @@ -1541,168 +1487,6 @@ void Tst_ShowFormConfig (void) Tst_ShowFormConfigTst (); } -/*****************************************************************************/ -/******************************* Enable a test tag ***************************/ -/*****************************************************************************/ - -void Tag_EnableTag (void) - { - long TagCod = Tag_GetParamTagCode (); - - /***** Change tag status to enabled *****/ - Tst_EnableOrDisableTag (TagCod,false); - - /***** Show again the form to edit tags *****/ - Tag_ShowFormEditTags (); - } - -/*****************************************************************************/ -/****************************** Disable a test tag ***************************/ -/*****************************************************************************/ - -void Tag_DisableTag (void) - { - long TagCod = Tag_GetParamTagCode (); - - /***** Change tag status to disabled *****/ - Tst_EnableOrDisableTag (TagCod,true); - - /***** Show again the form to edit tags *****/ - Tag_ShowFormEditTags (); - } - -/*****************************************************************************/ -/************************* Get parameter with tag code ***********************/ -/*****************************************************************************/ - -static long Tag_GetParamTagCode (void) - { - long TagCod; - - /***** Get tag code *****/ - if ((TagCod = Par_GetParToLong ("TagCod")) <= 0) - Lay_ShowErrorAndExit ("Code of tag is missing."); - - return TagCod; - } - -/*****************************************************************************/ -/************************ Rename a tag of test questions *********************/ -/*****************************************************************************/ - -void Tag_RenameTag (void) - { - extern const char *Txt_The_tag_X_has_been_renamed_as_Y; - extern const char *Txt_The_tag_X_has_not_changed; - char OldTagTxt[Tst_MAX_BYTES_TAG + 1]; - char NewTagTxt[Tst_MAX_BYTES_TAG + 1]; - long ExistingTagCod; - long OldTagCod; - bool ComplexRenaming; - - /***** Get old and new tags from the form *****/ - Par_GetParToText ("OldTagTxt",OldTagTxt,Tst_MAX_BYTES_TAG); - Par_GetParToText ("NewTagTxt",NewTagTxt,Tst_MAX_BYTES_TAG); - - /***** Check that the new tag is not empty *****/ - if (NewTagTxt[0]) // New tag not empty - { - /***** Check if the old tag is equal to the new one *****/ - if (!strcmp (OldTagTxt,NewTagTxt)) // The old and the new tag - // are exactly the same (case sensitively). - // This happens when user press INTRO - // without changing anything in the form. - Ale_ShowAlert (Ale_INFO,Txt_The_tag_X_has_not_changed, - NewTagTxt); - else // The old and the new tag - // are not exactly the same (case sensitively). - { - /***** Check if renaming is complex or easy *****/ - ComplexRenaming = false; - if (strcasecmp (OldTagTxt,NewTagTxt)) // The old and the new tag - // are not the same (case insensitively) - /* Check if the new tag text is equal to any of the tags - already present in the database */ - if ((ExistingTagCod = Tst_GetTagCodFromTagTxt (NewTagTxt)) > 0) - // The new tag was already in database - ComplexRenaming = true; - - if (ComplexRenaming) // Renaming is not easy - { - /***** Complex update made to not repeat tags: - - If the new tag existed for a question ==> - delete old tag from tst_question_tags; - the new tag will remain - - If the new tag did not exist for a question ==> - change old tag to new tag in tst_question_tags *****/ - /* Get tag code of the old tag */ - if ((OldTagCod = Tst_GetTagCodFromTagTxt (OldTagTxt)) < 0) - Lay_ShowErrorAndExit ("Tag does not exists."); - - /* Create a temporary table with all the question codes - that had the new tag as one of their tags */ - DB_Query ("can not remove temporary table", - "DROP TEMPORARY TABLE IF EXISTS tst_question_tags_tmp"); - - DB_Query ("can not create temporary table", - "CREATE TEMPORARY TABLE tst_question_tags_tmp" - " ENGINE=MEMORY" - " SELECT QstCod FROM tst_question_tags" - " WHERE TagCod=%ld", - ExistingTagCod); - - /* Remove old tag in questions where it would be repeated */ - // New tag existed for a question ==> delete old tag - DB_QueryDELETE ("can not remove a tag from some questions", - "DELETE FROM tst_question_tags" - " WHERE TagCod=%ld" - " AND QstCod IN" - " (SELECT QstCod FROM tst_question_tags_tmp)", - OldTagCod); - - /* Change old tag to new tag in questions where it would not be repeated */ - // New tag did not exist for a question ==> change old tag to new tag - DB_QueryUPDATE ("can not update a tag in some questions", - "UPDATE tst_question_tags" - " SET TagCod=%ld" - " WHERE TagCod=%ld" - " AND QstCod NOT IN" - " (SELECT QstCod FROM tst_question_tags_tmp)", - ExistingTagCod, - OldTagCod); - - /* Drop temporary table, no longer necessary */ - DB_Query ("can not remove temporary table", - "DROP TEMPORARY TABLE IF EXISTS tst_question_tags_tmp"); - - /***** Delete old tag from tst_tags - because it is not longer used *****/ - DB_QueryDELETE ("can not remove old tag", - "DELETE FROM tst_tags WHERE TagCod=%ld", - OldTagCod); - } - else // Renaming is easy - { - /***** Simple update replacing each instance of the old tag by the new tag *****/ - DB_QueryUPDATE ("can not update tag", - "UPDATE tst_tags SET TagTxt='%s',ChangeTime=NOW()" - " WHERE tst_tags.CrsCod=%ld" - " AND tst_tags.TagTxt='%s'", - NewTagTxt,Gbl.Hierarchy.Crs.CrsCod,OldTagTxt); - } - - /***** Write message to show the change made *****/ - Ale_ShowAlert (Ale_SUCCESS,Txt_The_tag_X_has_been_renamed_as_Y, - OldTagTxt,NewTagTxt); - } - } - else // New tag empty - Ale_ShowAlertYouCanNotLeaveFieldEmpty (); - - /***** Show again the form to edit tags *****/ - Tag_ShowFormEditTags (); - } - /*****************************************************************************/ /*************** Get configuration of test for current course ****************/ /*****************************************************************************/ @@ -1747,270 +1531,11 @@ bool Tst_CheckIfCourseHaveTestsAndPluggableIsUnknown (void) /***** Get if current course has tests from database *****/ if (TstCfg_GetConfigPluggable () == TstCfg_PLUGGABLE_UNKNOWN) - return Tst_CheckIfCurrentCrsHasTestTags (); // Return true if course has tests + return Tag_CheckIfCurrentCrsHasTestTags (); // Return true if course has tests return false; // Pluggable is not unknown } -/*****************************************************************************/ -/******************* Check if current course has test tags *******************/ -/*****************************************************************************/ -// Return the number of rows of the result - -static bool Tst_CheckIfCurrentCrsHasTestTags (void) - { - /***** Get available tags from database *****/ - return (DB_QueryCOUNT ("can not check if course has tags", - "SELECT COUNT(*) FROM tst_tags" - " WHERE CrsCod=%ld", - Gbl.Hierarchy.Crs.CrsCod) != 0); - } - -/*****************************************************************************/ -/********* Get all (enabled or disabled) test tags for this course ***********/ -/*****************************************************************************/ -// Return the number of rows of the result - -static unsigned Tst_GetAllTagsFromCurrentCrs (MYSQL_RES **mysql_res) - { - /***** Get available tags from database *****/ - return (unsigned) DB_QuerySELECT (mysql_res,"can not get available tags", - "SELECT TagCod," // row[0] - "TagTxt," // row[1] - "TagHidden" // row[2] - " FROM tst_tags" - " WHERE CrsCod=%ld" - " ORDER BY TagTxt", - Gbl.Hierarchy.Crs.CrsCod); - } - -/*****************************************************************************/ -/********************** Get enabled test tags for this course ****************/ -/*****************************************************************************/ -// Return the number of rows of the result - -static unsigned Tst_GetEnabledTagsFromThisCrs (MYSQL_RES **mysql_res) - { - /***** Get available not hidden tags from database *****/ - return (unsigned) DB_QuerySELECT (mysql_res,"can not get available enabled tags", - "SELECT TagCod," // row[0] - "TagTxt" // row[1] - " FROM tst_tags" - " WHERE CrsCod=%ld AND TagHidden='N'" - " ORDER BY TagTxt", - Gbl.Hierarchy.Crs.CrsCod); - } - -/*****************************************************************************/ -/********************* Show a form to select test tags ***********************/ -/*****************************************************************************/ - -static void Tag_ShowFormSelTags (const struct Tst_Tags *Tags, - MYSQL_RES *mysql_res, - bool ShowOnlyEnabledTags) - { - extern const char *The_ClassFormInBox[The_NUM_THEMES]; - extern const char *Txt_Tags; - extern const char *Txt_All_tags; - extern const char *Txt_Tag_not_allowed; - extern const char *Txt_Tag_allowed; - unsigned NumTag; - MYSQL_ROW row; - bool TagHidden = false; - bool Checked; - const char *Ptr; - char TagText[Tst_MAX_BYTES_TAG + 1]; - /* - row[0] TagCod - row[1] TagTxt - row[2] TagHidden - */ - HTM_TR_Begin (NULL); - - /***** Label *****/ - HTM_TD_Begin ("class=\"RT %s\"",The_ClassFormInBox[Gbl.Prefs.Theme]); - HTM_TxtF ("%s:",Txt_Tags); - HTM_TD_End (); - - /***** Select all tags *****/ - HTM_TD_Begin ("class=\"LT\""); - - HTM_TABLE_BeginPadding (2); - HTM_TR_Begin (NULL); - if (!ShowOnlyEnabledTags) - HTM_TD_Empty (1); - - HTM_TD_Begin ("class=\"LM\""); - HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]); - HTM_INPUT_CHECKBOX ("AllTags",HTM_DONT_SUBMIT_ON_CHANGE, - "value=\"Y\"%s onclick=\"togglecheckChildren(this,'ChkTag');\"", - Tags->All ? " checked=\"checked\"" : - ""); - HTM_TxtF (" %s",Txt_All_tags); - HTM_LABEL_End (); - HTM_TD_End (); - - HTM_TR_End (); - - /***** Select tags one by one *****/ - for (NumTag = 1; - NumTag <= Tags->Num; - NumTag++) - { - row = mysql_fetch_row (mysql_res); - HTM_TR_Begin (NULL); - - if (!ShowOnlyEnabledTags) - { - TagHidden = (row[2][0] == 'Y'); - HTM_TD_Begin ("class=\"LM\""); - Ico_PutIconOff (TagHidden ? "eye-slash-red.svg" : - "eye-green.svg", - TagHidden ? Txt_Tag_not_allowed : - Txt_Tag_allowed); - HTM_TD_End (); - } - - Checked = false; - if (Tags->List) - { - Ptr = Tags->List; - while (*Ptr) - { - Par_GetNextStrUntilSeparParamMult (&Ptr,TagText,Tst_MAX_BYTES_TAG); - if (!strcmp (row[1],TagText)) - { - Checked = true; - break; - } - } - } - - HTM_TD_Begin ("class=\"LM\""); - HTM_LABEL_Begin ("class=\"DAT\""); - HTM_INPUT_CHECKBOX ("ChkTag",HTM_DONT_SUBMIT_ON_CHANGE, - "value=\"%s\"%s onclick=\"checkParent(this,'AllTags');\"", - row[1], - Checked ? " checked=\"checked\"" : - ""); - HTM_TxtF (" %s",row[1]); - HTM_LABEL_End (); - HTM_TD_End (); - - HTM_TR_End (); - } - - HTM_TABLE_End (); - HTM_TD_End (); - HTM_TR_End (); - } - -/*****************************************************************************/ -/************* Show a form to enable/disable and rename test tags ************/ -/*****************************************************************************/ - -void Tag_ShowFormEditTags (void) - { - extern const char *Hlp_ASSESSMENT_Tests_configuring_tests; - extern const char *Txt_No_test_questions; - extern const char *Txt_Tags; - MYSQL_RES *mysql_res; - MYSQL_ROW row; - unsigned NumTags; - unsigned NumTag; - long TagCod; - - /***** Get current tags in current course *****/ - if ((NumTags = Tst_GetAllTagsFromCurrentCrs (&mysql_res))) - { - /***** Begin box and table *****/ - Box_BoxTableBegin (NULL,Txt_Tags, - NULL,NULL, - Hlp_ASSESSMENT_Tests_configuring_tests,Box_NOT_CLOSABLE,2); - - /***** Show tags *****/ - for (NumTag = 0; - NumTag < NumTags; - NumTag++) - { - row = mysql_fetch_row (mysql_res); - /* - row[0] TagCod - row[1] TagTxt - row[2] TagHidden - */ - if ((TagCod = Str_ConvertStrCodToLongCod (row[0])) < 0) - Lay_ShowErrorAndExit ("Wrong code of tag."); - - HTM_TR_Begin (NULL); - - /* Form to enable / disable this tag */ - if (row[2][0] == 'Y') // Tag disabled - Tag_PutIconEnable (TagCod,row[1]); - else - Tag_PutIconDisable (TagCod,row[1]); - - /* Form to rename this tag */ - HTM_TD_Begin ("class=\"LM\""); - Frm_StartForm (ActRenTag); - Par_PutHiddenParamString (NULL,"OldTagTxt",row[1]); - HTM_INPUT_TEXT ("NewTagTxt",Tst_MAX_CHARS_TAG,row[1], - HTM_SUBMIT_ON_CHANGE, - "size=\"36\" required=\"required\""); - Frm_EndForm (); - HTM_TD_End (); - - HTM_TR_End (); - } - - /***** End table and box *****/ - Box_BoxTableEnd (); - } - else - Ale_ShowAlert (Ale_INFO,Txt_No_test_questions); - - /* Free structure that stores the query result */ - DB_FreeMySQLResult (&mysql_res); - } - -/*****************************************************************************/ -/******************* Put a link and an icon to enable a tag ******************/ -/*****************************************************************************/ - -static void Tag_PutIconEnable (long TagCod,const char *TagTxt) - { - extern const char *Txt_Tag_X_not_allowed_Click_to_allow_it; - - HTM_TD_Begin ("class=\"BM\""); - Frm_StartForm (ActEnaTag); - Par_PutHiddenParamLong (NULL,"TagCod",TagCod); - Ico_PutIconLink ("eye-slash-red.svg", - Str_BuildStringStr (Txt_Tag_X_not_allowed_Click_to_allow_it, - TagTxt)); - Str_FreeString (); - Frm_EndForm (); - HTM_TD_End (); - } - -/*****************************************************************************/ -/****************** Put a link and an icon to disable a tag ******************/ -/*****************************************************************************/ - -static void Tag_PutIconDisable (long TagCod,const char *TagTxt) - { - extern const char *Txt_Tag_X_allowed_Click_to_disable_it; - - HTM_TD_Begin ("class=\"BM\""); - Frm_StartForm (ActDisTag); - Par_PutHiddenParamLong (NULL,"TagCod",TagCod); - Ico_PutIconLink ("eye-green.svg", - Str_BuildStringStr (Txt_Tag_X_allowed_Click_to_disable_it, - TagTxt)); - Str_FreeString (); - Frm_EndForm (); - HTM_TD_End (); - } - /*****************************************************************************/ /********************* Show a form to to configure test **********************/ /*****************************************************************************/ @@ -2366,7 +1891,7 @@ static void Tst_GetQuestions (struct Tst_Test *Test,MYSQL_RES **mysql_res) long LengthQuery; unsigned NumItemInList; const char *Ptr; - char TagText[Tst_MAX_BYTES_TAG + 1]; + char TagText[Tag_MAX_BYTES_TAG + 1]; char LongStr[Cns_MAX_DECIMAL_DIGITS_LONG + 1]; char UnsignedStr[Cns_MAX_DECIMAL_DIGITS_UINT + 1]; Tst_AnswerType_t AnsType; @@ -2425,7 +1950,7 @@ static void Tst_GetQuestions (struct Tst_Test *Test,MYSQL_RES **mysql_res) Ptr = Test->Tags.List; while (*Ptr) { - Par_GetNextStrUntilSeparParamMult (&Ptr,TagText,Tst_MAX_BYTES_TAG); + Par_GetNextStrUntilSeparParamMult (&Ptr,TagText,Tag_MAX_BYTES_TAG); LengthQuery = LengthQuery + 35 + strlen (TagText) + 1; if (LengthQuery > Tst_MAX_BYTES_QUERY_TEST - 256) Lay_ShowErrorAndExit ("Query size exceed."); @@ -2451,7 +1976,7 @@ static void Tst_GetQuestions (struct Tst_Test *Test,MYSQL_RES **mysql_res) Ptr = Test->AnswerTypes.List; while (*Ptr) { - Par_GetNextStrUntilSeparParamMult (&Ptr,UnsignedStr,Tst_MAX_BYTES_TAG); + Par_GetNextStrUntilSeparParamMult (&Ptr,UnsignedStr,Tag_MAX_BYTES_TAG); AnsType = Tst_ConvertFromUnsignedStrToAnsTyp (UnsignedStr); LengthQuery = LengthQuery + 35 + strlen (Tst_StrAnswerTypesDB[AnsType]) + 1; if (LengthQuery > Tst_MAX_BYTES_QUERY_TEST - 256) @@ -2525,7 +2050,7 @@ static void Tst_GetQuestionsForNewTestFromDB (struct Tst_Test *Test, long LengthQuery; unsigned NumItemInList; const char *Ptr; - char TagText[Tst_MAX_BYTES_TAG + 1]; + char TagText[Tag_MAX_BYTES_TAG + 1]; char UnsignedStr[Cns_MAX_DECIMAL_DIGITS_UINT + 1]; Tst_AnswerType_t AnswerType; bool Shuffle; @@ -2572,7 +2097,7 @@ static void Tst_GetQuestionsForNewTestFromDB (struct Tst_Test *Test, Ptr = Test->Tags.List; while (*Ptr) { - Par_GetNextStrUntilSeparParamMult (&Ptr,TagText,Tst_MAX_BYTES_TAG); + Par_GetNextStrUntilSeparParamMult (&Ptr,TagText,Tag_MAX_BYTES_TAG); LengthQuery = LengthQuery + 35 + strlen (TagText) + 1; if (LengthQuery > Tst_MAX_BYTES_QUERY_TEST - 128) Lay_ShowErrorAndExit ("Query size exceed."); @@ -2598,7 +2123,7 @@ static void Tst_GetQuestionsForNewTestFromDB (struct Tst_Test *Test, Ptr = Test->AnswerTypes.List; while (*Ptr) { - Par_GetNextStrUntilSeparParamMult (&Ptr,UnsignedStr,Tst_MAX_BYTES_TAG); + Par_GetNextStrUntilSeparParamMult (&Ptr,UnsignedStr,Tag_MAX_BYTES_TAG); AnswerType = Tst_ConvertFromUnsignedStrToAnsTyp (UnsignedStr); LengthQuery = LengthQuery + 35 + strlen (Tst_StrAnswerTypesDB[AnswerType]) + 1; if (LengthQuery > Tst_MAX_BYTES_QUERY_TEST - 128) @@ -3872,9 +3397,9 @@ static bool Tst_GetParamsTst (struct Tst_Test *Test, Test->Tags.All = Par_GetParToBool ("AllTags"); /* Get the tags */ - if ((Test->Tags.List = (char *) malloc (Tst_MAX_BYTES_TAGS_LIST + 1)) == NULL) + if ((Test->Tags.List = (char *) malloc (Tag_MAX_BYTES_TAGS_LIST + 1)) == NULL) Lay_NotEnoughMemoryExit (); - Par_GetParMultiToText ("ChkTag",Test->Tags.List,Tst_MAX_BYTES_TAGS_LIST); + Par_GetParMultiToText ("ChkTag",Test->Tags.List,Tag_MAX_BYTES_TAGS_LIST); /* Check number of tags selected */ if (Tst_CountNumTagsInList (&Test->Tags) == 0) // If no tags selected... @@ -3982,17 +3507,17 @@ static unsigned Tst_GetParamNumQsts (void) /***************** Count number of tags in the list of tags ******************/ /*****************************************************************************/ -static unsigned Tst_CountNumTagsInList (const struct Tst_Tags *Tags) +static unsigned Tst_CountNumTagsInList (const struct Tag_Tags *Tags) { const char *Ptr; unsigned NumTags = 0; - char TagText[Tst_MAX_BYTES_TAG + 1]; + char TagText[Tag_MAX_BYTES_TAG + 1]; /***** Go over the list of tags counting the number of tags *****/ Ptr = Tags->List; while (*Ptr) { - Par_GetNextStrUntilSeparParamMult (&Ptr,TagText,Tst_MAX_BYTES_TAG); + Par_GetNextStrUntilSeparParamMult (&Ptr,TagText,Tag_MAX_BYTES_TAG); NumTags++; } @@ -4149,7 +3674,7 @@ static void Tst_PutFormEditOneQst (struct Tst_Question *Question) HTM_TR_End (); /***** Get tags already existing for questions in current course *****/ - NumTags = Tst_GetAllTagsFromCurrentCrs (&mysql_res); + NumTags = Tag_GetAllTagsFromCurrentCrs (&mysql_res); /***** Write the tags *****/ HTM_TR_Begin (NULL); @@ -4162,7 +3687,7 @@ static void Tst_PutFormEditOneQst (struct Tst_Question *Question) HTM_TABLE_BeginPadding (2); // Table for tags for (IndTag = 0; - IndTag < Tst_MAX_TAGS_PER_QUESTION; + IndTag < Tag_MAX_TAGS_PER_QUESTION; IndTag++) { HTM_TR_Begin (NULL); @@ -4213,7 +3738,7 @@ static void Tst_PutFormEditOneQst (struct Tst_Question *Question) snprintf (StrTagTxt,sizeof (StrTagTxt), "TagTxt%u", IndTag); - HTM_INPUT_TEXT (StrTagTxt,Tst_MAX_CHARS_TAG,Question->Tags.Txt[IndTag], + HTM_INPUT_TEXT (StrTagTxt,Tag_MAX_CHARS_TAG,Question->Tags.Txt[IndTag], HTM_DONT_SUBMIT_ON_CHANGE, "id=\"%s\" class=\"TAG_TXT\" onchange=\"changeSelTag('%u')\"", StrTagTxt,IndTag); @@ -4545,7 +4070,7 @@ void Tst_QstConstructor (struct Tst_Question *Question) unsigned NumOpt; /***** Reset question tags *****/ - Tst_ResetTags (&Question->Tags); + Tag_ResetTags (&Question->Tags); /***** Reset edition time *****/ Question->EditTime = (time_t) 0; @@ -4820,7 +4345,7 @@ bool Tst_GetQstDataFromDB (struct Tst_Question *Question) { row = mysql_fetch_row (mysql_res); Str_Copy (Question->Tags.Txt[NumRow],row[0], - Tst_MAX_BYTES_TAG); + Tag_MAX_BYTES_TAG); } /* Free structure that stores the query result */ @@ -5072,18 +4597,18 @@ static void Tst_GetQstFromForm (struct Tst_Question *Question) /***** Get question tags *****/ for (NumTag = 0; - NumTag < Tst_MAX_TAGS_PER_QUESTION; + NumTag < Tag_MAX_TAGS_PER_QUESTION; NumTag++) { snprintf (TagStr,sizeof (TagStr), "TagTxt%u", NumTag); - Par_GetParToText (TagStr,Question->Tags.Txt[NumTag],Tst_MAX_BYTES_TAG); + Par_GetParToText (TagStr,Question->Tags.Txt[NumTag],Tag_MAX_BYTES_TAG); if (Question->Tags.Txt[NumTag][0]) { Str_ChangeFormat (Str_FROM_FORM,Str_TO_TEXT, - Question->Tags.Txt[NumTag],Tst_MAX_BYTES_TAG,true); + Question->Tags.Txt[NumTag],Tag_MAX_BYTES_TAG,true); /* Check if not repeated */ for (NumTagRead = 0; NumTagRead < NumTag; @@ -5230,7 +4755,7 @@ static void Tst_GetQstFromForm (struct Tst_Question *Question) /***** Adjust variables related to this test question *****/ for (NumTag = 0, Question->Tags.Num = 0; - NumTag < Tst_MAX_TAGS_PER_QUESTION; + NumTag < Tag_MAX_TAGS_PER_QUESTION; NumTag++) if (Question->Tags.Txt[NumTag][0]) Question->Tags.Num++; @@ -5580,75 +5105,6 @@ long Tst_GetIntAnsFromStr (char *Str) return LongNum; } -/*****************************************************************************/ -/***************** Check if this tag exists for current course ***************/ -/*****************************************************************************/ - -static long Tst_GetTagCodFromTagTxt (const char *TagTxt) - { - MYSQL_RES *mysql_res; - MYSQL_ROW row; - unsigned long NumRows; - long TagCod = -1L; // -1 means that the tag does not exist in database - - /***** Get tag code from database *****/ - NumRows = DB_QuerySELECT (&mysql_res,"can not get tag", - "SELECT TagCod FROM tst_tags" - " WHERE CrsCod=%ld AND TagTxt='%s'", - Gbl.Hierarchy.Crs.CrsCod,TagTxt); - if (NumRows == 1) - { - /***** Get tag code *****/ - row = mysql_fetch_row (mysql_res); - if ((TagCod = Str_ConvertStrCodToLongCod (row[0])) < 0) - Ale_CreateAlert (Ale_ERROR,NULL, - "Wrong code of tag."); - } - else if (NumRows > 1) - Ale_CreateAlert (Ale_ERROR,NULL, - "Duplicated tag."); - - /***** Free structure that stores the query result *****/ - DB_FreeMySQLResult (&mysql_res); - - /***** Abort on error *****/ - if (Ale_GetTypeOfLastAlert () == Ale_ERROR) - Ale_ShowAlertsAndExit (); - - return TagCod; - } - -/*****************************************************************************/ -/********************* Insert new tag into tst_tags table ********************/ -/*****************************************************************************/ - -static long Tst_CreateNewTag (long CrsCod,const char *TagTxt) - { - /***** Insert new tag into tst_tags table *****/ - return - DB_QueryINSERTandReturnCode ("can not create new tag", - "INSERT INTO tst_tags" - " (CrsCod,ChangeTime,TagTxt,TagHidden)" - " VALUES" - " (%ld,NOW(),'%s','Y')", // Hidden by default - CrsCod,TagTxt); - } - -/*****************************************************************************/ -/********** Change visibility of an existing tag into tst_tags table *********/ -/*****************************************************************************/ - -static void Tst_EnableOrDisableTag (long TagCod,bool TagHidden) - { - /***** Insert new tag into tst_tags table *****/ - DB_QueryUPDATE ("can not update the visibility of a tag", - "UPDATE tst_tags SET TagHidden='%c',ChangeTime=NOW()" - " WHERE TagCod=%ld AND CrsCod=%ld", - TagHidden ? 'Y' : - 'N', - TagCod,Gbl.Hierarchy.Crs.CrsCod); - } - /*****************************************************************************/ /***************** Request the removal of selected questions *****************/ /*****************************************************************************/ @@ -5878,8 +5334,8 @@ static void Tst_RemoveOneQstFromDB (long CrsCod,long QstCod) /***** Remove the question from all the tables *****/ /* Remove answers and tags from this test question */ Tst_RemAnsFromQst (QstCod); - Tst_RemTagsFromQst (QstCod); - Tst_RemoveUnusedTagsFromCrs (CrsCod); + Tag_RemTagsFromQst (QstCod); + Tag_RemoveUnusedTagsFromCrs (CrsCod); /* Remove the question itself */ DB_QueryDELETE ("can not remove a question", @@ -5973,10 +5429,10 @@ void Tst_InsertOrUpdateQstTagsAnsIntoDB (struct Tst_Question *Question) if (Question->QstCod > 0) { /***** Insert tags in the tags table *****/ - Tst_InsertTagsIntoDB (Question); + Tag_InsertTagsIntoDB (Question->QstCod,&Question->Tags); /***** Remove unused tags in current course *****/ - Tst_RemoveUnusedTagsFromCrs (Gbl.Hierarchy.Crs.CrsCod); + Tag_RemoveUnusedTagsFromCrs (Gbl.Hierarchy.Crs.CrsCod); /***** Insert answers in the answers table *****/ Tst_InsertAnswersIntoDB (Question); @@ -6047,43 +5503,10 @@ static void Tst_InsertOrUpdateQstIntoDB (struct Tst_Question *Question) /* Remove answers and tags from this test question */ Tst_RemAnsFromQst (Question->QstCod); - Tst_RemTagsFromQst (Question->QstCod); + Tag_RemTagsFromQst (Question->QstCod); } } -/*****************************************************************************/ -/*********************** Insert tags in the tags table ***********************/ -/*****************************************************************************/ - -static void Tst_InsertTagsIntoDB (const struct Tst_Question *Question) - { - unsigned NumTag; - unsigned TagIdx; - long TagCod; - - /***** For each tag... *****/ - for (NumTag = 0, TagIdx = 0; - TagIdx < Question->Tags.Num; - NumTag++) - if (Question->Tags.Txt[NumTag][0]) - { - /***** Check if this tag exists for current course *****/ - if ((TagCod = Tst_GetTagCodFromTagTxt (Question->Tags.Txt[NumTag])) < 0) - /* This tag is new for current course. Add it to tags table */ - TagCod = Tst_CreateNewTag (Gbl.Hierarchy.Crs.CrsCod,Question->Tags.Txt[NumTag]); - - /***** Insert tag in tst_question_tags *****/ - DB_QueryINSERT ("can not create tag", - "INSERT INTO tst_question_tags" - " (QstCod,TagCod,TagInd)" - " VALUES" - " (%ld,%ld,%u)", - Question->QstCod,TagCod,TagIdx); - - TagIdx++; - } - } - /*****************************************************************************/ /******************* Insert answers in the answers table *********************/ /*****************************************************************************/ @@ -6240,36 +5663,6 @@ static void Tst_RemAnsFromQst (long QstCod) QstCod); } -/*****************************************************************************/ -/************************** Remove tags from a test question *****************/ -/*****************************************************************************/ - -static void Tst_RemTagsFromQst (long QstCod) - { - /***** Remove tags *****/ - DB_QueryDELETE ("can not remove the tags of a question", - "DELETE FROM tst_question_tags WHERE QstCod=%ld", - QstCod); - } - -/*****************************************************************************/ -/********************** Remove unused tags in a course ***********************/ -/*****************************************************************************/ - -static void Tst_RemoveUnusedTagsFromCrs (long CrsCod) - { - /***** Remove unused tags from tst_tags *****/ - DB_QueryDELETE ("can not remove unused tags", - "DELETE FROM tst_tags" - " WHERE CrsCod=%ld AND TagCod NOT IN" - " (SELECT DISTINCT tst_question_tags.TagCod" - " FROM tst_questions,tst_question_tags" - " WHERE tst_questions.CrsCod=%ld" - " AND tst_questions.QstCod=tst_question_tags.QstCod)", - CrsCod, - CrsCod); - } - /*****************************************************************************/ /** Remove all media associated to stems of all test questions in a course ***/ /*****************************************************************************/ diff --git a/swad_test.h b/swad_test.h index d8d4adc6c..d2fb1b827 100644 --- a/swad_test.h +++ b/swad_test.h @@ -66,7 +66,7 @@ typedef enum struct Tst_Test { - struct Tst_Tags Tags; // Selected tags + struct Tag_Tags Tags; // Selected tags struct Tst_AnswerTypes AnswerTypes; // Selected answer types Tst_QuestionsOrder_t SelectedOrder; // Order for listing questions unsigned NumQsts; // Number of questions @@ -143,14 +143,9 @@ unsigned long Tst_GetTagsQst (long QstCod,MYSQL_RES **mysql_res); void Tst_GetAndWriteTagsQst (long QstCod); void Tst_ShowFormConfig (void); -void Tag_EnableTag (void); -void Tag_DisableTag (void); -void Tag_RenameTag (void); bool Tst_CheckIfCourseHaveTestsAndPluggableIsUnknown (void); -void Tag_ShowFormEditTags (void); - unsigned Tst_CountNumQuestionsInList (const char *ListQuestions); void Tst_ShowFormEditOneQst (void); diff --git a/swad_test_import.c b/swad_test_import.c index 3a586e930..78b613286 100644 --- a/swad_test_import.c +++ b/swad_test_import.c @@ -566,7 +566,7 @@ static void TsI_ImportQuestionsFromXMLBuffer (const char *XMLBuffer) if (!strcmp (TagsElem->TagName,"tags")) { for (TagElem = TagsElem->FirstChild; - TagElem != NULL && Question.Tags.Num < Tst_MAX_TAGS_PER_QUESTION; + TagElem != NULL && Question.Tags.Num < Tag_MAX_TAGS_PER_QUESTION; TagElem = TagElem->NextBrother) if (!strcmp (TagElem->TagName,"tag")) { @@ -574,7 +574,7 @@ static void TsI_ImportQuestionsFromXMLBuffer (const char *XMLBuffer) { Str_Copy (Question.Tags.Txt[Question.Tags.Num], TagElem->Content, - Tst_MAX_BYTES_TAG); + Tag_MAX_BYTES_TAG); Question.Tags.Num++; } } diff --git a/swad_test_type.h b/swad_test_type.h index a8f083fc3..a6cef58b7 100644 --- a/swad_test_type.h +++ b/swad_test_type.h @@ -28,16 +28,12 @@ /*****************************************************************************/ #include "swad_media.h" +#include "swad_tag.h" /*****************************************************************************/ /***************************** Public constants ******************************/ /*****************************************************************************/ -#define Tst_MAX_TAGS_PER_QUESTION 5 - -#define Tst_MAX_CHARS_TAG (128 - 1) // 127 -#define Tst_MAX_BYTES_TAG ((Tst_MAX_CHARS_TAG + 1) * Str_MAX_BYTES_PER_CHAR - 1) // 2047 - #define Tst_MAX_BYTES_FLOAT_ANSWER 30 // Maximum length of the strings that store an floating point answer #define Tst_MAX_OPTIONS_PER_QUESTION 10 @@ -53,14 +49,6 @@ /******************************* Public types ********************************/ /*****************************************************************************/ -struct Tst_Tags - { - unsigned Num; - bool All; - char *List; - char Txt[Tst_MAX_TAGS_PER_QUESTION][Tst_MAX_BYTES_TAG + 1]; - }; - #define Tst_NUM_ANS_TYPES 6 #define Tst_MAX_BYTES_LIST_ANSWER_TYPES (Tst_NUM_ANS_TYPES * (Cns_MAX_DECIMAL_DIGITS_UINT + 1)) typedef enum @@ -77,7 +65,7 @@ typedef enum struct Tst_Question { long QstCod; - struct Tst_Tags Tags; + struct Tag_Tags Tags; time_t EditTime; char *Stem; char *Feedback;