// swad_survey.c: surveys

/*
    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-2019 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 <http://www.gnu.org/licenses/>.
*/
/*****************************************************************************/
/********************************* Headers ***********************************/
/*****************************************************************************/

#define _GNU_SOURCE 		// For asprintf
#include <linux/limits.h>	// For PATH_MAX
#include <linux/stddef.h>	// For NULL
#include <stdio.h>		// For asprintf
#include <stdlib.h>		// For calloc
#include <string.h>		// For string functions

#include "swad_box.h"
#include "swad_database.h"
#include "swad_form.h"
#include "swad_global.h"
#include "swad_group.h"
#include "swad_notification.h"
#include "swad_pagination.h"
#include "swad_parameter.h"
#include "swad_role.h"
#include "swad_setting.h"
#include "swad_survey.h"
#include "swad_table.h"

/*****************************************************************************/
/************** External global variables from others modules ****************/
/*****************************************************************************/

extern struct Globals Gbl;

/*****************************************************************************/
/***************************** Private constants *****************************/
/*****************************************************************************/

#define Svy_MAX_CHARS_ANSWER	(1024 - 1)	// 1023
#define Svy_MAX_BYTES_ANSWER	((Svy_MAX_CHARS_ANSWER + 1) * Str_MAX_BYTES_PER_CHAR - 1)	// 16383

#define Svy_MAX_BYTES_LIST_ANSWER_TYPES	(10 + (Svy_NUM_ANS_TYPES - 1) * (1 + 10))

const char *Svy_StrAnswerTypesDB[Svy_NUM_ANS_TYPES] =
  {
   "unique_choice",
   "multiple_choice",
  };

#define Svy_MAX_ANSWERS_PER_QUESTION	10

struct SurveyQuestion	// Must be initialized to 0 before using it
  {
   long QstCod;
   unsigned QstInd;
   Svy_AnswerType_t AnswerType;
   struct
     {
      char *Text;
     } AnsChoice[Svy_MAX_ANSWERS_PER_QUESTION];
   bool AllAnsTypes;
   char ListAnsTypes[Svy_MAX_BYTES_LIST_ANSWER_TYPES + 1];
  };

/*****************************************************************************/
/******************************* Private types *******************************/
/*****************************************************************************/

/*****************************************************************************/
/***************************** Private variables *****************************/
/*****************************************************************************/

long Svy_CurrentSvyCod;	// Used as parameter in contextual links
long Svy_CurrentQstCod;	// Used as parameter in contextual links

/*****************************************************************************/
/***************************** Private prototypes ****************************/
/*****************************************************************************/

static void Svy_ListAllSurveys (struct SurveyQuestion *SvyQst);
static bool Svy_CheckIfICanCreateSvy (void);
static void Svy_PutIconsListSurveys (void);
static void Svy_PutIconToCreateNewSvy (void);
static void Svy_PutButtonToCreateNewSvy (void);
static void Svy_PutParamsToCreateNewSvy (void);
static void Svy_ParamsWhichGroupsToShow (void);
static void Svy_ShowOneSurvey (long SvyCod,struct SurveyQuestion *SvyQst,
                               bool ShowOnlyThisSvyComplete);
static void Svy_WriteAuthor (struct Survey *Svy);
static void Svy_WriteStatus (struct Survey *Svy);
static void Svy_GetParamSvyOrder (void);

static void Svy_PutFormsToRemEditOneSvy (const struct Survey *Svy,
                                         const char *Anchor);
static void Svy_PutParams (void);

static void Svy_SetAllowedAndHiddenScopes (unsigned *ScopesAllowed,
                                           unsigned *HiddenAllowed);

static void Svy_GetSurveyTxtFromDB (long SvyCod,char Txt[Cns_MAX_BYTES_TEXT + 1]);
static void Svy_PutParamSvyCod (long SvyCod);
static long Svy_GetParamSvyCod (void);

static void Svy_PutButtonToResetSurvey (void);

static bool Svy_CheckIfSimilarSurveyExists (struct Survey *Svy);
static void Svy_SetDefaultAndAllowedScope (struct Survey *Svy);
static void Svy_ShowLstGrpsToEditSurvey (long SvyCod);
static void Svy_UpdateNumUsrsNotifiedByEMailAboutSurvey (long SvyCod,
                                                         unsigned NumUsrsToBeNotifiedByEMail);
static void Svy_CreateSurvey (struct Survey *Svy,const char *Txt);
static void Svy_UpdateSurvey (struct Survey *Svy,const char *Txt);
static bool Svy_CheckIfSvyIsAssociatedToGrps (long SvyCod);
static void Svy_RemoveAllTheGrpsAssociatedToAndSurvey (long SvyCod);
static void Svy_CreateGrps (long SvyCod);
static void Svy_GetAndWriteNamesOfGrpsAssociatedToSvy (struct Survey *Svy);
static bool Svy_CheckIfICanDoThisSurveyBasedOnGrps (long SvyCod);

static unsigned Svy_GetNumQstsSvy (long SvyCod);
static void Svy_ShowFormEditOneQst (long SvyCod,struct SurveyQuestion *SvyQst,
                                    char Txt[Cns_MAX_BYTES_TEXT + 1]);
static void Svy_InitQst (struct SurveyQuestion *SvyQst);
static void Svy_PutParamQstCod (long QstCod);
static long Svy_GetParamQstCod (void);
static void Svy_RemAnswersOfAQuestion (long QstCod);
static Svy_AnswerType_t Svy_ConvertFromStrAnsTypDBToAnsTyp (const char *StrAnsTypeBD);
static bool Svy_CheckIfAnswerExists (long QstCod,unsigned AnsInd);
static unsigned Svy_GetAnswersQst (long QstCod,MYSQL_RES **mysql_res);
static bool Svy_AllocateTextChoiceAnswer (struct SurveyQuestion *SvyQst,unsigned NumAns);
static void Svy_FreeTextChoiceAnswers (struct SurveyQuestion *SvyQst,unsigned NumAnswers);
static void Svy_FreeTextChoiceAnswer (struct SurveyQuestion *SvyQst,unsigned NumAns);

static unsigned Svy_GetQstIndFromQstCod (long QstCod);
static unsigned Svy_GetNextQuestionIndexInSvy (long SvyCod);
static void Svy_ListSvyQuestions (struct Survey *Svy,
                                  struct SurveyQuestion *SvyQst);
static void Svy_PutParamsToEditQuestion (void);
static void Svy_PutIconToAddNewQuestion (void);
static void Svy_PutButtonToCreateNewQuestion (void);
static void Svy_WriteQstStem (const char *Stem);
static void Svy_WriteAnswersOfAQst (struct Survey *Svy,
                                    struct SurveyQuestion *SvyQst,
                                    bool PutFormAnswerSurvey);
static void Svy_DrawBarNumUsrs (unsigned NumUsrs,unsigned MaxUsrs);

static void Svy_PutIconToRemoveOneQst (void);
static void Svy_PutParamsRemoveOneQst (void);

static void Svy_ReceiveAndStoreUserAnswersToASurvey (long SvyCod);
static void Svy_IncreaseAnswerInDB (long QstCod,unsigned AnsInd);
static void Svy_RegisterIHaveAnsweredSvy (long SvyCod);
static bool Svy_CheckIfIHaveAnsweredSvy (long SvyCod);
static unsigned Svy_GetNumUsrsWhoHaveAnsweredSvy (long SvyCod);

/*****************************************************************************/
/************************** List all the surveys *****************************/
/*****************************************************************************/

void Svy_SeeAllSurveys (void)
  {
   struct SurveyQuestion SvyQst;

   /***** Initialize question to zero *****/
   Svy_InitQst (&SvyQst);

   /***** Get parameters *****/
   Svy_GetParamSvyOrder ();
   Grp_GetParamWhichGrps ();
   Gbl.Svys.CurrentPage = Pag_GetParamPagNum (Pag_SURVEYS);

   /***** Show all the surveys *****/
   Svy_ListAllSurveys (&SvyQst);
  }

/*****************************************************************************/
/*************************** Show all the surveys ****************************/
/*****************************************************************************/

static void Svy_ListAllSurveys (struct SurveyQuestion *SvyQst)
  {
   extern const char *Hlp_ASSESSMENT_Surveys;
   extern const char *Txt_Surveys;
   extern const char *Txt_START_END_TIME_HELP[Dat_NUM_START_END_TIME];
   extern const char *Txt_START_END_TIME[Dat_NUM_START_END_TIME];
   extern const char *Txt_Survey;
   extern const char *Txt_Status;
   extern const char *Txt_No_surveys;
   Svy_Order_t Order;
   struct Pagination Pagination;
   unsigned NumSvy;

   /***** Get number of groups in current course *****/
   if (!Gbl.Crs.Grps.NumGrps)
      Gbl.Crs.Grps.WhichGrps = Grp_ALL_GROUPS;

   /***** Get list of surveys *****/
   Svy_GetListSurveys ();

   /***** Compute variables related to pagination *****/
   Pagination.NumItems = Gbl.Svys.Num;
   Pagination.CurrentPage = (int) Gbl.Svys.CurrentPage;
   Pag_CalculatePagination (&Pagination);
   Gbl.Svys.CurrentPage = (unsigned) Pagination.CurrentPage;

   /***** Write links to pages *****/
   if (Pagination.MoreThanOnePage)
      Pag_WriteLinksToPagesCentered (Pag_SURVEYS,
                                     0,
                                     &Pagination);

   /***** Start box *****/
   Box_StartBox ("100%",Txt_Surveys,Svy_PutIconsListSurveys,
                 Hlp_ASSESSMENT_Surveys,Box_NOT_CLOSABLE);

   /***** Select whether show only my groups or all groups *****/
   if (Gbl.Crs.Grps.NumGrps)
     {
      Set_StartSettingsHead ();
      Grp_ShowFormToSelWhichGrps (ActSeeAllSvy,Svy_ParamsWhichGroupsToShow);
      Set_EndSettingsHead ();
     }

   if (Gbl.Svys.Num)
     {
      /***** Table head *****/
      Tbl_StartTableWideMargin (2);
      fprintf (Gbl.F.Out,"<tr>"
			 "<th class=\"CONTEXT_COL\"></th>");	// Column for contextual icons
      for (Order = Svy_ORDER_BY_START_DATE;
	   Order <= Svy_ORDER_BY_END_DATE;
	   Order++)
	{
	 fprintf (Gbl.F.Out,"<th class=\"LEFT_MIDDLE\">");

	 /* Form to change order */
	 Frm_StartForm (ActSeeAllSvy);
	 Grp_PutParamWhichGrps ();
	 Pag_PutHiddenParamPagNum (Pag_SURVEYS,Gbl.Svys.CurrentPage);
	 Par_PutHiddenParamUnsigned ("Order",(unsigned) Order);
	 Frm_LinkFormSubmit (Txt_START_END_TIME_HELP[Order],"TIT_TBL",NULL);
	 if (Order == Gbl.Svys.SelectedOrder)
	    fprintf (Gbl.F.Out,"<u>");
	 fprintf (Gbl.F.Out,"%s",Txt_START_END_TIME[Order]);
	 if (Order == Gbl.Svys.SelectedOrder)
	    fprintf (Gbl.F.Out,"</u>");
	 fprintf (Gbl.F.Out,"</a>");
	 Frm_EndForm ();

	 fprintf (Gbl.F.Out,"</th>");
	}
      fprintf (Gbl.F.Out,"<th class=\"LEFT_MIDDLE\">"
			 "%s"
			 "</th>"
			 "<th class=\"CENTER_MIDDLE\">"
			 "%s"
			 "</th>"
			 "</tr>",
	       Txt_Survey,
	       Txt_Status);

      /***** Write all the surveys *****/
      for (NumSvy = Pagination.FirstItemVisible;
	   NumSvy <= Pagination.LastItemVisible;
	   NumSvy++)
	 Svy_ShowOneSurvey (Gbl.Svys.LstSvyCods[NumSvy - 1],SvyQst,false);

      /***** End table *****/
      Tbl_EndTable ();
     }
   else	// No surveys created
      Ale_ShowAlert (Ale_INFO,Txt_No_surveys);

   /***** Button to create a new survey *****/
   if (Svy_CheckIfICanCreateSvy ())
      Svy_PutButtonToCreateNewSvy ();

   /***** End box *****/
   Box_EndBox ();

   /***** Write again links to pages *****/
   if (Pagination.MoreThanOnePage)
      Pag_WriteLinksToPagesCentered (Pag_SURVEYS,
                                     0,
                                     &Pagination);

   /***** Free list of surveys *****/
   Svy_FreeListSurveys ();
  }

/*****************************************************************************/
/******************* Check if I can create a new survey **********************/
/*****************************************************************************/

static bool Svy_CheckIfICanCreateSvy (void)
  {
   switch (Gbl.Usrs.Me.Role.Logged)
     {
      case Rol_TCH:
      case Rol_DEG_ADM:
      case Rol_CTR_ADM:
      case Rol_INS_ADM:
      case Rol_SYS_ADM:
         return true;
      default:
         return false;
     }
   return false;
  }

/*****************************************************************************/
/***************** Put contextual icons in list of surveys *******************/
/*****************************************************************************/

static void Svy_PutIconsListSurveys (void)
  {
   /***** Put icon to create a new survey *****/
   if (Svy_CheckIfICanCreateSvy ())
      Svy_PutIconToCreateNewSvy ();

   /***** Put icon to show a figure *****/
   Gbl.Figures.FigureType = Fig_SURVEYS;
   Fig_PutIconToShowFigure ();
  }

/*****************************************************************************/
/********************** Put icon to create a new survey **********************/
/*****************************************************************************/

static void Svy_PutIconToCreateNewSvy (void)
  {
   extern const char *Txt_New_survey;

   Ico_PutContextualIconToAdd (ActFrmNewSvy,NULL,Svy_PutParamsToCreateNewSvy,
			       Txt_New_survey);
  }

/*****************************************************************************/
/********************* Put button to create a new survey *********************/
/*****************************************************************************/

static void Svy_PutButtonToCreateNewSvy (void)
  {
   extern const char *Txt_New_survey;

   Frm_StartForm (ActFrmNewSvy);
   Svy_PutParamsToCreateNewSvy ();
   Btn_PutConfirmButton (Txt_New_survey);
   Frm_EndForm ();
  }

/*****************************************************************************/
/******************* Put parameters to create a new survey *******************/
/*****************************************************************************/

static void Svy_PutParamsToCreateNewSvy (void)
  {
   Svy_PutHiddenParamSvyOrder ();
   Grp_PutParamWhichGrps ();
   Pag_PutHiddenParamPagNum (Pag_SURVEYS,Gbl.Svys.CurrentPage);
  }

/*****************************************************************************/
/**************** Put params to select which groups to show ******************/
/*****************************************************************************/

static void Svy_ParamsWhichGroupsToShow (void)
  {
   Svy_PutHiddenParamSvyOrder ();
   Pag_PutHiddenParamPagNum (Pag_SURVEYS,Gbl.Svys.CurrentPage);
  }

/*****************************************************************************/
/****************************** Show one survey ******************************/
/*****************************************************************************/

void Svy_SeeOneSurvey (void)
  {
   struct Survey Svy;
   struct SurveyQuestion SvyQst;

   /***** Initialize question to zero *****/
   Svy_InitQst (&SvyQst);

   /***** Get parameters *****/
   Svy_GetParamSvyOrder ();
   Grp_GetParamWhichGrps ();
   Gbl.Svys.CurrentPage = Pag_GetParamPagNum (Pag_SURVEYS);

   /***** Get survey code *****/
   if ((Svy.SvyCod = Svy_GetParamSvyCod ()) == -1L)
      Lay_ShowErrorAndExit ("Code of survey is missing.");

   /***** Show survey *****/
   Svy_ShowOneSurvey (Svy.SvyCod,&SvyQst,true);
  }

/*****************************************************************************/
/****************************** Show one survey ******************************/
/*****************************************************************************/

static void Svy_ShowOneSurvey (long SvyCod,struct SurveyQuestion *SvyQst,
                               bool ShowOnlyThisSvyComplete)
  {
   extern const char *Hlp_ASSESSMENT_Surveys;
   extern const char *Txt_Survey;
   extern const char *Txt_Today;
   extern const char *Txt_View_survey;
   extern const char *Txt_No_of_questions;
   extern const char *Txt_No_of_users;
   extern const char *Txt_Scope;
   extern const char *Txt_Country;
   extern const char *Txt_Institution;
   extern const char *Txt_Centre;
   extern const char *Txt_Degree;
   extern const char *Txt_Course;
   extern const char *Txt_Users;
   extern const char *Txt_Answer_survey;
   extern const char *Txt_View_survey_results;
   char *Anchor = NULL;
   static unsigned UniqueId = 0;
   struct Survey Svy;
   char Txt[Cns_MAX_BYTES_TEXT + 1];

   /***** Start box *****/
   if (ShowOnlyThisSvyComplete)
      Box_StartBox (NULL,Txt_Survey,NULL,
                    Hlp_ASSESSMENT_Surveys,Box_NOT_CLOSABLE);

   /***** Get data of this survey *****/
   Svy.SvyCod = SvyCod;
   Svy_GetDataOfSurveyByCod (&Svy);

   /***** Set anchor string *****/
   Frm_SetAnchorStr (Svy.SvyCod,&Anchor);

   /***** Start table *****/
   if (ShowOnlyThisSvyComplete)
      Tbl_StartTableWide (2);

   /***** Write first row of data of this assignment *****/
   /* Forms to remove/edit this assignment */
   fprintf (Gbl.F.Out,"<tr>"
	              "<td rowspan=\"2\" class=\"CONTEXT_COL");
   if (!ShowOnlyThisSvyComplete)
      fprintf (Gbl.F.Out," COLOR%u",Gbl.RowEvenOdd);
   fprintf (Gbl.F.Out,"\">");
   if (Svy.Status.ICanEdit)
      Svy_PutFormsToRemEditOneSvy (&Svy,Anchor);
   fprintf (Gbl.F.Out,"</td>");

   /* Start date/time */
   UniqueId++;
   fprintf (Gbl.F.Out,"<td id=\"svy_date_start_%u\" class=\"%s LEFT_TOP",
	    UniqueId,
            Svy.Status.Visible ? (Svy.Status.Open ? "DATE_GREEN" :
        	                                    "DATE_RED") :
                                 (Svy.Status.Open ? "DATE_GREEN_LIGHT" :
                                                    "DATE_RED_LIGHT"));
   if (!ShowOnlyThisSvyComplete)
      fprintf (Gbl.F.Out," COLOR%u",Gbl.RowEvenOdd);
   fprintf (Gbl.F.Out,"\">"
                      "<script type=\"text/javascript\">"
                      "writeLocalDateHMSFromUTC('svy_date_start_%u',%ld,"
                      "%u,'<br />','%s',true,true,0x7);"
                      "</script>"
	              "</td>",
            UniqueId,Svy.TimeUTC[Svy_START_TIME],
            (unsigned) Gbl.Prefs.DateFormat,Txt_Today);

   /* End date/time */
   fprintf (Gbl.F.Out,"<td id=\"svy_date_end_%u\" class=\"%s LEFT_TOP",
            UniqueId,
            Svy.Status.Visible ? (Svy.Status.Open ? "DATE_GREEN" :
        	                                    "DATE_RED") :
                                 (Svy.Status.Open ? "DATE_GREEN_LIGHT" :
                                                    "DATE_RED_LIGHT"));
   if (!ShowOnlyThisSvyComplete)
      fprintf (Gbl.F.Out," COLOR%u",Gbl.RowEvenOdd);
   fprintf (Gbl.F.Out,"\">"
                      "<script type=\"text/javascript\">"
                      "writeLocalDateHMSFromUTC('svy_date_end_%u',%ld,"
                      "%u,'<br />','%s',false,true,0x7);"
                      "</script>"
	              "</td>",
            UniqueId,Svy.TimeUTC[Svy_END_TIME],
            (unsigned) Gbl.Prefs.DateFormat,Txt_Today);

   /* Survey title */
   fprintf (Gbl.F.Out,"<td class=\"LEFT_TOP");
   if (!ShowOnlyThisSvyComplete)
      fprintf (Gbl.F.Out," COLOR%u",Gbl.RowEvenOdd);
   fprintf (Gbl.F.Out,"\">");
   Lay_StartArticle (Anchor);
   Frm_StartForm (ActSeeSvy);
   Svy_PutParamSvyCod (SvyCod);
   Svy_PutHiddenParamSvyOrder ();
   Grp_PutParamWhichGrps ();
   Pag_PutHiddenParamPagNum (Pag_SURVEYS,Gbl.Svys.CurrentPage);
   Frm_LinkFormSubmit (Txt_View_survey,
                       Svy.Status.Visible ? "ASG_TITLE" :
	                                    "ASG_TITLE_LIGHT",NULL);
   fprintf (Gbl.F.Out,"%s</a>",
            Svy.Title);
   Frm_EndForm ();
   Lay_EndArticle ();

   /* Number of questions and number of distinct users who have already answered this survey */
   fprintf (Gbl.F.Out,"<div class=\"%s\">%s: %u; %s: %u</div>"
	              "</td>",
            Svy.Status.Visible ? "ASG_GRP" :
        	                 "ASG_GRP_LIGHT",
            Txt_No_of_questions,
            Svy.NumQsts,
            Txt_No_of_users,
            Svy.NumUsrs);

   /* Status of the survey */
   fprintf (Gbl.F.Out,"<td rowspan=\"2\" class=\"LEFT_TOP");
   if (!ShowOnlyThisSvyComplete)
      fprintf (Gbl.F.Out," COLOR%u",Gbl.RowEvenOdd);
   fprintf (Gbl.F.Out,"\">");
   Svy_WriteStatus (&Svy);

   if (!ShowOnlyThisSvyComplete)
     {
      /* Possible button to answer this survey */
      if (Svy.Status.ICanAnswer)
	{
	 fprintf (Gbl.F.Out,"<div class=\"BUTTONS_AFTER_ALERT\">");

	 Frm_StartForm (ActSeeSvy);
	 Svy_PutParamSvyCod (Svy.SvyCod);
	 Svy_PutHiddenParamSvyOrder ();
	 Grp_PutParamWhichGrps ();
	 Pag_PutHiddenParamPagNum (Pag_SURVEYS,Gbl.Svys.CurrentPage);
	 Btn_PutCreateButtonInline (Txt_Answer_survey);
	 Frm_EndForm ();

	 fprintf (Gbl.F.Out,"</div>");
	}
      /* Possible button to see the result of the survey */
      else if (Svy.Status.ICanViewResults)
	{
	 fprintf (Gbl.F.Out,"<div class=\"BUTTONS_AFTER_ALERT\">");

	 Frm_StartForm (ActSeeSvy);
	 Svy_PutParamSvyCod (Svy.SvyCod);
	 Svy_PutHiddenParamSvyOrder ();
	 Grp_PutParamWhichGrps ();
	 Pag_PutHiddenParamPagNum (Pag_SURVEYS,Gbl.Svys.CurrentPage);
	 Btn_PutConfirmButtonInline (Txt_View_survey_results);
	 Frm_EndForm ();

	 fprintf (Gbl.F.Out,"</div>");
	}
     }

   fprintf (Gbl.F.Out,"</td>"
	              "</tr>");

   /***** Write second row of data of this survey *****/
   fprintf (Gbl.F.Out,"<tr>"
	              "<td colspan=\"2\" class=\"LEFT_TOP");
   if (!ShowOnlyThisSvyComplete)
      fprintf (Gbl.F.Out," COLOR%u",Gbl.RowEvenOdd);
   fprintf (Gbl.F.Out,"\">");

   /* Author of the survey */
   Svy_WriteAuthor (&Svy);

   fprintf (Gbl.F.Out,"</td>"
                      "<td class=\"LEFT_TOP");
   if (!ShowOnlyThisSvyComplete)
      fprintf (Gbl.F.Out," COLOR%u",Gbl.RowEvenOdd);
   fprintf (Gbl.F.Out,"\">");

   /* Scope of the survey */
   fprintf (Gbl.F.Out,"<div class=\"%s\">%s: ",
            Svy.Status.Visible ? "ASG_GRP" :
        	                 "ASG_GRP_LIGHT",
            Txt_Scope);
   switch (Svy.Scope)
     {
      case Hie_UNK:	// Unknown
         Lay_ShowErrorAndExit ("Wrong survey scope.");
         break;
      case Hie_SYS:	// System
         fprintf (Gbl.F.Out,"%s",
                  Cfg_PLATFORM_SHORT_NAME);
	 break;
      case Hie_CTY:	// Country
         fprintf (Gbl.F.Out,"%s %s",
                  Txt_Country,Gbl.Hierarchy.Cty.Name[Gbl.Prefs.Language]);
	 break;
      case Hie_INS:	// Institution
         fprintf (Gbl.F.Out,"%s %s",
                  Txt_Institution,Gbl.Hierarchy.Ins.ShrtName);
	 break;
      case Hie_CTR:	// Centre
         fprintf (Gbl.F.Out,"%s %s",
                  Txt_Centre,Gbl.Hierarchy.Ctr.ShrtName);
	 break;
      case Hie_DEG:	// Degree
         fprintf (Gbl.F.Out,"%s %s",
                  Txt_Degree,Gbl.Hierarchy.Deg.ShrtName);
 	 break;
      case Hie_CRS:	// Course
	 fprintf (Gbl.F.Out,"%s %s",
	          Txt_Course,Gbl.Hierarchy.Crs.ShrtName);
	 break;
     }
   fprintf (Gbl.F.Out,"</div>");

   /* Users' roles who can answer the survey */
   fprintf (Gbl.F.Out,"<div class=\"%s\">%s:<br />",
            Svy.Status.Visible ? "ASG_GRP" :
        	                 "ASG_GRP_LIGHT",
            Txt_Users);
   Rol_WriteSelectorRoles (1 << Rol_STD |
                           1 << Rol_NET |
			   1 << Rol_TCH,
			   Svy.Roles,
			   true,false);
   fprintf (Gbl.F.Out,"</div>");

   /* Groups whose users can answer this survey */
   if (Svy.Scope == Hie_CRS)
      if (Gbl.Crs.Grps.NumGrps)
         Svy_GetAndWriteNamesOfGrpsAssociatedToSvy (&Svy);

   /* Text of the survey */
   Svy_GetSurveyTxtFromDB (Svy.SvyCod,Txt);
   Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
                     Txt,Cns_MAX_BYTES_TEXT,false);	// Convert from HTML to rigorous HTML
   Str_InsertLinks (Txt,Cns_MAX_BYTES_TEXT,60);	// Insert links
   fprintf (Gbl.F.Out,"<div class=\"PAR %s\">%s</div>"
                      "</td>"
                      "</tr>",
            Svy.Status.Visible ? "DAT" :
        	                 "DAT_LIGHT",
            Txt);

   /***** Write questions of this survey *****/
   if (ShowOnlyThisSvyComplete)
     {
      fprintf (Gbl.F.Out,"<tr>"
			 "<td colspan=\"5\">");
      Svy_ListSvyQuestions (&Svy,SvyQst);
      fprintf (Gbl.F.Out,"</td>"
			 "</tr>");
     }

   Gbl.RowEvenOdd = 1 - Gbl.RowEvenOdd;

   /***** Mark possible notification as seen *****/
   if (Svy.Scope == Hie_CRS)	// Only course surveys are notified
      Ntf_MarkNotifAsSeen (Ntf_EVENT_SURVEY,
	                   SvyCod,Svy.Cod,
	                   Gbl.Usrs.Me.UsrDat.UsrCod);

   if (ShowOnlyThisSvyComplete)
     {
      /***** End table *****/
      Tbl_EndTable ();

      /***** End box *****/
      Box_EndBox ();
     }

   /***** Free anchor string *****/
   Frm_FreeAnchorStr (Anchor);
  }

/*****************************************************************************/
/*********************** Write the author of a survey ************************/
/*****************************************************************************/

static void Svy_WriteAuthor (struct Survey *Svy)
  {
   Usr_WriteAuthor1Line (Svy->UsrCod,!Svy->Status.Visible);
  }

/*****************************************************************************/
/************************ Write status of a survey ***************************/
/*****************************************************************************/

static void Svy_WriteStatus (struct Survey *Svy)
  {
   extern const char *Txt_Hidden_survey;
   extern const char *Txt_Visible_survey;
   extern const char *Txt_Closed_survey;
   extern const char *Txt_Open_survey;
   extern const char *Txt_SURVEY_Type_of_user_not_allowed;
   extern const char *Txt_SURVEY_Type_of_user_allowed;
   extern const char *Txt_SURVEY_You_belong_to_the_scope_of_the_survey;
   extern const char *Txt_SURVEY_You_dont_belong_to_the_scope_of_the_survey;
   extern const char *Txt_SURVEY_You_have_already_answered;
   extern const char *Txt_SURVEY_You_have_not_answered;

   /***** Start list with items of status *****/
   fprintf (Gbl.F.Out,"<ul>");

   /* Write whether survey is visible or hidden */
   if (Svy->Status.Visible)
      fprintf (Gbl.F.Out,"<li class=\"STATUS_GREEN\">%s</li>",
               Txt_Visible_survey);
   else
      fprintf (Gbl.F.Out,"<li class=\"STATUS_RED_LIGHT\">%s</li>",
               Txt_Hidden_survey);

   /* Write whether survey is open or closed */
   if (Svy->Status.Open)
      fprintf (Gbl.F.Out,"<li class=\"%s\">%s</li>",
               Svy->Status.Visible ? "STATUS_GREEN" :
        	                     "STATUS_GREEN_LIGHT",
               Txt_Open_survey);
   else
      fprintf (Gbl.F.Out,"<li class=\"%s\">%s</li>",
               Svy->Status.Visible ? "STATUS_RED" :
        	                     "STATUS_RED_LIGHT",
               Txt_Closed_survey);

   /* Write whether survey can be answered by me or not depending on user type */
   if (Svy->Status.IAmLoggedWithAValidRoleToAnswer)
      fprintf (Gbl.F.Out,"<li class=\"%s\">%s</li>",
               Svy->Status.Visible ? "STATUS_GREEN" :
        	                     "STATUS_GREEN_LIGHT",
               Txt_SURVEY_Type_of_user_allowed);
   else
      fprintf (Gbl.F.Out,"<li class=\"%s\">%s</li>",
               Svy->Status.Visible ? "STATUS_RED" :
        	                     "STATUS_RED_LIGHT",
               Txt_SURVEY_Type_of_user_not_allowed);

   /* Write whether survey can be answered by me or not depending on groups */
   if (Svy->Status.IBelongToScope)
      fprintf (Gbl.F.Out,"<li class=\"%s\">%s</li>",
               Svy->Status.Visible ? "STATUS_GREEN" :
        	                     "STATUS_GREEN_LIGHT",
               Txt_SURVEY_You_belong_to_the_scope_of_the_survey);
   else
      fprintf (Gbl.F.Out,"<li class=\"%s\">%s</li>",
               Svy->Status.Visible ? "STATUS_RED" :
        	                     "STATUS_RED_LIGHT",
               Txt_SURVEY_You_dont_belong_to_the_scope_of_the_survey);

   /* Write whether survey has been already answered by me or not */
   if (Svy->Status.IHaveAnswered)
      fprintf (Gbl.F.Out,"<li class=\"%s\">%s</li>",
               Svy->Status.Visible ? "STATUS_GREEN" :
        	                     "STATUS_GREEN_LIGHT",
               Txt_SURVEY_You_have_already_answered);
   else
      fprintf (Gbl.F.Out,"<li class=\"%s\">%s</li>",
               Svy->Status.Visible ? "STATUS_RED" :
        	                     "STATUS_RED_LIGHT",
               Txt_SURVEY_You_have_not_answered);

   /***** End list with items of status *****/
   fprintf (Gbl.F.Out,"</ul>");
  }

/*****************************************************************************/
/********* Get parameter with the type or order in list of surveys ***********/
/*****************************************************************************/

static void Svy_GetParamSvyOrder (void)
  {
   Gbl.Svys.SelectedOrder = (Svy_Order_t)
	                    Par_GetParToUnsignedLong ("Order",
	                                              0,
	                                              Svy_NUM_ORDERS - 1,
	                                              (unsigned long) Svy_ORDER_DEFAULT);
  }

/*****************************************************************************/
/***** Put a hidden parameter with the type of order in list of surveys ******/
/*****************************************************************************/

void Svy_PutHiddenParamSvyOrder (void)
  {
   Par_PutHiddenParamUnsigned ("Order",(unsigned) Gbl.Svys.SelectedOrder);
  }

/*****************************************************************************/
/******************* Put a link (form) to edit one survey ********************/
/*****************************************************************************/

static void Svy_PutFormsToRemEditOneSvy (const struct Survey *Svy,
                                         const char *Anchor)
  {
   extern const char *Txt_Reset;

   Svy_CurrentSvyCod = Svy->SvyCod;	// Used as parameters in contextual links

   /***** Put form to remove survey *****/
   Ico_PutContextualIconToRemove (ActReqRemSvy,Svy_PutParams);

   /***** Put form to reset survey *****/
   Lay_PutContextualLinkOnlyIcon (ActReqRstSvy,NULL,Svy_PutParams,
				  "recycle.svg",
				  Txt_Reset);

   /***** Put form to hide/show survey *****/
   if (Svy->Status.Visible)
      Ico_PutContextualIconToHide (ActHidSvy,Anchor,Svy_PutParams);
   else
      Ico_PutContextualIconToUnhide (ActShoSvy,Anchor,Svy_PutParams);

   /***** Put form to edit survey *****/
   Ico_PutContextualIconToEdit (ActEdiOneSvy,Svy_PutParams);
  }

/*****************************************************************************/
/********************** Params used to edit a survey *************************/
/*****************************************************************************/

static void Svy_PutParams (void)
  {
   if (Svy_CurrentSvyCod > 0)
      Svy_PutParamSvyCod (Svy_CurrentSvyCod);
   Att_PutHiddenParamAttOrder ();
   Grp_PutParamWhichGrps ();
   Pag_PutHiddenParamPagNum (Pag_SURVEYS,Gbl.Svys.CurrentPage);
  }

/*****************************************************************************/
/*********************** Get list of all the surveys *************************/
/*****************************************************************************/

void Svy_GetListSurveys (void)
  {
   char *SubQuery[Hie_NUM_LEVELS];
   static const char *OrderBySubQuery[Svy_NUM_ORDERS] =
     {
      "StartTime DESC,EndTime DESC,Title DESC",	// Svy_ORDER_BY_START_DATE
      "EndTime DESC,StartTime DESC,Title DESC",	// Svy_ORDER_BY_END_DATE
     };
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;
   unsigned long NumRows;
   unsigned NumSvy;
   unsigned ScopesAllowed = 0;
   unsigned HiddenAllowed = 0;
   long Cods[Hie_NUM_LEVELS];
   Hie_Level_t Scope;
   bool SubQueryFilled = false;

   /***** Free list of surveys *****/
   if (Gbl.Svys.LstIsRead)
      Svy_FreeListSurveys ();

   /***** Set allowed and hidden scopes to get list depending on my user's role *****/
   Svy_SetAllowedAndHiddenScopes (&ScopesAllowed,&HiddenAllowed);

   /***** Get list of surveys from database *****/
   Cods[Hie_SYS] = -1L;				// System
   Cods[Hie_CTY] = Gbl.Hierarchy.Cty.CtyCod;	// Country
   Cods[Hie_INS] = Gbl.Hierarchy.Ins.InsCod;	// Institution
   Cods[Hie_CTR] = Gbl.Hierarchy.Ctr.CtrCod;	// Centre
   Cods[Hie_DEG] = Gbl.Hierarchy.Deg.DegCod;	// Degree
   Cods[Hie_CRS] = Gbl.Hierarchy.Crs.CrsCod;	// Course

   /* Fill subqueries for system, country, institution, centre and degree */
   for (Scope  = Hie_SYS;
	Scope <= Hie_DEG;
	Scope++)
      if (ScopesAllowed & 1 << Scope)
	{
	 if (asprintf (&SubQuery[Scope],"%s(Scope='%s' AND Cod=%ld%s)",
		       SubQueryFilled ? " OR " :
					"",
		       Sco_GetDBStrFromScope (Scope),Cods[Scope],
		       (HiddenAllowed & 1 << Scope) ? "" :
						      " AND Hidden='N'") < 0)
	    Lay_NotEnoughMemoryExit ();
	 SubQueryFilled = true;
	}
      else
        {
	 if (asprintf (&SubQuery[Scope],"%s","") < 0)
	    Lay_NotEnoughMemoryExit ();
        }

   /* Fill subquery for course */
   if (ScopesAllowed & 1 << Hie_CRS)
     {
      if (Gbl.Crs.Grps.WhichGrps == Grp_ONLY_MY_GROUPS)
        {
	 if (asprintf (&SubQuery[Hie_CRS],"%s("
						"Scope='%s' AND Cod=%ld%s"
						" AND "
						"(SvyCod NOT IN"
						" (SELECT SvyCod FROM svy_grp)"
						" OR"
						" SvyCod IN"
						" (SELECT svy_grp.SvyCod"
						" FROM svy_grp,crs_grp_usr"
						" WHERE crs_grp_usr.UsrCod=%ld"
						" AND svy_grp.GrpCod=crs_grp_usr.GrpCod))"
						")",
		       SubQueryFilled ? " OR " :
					"",
		       Sco_GetDBStrFromScope (Hie_CRS),Cods[Hie_CRS],
		       (HiddenAllowed & 1 << Hie_CRS) ? "" :
							      " AND Hidden='N'",
		       Gbl.Usrs.Me.UsrDat.UsrCod) < 0)
	    Lay_NotEnoughMemoryExit ();
        }
      else	// Gbl.Crs.Grps.WhichGrps == Grp_ALL_GROUPS
        {
	 if (asprintf (&SubQuery[Hie_CRS],"%s(Scope='%s' AND Cod=%ld%s)",
		       SubQueryFilled ? " OR " :
					"",
		       Sco_GetDBStrFromScope (Hie_CRS),Cods[Hie_CRS],
		       (HiddenAllowed & 1 << Hie_CRS) ? "" :
							      " AND Hidden='N'") < 0)
	    Lay_NotEnoughMemoryExit ();
        }
      SubQueryFilled = true;
     }
   else
     {
      if (asprintf (&SubQuery[Hie_CRS],"%s","") < 0)
	 Lay_NotEnoughMemoryExit ();
     }

   /* Make query */
   if (SubQueryFilled)
      NumRows = DB_QuerySELECT (&mysql_res,"can not get surveys",
				"SELECT SvyCod FROM surveys"
				" WHERE %s%s%s%s%s%s"
				" ORDER BY %s",
				SubQuery[Hie_SYS],
				SubQuery[Hie_CTY],
				SubQuery[Hie_INS],
				SubQuery[Hie_CTR],
				SubQuery[Hie_DEG],
				SubQuery[Hie_CRS],
				OrderBySubQuery[Gbl.Svys.SelectedOrder]);
   else
     {
      Lay_ShowErrorAndExit ("Can not get list of surveys.");
      NumRows = 0;	// Not reached. Initialized to avoid warning
     }

   /* Free allocated memory for subqueries */
   for (Scope  = Hie_SYS;
	Scope <= Hie_CRS;
	Scope++)
      free ((void *) SubQuery[Scope]);

   if (NumRows) // Surveys found...
     {
      Gbl.Svys.Num = (unsigned) NumRows;

      /***** Create list of surveys *****/
      if ((Gbl.Svys.LstSvyCods = (long *) calloc (NumRows,sizeof (long))) == NULL)
         Lay_NotEnoughMemoryExit ();

      /***** Get the surveys codes *****/
      for (NumSvy = 0;
	   NumSvy < Gbl.Svys.Num;
	   NumSvy++)
        {
         /* Get next survey code */
         row = mysql_fetch_row (mysql_res);
         if ((Gbl.Svys.LstSvyCods[NumSvy] = Str_ConvertStrCodToLongCod (row[0])) < 0)
            Lay_ShowErrorAndExit ("Error: wrong survey code.");
        }
     }
   else
      Gbl.Svys.Num = 0;

   /***** Free structure that stores the query result *****/
   DB_FreeMySQLResult (&mysql_res);

   Gbl.Svys.LstIsRead = true;
  }

/*****************************************************************************/
/*** Set allowed and hidden scopes to get list depending on my user's role ***/
/*****************************************************************************/

static void Svy_SetAllowedAndHiddenScopes (unsigned *ScopesAllowed,
                                           unsigned *HiddenAllowed)
  {
   switch (Gbl.Usrs.Me.Role.Logged)
     {
      case Rol_UNK:	// User not logged in *********************************
	 *ScopesAllowed = 0;
	 *HiddenAllowed = 0;
         break;
      case Rol_GST:	// User not belonging to any course *******************
	 *ScopesAllowed = 1 << Hie_SYS;
	 *HiddenAllowed = 0;
	 break;
      case Rol_USR:	// Student or teacher in other courses...
   	   	   	// ...but not belonging to the current course *********
	 *ScopesAllowed = 1 << Hie_SYS;
	 *HiddenAllowed = 0;
	 if (Usr_CheckIfIBelongToCty (Gbl.Hierarchy.Cty.CtyCod))
	   {
	    *ScopesAllowed |= 1 << Hie_CTY;
	    if (Usr_CheckIfIBelongToIns (Gbl.Hierarchy.Ins.InsCod))
	      {
	       *ScopesAllowed |= 1 << Hie_INS;
	       if (Usr_CheckIfIBelongToCtr (Gbl.Hierarchy.Ctr.CtrCod))
		 {
		  *ScopesAllowed |= 1 << Hie_CTR;
		  if (Usr_CheckIfIBelongToDeg (Gbl.Hierarchy.Deg.DegCod))
		     *ScopesAllowed |= 1 << Hie_DEG;
		 }
	      }
	   }
         break;
      case Rol_STD:	// Student in current course **************************
	 *ScopesAllowed = 1 << Hie_SYS;
	 *HiddenAllowed = 0;
	 if (Usr_CheckIfIBelongToCty (Gbl.Hierarchy.Cty.CtyCod))
	   {
	    *ScopesAllowed |= 1 << Hie_CTY;
	    if (Usr_CheckIfIBelongToIns (Gbl.Hierarchy.Ins.InsCod))
	      {
	       *ScopesAllowed |= 1 << Hie_INS;
	       if (Usr_CheckIfIBelongToCtr (Gbl.Hierarchy.Ctr.CtrCod))
		 {
		  *ScopesAllowed |= 1 << Hie_CTR;
		  if (Usr_CheckIfIBelongToDeg (Gbl.Hierarchy.Deg.DegCod))
		    {
		     *ScopesAllowed |= 1 << Hie_DEG;
		     if (Gbl.Usrs.Me.IBelongToCurrentCrs)
			*ScopesAllowed |= 1 << Hie_CRS;
		    }
		 }
	      }
	   }
         break;
      case Rol_NET:	// Non-editing teacher in current course **************
      case Rol_TCH:	// Teacher in current course **************************
	 *ScopesAllowed = 1 << Hie_SYS;
	 *HiddenAllowed = 0;
	 if (Usr_CheckIfIBelongToCty (Gbl.Hierarchy.Cty.CtyCod))
	   {
	    *ScopesAllowed |= 1 << Hie_CTY;
	    if (Usr_CheckIfIBelongToIns (Gbl.Hierarchy.Ins.InsCod))
	      {
	       *ScopesAllowed |= 1 << Hie_INS;
	       if (Usr_CheckIfIBelongToCtr (Gbl.Hierarchy.Ctr.CtrCod))
		 {
		  *ScopesAllowed |= 1 << Hie_CTR;
		  if (Usr_CheckIfIBelongToDeg (Gbl.Hierarchy.Deg.DegCod))
		    {
		     *ScopesAllowed |= 1 << Hie_DEG;
		     if (Gbl.Usrs.Me.IBelongToCurrentCrs)
		       {
			*ScopesAllowed |= 1 << Hie_CRS;
			*HiddenAllowed |= 1 << Hie_CRS;	// A non-editing teacher or teacher can view hidden course surveys
		       }
		    }
		 }
	      }
	   }
         break;
      case Rol_DEG_ADM:	// Degree administrator *******************************
	 *ScopesAllowed = 1 << Hie_SYS;
	 *HiddenAllowed = 0;
	 if (Gbl.Hierarchy.Cty.CtyCod > 0)			// Country selected
	   {
	    *ScopesAllowed |= 1 << Hie_CTY;
	    if (Gbl.Hierarchy.Ins.InsCod > 0)			// Institution selected
	      {
	       *ScopesAllowed |= 1 << Hie_INS;
	       if (Gbl.Hierarchy.Ctr.CtrCod > 0)		// Centre selected
		 {
		  *ScopesAllowed |= 1 << Hie_CTR;
		  if (Gbl.Hierarchy.Deg.DegCod > 0)		// Degree selected
		    {
		     *ScopesAllowed |= 1 << Hie_DEG;
		     *HiddenAllowed |= 1 << Hie_DEG;	// A degree admin can view hidden degree surveys
		    }
	         }
	      }
	   }
	 break;
      case Rol_CTR_ADM:	// Centre administrator *******************************
	 *ScopesAllowed = 1 << Hie_SYS;
	 *HiddenAllowed = 0;
	 if (Gbl.Hierarchy.Cty.CtyCod > 0)			// Country selected
	   {
	    *ScopesAllowed |= 1 << Hie_CTY;
	    if (Gbl.Hierarchy.Ins.InsCod > 0)			// Institution selected
	      {
	       *ScopesAllowed |= 1 << Hie_INS;
	       if (Gbl.Hierarchy.Ctr.CtrCod > 0)		// Centre selected
		 {
		  *ScopesAllowed |= 1 << Hie_CTR;
		  *HiddenAllowed |= 1 << Hie_CTR;		// A centre admin can view hidden centre surveys
		 }
	      }
	   }
	 break;
      case Rol_INS_ADM:	// Institution administrator **************************
	 *ScopesAllowed = 1 << Hie_SYS;
	 *HiddenAllowed = 0;
	 if (Gbl.Hierarchy.Cty.CtyCod > 0)			// Country selected
	   {
	    *ScopesAllowed |= 1 << Hie_CTY;
	    if (Gbl.Hierarchy.Ins.InsCod > 0)			// Institution selected
	      {
	       *ScopesAllowed |= 1 << Hie_INS;
	       *HiddenAllowed |= 1 << Hie_INS;		// An institution admin can view hidden institution surveys
	      }
	   }
	 break;
      case Rol_SYS_ADM:	// System administrator (superuser) *******************
	 *ScopesAllowed = 1 << Hie_SYS;
	 *HiddenAllowed = 1 << Hie_SYS;			// A system admin can view hidden system surveys
	 if (Gbl.Hierarchy.Cty.CtyCod > 0)			// Country selected
	   {
	    *ScopesAllowed |= 1 << Hie_CTY;
	    *HiddenAllowed |= 1 << Hie_CTY;		// A system admin can view hidden country surveys
	    if (Gbl.Hierarchy.Ins.InsCod > 0)			// Institution selected
	      {
	       *ScopesAllowed |= 1 << Hie_INS;
	       *HiddenAllowed |= 1 << Hie_INS;		// A system admin can view hidden institution surveys
	       if (Gbl.Hierarchy.Ctr.CtrCod > 0)		// Centre selected
		 {
		  *ScopesAllowed |= 1 << Hie_CTR;
	          *HiddenAllowed |= 1 << Hie_CTR;		// A system admin can view hidden centre surveys
		  if (Gbl.Hierarchy.Deg.DegCod > 0)		// Degree selected
		    {
		     *ScopesAllowed |= 1 << Hie_DEG;
	             *HiddenAllowed |= 1 << Hie_DEG;	// A system admin can view hidden degree surveys
		     if (Gbl.Hierarchy.Level == Hie_CRS)	// Course selected
		       {
			*ScopesAllowed |= 1 << Hie_CRS;
	                *HiddenAllowed |= 1 << Hie_CRS;	// A system admin can view hidden course surveys
		       }
		    }
		 }
	      }
	   }
	 break;
     }
  }

/*****************************************************************************/
/********************* Get survey data using its code ************************/
/*****************************************************************************/

void Svy_GetDataOfSurveyByCod (struct Survey *Svy)
  {
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;
   unsigned long NumRows;

   /***** Get data of survey from database *****/
   NumRows = DB_QuerySELECT (&mysql_res,"can not get survey data",
			     "SELECT SvyCod,Scope,Cod,Hidden,Roles,UsrCod,"
			     "UNIX_TIMESTAMP(StartTime),"
			     "UNIX_TIMESTAMP(EndTime),"
			     "NOW() BETWEEN StartTime AND EndTime,"
			     "Title"
			     " FROM surveys"
			     " WHERE SvyCod=%ld",
			     Svy->SvyCod);

   if (NumRows) // Survey found...
     {
      /* Get row */
      row = mysql_fetch_row (mysql_res);

      /* Get code of the survey (row[0]) */
      Svy->SvyCod = Str_ConvertStrCodToLongCod (row[0]);

      /* Get survey scope (row[1]) */
      if ((Svy->Scope = Sco_GetScopeFromDBStr (row[1])) == Hie_UNK)
         Lay_ShowErrorAndExit ("Wrong survey scope.");

      /* Get code of the country, institution, centre, degree or course (row[2]) */
      Svy->Cod = Str_ConvertStrCodToLongCod (row[2]);

      /* Get whether the survey is hidden (row[3]) */
      Svy->Status.Visible = (row[3][0] == 'N');

      /* Get roles (row[4]) */
      if (sscanf (row[4],"%u",&Svy->Roles) != 1)
      	 Lay_ShowErrorAndExit ("Error when reading roles of survey.");

      /* Get author of the survey (row[5]) */
      Svy->UsrCod = Str_ConvertStrCodToLongCod (row[5]);

      /* Get start date (row[6] holds the start UTC time) */
      Svy->TimeUTC[Att_START_TIME] = Dat_GetUNIXTimeFromStr (row[6]);

      /* Get end   date (row[7] holds the end   UTC time) */
      Svy->TimeUTC[Att_END_TIME  ] = Dat_GetUNIXTimeFromStr (row[7]);

      /* Get whether the survey is open or closed (row(8)) */
      Svy->Status.Open = (row[8][0] == '1');

      /* Get the title of the survey (row[9]) */
      Str_Copy (Svy->Title,row[9],
                Svy_MAX_BYTES_SURVEY_TITLE);

      /* Get number of questions and number of users who have already answer this survey */
      Svy->NumQsts = Svy_GetNumQstsSvy (Svy->SvyCod);
      Svy->NumUsrs = Svy_GetNumUsrsWhoHaveAnsweredSvy (Svy->SvyCod);

      /* Am I logged with a valid role to answer this survey? */
      Svy->Status.IAmLoggedWithAValidRoleToAnswer = (Svy->Roles & (1 << Gbl.Usrs.Me.Role.Logged));

      /* Do I belong to valid groups to answer this survey? */
      switch (Svy->Scope)
        {
	 case Hie_UNK:	// Unknown
            Lay_ShowErrorAndExit ("Wrong survey scope.");
	    break;
	 case Hie_SYS:	// System
            Svy->Status.IBelongToScope = Gbl.Usrs.Me.Logged;
	    break;
	 case Hie_CTY:	// Country
            Svy->Status.IBelongToScope = Usr_CheckIfIBelongToCty (Svy->Cod);
	    break;
	 case Hie_INS:	// Institution
            Svy->Status.IBelongToScope = Usr_CheckIfIBelongToIns (Svy->Cod);
	    break;
	 case Hie_CTR:	// Centre
            Svy->Status.IBelongToScope = Usr_CheckIfIBelongToCtr (Svy->Cod);
	    break;
	 case Hie_DEG:	// Degree
            Svy->Status.IBelongToScope = Usr_CheckIfIBelongToDeg (Svy->Cod);
	    break;
	 case Hie_CRS:	// Course
	    Svy->Status.IBelongToScope = Usr_CheckIfIBelongToCrs (Svy->Cod) &&
					 Svy_CheckIfICanDoThisSurveyBasedOnGrps (Svy->SvyCod);
	    break;
        }

      /* Have I answered this survey? */
      Svy->Status.IHaveAnswered = Svy_CheckIfIHaveAnsweredSvy (Svy->SvyCod);

      /* Can I answer survey? */
      Svy->Status.ICanAnswer = (Svy->NumQsts != 0) &&
                                Svy->Status.Visible &&
                                Svy->Status.Open &&
                                Svy->Status.IAmLoggedWithAValidRoleToAnswer &&
                                Svy->Status.IBelongToScope &&
                               !Svy->Status.IHaveAnswered;

      /* Can I view results of the survey?
         Can I edit survey? */
      switch (Gbl.Usrs.Me.Role.Logged)
        {
         case Rol_STD:
            Svy->Status.ICanViewResults = (Svy->Scope == Hie_CRS ||
        	                           Svy->Scope == Hie_DEG ||
        	                           Svy->Scope == Hie_CTR ||
        	                           Svy->Scope == Hie_INS ||
        	                           Svy->Scope == Hie_CTY ||
        	                           Svy->Scope == Hie_SYS) &&
        	                          (Svy->NumQsts != 0) &&
                                           Svy->Status.Visible &&
                                           Svy->Status.Open &&
                                           Svy->Status.IAmLoggedWithAValidRoleToAnswer &&
                                           Svy->Status.IBelongToScope &&
                                           Svy->Status.IHaveAnswered;
            Svy->Status.ICanEdit         = false;
            break;
         case Rol_NET:
            Svy->Status.ICanViewResults = (Svy->Scope == Hie_CRS ||
        	                           Svy->Scope == Hie_DEG ||
        	                           Svy->Scope == Hie_CTR ||
        	                           Svy->Scope == Hie_INS ||
        	                           Svy->Scope == Hie_CTY ||
        	                           Svy->Scope == Hie_SYS) &&
        	                           Svy->NumQsts != 0 &&
                                          !Svy->Status.ICanAnswer;
            Svy->Status.ICanEdit        = false;
            break;
         case Rol_TCH:
            Svy->Status.ICanViewResults = (Svy->Scope == Hie_CRS ||
        	                           Svy->Scope == Hie_DEG ||
        	                           Svy->Scope == Hie_CTR ||
        	                           Svy->Scope == Hie_INS ||
        	                           Svy->Scope == Hie_CTY ||
        	                           Svy->Scope == Hie_SYS) &&
        	                           Svy->NumQsts != 0 &&
                                          !Svy->Status.ICanAnswer;
            Svy->Status.ICanEdit        =  Svy->Scope == Hie_CRS &&
                                           Svy->Status.IBelongToScope;
            break;
         case Rol_DEG_ADM:
            Svy->Status.ICanViewResults = (Svy->Scope == Hie_DEG ||
        	                           Svy->Scope == Hie_CTR ||
        	                           Svy->Scope == Hie_INS ||
        	                           Svy->Scope == Hie_CTY ||
        	                           Svy->Scope == Hie_SYS) &&
        	                          (Svy->NumQsts != 0) &&
                                          !Svy->Status.ICanAnswer;
            Svy->Status.ICanEdit        =  Svy->Scope == Hie_DEG &&
                                           Svy->Status.IBelongToScope;
            break;
         case Rol_CTR_ADM:
            Svy->Status.ICanViewResults = (Svy->Scope == Hie_CTR ||
        	                           Svy->Scope == Hie_INS ||
        	                           Svy->Scope == Hie_CTY ||
        	                           Svy->Scope == Hie_SYS) &&
        	                          (Svy->NumQsts != 0) &&
                                          !Svy->Status.ICanAnswer;
            Svy->Status.ICanEdit        =  Svy->Scope == Hie_CTR &&
                                           Svy->Status.IBelongToScope;
            break;
         case Rol_INS_ADM:
            Svy->Status.ICanViewResults = (Svy->Scope == Hie_INS ||
        	                           Svy->Scope == Hie_CTY ||
        	                           Svy->Scope == Hie_SYS) &&
        	                          (Svy->NumQsts != 0) &&
                                          !Svy->Status.ICanAnswer;
            Svy->Status.ICanEdit        =  Svy->Scope == Hie_INS &&
                                           Svy->Status.IBelongToScope;
            break;
         case Rol_SYS_ADM:
            Svy->Status.ICanViewResults = (Svy->NumQsts != 0);
            Svy->Status.ICanEdit        = true;
            break;
         default:
            Svy->Status.ICanViewResults = false;
            Svy->Status.ICanEdit        = false;
            break;
        }
     }
   else
     {
      /* Initialize to empty survey */
      Svy->SvyCod                  = -1L;
      Svy->Scope                   = Hie_UNK;
      Svy->Roles                   = 0;
      Svy->UsrCod                  = -1L;
      Svy->TimeUTC[Svy_START_TIME] =
      Svy->TimeUTC[Svy_END_TIME  ] = (time_t) 0;
      Svy->Title[0]                = '\0';
      Svy->NumQsts                 = 0;
      Svy->NumUsrs                 = 0;
      Svy->Status.Visible                         = true;
      Svy->Status.Open                            = false;
      Svy->Status.IAmLoggedWithAValidRoleToAnswer = false;
      Svy->Status.IBelongToScope                  = false;
      Svy->Status.IHaveAnswered                   = false;
      Svy->Status.ICanAnswer                      = false;
      Svy->Status.ICanViewResults                 = false;
      Svy->Status.ICanEdit                        = false;
     }

   /***** Free structure that stores the query result *****/
   DB_FreeMySQLResult (&mysql_res);
  }

/*****************************************************************************/
/**************************** Free list of surveys ***************************/
/*****************************************************************************/

void Svy_FreeListSurveys (void)
  {
   if (Gbl.Svys.LstIsRead && Gbl.Svys.LstSvyCods)
     {
      /***** Free memory used by the list of surveys *****/
      free ((void *) Gbl.Svys.LstSvyCods);
      Gbl.Svys.LstSvyCods = NULL;
      Gbl.Svys.Num = 0;
      Gbl.Svys.LstIsRead = false;
     }
  }

/*****************************************************************************/
/********************** Get survey text from database ************************/
/*****************************************************************************/

static void Svy_GetSurveyTxtFromDB (long SvyCod,char Txt[Cns_MAX_BYTES_TEXT + 1])
  {
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;
   unsigned long NumRows;

   /***** Get text of survey from database *****/
   NumRows = DB_QuerySELECT (&mysql_res,"can not get survey text",
			     "SELECT Txt FROM surveys WHERE SvyCod=%ld",
			     SvyCod);

   /***** The result of the query must have one row or none *****/
   if (NumRows == 1)
     {
      /* Get info text */
      row = mysql_fetch_row (mysql_res);
      Str_Copy (Txt,row[0],
                Cns_MAX_BYTES_TEXT);
     }
   else
      Txt[0] = '\0';

   /***** Free structure that stores the query result *****/
   DB_FreeMySQLResult (&mysql_res);

   if (NumRows > 1)
      Lay_ShowErrorAndExit ("Error when getting survey text.");
  }

/*****************************************************************************/
/******************** Get summary and content of a survey  *******************/
/*****************************************************************************/
// This function may be called inside a web service, so don't report error

void Svy_GetNotifSurvey (char SummaryStr[Ntf_MAX_BYTES_SUMMARY + 1],
                         char **ContentStr,
                         long SvyCod,bool GetContent)
  {
   char Query[128];
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;
   size_t Length;

   SummaryStr[0] = '\0';	// Return nothing on error

   /***** Build query *****/
   sprintf (Query,"SELECT Title,Txt FROM surveys WHERE SvyCod=%ld",
            SvyCod);
   if (!mysql_query (&Gbl.mysql,Query))
      if ((mysql_res = mysql_store_result (&Gbl.mysql)) != NULL)
        {
         /***** Result should have a unique row *****/
         if (mysql_num_rows (mysql_res) == 1)
           {
            /***** Get row *****/
            row = mysql_fetch_row (mysql_res);

            /***** Get summary *****/
            Str_Copy (SummaryStr,row[0],
                      Ntf_MAX_BYTES_SUMMARY);

            /***** Get content *****/
            if (GetContent)
              {
               Length = strlen (row[1]);
               if ((*ContentStr = (char *) malloc (Length + 1)) == NULL)
                  Lay_ShowErrorAndExit ("Error allocating memory for notification content.");
               Str_Copy (*ContentStr,row[1],
                         Length);
              }
           }
         mysql_free_result (mysql_res);
        }
  }

/*****************************************************************************/
/******************* Write parameter with code of survey *********************/
/*****************************************************************************/

static void Svy_PutParamSvyCod (long SvyCod)
  {
   Par_PutHiddenParamLong ("SvyCod",SvyCod);
  }

/*****************************************************************************/
/******************** Get parameter with code of survey **********************/
/*****************************************************************************/

static long Svy_GetParamSvyCod (void)
  {
   /***** Get code of survey *****/
   return Par_GetParToLong ("SvyCod");
  }

/*****************************************************************************/
/*************** Ask for confirmation of removing of a survey ****************/
/*****************************************************************************/

void Svy_AskRemSurvey (void)
  {
   extern const char *Txt_Do_you_really_want_to_remove_the_survey_X;
   extern const char *Txt_Remove_survey;
   struct Survey Svy;
   struct SurveyQuestion SvyQst;

   /***** Initialize question to zero *****/
   Svy_InitQst (&SvyQst);

   /***** Get parameters *****/
   Svy_GetParamSvyOrder ();
   Grp_GetParamWhichGrps ();
   Gbl.Svys.CurrentPage = Pag_GetParamPagNum (Pag_SURVEYS);

   /***** Get survey code *****/
   if ((Svy.SvyCod = Svy_GetParamSvyCod ()) == -1L)
      Lay_ShowErrorAndExit ("Code of survey is missing.");

   /***** Get data of the survey from database *****/
   Svy_GetDataOfSurveyByCod (&Svy);
   if (!Svy.Status.ICanEdit)
      Lay_ShowErrorAndExit ("You can not remove this survey.");

   /***** Show question and button to remove survey *****/
   Svy_CurrentSvyCod = Svy.SvyCod;
   Ale_ShowAlertAndButton (ActRemSvy,NULL,NULL,Svy_PutParams,
			   Btn_REMOVE_BUTTON,Txt_Remove_survey,
			   Ale_QUESTION,Txt_Do_you_really_want_to_remove_the_survey_X,
			   Svy.Title);

   /***** Show surveys again *****/
   Svy_ListAllSurveys (&SvyQst);
  }

/*****************************************************************************/
/****************************** Remove a survey ******************************/
/*****************************************************************************/

void Svy_RemoveSurvey (void)
  {
   extern const char *Txt_Survey_X_removed;
   struct Survey Svy;
   struct SurveyQuestion SvyQst;

   /***** Initialize question to zero *****/
   Svy_InitQst (&SvyQst);

   /***** Get survey code *****/
   if ((Svy.SvyCod = Svy_GetParamSvyCod ()) == -1L)
      Lay_ShowErrorAndExit ("Code of survey is missing.");

   /***** Get data of the survey from database *****/
   Svy_GetDataOfSurveyByCod (&Svy);
   if (!Svy.Status.ICanEdit)
      Lay_ShowErrorAndExit ("You can not remove this survey.");

   /***** Remove all the users in this survey *****/
   DB_QueryDELETE ("can not remove users who are answered a survey",
		   "DELETE FROM svy_users WHERE SvyCod=%ld",
		   Svy.SvyCod);

   /***** Remove all the answers in this survey *****/
   DB_QueryDELETE ("can not remove answers of a survey",
		   "DELETE FROM svy_answers USING svy_questions,svy_answers"
                   " WHERE svy_questions.SvyCod=%ld"
                   " AND svy_questions.QstCod=svy_answers.QstCod",
		   Svy.SvyCod);

   /***** Remove all the questions in this survey *****/
   DB_QueryDELETE ("can not remove questions of a survey",
		   "DELETE FROM svy_questions"
                   " WHERE SvyCod=%ld",
		   Svy.SvyCod);

   /***** Remove all the groups of this survey *****/
   Svy_RemoveAllTheGrpsAssociatedToAndSurvey (Svy.SvyCod);

   /***** Remove survey *****/
   DB_QueryDELETE ("can not remove survey",
		   "DELETE FROM surveys WHERE SvyCod=%ld",
		   Svy.SvyCod);

   /***** Mark possible notifications as removed *****/
   Ntf_MarkNotifAsRemoved (Ntf_EVENT_SURVEY,Svy.SvyCod);

   /***** Write message to show the change made *****/
   Ale_ShowAlert (Ale_SUCCESS,Txt_Survey_X_removed,
                  Svy.Title);

   /***** Show surveys again *****/
   Svy_ListAllSurveys (&SvyQst);
  }

/*****************************************************************************/
/***************** Ask for confirmation of reset of a survey *****************/
/*****************************************************************************/

void Svy_AskResetSurvey (void)
  {
   extern const char *Txt_Do_you_really_want_to_reset_the_survey_X;
   struct Survey Svy;
   struct SurveyQuestion SvyQst;

   /***** Initialize question to zero *****/
   Svy_InitQst (&SvyQst);

   /***** Get parameters *****/
   Svy_GetParamSvyOrder ();
   Grp_GetParamWhichGrps ();
   Gbl.Svys.CurrentPage = Pag_GetParamPagNum (Pag_SURVEYS);

   /***** Get survey code *****/
   if ((Svy.SvyCod = Svy_GetParamSvyCod ()) == -1L)
      Lay_ShowErrorAndExit ("Code of survey is missing.");

   /***** Get data of the survey from database *****/
   Svy_GetDataOfSurveyByCod (&Svy);
   if (!Svy.Status.ICanEdit)
      Lay_ShowErrorAndExit ("You can not reset this survey.");

   /***** Ask for confirmation of reset *****/
   Ale_ShowAlert (Ale_WARNING,Txt_Do_you_really_want_to_reset_the_survey_X,
                  Svy.Title);

   /***** Button of confirmation of reset *****/
   Svy_CurrentSvyCod = Svy.SvyCod;
   Svy_PutButtonToResetSurvey ();

   /***** Show surveys again *****/
   Svy_ListAllSurveys (&SvyQst);
  }

/*****************************************************************************/
/************************* Put button to reset survey ************************/
/*****************************************************************************/

static void Svy_PutButtonToResetSurvey (void)
  {
   extern const char *Txt_Reset_survey;

   Frm_StartForm (ActRstSvy);
   Svy_PutParams ();
   Btn_PutRemoveButton (Txt_Reset_survey);
   Frm_EndForm ();
  }

/*****************************************************************************/
/******************************* Reset a survey ******************************/
/*****************************************************************************/

void Svy_ResetSurvey (void)
  {
   extern const char *Txt_Survey_X_reset;
   struct Survey Svy;
   struct SurveyQuestion SvyQst;

   /***** Initialize question to zero *****/
   Svy_InitQst (&SvyQst);

   /***** Get survey code *****/
   if ((Svy.SvyCod = Svy_GetParamSvyCod ()) == -1L)
      Lay_ShowErrorAndExit ("Code of survey is missing.");

   /***** Get data of the survey from database *****/
   Svy_GetDataOfSurveyByCod (&Svy);
   if (!Svy.Status.ICanEdit)
      Lay_ShowErrorAndExit ("You can not reset this survey.");

   /***** Remove all the users in this survey *****/
   DB_QueryDELETE ("can not remove users who are answered a survey",
		   "DELETE FROM svy_users WHERE SvyCod=%ld",
		   Svy.SvyCod);

   /***** Reset all the answers in this survey *****/
   DB_QueryUPDATE ("can not reset answers of a survey",
		   "UPDATE svy_answers,svy_questions SET svy_answers.NumUsrs=0"
                   " WHERE svy_questions.SvyCod=%ld"
                   " AND svy_questions.QstCod=svy_answers.QstCod",
		   Svy.SvyCod);

   /***** Write message to show the change made *****/
   Ale_ShowAlert (Ale_SUCCESS,Txt_Survey_X_reset,
                  Svy.Title);

   /***** Show surveys again *****/
   Svy_ListAllSurveys (&SvyQst);
  }

/*****************************************************************************/
/******************************** Hide a survey ******************************/
/*****************************************************************************/

void Svy_HideSurvey (void)
  {
   struct Survey Svy;
   struct SurveyQuestion SvyQst;

   /***** Initialize question to zero *****/
   Svy_InitQst (&SvyQst);

   /***** Get survey code *****/
   if ((Svy.SvyCod = Svy_GetParamSvyCod ()) == -1L)
      Lay_ShowErrorAndExit ("Code of survey is missing.");

   /***** Get data of the survey from database *****/
   Svy_GetDataOfSurveyByCod (&Svy);
   if (!Svy.Status.ICanEdit)
      Lay_ShowErrorAndExit ("You can not hide this survey.");

   /***** Hide survey *****/
   DB_QueryUPDATE ("can not hide survey",
		   "UPDATE surveys SET Hidden='Y' WHERE SvyCod=%ld",
		   Svy.SvyCod);

   /***** Show surveys again *****/
   Svy_ListAllSurveys (&SvyQst);
  }

/*****************************************************************************/
/******************************** Show a survey ******************************/
/*****************************************************************************/

void Svy_UnhideSurvey (void)
  {
   struct Survey Svy;
   struct SurveyQuestion SvyQst;

   /***** Initialize question to zero *****/
   Svy_InitQst (&SvyQst);

   /***** Get survey code *****/
   if ((Svy.SvyCod = Svy_GetParamSvyCod ()) == -1L)
      Lay_ShowErrorAndExit ("Code of survey is missing.");

   /***** Get data of the survey from database *****/
   Svy_GetDataOfSurveyByCod (&Svy);
   if (!Svy.Status.ICanEdit)
      Lay_ShowErrorAndExit ("You can not unhide this survey.");

   /***** Show survey *****/
   DB_QueryUPDATE ("can not show survey",
		   "UPDATE surveys SET Hidden='N' WHERE SvyCod=%ld",
		   Svy.SvyCod);

   /***** Show surveys again *****/
   Svy_ListAllSurveys (&SvyQst);
  }

/*****************************************************************************/
/******************* Check if the title of a survey exists *******************/
/*****************************************************************************/

static bool Svy_CheckIfSimilarSurveyExists (struct Survey *Svy)
  {
   /***** Get number of surveys with a field value from database *****/
   return (DB_QueryCOUNT ("can not get similar surveys",
			  "SELECT COUNT(*) FROM surveys"
			  " WHERE Scope='%s' AND Cod=%ld"
			  " AND Title='%s' AND SvyCod<>%ld",
			  Sco_GetDBStrFromScope (Svy->Scope),Svy->Cod,
			  Svy->Title,Svy->SvyCod) != 0);
  }

/*****************************************************************************/
/********************* Put a form to create a new survey *********************/
/*****************************************************************************/

void Svy_RequestCreatOrEditSvy (void)
  {
   extern const char *Hlp_ASSESSMENT_Surveys_new_survey;
   extern const char *Hlp_ASSESSMENT_Surveys_edit_survey;
   extern const char *The_ClassFormInBox[The_NUM_THEMES];
   extern const char *Txt_New_survey;
   extern const char *Txt_Scope;
   extern const char *Txt_Edit_survey;
   extern const char *Txt_Title;
   extern const char *Txt_Description;
   extern const char *Txt_Users;
   extern const char *Txt_Create_survey;
   extern const char *Txt_Save_changes;
   struct Survey Svy;
   struct SurveyQuestion SvyQst;
   bool ItsANewSurvey;
   char Txt[Cns_MAX_BYTES_TEXT + 1];

   /***** Initialize question to zero *****/
   Svy_InitQst (&SvyQst);

   /***** Get parameters *****/
   Svy_GetParamSvyOrder ();
   Grp_GetParamWhichGrps ();
   Gbl.Svys.CurrentPage = Pag_GetParamPagNum (Pag_SURVEYS);

   /***** Get the code of the survey *****/
   ItsANewSurvey = ((Svy.SvyCod = Svy_GetParamSvyCod ()) == -1L);

   /***** Get from the database the data of the survey *****/
   if (ItsANewSurvey)
     {
      /***** Put link (form) to create new survey *****/
      if (!Svy_CheckIfICanCreateSvy ())
         Lay_ShowErrorAndExit ("You can not create a new survey here.");

      /* Initialize to empty survey */
      Svy.SvyCod = -1L;
      Svy.Scope  = Hie_UNK;
      Svy.Roles  = (1 << Rol_STD);
      Svy.UsrCod = Gbl.Usrs.Me.UsrDat.UsrCod;
      Svy.TimeUTC[Svy_START_TIME] = Gbl.StartExecutionTimeUTC;
      Svy.TimeUTC[Svy_END_TIME  ] = Gbl.StartExecutionTimeUTC + (24 * 60 * 60);	// +24 hours
      Svy.Title[0] = '\0';
      Svy.NumQsts = 0;
      Svy.NumUsrs = 0;
      Svy.Status.Visible = true;
      Svy.Status.Open = true;
      Svy.Status.IAmLoggedWithAValidRoleToAnswer = false;
      Svy.Status.IBelongToScope = false;
      Svy.Status.IHaveAnswered = false;
      Svy.Status.ICanAnswer = false;
      Svy.Status.ICanViewResults = false;
     }
   else
     {
      /* Get data of the survey from database */
      Svy_GetDataOfSurveyByCod (&Svy);
      if (!Svy.Status.ICanEdit)
         Lay_ShowErrorAndExit ("You can not update this survey.");

      /* Get text of the survey from database */
      Svy_GetSurveyTxtFromDB (Svy.SvyCod,Txt);
     }

   /***** Start form *****/
   Svy_CurrentSvyCod = Svy.SvyCod;
   Frm_StartForm (ItsANewSurvey ? ActNewSvy :
	                          ActChgSvy);
   Svy_PutParams ();

   /***** Start box and table *****/
   if (ItsANewSurvey)
      Box_StartBoxTable (NULL,Txt_New_survey,NULL,
                         Hlp_ASSESSMENT_Surveys_new_survey,Box_NOT_CLOSABLE,2);
   else
      Box_StartBoxTable (NULL,
                         Svy.Title[0] ? Svy.Title :
                	                Txt_Edit_survey,
                         NULL,
                         Hlp_ASSESSMENT_Surveys_edit_survey,Box_NOT_CLOSABLE,2);

   /***** Scope of the survey *****/
   fprintf (Gbl.F.Out,"<tr>"
                      "<td class=\"RIGHT_MIDDLE\">"
                      "<label for=\"ScopeSvy\" class=\"%s\">%s:</label>"
                      "</td>"
                      "<td class=\"LEFT_MIDDLE\">",
            The_ClassFormInBox[Gbl.Prefs.Theme],
            Txt_Scope);
   Svy_SetDefaultAndAllowedScope (&Svy);
   Sco_GetScope ("ScopeSvy");
   Sco_PutSelectorScope ("ScopeSvy",false);
   fprintf (Gbl.F.Out,"</td>"
	              "</tr>");

   /***** Survey title *****/
   fprintf (Gbl.F.Out,"<tr>"
	              "<td class=\"RIGHT_MIDDLE\">"
	              "<label for=\"Title\" class=\"%s\">%s:</label>"
	              "</td>"
                      "<td class=\"LEFT_MIDDLE\">"
                      "<input type=\"text\" id=\"Title\" name=\"Title\""
                      " size=\"45\" maxlength=\"%u\" value=\"%s\""
                      " required=\"required\" />"
                      "</td>"
                      "</tr>",
            The_ClassFormInBox[Gbl.Prefs.Theme],
            Txt_Title,
            Svy_MAX_CHARS_SURVEY_TITLE,Svy.Title);

   /***** Survey start and end dates *****/
   Dat_PutFormStartEndClientLocalDateTimes (Svy.TimeUTC,Dat_FORM_SECONDS_ON);

   /***** Survey text *****/
   fprintf (Gbl.F.Out,"<tr>"
	              "<td class=\"RIGHT_TOP\">"
	              "<label for=\"Txt\" class=\"%s\">%s:</label>"
	              "</td>"
                      "<td class=\"LEFT_TOP\">"
                      "<textarea id=\"Txt\" name=\"Txt\""
                      " cols=\"60\" rows=\"10\">",
            The_ClassFormInBox[Gbl.Prefs.Theme],
            Txt_Description);
   if (!ItsANewSurvey)
      fprintf (Gbl.F.Out,"%s",Txt);
   fprintf (Gbl.F.Out,"</textarea>"
                      "</td>"
                      "</tr>");

   /***** Users' roles who can answer the survey *****/
   fprintf (Gbl.F.Out,"<tr>"
	              "<td class=\"RIGHT_TOP %s\">%s:"
	              "</td>"
                      "<td class=\"DAT LEFT_MIDDLE\">",
            The_ClassFormInBox[Gbl.Prefs.Theme],
            Txt_Users);
   Rol_WriteSelectorRoles (1 << Rol_STD |
                           1 << Rol_NET |
                           1 << Rol_TCH,
                           Svy.Roles,
                           false,false);
   fprintf (Gbl.F.Out,"</td>"
	              "</tr>");

   /***** Groups *****/
   Svy_ShowLstGrpsToEditSurvey (Svy.SvyCod);

   /***** End table, send button and end box *****/
   if (ItsANewSurvey)
      Box_EndBoxTableWithButton (Btn_CREATE_BUTTON,Txt_Create_survey);
   else
      Box_EndBoxTableWithButton (Btn_CONFIRM_BUTTON,Txt_Save_changes);

   /***** End form *****/
   Frm_EndForm ();

   /***** Show questions of the survey ready to be edited *****/
   if (!ItsANewSurvey)
      Svy_ListSvyQuestions (&Svy,&SvyQst);
  }

/*****************************************************************************/
/****** Set default and allowed scopes depending on logged user's role *******/
/*****************************************************************************/

static void Svy_SetDefaultAndAllowedScope (struct Survey *Svy)
  {
   bool ICanEdit = false;

   /***** Set default scope *****/
   Gbl.Scope.Default = Hie_UNK;
   Gbl.Scope.Allowed = 0;

   switch (Gbl.Usrs.Me.Role.Logged)
     {
      case Rol_TCH:	// Teachers only can edit course surveys
	 if (Gbl.Hierarchy.Level == Hie_CRS)	// Course selected
	   {
	    if (Svy->Scope == Hie_UNK)		// Scope not defined
	       Svy->Scope = Hie_CRS;
	    if (Svy->Scope == Hie_CRS)
	      {
               Gbl.Scope.Default = Svy->Scope;
	       Gbl.Scope.Allowed = 1 << Hie_CRS;
	       ICanEdit = true;
	      }
	   }
         break;
      case Rol_DEG_ADM:	// Degree admins only can edit degree surveys
	 if (Svy->Scope == Hie_UNK)		// Scope not defined
	    Svy->Scope = Hie_DEG;
	 if (Svy->Scope == Hie_DEG)
	   {
	    Gbl.Scope.Default = Svy->Scope;
	    Gbl.Scope.Allowed = 1 << Hie_DEG;
	    ICanEdit = true;
	   }
         break;
      case Rol_CTR_ADM:	// Centre admins only can edit centre surveys
	 if (Svy->Scope == Hie_UNK)		// Scope not defined
	    Svy->Scope = Hie_CTR;
	 if (Svy->Scope == Hie_CTR)
	   {
	    Gbl.Scope.Default = Svy->Scope;
	    Gbl.Scope.Allowed = 1 << Hie_CTR;
	    ICanEdit = true;
	   }
         break;
      case Rol_INS_ADM:	// Institution admins only can edit institution surveys
	 if (Svy->Scope == Hie_UNK)		// Scope not defined
	    Svy->Scope = Hie_INS;
	 if (Svy->Scope == Hie_INS)
	   {
	    Gbl.Scope.Default = Svy->Scope;
	    Gbl.Scope.Allowed = 1 << Hie_INS;
	    ICanEdit = true;
	   }
         break;
      case Rol_SYS_ADM:// System admins can edit any survey
	 if (Svy->Scope == Hie_UNK)	// Scope not defined
	    Svy->Scope = (Gbl.Hierarchy.Level < Hie_NUM_LEVELS &&
		          Gbl.Hierarchy.Level != Hie_UNK) ? Gbl.Hierarchy.Level :
		        	                            Hie_SYS;
         Gbl.Scope.Default = Svy->Scope;
         Gbl.Scope.Allowed = 1 << Hie_SYS |
	                     1 << Hie_CTY |
	                     1 << Hie_INS |
	                     1 << Hie_CTR |
                             1 << Hie_DEG |
                             1 << Hie_CRS;
	 ICanEdit = true;
	 break;
      default:
	 break;
     }

   if (!ICanEdit)
      Lay_NoPermissionExit ();
  }

/*****************************************************************************/
/******************** Show list of groups to edit a survey *******************/
/*****************************************************************************/

static void Svy_ShowLstGrpsToEditSurvey (long SvyCod)
  {
   extern const char *The_ClassFormInBox[The_NUM_THEMES];
   extern const char *Txt_Groups;
   extern const char *Txt_The_whole_course;
   unsigned NumGrpTyp;

   /***** Get list of groups types and groups in this course *****/
   Grp_GetListGrpTypesAndGrpsInThisCrs (Grp_ONLY_GROUP_TYPES_WITH_GROUPS);

   if (Gbl.Crs.Grps.GrpTypes.Num)
     {
      /***** Start box and table *****/
      fprintf (Gbl.F.Out,"<tr>"
	                 "<td class=\"%s RIGHT_TOP\">"
	                 "%s:"
	                 "</td>"
                         "<td class=\"LEFT_TOP\">",
               The_ClassFormInBox[Gbl.Prefs.Theme],
               Txt_Groups);
      Box_StartBoxTable ("95%",NULL,NULL,
                         NULL,Box_NOT_CLOSABLE,0);

      /***** First row: checkbox to select the whole course *****/
      fprintf (Gbl.F.Out,"<tr>"
	                 "<td colspan=\"7\" class=\"DAT LEFT_MIDDLE\">"
                         "<label>"
                         "<input type=\"checkbox\""
                         " id=\"WholeCrs\" name=\"WholeCrs\" value=\"Y\"");
      if (!Svy_CheckIfSvyIsAssociatedToGrps (SvyCod))
         fprintf (Gbl.F.Out," checked=\"checked\"");
      fprintf (Gbl.F.Out," onclick=\"uncheckChildren(this,'GrpCods')\" />"
	                 "%s %s"
	                 "</label>"
	                 "</td>"
	                 "</tr>",
               Txt_The_whole_course,Gbl.Hierarchy.Crs.ShrtName);

      /***** List the groups for each group type *****/
      for (NumGrpTyp = 0;
	   NumGrpTyp < Gbl.Crs.Grps.GrpTypes.Num;
	   NumGrpTyp++)
         if (Gbl.Crs.Grps.GrpTypes.LstGrpTypes[NumGrpTyp].NumGrps)
            Grp_ListGrpsToEditAsgAttSvyGam (&Gbl.Crs.Grps.GrpTypes.LstGrpTypes[NumGrpTyp],
                                               SvyCod,Grp_SURVEY);

      /***** End table and box *****/
      Box_EndBoxTable ();
      fprintf (Gbl.F.Out,"</td>"
	                 "</tr>");
     }

   /***** Free list of groups types and groups in this course *****/
   Grp_FreeListGrpTypesAndGrps ();
  }

/*****************************************************************************/
/********************* Receive form to create a new survey *******************/
/*****************************************************************************/

void Svy_RecFormSurvey (void)
  {
   extern const char *Txt_Already_existed_a_survey_with_the_title_X;
   extern const char *Txt_You_must_specify_the_title_of_the_survey;
   struct Survey OldSvy;
   struct Survey NewSvy;
   struct SurveyQuestion SvyQst;
   bool ItsANewSurvey;
   bool NewSurveyIsCorrect = true;
   unsigned NumUsrsToBeNotifiedByEMail;
   char Txt[Cns_MAX_BYTES_TEXT + 1];

   /***** Initialize question to zero *****/
   Svy_InitQst (&SvyQst);

   /***** Get the code of the survey *****/
   ItsANewSurvey = ((NewSvy.SvyCod = Svy_GetParamSvyCod ()) == -1L);

   if (ItsANewSurvey)
      NewSvy.Scope = Hie_UNK;
   else
     {
      /* Get data of the old (current) survey from database */
      OldSvy.SvyCod = NewSvy.SvyCod;
      Svy_GetDataOfSurveyByCod (&OldSvy);
      if (!OldSvy.Status.ICanEdit)
         Lay_ShowErrorAndExit ("You can not update this survey.");
      NewSvy.Scope = OldSvy.Scope;
     }

   /***** Get scope *****/
   Svy_SetDefaultAndAllowedScope (&NewSvy);
   Sco_GetScope ("ScopeSvy");
   switch (Gbl.Scope.Current)
     {
      case Hie_SYS:
	 if (Gbl.Usrs.Me.Role.Logged != Rol_SYS_ADM)
	    Lay_ShowErrorAndExit ("Wrong survey scope.");
         NewSvy.Scope = Hie_SYS;
         NewSvy.Cod = -1L;
         break;
      case Hie_CTY:
	 if (Gbl.Usrs.Me.Role.Logged != Rol_SYS_ADM)
	    Lay_ShowErrorAndExit ("Wrong survey scope.");
	 NewSvy.Scope = Hie_CTY;
	 NewSvy.Cod = Gbl.Hierarchy.Cty.CtyCod;
         break;
      case Hie_INS:
	 if (Gbl.Usrs.Me.Role.Logged != Rol_SYS_ADM &&
	     Gbl.Usrs.Me.Role.Logged != Rol_INS_ADM)
	    Lay_ShowErrorAndExit ("Wrong survey scope.");
	 NewSvy.Scope = Hie_INS;
	 NewSvy.Cod = Gbl.Hierarchy.Ins.InsCod;
         break;
      case Hie_CTR:
	 if (Gbl.Usrs.Me.Role.Logged != Rol_SYS_ADM &&
	     Gbl.Usrs.Me.Role.Logged != Rol_CTR_ADM)
	    Lay_ShowErrorAndExit ("Wrong survey scope.");
	 NewSvy.Scope = Hie_CTR;
	 NewSvy.Cod = Gbl.Hierarchy.Ctr.CtrCod;
         break;
      case Hie_DEG:
	 if (Gbl.Usrs.Me.Role.Logged != Rol_SYS_ADM &&
	     Gbl.Usrs.Me.Role.Logged != Rol_DEG_ADM)
	    Lay_ShowErrorAndExit ("Wrong survey scope.");
	 NewSvy.Scope = Hie_DEG;
	 NewSvy.Cod = Gbl.Hierarchy.Deg.DegCod;
         break;
      case Hie_CRS:
	 if (Gbl.Usrs.Me.Role.Logged != Rol_SYS_ADM &&
	     Gbl.Usrs.Me.Role.Logged != Rol_TCH)
	    Lay_ShowErrorAndExit ("Wrong survey scope.");
	 NewSvy.Scope = Hie_CRS;
	 NewSvy.Cod = Gbl.Hierarchy.Crs.CrsCod;
         break;
      default:
	 Lay_WrongScopeExit ();
	 break;
     }

   /***** Get start/end date-times *****/
   NewSvy.TimeUTC[Dat_START_TIME] = Dat_GetTimeUTCFromForm ("StartTimeUTC");
   NewSvy.TimeUTC[Dat_END_TIME  ] = Dat_GetTimeUTCFromForm ("EndTimeUTC"  );

   /***** Get survey title *****/
   Par_GetParToText ("Title",NewSvy.Title,Svy_MAX_BYTES_SURVEY_TITLE);

   /***** Get survey text and insert links *****/
   Par_GetParToHTML ("Txt",Txt,Cns_MAX_BYTES_TEXT);	// Store in HTML format (not rigorous)

   /***** Adjust dates *****/
   if (NewSvy.TimeUTC[Svy_START_TIME] == 0)
      NewSvy.TimeUTC[Svy_START_TIME] = Gbl.StartExecutionTimeUTC;
   if (NewSvy.TimeUTC[Svy_END_TIME] == 0)
      NewSvy.TimeUTC[Svy_END_TIME] = NewSvy.TimeUTC[Svy_START_TIME] + 24 * 60 * 60;	// +24 hours

   /***** Get users who can answer this survey *****/
   NewSvy.Roles = Rol_GetSelectedRoles ();

   /***** Check if title is correct *****/
   if (NewSvy.Title[0])	// If there's a survey title
     {
      /* If title of survey was in database... */
      if (Svy_CheckIfSimilarSurveyExists (&NewSvy))
        {
         NewSurveyIsCorrect = false;
         Ale_ShowAlert (Ale_WARNING,Txt_Already_existed_a_survey_with_the_title_X,
                        NewSvy.Title);
        }
     }
   else	// If there is not a survey title
     {
      NewSurveyIsCorrect = false;
      Ale_ShowAlert (Ale_WARNING,Txt_You_must_specify_the_title_of_the_survey);
     }

   /***** Create a new survey or update an existing one *****/
   if (NewSurveyIsCorrect)
     {
      /* Get groups for this surveys */
      Grp_GetParCodsSeveralGrps ();

      if (ItsANewSurvey)
         Svy_CreateSurvey (&NewSvy,Txt);	// Add new survey to database
      else
         Svy_UpdateSurvey (&NewSvy,Txt);

      /* Free memory for list of selected groups */
      Grp_FreeListCodSelectedGrps ();
     }
   else
      Svy_RequestCreatOrEditSvy ();

   /***** Notify by email about the new survey *****/
   if (NewSvy.Scope == Hie_CRS)	// Notify only the surveys for a course, not for a degree or global
      if ((NumUsrsToBeNotifiedByEMail = Ntf_StoreNotifyEventsToAllUsrs (Ntf_EVENT_SURVEY,NewSvy.SvyCod)))
         Svy_UpdateNumUsrsNotifiedByEMailAboutSurvey (NewSvy.SvyCod,NumUsrsToBeNotifiedByEMail);

   /***** Show surveys again *****/
   Svy_ListAllSurveys (&SvyQst);
  }

/*****************************************************************************/
/*********** Update number of users notified in table of surveys *************/
/*****************************************************************************/

static void Svy_UpdateNumUsrsNotifiedByEMailAboutSurvey (long SvyCod,
                                                         unsigned NumUsrsToBeNotifiedByEMail)
  {
   /***** Update number of users notified *****/
   DB_QueryUPDATE ("can not update the number of notifications of a survey",
		   "UPDATE surveys SET NumNotif=NumNotif+%u"
                   " WHERE SvyCod=%ld",
		   NumUsrsToBeNotifiedByEMail,SvyCod);
  }

/*****************************************************************************/
/*************************** Create a new survey *****************************/
/*****************************************************************************/

static void Svy_CreateSurvey (struct Survey *Svy,const char *Txt)
  {
   extern const char *Txt_Created_new_survey_X;

   /***** Create a new survey *****/
   Svy->SvyCod =
   DB_QueryINSERTandReturnCode ("can not create new survey",
				"INSERT INTO surveys"
				" (Scope,Cod,Hidden,Roles,UsrCod,StartTime,EndTime,Title,Txt)"
				" VALUES"
				" ('%s',%ld,'N',%u,%ld,"
				"FROM_UNIXTIME(%ld),FROM_UNIXTIME(%ld),"
				"'%s','%s')",
				Sco_GetDBStrFromScope (Svy->Scope),Svy->Cod,
				Svy->Roles,
				Gbl.Usrs.Me.UsrDat.UsrCod,
				Svy->TimeUTC[Svy_START_TIME],
				Svy->TimeUTC[Svy_END_TIME  ],
				Svy->Title,
				Txt);

   /***** Create groups *****/
   if (Gbl.Crs.Grps.LstGrpsSel.NumGrps)
      Svy_CreateGrps (Svy->SvyCod);

   /***** Write success message *****/
   Ale_ShowAlert (Ale_SUCCESS,Txt_Created_new_survey_X,
                  Svy->Title);
  }

/*****************************************************************************/
/************************* Update an existing survey *************************/
/*****************************************************************************/

static void Svy_UpdateSurvey (struct Survey *Svy,const char *Txt)
  {
   extern const char *Txt_The_survey_has_been_modified;

   /***** Update the data of the survey *****/
   DB_QueryUPDATE ("can not update survey",
		   "UPDATE surveys"
	           " SET Scope='%s',Cod=%ld,Roles=%u,"
	           "StartTime=FROM_UNIXTIME(%ld),"
	           "EndTime=FROM_UNIXTIME(%ld),"
	           "Title='%s',Txt='%s'"
                   " WHERE SvyCod=%ld",
		   Sco_GetDBStrFromScope (Svy->Scope),Svy->Cod,
		   Svy->Roles,
		   Svy->TimeUTC[Svy_START_TIME],
		   Svy->TimeUTC[Svy_END_TIME  ],
		   Svy->Title,
		   Txt,
		   Svy->SvyCod);

   /***** Update groups *****/
   /* Remove old groups */
   Svy_RemoveAllTheGrpsAssociatedToAndSurvey (Svy->SvyCod);

   /* Create new groups */
   if (Gbl.Crs.Grps.LstGrpsSel.NumGrps)
      Svy_CreateGrps (Svy->SvyCod);

   /***** Write success message *****/
   Ale_ShowAlert (Ale_SUCCESS,Txt_The_survey_has_been_modified);
  }

/*****************************************************************************/
/*************** Check if a survey is associated to any group ****************/
/*****************************************************************************/

static bool Svy_CheckIfSvyIsAssociatedToGrps (long SvyCod)
  {
   /***** Get if a survey is associated to a group from database *****/
   return (DB_QueryCOUNT ("can not check if a survey is associated to groups",
			  "SELECT COUNT(*) FROM svy_grp"
			  " WHERE SvyCod=%ld",
			  SvyCod) != 0);
  }

/*****************************************************************************/
/**************** Check if a survey is associated to a group *****************/
/*****************************************************************************/

bool Svy_CheckIfSvyIsAssociatedToGrp (long SvyCod,long GrpCod)
  {
   /***** Get if a survey is associated to a group from database *****/
   return (DB_QueryCOUNT ("can not check if a survey is associated to a group",
			  "SELECT COUNT(*) FROM svy_grp"
			  " WHERE SvyCod=%ld AND GrpCod=%ld",
			  SvyCod,GrpCod) != 0);
  }

/*****************************************************************************/
/************************* Remove groups of a survey *************************/
/*****************************************************************************/

static void Svy_RemoveAllTheGrpsAssociatedToAndSurvey (long SvyCod)
  {
   /***** Remove groups of the survey *****/
   DB_QueryDELETE ("can not remove the groups associated to a survey",
		   "DELETE FROM svy_grp WHERE SvyCod=%ld",
		   SvyCod);
  }

/*****************************************************************************/
/******************* Remove one group from all the surveys *******************/
/*****************************************************************************/

void Svy_RemoveGroup (long GrpCod)
  {
   /***** Remove group from all the surveys *****/
   DB_QueryDELETE ("can not remove group from the associations"
		   " between surveys and groups",
		   "DELETE FROM svy_grp WHERE GrpCod=%ld",
		   GrpCod);
  }

/*****************************************************************************/
/*************** Remove groups of one type from all the surveys **************/
/*****************************************************************************/

void Svy_RemoveGroupsOfType (long GrpTypCod)
  {
   /***** Remove group from all the surveys *****/
   DB_QueryDELETE ("can not remove groups of a type"
	           " from the associations between surveys and groups",
		   "DELETE FROM svy_grp USING crs_grp,svy_grp"
                   " WHERE crs_grp.GrpTypCod=%ld"
                   " AND crs_grp.GrpCod=svy_grp.GrpCod",
		   GrpTypCod);
  }

/*****************************************************************************/
/************************ Create groups of a survey **************************/
/*****************************************************************************/

static void Svy_CreateGrps (long SvyCod)
  {
   unsigned NumGrpSel;

   /***** Create groups of the survey *****/
   for (NumGrpSel = 0;
	NumGrpSel < Gbl.Crs.Grps.LstGrpsSel.NumGrps;
	NumGrpSel++)
      /* Create group */
      DB_QueryINSERT ("can not associate a group to a survey",
		      "INSERT INTO svy_grp"
	              " (SvyCod,GrpCod)"
	              " VALUES"
	              " (%ld,%ld)",
		      SvyCod,Gbl.Crs.Grps.LstGrpsSel.GrpCods[NumGrpSel]);
  }

/*****************************************************************************/
/************ Get and write the names of the groups of a survey **************/
/*****************************************************************************/

static void Svy_GetAndWriteNamesOfGrpsAssociatedToSvy (struct Survey *Svy)
  {
   extern const char *Txt_Group;
   extern const char *Txt_Groups;
   extern const char *Txt_and;
   extern const char *Txt_The_whole_course;
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;
   unsigned long NumRow;
   unsigned long NumRows;

   /***** Get groups associated to a survey from database *****/
   NumRows = DB_QuerySELECT (&mysql_res,"can not get groups of a survey",
			     "SELECT crs_grp_types.GrpTypName,crs_grp.GrpName"
			     " FROM svy_grp,crs_grp,crs_grp_types"
			     " WHERE svy_grp.SvyCod=%ld"
			     " AND svy_grp.GrpCod=crs_grp.GrpCod"
			     " AND crs_grp.GrpTypCod=crs_grp_types.GrpTypCod"
			     " ORDER BY crs_grp_types.GrpTypName,"
			     "crs_grp.GrpName",
			     Svy->SvyCod);

   /***** Write heading *****/
   fprintf (Gbl.F.Out,"<div class=\"%s\">%s: ",
            Svy->Status.Visible ? "ASG_GRP" :
        	                  "ASG_GRP_LIGHT",
            NumRows == 1 ? Txt_Group  :
                           Txt_Groups);

   /***** Write groups *****/
   if (NumRows) // Groups found...
     {
      /* Get and write the group types and names */
      for (NumRow = 0;
	   NumRow < NumRows;
	   NumRow++)
        {
         /* Get next group */
         row = mysql_fetch_row (mysql_res);

         /* Write group type name and group name */
         fprintf (Gbl.F.Out,"%s %s",row[0],row[1]);

         if (NumRows >= 2)
           {
            if (NumRow == NumRows-2)
               fprintf (Gbl.F.Out," %s ",Txt_and);
            if (NumRows >= 3)
              if (NumRow < NumRows-2)
                  fprintf (Gbl.F.Out,", ");
           }
        }
     }
   else
      fprintf (Gbl.F.Out,"%s %s",
               Txt_The_whole_course,Gbl.Hierarchy.Crs.ShrtName);

   fprintf (Gbl.F.Out,"</div>");

   /***** Free structure that stores the query result *****/
   DB_FreeMySQLResult (&mysql_res);
  }

/*****************************************************************************/
/************ Remove all the surveys of a place on the hierarchy *************/
/************ (country, institution, centre, degree or course)   *************/
/*****************************************************************************/

void Svy_RemoveSurveys (Hie_Level_t Scope,long Cod)
  {
   /***** Remove all the users in course surveys *****/
   DB_QueryDELETE ("can not remove users"
	           " who had answered surveys in a place on the hierarchy",
		   "DELETE FROM svy_users"
	           " USING surveys,svy_users"
                   " WHERE surveys.Scope='%s' AND surveys.Cod=%ld"
                   " AND surveys.SvyCod=svy_users.SvyCod",
		   Sco_GetDBStrFromScope (Scope),Cod);

   /***** Remove all the answers in course surveys *****/
   DB_QueryDELETE ("can not remove answers of surveys"
		   " in a place on the hierarchy",
		   "DELETE FROM svy_answers"
	           " USING surveys,svy_questions,svy_answers"
                   " WHERE surveys.Scope='%s' AND surveys.Cod=%ld"
                   " AND surveys.SvyCod=svy_questions.SvyCod"
                   " AND svy_questions.QstCod=svy_answers.QstCod",
		   Sco_GetDBStrFromScope (Scope),Cod);

   /***** Remove all the questions in course surveys *****/
   DB_QueryDELETE ("can not remove questions of surveys"
		   " in a place on the hierarchy",
		   "DELETE FROM svy_questions"
	           " USING surveys,svy_questions"
                   " WHERE surveys.Scope='%s' AND surveys.Cod=%ld"
                   " AND surveys.SvyCod=svy_questions.SvyCod",
		   Sco_GetDBStrFromScope (Scope),Cod);

   /***** Remove groups *****/
   DB_QueryDELETE ("can not remove all the groups"
	           " associated to surveys of a course",
		   "DELETE FROM svy_grp"
	           " USING surveys,svy_grp"
                   " WHERE surveys.Scope='%s' AND surveys.Cod=%ld"
                   " AND surveys.SvyCod=svy_grp.SvyCod",
		   Sco_GetDBStrFromScope (Scope),Cod);

   /***** Remove course surveys *****/
   DB_QueryDELETE ("can not remove all the surveys"
		   " in a place on the hierarchy",
		   "DELETE FROM surveys"
	           " WHERE Scope='%s' AND Cod=%ld",
		   Sco_GetDBStrFromScope (Scope),Cod);
  }

/*****************************************************************************/
/************ Check if I belong to any of the groups of a survey *************/
/*****************************************************************************/

static bool Svy_CheckIfICanDoThisSurveyBasedOnGrps (long SvyCod)
  {
   /***** Get if I can do a survey from database *****/
   return (DB_QueryCOUNT ("can not check if I can do a survey",
			  "SELECT COUNT(*) FROM surveys"
			  " WHERE SvyCod=%ld"
			  " AND (SvyCod NOT IN (SELECT SvyCod FROM svy_grp) OR"
			  " SvyCod IN (SELECT svy_grp.SvyCod FROM svy_grp,crs_grp_usr"
			  " WHERE crs_grp_usr.UsrCod=%ld"
			  " AND svy_grp.GrpCod=crs_grp_usr.GrpCod))",
			  SvyCod,Gbl.Usrs.Me.UsrDat.UsrCod) != 0);
  }

/*****************************************************************************/
/******************* Get number of questions of a survey *********************/
/*****************************************************************************/

static unsigned Svy_GetNumQstsSvy (long SvyCod)
  {
   /***** Get data of questions from database *****/
   return
   (unsigned) DB_QueryCOUNT ("can not get number of questions of a survey",
			     "SELECT COUNT(*) FROM svy_questions"
			     " WHERE SvyCod=%ld",
			     SvyCod);
  }

/*****************************************************************************/
/*********** Put a form to edit/create a question in survey  *****************/
/*****************************************************************************/

void Svy_RequestEditQuestion (void)
  {
   long SvyCod;
   struct SurveyQuestion SvyQst;
   char Txt[Cns_MAX_BYTES_TEXT + 1];

   /***** Initialize question to zero *****/
   Svy_InitQst (&SvyQst);

   /***** Initialize text to empty string *****/
   Txt[0] = '\0';

   /***** Get survey code *****/
   if ((SvyCod = Svy_GetParamSvyCod ()) == -1L)
      Lay_ShowErrorAndExit ("Code of survey is missing.");

   /* Get the question code */
   SvyQst.QstCod = Svy_GetParamQstCod ();

   /***** Get other parameters *****/
   Svy_GetParamSvyOrder ();
   Grp_GetParamWhichGrps ();
   Gbl.Svys.CurrentPage = Pag_GetParamPagNum (Pag_SURVEYS);

   /***** Show form to create a new question in this survey *****/
   Svy_ShowFormEditOneQst (SvyCod,&SvyQst,Txt);

   /***** Show current survey *****/
   Svy_ShowOneSurvey (SvyCod,&SvyQst,true);
  }

/*****************************************************************************/
/******************* Show form to edit one survey question *******************/
/*****************************************************************************/

static void Svy_ShowFormEditOneQst (long SvyCod,struct SurveyQuestion *SvyQst,
                                    char Txt[Cns_MAX_BYTES_TEXT + 1])
  {
   extern const char *Hlp_ASSESSMENT_Surveys_questions;
   extern const char *The_ClassFormInBox[The_NUM_THEMES];
   extern const char *Txt_Question;
   extern const char *Txt_New_question;
   extern const char *Txt_Stem;
   extern const char *Txt_Type;
   extern const char *Txt_SURVEY_STR_ANSWER_TYPES[Svy_NUM_ANS_TYPES];
   extern const char *Txt_Save_changes;
   extern const char *Txt_Create_question;
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;
   unsigned NumAns;
   unsigned NumAnswers = 0;
   Svy_AnswerType_t AnsType;

   if (Gbl.Action.Act == ActEdiOneSvyQst) // If no receiving the question, but editing a new or existing question
     {
      if ((SvyQst->QstCod > 0))	// If parameter QstCod received ==> question already exists in the database
        {
         /***** Get the type of answer and the stem from the database *****/
         /* Get the question from database */
         DB_QuerySELECT (&mysql_res,"can not get a question",
			 "SELECT QstInd,AnsType,Stem FROM svy_questions"
                         " WHERE QstCod=%ld AND SvyCod=%ld",
			 SvyQst->QstCod,SvyCod);

         row = mysql_fetch_row (mysql_res);

         /* Get question index inside survey (row[0]) */
         if (sscanf (row[0],"%u",&(SvyQst->QstInd)) != 1)
            Lay_ShowErrorAndExit ("Error: wrong question index.");

         /* Get the type of answer (row[1]) */
         SvyQst->AnswerType = Svy_ConvertFromStrAnsTypDBToAnsTyp (row[1]);

         /* Get the stem of the question from the database (row[2]) */
         Str_Copy (Txt,row[2],
                   Cns_MAX_BYTES_TEXT);

         /* Free structure that stores the query result */
	 DB_FreeMySQLResult (&mysql_res);

         /***** Get the answers from the database *****/
         NumAnswers = Svy_GetAnswersQst (SvyQst->QstCod,&mysql_res);	// Result: AnsInd,NumUsrs,Answer
         for (NumAns = 0;
              NumAns < NumAnswers;
              NumAns++)
           {
            row = mysql_fetch_row (mysql_res);

            if (NumAnswers > Svy_MAX_ANSWERS_PER_QUESTION)
               Lay_ShowErrorAndExit ("Wrong answer.");
            if (!Svy_AllocateTextChoiceAnswer (SvyQst,NumAns))
	       /* Abort on error */
	       Ale_ShowAlertsAndExit ();

            Str_Copy (SvyQst->AnsChoice[NumAns].Text,row[2],
                      Svy_MAX_BYTES_ANSWER);
           }
         /* Free structure that stores the query result */
	 DB_FreeMySQLResult (&mysql_res);
        }
     }

   /***** Start box *****/
   if (SvyQst->QstCod > 0)	// If the question already has assigned a code
     {
      /* Parameters for contextual icon */
      Svy_CurrentSvyCod    = SvyCod;
      Svy_CurrentQstCod = SvyQst->QstCod;

      snprintf (Gbl.Title,sizeof (Gbl.Title),
	        "%s %u",
                Txt_Question,SvyQst->QstInd + 1);	// Question index may be 0, 1, 2, 3,...
      Box_StartBox (NULL,Gbl.Title,Svy_PutIconToRemoveOneQst,
                    NULL,Box_NOT_CLOSABLE);
     }
   else
      Box_StartBox (NULL,Txt_New_question,NULL,
                    Hlp_ASSESSMENT_Surveys_questions,Box_NOT_CLOSABLE);

   /***** Start form *****/
   Frm_StartForm (ActRcvSvyQst);
   Svy_PutParamSvyCod (SvyCod);
   if (SvyQst->QstCod > 0)	// If the question already has assigned a code
      Svy_PutParamQstCod (SvyQst->QstCod);

   /***** Start table *****/
   Tbl_StartTableWide (2);

   /***** Stem *****/
   fprintf (Gbl.F.Out,"<tr>"
	              "<td class=\"RIGHT_TOP\">"
	              "<label for=\"Txt\" class=\"%s\">%s:</label>"
	              "</td>"
                      "<td class=\"LEFT_TOP\">"
                      "<textarea id=\"Txt\" name=\"Txt\""
                      " cols=\"60\" rows=\"4\">"
	              "%s"
                      "</textarea>"
                      "</td>"
                      "</tr>",
            The_ClassFormInBox[Gbl.Prefs.Theme],Txt_Stem,
	    Txt);

   /***** Type of answer *****/
   fprintf (Gbl.F.Out,"<tr>"
	              "<td class=\"%s RIGHT_TOP\">"
	              "%s:"
	              "</td>"
	              "<td class=\"%s LEFT_TOP\">",
            The_ClassFormInBox[Gbl.Prefs.Theme],
            Txt_Type,
            The_ClassFormInBox[Gbl.Prefs.Theme]);
   for (AnsType = (Svy_AnswerType_t) 0;
	AnsType < Svy_NUM_ANS_TYPES;
	AnsType++)
     {
      fprintf (Gbl.F.Out,"<label>"
	                 "<input type=\"radio\" name=\"AnswerType\""
	                 " value=\"%u\"",
               (unsigned) AnsType);
      if (AnsType == SvyQst->AnswerType)
         fprintf (Gbl.F.Out," checked=\"checked\"");
      fprintf (Gbl.F.Out," />"
	                 "%s"
	                 "</label><br />",
               Txt_SURVEY_STR_ANSWER_TYPES[AnsType]);
     }
   fprintf (Gbl.F.Out,"</td>"
	              "</tr>");

   /***** Answers *****/
   /* Unique or multiple choice answers */
   fprintf (Gbl.F.Out,"<tr>"
	              "<td></td>"
                      "<td class=\"LEFT_TOP\">");
   Tbl_StartTable (2);
   for (NumAns = 0;
	NumAns < Svy_MAX_ANSWERS_PER_QUESTION;
	NumAns++)
     {
      /* Label with the number of the answer */
      fprintf (Gbl.F.Out,"<tr>"
	                 "<td class=\"RIGHT_TOP\">"
	                 "<label for=\"AnsStr%u\" class=\"%s\">%u)</label>"
	                 "</td>",
               NumAns,The_ClassFormInBox[Gbl.Prefs.Theme],NumAns + 1);

      /* Answer text */
      fprintf (Gbl.F.Out,"<td class=\"RIGHT_TOP\">"
                         "<textarea id=\"AnsStr%u\" name=\"AnsStr%u\""
                         " cols=\"50\" rows=\"1\">",
               NumAns,NumAns);
      if (SvyQst->AnsChoice[NumAns].Text)
         fprintf (Gbl.F.Out,"%s",SvyQst->AnsChoice[NumAns].Text);
      fprintf (Gbl.F.Out,"</textarea>"
	                 "</td>"
	                 "</tr>");
     }
   Tbl_EndTable ();
   fprintf (Gbl.F.Out,"</td>"
	              "</tr>");

   /***** End table *****/
   Tbl_EndTable ();

   /***** Send button *****/
   if (SvyQst->QstCod > 0)	// If the question already has assigned a code
      Btn_PutConfirmButton (Txt_Save_changes);
   else
      Btn_PutCreateButton (Txt_Create_question);

   /***** End form *****/
   Frm_EndForm ();

   /***** End box *****/
   Box_EndBox ();

   /***** Free memory for answers *****/
   Svy_FreeTextChoiceAnswers (SvyQst,NumAnswers);
  }

/*****************************************************************************/
/********************* Initialize a new question to zero *********************/
/*****************************************************************************/

static void Svy_InitQst (struct SurveyQuestion *SvyQst)
  {
   unsigned NumAns;

   SvyQst->QstCod = -1L;
   SvyQst->QstInd = 0;
   SvyQst->AnswerType = Svy_ANS_UNIQUE_CHOICE;
   for (NumAns = 0;
	NumAns < Svy_MAX_ANSWERS_PER_QUESTION;
	NumAns++)
      SvyQst->AnsChoice[NumAns].Text = NULL;
  }

/*****************************************************************************/
/****************** Write parameter with code of question ********************/
/*****************************************************************************/

static void Svy_PutParamQstCod (long QstCod)
  {
   Par_PutHiddenParamLong ("QstCod",QstCod);
  }

/*****************************************************************************/
/******************* Get parameter with code of question *********************/
/*****************************************************************************/

static long Svy_GetParamQstCod (void)
  {
   /***** Get code of question *****/
   return Par_GetParToLong ("QstCod");
  }

/*****************************************************************************/
/********************* Remove answers of a survey question *******************/
/*****************************************************************************/

static void Svy_RemAnswersOfAQuestion (long QstCod)
  {
   /***** Remove answers *****/
   DB_QueryDELETE ("can not remove the answers of a question",
		   "DELETE FROM svy_answers WHERE QstCod=%ld",
		   QstCod);
  }

/*****************************************************************************/
/*********** Convert a string with the answer type to answer type ************/
/*****************************************************************************/

static Svy_AnswerType_t Svy_ConvertFromStrAnsTypDBToAnsTyp (const char *StrAnsTypeBD)
  {
   Svy_AnswerType_t AnsType;

   for (AnsType = (Svy_AnswerType_t) 0;
	AnsType < Svy_NUM_ANS_TYPES;
	AnsType++)
      if (!strcmp (StrAnsTypeBD,Svy_StrAnswerTypesDB[AnsType]))
         return AnsType;

   return (Svy_AnswerType_t) 0;
  }

/*****************************************************************************/
/*********** Check if an answer of a question exists in database *************/
/*****************************************************************************/

static bool Svy_CheckIfAnswerExists (long QstCod,unsigned AnsInd)
  {
   /***** Get answers of a question from database *****/
   return (DB_QueryCOUNT ("can not check if an answer exists",
			  "SELECT COUNT(*) FROM svy_answers"
			  " WHERE QstCod=%ld AND AnsInd=%u",
			  QstCod,AnsInd) != 0);
  }

/*****************************************************************************/
/************** Get answers of a survey question from database ***************/
/*****************************************************************************/

static unsigned Svy_GetAnswersQst (long QstCod,MYSQL_RES **mysql_res)
  {
   unsigned long NumRows;

   /***** Get answers of a question from database *****/
   NumRows = DB_QuerySELECT (mysql_res,"can not get answers of a question",
			     "SELECT AnsInd,NumUsrs,Answer FROM svy_answers"
			     " WHERE QstCod=%ld ORDER BY AnsInd",
			     QstCod);

   /***** Count number of rows of result *****/
   if (NumRows == 0)
      Ale_ShowAlert (Ale_ERROR,"Error when getting answers of a question.");

   return (unsigned) NumRows;
  }

/*****************************************************************************/
/******************* Allocate memory for a choice answer *********************/
/*****************************************************************************/

static bool Svy_AllocateTextChoiceAnswer (struct SurveyQuestion *SvyQst,
                                          unsigned NumAns)
  {
   Svy_FreeTextChoiceAnswer (SvyQst,NumAns);
   if ((SvyQst->AnsChoice[NumAns].Text = (char *) malloc (Svy_MAX_BYTES_ANSWER + 1)) == NULL)
     {
      Ale_CreateAlert (Ale_ERROR,NULL,
	               "Not enough memory to store answer.");
      return false;
     }
   SvyQst->AnsChoice[NumAns].Text[0] = '\0';
   return true;
  }

/*****************************************************************************/
/******************** Free memory of all choice answers **********************/
/*****************************************************************************/

static void Svy_FreeTextChoiceAnswers (struct SurveyQuestion *SvyQst,unsigned NumAnswers)
  {
   unsigned NumAns;

   for (NumAns = 0;
	NumAns < NumAnswers;
	NumAns++)
      Svy_FreeTextChoiceAnswer (SvyQst,NumAns);
  }

/*****************************************************************************/
/********************** Free memory of a choice answer ***********************/
/*****************************************************************************/

static void Svy_FreeTextChoiceAnswer (struct SurveyQuestion *SvyQst,unsigned NumAns)
  {
   if (SvyQst->AnsChoice[NumAns].Text)
     {
      free ((void *) SvyQst->AnsChoice[NumAns].Text);
      SvyQst->AnsChoice[NumAns].Text = NULL;
     }
  }

/*****************************************************************************/
/*********************** Receive a question of a survey **********************/
/*****************************************************************************/

void Svy_ReceiveQst (void)
  {
   extern const char *Txt_You_must_type_the_stem_of_the_question;
   extern const char *Txt_You_can_not_leave_empty_intermediate_answers;
   extern const char *Txt_You_must_type_at_least_the_first_two_answers;
   extern const char *Txt_The_survey_has_been_modified;
   char Txt[Cns_MAX_BYTES_TEXT + 1];
   long SvyCod;
   struct SurveyQuestion SvyQst;
   unsigned NumAns;
   char AnsStr[8 + 10 + 1];
   unsigned NumLastAns;
   bool ThereIsEndOfAnswers;
   bool Error = false;

   /***** Initialize question to zero *****/
   Svy_InitQst (&SvyQst);

   /***** Get parameters from form *****/
   /* Get survey code */
   if ((SvyCod = Svy_GetParamSvyCod ()) == -1L)
      Lay_ShowErrorAndExit ("Code of survey is missing.");

   /* Get question code */
   SvyQst.QstCod = Svy_GetParamQstCod ();

   /* Get answer type */
   SvyQst.AnswerType = (Svy_AnswerType_t)
	               Par_GetParToUnsignedLong ("AnswerType",
	                                         0,
	                                         Svy_NUM_ANS_TYPES - 1,
                                                 (unsigned long) Svy_ANSWER_TYPE_DEFAULT);

   /* Get question text */
   Par_GetParToHTML ("Txt",Txt,Cns_MAX_BYTES_TEXT);

   /* Get the texts of the answers */
   for (NumAns = 0;
	NumAns < Svy_MAX_ANSWERS_PER_QUESTION;
	NumAns++)
     {
      if (!Svy_AllocateTextChoiceAnswer (&SvyQst,NumAns))
	 /* Abort on error */
	 Ale_ShowAlertsAndExit ();
      snprintf (AnsStr,sizeof (AnsStr),
	        "AnsStr%u",
		NumAns);
      Par_GetParToHTML (AnsStr,SvyQst.AnsChoice[NumAns].Text,Svy_MAX_BYTES_ANSWER);
     }

   /***** Make sure that stem and answer are not empty *****/
   if (Txt[0])
     {
      if (SvyQst.AnsChoice[0].Text[0])	// If the first answer has been filled
        {
         for (NumAns = 0, NumLastAns = 0, ThereIsEndOfAnswers = false;
              !Error && NumAns < Svy_MAX_ANSWERS_PER_QUESTION;
              NumAns++)
            if (SvyQst.AnsChoice[NumAns].Text[0])
              {
               if (ThereIsEndOfAnswers)
                 {
                  Ale_ShowAlert (Ale_WARNING,Txt_You_can_not_leave_empty_intermediate_answers);
                  Error = true;
                 }
               else
                  NumLastAns = NumAns;
              }
            else
               ThereIsEndOfAnswers = true;
         if (!Error)
           {
            if (NumLastAns < 1)
              {
               Ale_ShowAlert (Ale_WARNING,Txt_You_must_type_at_least_the_first_two_answers);
               Error = true;
              }
           }
        }
      else	// If first answer is empty
        {
         Ale_ShowAlert (Ale_WARNING,Txt_You_must_type_at_least_the_first_two_answers);
         Error = true;
        }
     }
   else
     {
      Ale_ShowAlert (Ale_WARNING,Txt_You_must_type_the_stem_of_the_question);
      Error = true;
     }

   if (Error)
      Svy_ShowFormEditOneQst (SvyCod,&SvyQst,Txt);
   else
     {
      /***** Form is received OK ==> insert question and answer in the database *****/
      if (SvyQst.QstCod < 0)	// It's a new question
        {
         SvyQst.QstInd = Svy_GetNextQuestionIndexInSvy (SvyCod);

         /* Insert question in the table of questions */
         SvyQst.QstCod =
         DB_QueryINSERTandReturnCode ("can not create question",
				      "INSERT INTO svy_questions"
				      " (SvyCod,QstInd,AnsType,Stem)"
				      " VALUES"
				      " (%ld,%u,'%s','%s')",
				      SvyCod,SvyQst.QstInd,
				      Svy_StrAnswerTypesDB[SvyQst.AnswerType],Txt);
        }
      else			// It's an existing question
         /* Update question */
         DB_QueryUPDATE ("can not update question",
			 "UPDATE svy_questions SET Stem='%s',AnsType='%s'"
                         " WHERE QstCod=%ld AND SvyCod=%ld",
			 Txt,Svy_StrAnswerTypesDB[SvyQst.AnswerType],
			 SvyQst.QstCod,SvyCod);

      /* Insert, update or delete answers in the answers table */
      for (NumAns = 0;
	   NumAns < Svy_MAX_ANSWERS_PER_QUESTION;
	   NumAns++)
         if (Svy_CheckIfAnswerExists (SvyQst.QstCod,NumAns))	// If this answer exists...
           {
            if (SvyQst.AnsChoice[NumAns].Text[0])	// Answer is not empty
               /* Update answer text */
               DB_QueryUPDATE ("can not update answer",
        		       "UPDATE svy_answers SET Answer='%s'"
                               " WHERE QstCod=%ld AND AnsInd=%u",
			       SvyQst.AnsChoice[NumAns].Text,SvyQst.QstCod,NumAns);
            else	// Answer is empty
               /* Delete answer from database */
               DB_QueryDELETE ("can not delete answer",
        		       "DELETE FROM svy_answers"
                               " WHERE QstCod=%ld AND AnsInd=%u",
			       SvyQst.QstCod,NumAns);
           }
         else	// If this answer does not exist...
           {
            if (SvyQst.AnsChoice[NumAns].Text[0])	// Answer is not empty
               /* Create answer into database */
               DB_QueryINSERT ("can not create answer",
        		       "INSERT INTO svy_answers"
        	               " (QstCod,AnsInd,NumUsrs,Answer)"
                               " VALUES"
                               " (%ld,%u,0,'%s')",
			       SvyQst.QstCod,NumAns,SvyQst.AnsChoice[NumAns].Text);
           }

      /***** List the questions of this survey, including the new one just inserted into the database *****/
      Ale_ShowAlert (Ale_SUCCESS,Txt_The_survey_has_been_modified);
     }

   /***** Free answers *****/
   Svy_FreeTextChoiceAnswers (&SvyQst,Svy_MAX_ANSWERS_PER_QUESTION);

   /***** Show current survey *****/
   Svy_ShowOneSurvey (SvyCod,&SvyQst,true);
  }

/*****************************************************************************/
/******************* Get next question index in a survey *********************/
/*****************************************************************************/

static unsigned Svy_GetQstIndFromQstCod (long QstCod)
  {
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;
   unsigned long NumRows;
   unsigned QstInd = 0;

   /***** Get number of surveys with a field value from database *****/
   NumRows = DB_QuerySELECT (&mysql_res,"can not get question index",
			     "SELECT QstInd FROM svy_questions"
			     " WHERE QstCod=%ld",
			     QstCod);

   /***** Get number of users *****/
   if (NumRows)
     {
      row = mysql_fetch_row (mysql_res);
      if (sscanf (row[0],"%u",&QstInd) != 1)
         Lay_ShowErrorAndExit ("Error when getting question index.");
     }
   else
      Lay_ShowErrorAndExit ("Error when getting question index.");

   /***** Free structure that stores the query result *****/
   DB_FreeMySQLResult (&mysql_res);

   return QstInd;
  }

/*****************************************************************************/
/******************* Get next question index in a survey *********************/
/*****************************************************************************/

static unsigned Svy_GetNextQuestionIndexInSvy (long SvyCod)
  {
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;
   unsigned QstInd = 0;

   /***** Get number of surveys with a field value from database *****/
   DB_QuerySELECT (&mysql_res,"can not get last question index",
		   "SELECT MAX(QstInd) FROM svy_questions WHERE SvyCod=%ld",
		   SvyCod);

   /***** Get number of users *****/
   row = mysql_fetch_row (mysql_res);
   if (row[0])	// There are questions
     {
      if (sscanf (row[0],"%u",&QstInd) != 1)
         Lay_ShowErrorAndExit ("Error when getting last question index.");
      QstInd++;
     }

   /***** Free structure that stores the query result *****/
   DB_FreeMySQLResult (&mysql_res);

   return QstInd;
  }

/*****************************************************************************/
/************************ List the questions of a survey *********************/
/*****************************************************************************/

static void Svy_ListSvyQuestions (struct Survey *Svy,
                                  struct SurveyQuestion *SvyQst)
  {
   extern const char *Hlp_ASSESSMENT_Surveys_questions;
   extern const char *Txt_Questions;
   extern const char *Txt_No_INDEX;
   extern const char *Txt_Type;
   extern const char *Txt_Question;
   extern const char *Txt_SURVEY_STR_ANSWER_TYPES[Svy_NUM_ANS_TYPES];
   extern const char *Txt_This_survey_has_no_questions;
   extern const char *Txt_Done;
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;
   unsigned NumQsts;
   unsigned NumQst;
   bool Editing = (Gbl.Action.Act == ActEdiOneSvy    ||
	           Gbl.Action.Act == ActEdiOneSvyQst ||
	           Gbl.Action.Act == ActRcvSvyQst);
   bool PutFormAnswerSurvey = Svy->Status.ICanAnswer && !Editing;

   /***** Get data of questions from database *****/
   NumQsts = (unsigned) DB_QuerySELECT (&mysql_res,"can not get data of a question",
				        "SELECT QstCod,QstInd,AnsType,Stem"
				        " FROM svy_questions"
				        " WHERE SvyCod=%ld ORDER BY QstInd",
				        Svy->SvyCod);

   /***** Start box *****/
   Svy_CurrentSvyCod = Svy->SvyCod;
   Box_StartBox (NULL,Txt_Questions,Svy->Status.ICanEdit ? Svy_PutIconToAddNewQuestion :
                                                           NULL,
                 Hlp_ASSESSMENT_Surveys_questions,Box_NOT_CLOSABLE);

   if (NumQsts)
     {
      if (PutFormAnswerSurvey)
	{
	 /***** Start form to send answers to survey *****/
	 Frm_StartForm (ActAnsSvy);
	 Svy_PutParamSvyCod (Svy->SvyCod);
	}

      /***** Write the heading *****/
      Tbl_StartTableWideMargin (2);
      fprintf (Gbl.F.Out,"<tr>");
      if (Svy->Status.ICanEdit)
         fprintf (Gbl.F.Out,"<th></th>");
      fprintf (Gbl.F.Out,"<th class=\"CENTER_TOP\">"
	                 "%s"
	                 "</th>"
                         "<th class=\"CENTER_TOP\">"
                         "%s"
                         "</th>"
                         "<th class=\"LEFT_TOP\">"
                         "%s"
                         "</th>"
                         "</tr>",
               Txt_No_INDEX,
               Txt_Type,
               Txt_Question);

      /***** Write questions one by one *****/
      for (NumQst = 0;
	   NumQst < NumQsts;
	   NumQst++)
        {
         Gbl.RowEvenOdd = (int) (NumQst % 2);

         row = mysql_fetch_row (mysql_res);

         /* row[0] holds the code of the question */
         if (sscanf (row[0],"%ld",&(SvyQst->QstCod)) != 1)
            Lay_ShowErrorAndExit ("Wrong code of question.");

         fprintf (Gbl.F.Out,"<tr>");

         if (Svy->Status.ICanEdit)
           {
	    fprintf (Gbl.F.Out,"<td class=\"BT%u\">",Gbl.RowEvenOdd);

            /* Write icon to remove the question */
            Frm_StartForm (ActReqRemSvyQst);
            Svy_PutParamSvyCod (Svy->SvyCod);
            Svy_PutParamQstCod (SvyQst->QstCod);
            Ico_PutIconRemove ();
            Frm_EndForm ();

            /* Write icon to edit the question */
            Svy_CurrentSvyCod    = Svy->SvyCod;
            Svy_CurrentQstCod = SvyQst->QstCod;
            Ico_PutContextualIconToEdit (ActEdiOneSvyQst,Svy_PutParamsToEditQuestion);

            fprintf (Gbl.F.Out,"</td>");
           }

         /* Write index of question inside survey (row[1]) */
         if (sscanf (row[1],"%u",&(SvyQst->QstInd)) != 1)
            Lay_ShowErrorAndExit ("Error: wrong question index.");
         fprintf (Gbl.F.Out,"<td class=\"DAT_SMALL CENTER_TOP COLOR%u\">"
                            "%u"
                            "</td>",
                  Gbl.RowEvenOdd,SvyQst->QstInd + 1);

         /* Write the question type (row[2]) */
         SvyQst->AnswerType = Svy_ConvertFromStrAnsTypDBToAnsTyp (row[2]);
         fprintf (Gbl.F.Out,"<td class=\"DAT_SMALL CENTER_TOP COLOR%u\">"
                            "%s"
                            "</td>",
	          Gbl.RowEvenOdd,
                  Txt_SURVEY_STR_ANSWER_TYPES[SvyQst->AnswerType]);

         /* Write the stem (row[3]) and the answers of this question */
         fprintf (Gbl.F.Out,"<td class=\"DAT LEFT_TOP COLOR%u\">",
	          Gbl.RowEvenOdd);
         Svy_WriteQstStem (row[3]);
         Svy_WriteAnswersOfAQst (Svy,SvyQst,PutFormAnswerSurvey);
         fprintf (Gbl.F.Out,"</td>"
                            "</tr>");
        }

      Tbl_EndTable ();

      if (PutFormAnswerSurvey)
	{
	 /***** Button to create/modify survey *****/
	 Btn_PutConfirmButton (Txt_Done);

	 /***** End form *****/
	 Frm_EndForm ();
	}
     }
   else	// This survey has no questions
      Ale_ShowAlert (Ale_INFO,Txt_This_survey_has_no_questions);

   if (Svy->Status.ICanEdit &&	// I can edit
       (!NumQsts ||		// This survey has no questions
	Editing))		// I am editing
      /***** Put button to add a new question in this survey *****/
      Svy_PutButtonToCreateNewQuestion ();

   /***** Free structure that stores the query result *****/
   DB_FreeMySQLResult (&mysql_res);

   /***** End box *****/
   Box_EndBox ();
  }

/*****************************************************************************/
/******************** Put parameters to edit a question **********************/
/*****************************************************************************/

static void Svy_PutParamsToEditQuestion (void)
  {
   Svy_PutParamSvyCod (Svy_CurrentSvyCod);
   Svy_PutParamQstCod (Svy_CurrentQstCod);
  }

/*****************************************************************************/
/***************** Put icon to add a new question to survey ******************/
/*****************************************************************************/

static void Svy_PutIconToAddNewQuestion (void)
  {
   extern const char *Txt_New_question;

   /***** Put form to create a new question *****/
   Ico_PutContextualIconToAdd (ActEdiOneSvyQst,NULL,Svy_PutParams,
                               Txt_New_question);
  }

/*****************************************************************************/
/**************** Put button to add a new question to survey *****************/
/*****************************************************************************/

static void Svy_PutButtonToCreateNewQuestion (void)
  {
   extern const char *Txt_New_question;

   Frm_StartForm (ActEdiOneSvyQst);
   Svy_PutParams ();
   Btn_PutConfirmButton (Txt_New_question);
   Frm_EndForm ();
  }

/*****************************************************************************/
/****************** Write the heading of a survey question *******************/
/*****************************************************************************/

static void Svy_WriteQstStem (const char *Stem)
  {
   char *HeadingRigorousHTML;
   size_t Length;

   /* Convert the stem, that is in HTML, to rigorous HTML */
   Length = strlen (Stem) * Str_MAX_BYTES_PER_CHAR;
   if ((HeadingRigorousHTML = (char *) malloc (Length + 1)) == NULL)
      Lay_NotEnoughMemoryExit ();
   Str_Copy (HeadingRigorousHTML,Stem,
             Length);
   Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
                     HeadingRigorousHTML,Length,false);

   /* Write the stem */
   fprintf (Gbl.F.Out,"%s",HeadingRigorousHTML);

   /* Free memory allocated for the stem */
   free ((void *) HeadingRigorousHTML);
  }

/*****************************************************************************/
/************** Get and write the answers of a survey question ***************/
/*****************************************************************************/

static void Svy_WriteAnswersOfAQst (struct Survey *Svy,
                                    struct SurveyQuestion *SvyQst,
                                    bool PutFormAnswerSurvey)
  {
   unsigned NumAnswers;
   unsigned NumAns;
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;
   unsigned NumUsrsThisAnswer;

   /***** Get answers of this question *****/
   NumAnswers = Svy_GetAnswersQst (SvyQst->QstCod,&mysql_res);	// Result: AnsInd,NumUsrs,Answer

   /***** Write the answers *****/
   if (NumAnswers)
     {
      /* Check number of answers */
      if (NumAnswers > Svy_MAX_ANSWERS_PER_QUESTION)
	 Lay_ShowErrorAndExit ("Wrong number of answers.");

      /* Write one row for each answer */
      Tbl_StartTable (5);
      for (NumAns = 0;
	   NumAns < NumAnswers;
	   NumAns++)
	{
	 row = mysql_fetch_row (mysql_res);

	 /* Get number of users who have marked this answer (row[1]) */
	 if (sscanf (row[1],"%u",&NumUsrsThisAnswer) != 1)
	    Lay_ShowErrorAndExit ("Error when getting number of users who have marked an answer.");

	 /* Convert the answer (row[2]), that is in HTML, to rigorous HTML */
	 if (!Svy_AllocateTextChoiceAnswer (SvyQst,NumAns))
	    /* Abort on error */
	    Ale_ShowAlertsAndExit ();

	 Str_Copy (SvyQst->AnsChoice[NumAns].Text,row[2],
	           Svy_MAX_BYTES_ANSWER);
	 Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
			   SvyQst->AnsChoice[NumAns].Text,Svy_MAX_BYTES_ANSWER,false);

	 /* Selectors and label with the letter of the answer */
	 fprintf (Gbl.F.Out,"<tr>");

	 if (PutFormAnswerSurvey)
	   {
	    /* Write selector to choice this answer */
	    fprintf (Gbl.F.Out,"<td class=\"LEFT_TOP\">"
			       "<input type=\"");
	    if (SvyQst->AnswerType == Svy_ANS_UNIQUE_CHOICE)
	       fprintf (Gbl.F.Out,"radio\""
				  " onclick=\"selectUnselectRadio(this,this.form.Ans%010u,%u)\"",
			(unsigned) SvyQst->QstCod,NumAnswers);
	    else // SvyQst->AnswerType == Svy_ANS_MULTIPLE_CHOICE
	       fprintf (Gbl.F.Out,"checkbox\"");
	    fprintf (Gbl.F.Out," id=\"Ans%010u_%010u\" name=\"Ans%010u\""
		               " value=\"%u\" />"
			       "</td>",
		     (unsigned) SvyQst->QstCod,NumAns,(unsigned) SvyQst->QstCod,
		     NumAns);
	   }

	 /* Write the number of option */
	 fprintf (Gbl.F.Out,"<td class=\"LEFT_TOP\" style=\"width:50px;\">"
			    "<label for=\"Ans%010u_%010u\" class=\"DAT\">"
			    "%u)"
			    "</label>"
			    "</td>",
		  (unsigned) SvyQst->QstCod,NumAns,NumAns + 1);

	 /* Write the text of the answer */
	 fprintf (Gbl.F.Out,"<td class=\"LEFT_TOP\">"
			    "<label for=\"Ans%010u_%010u\" class=\"DAT\">%s</label>"
	                    "</td>",
		  (unsigned) SvyQst->QstCod,NumAns,
		  SvyQst->AnsChoice[NumAns].Text);

	 /* Show stats of this answer */
	 if (Svy->Status.ICanViewResults)
	    Svy_DrawBarNumUsrs (NumUsrsThisAnswer,Svy->NumUsrs);

	 fprintf (Gbl.F.Out,"</tr>");

	 /* Free memory allocated for the answer */
	 Svy_FreeTextChoiceAnswer (SvyQst,NumAns);
	}
      Tbl_EndTable ();
     }

   /***** Free structure that stores the query result *****/
   DB_FreeMySQLResult (&mysql_res);
  }

/*****************************************************************************/
/***************** Draw a bar with the percentage of answers *****************/
/*****************************************************************************/

#define Svy_MAX_BAR_WIDTH 125

static void Svy_DrawBarNumUsrs (unsigned NumUsrs,unsigned MaxUsrs)
  {
   extern const char *Txt_of_PART_OF_A_TOTAL;
   unsigned BarWidth = 0;

   /***** String with the number of users *****/
   if (MaxUsrs)
      snprintf (Gbl.Title,sizeof (Gbl.Title),
	        "%u&nbsp;(%u%%&nbsp;%s&nbsp;%u)",
                NumUsrs,
                (unsigned) ((((float) NumUsrs * 100.0) / (float) MaxUsrs) + 0.5),
                Txt_of_PART_OF_A_TOTAL,MaxUsrs);
   else
      snprintf (Gbl.Title,sizeof (Gbl.Title),
	        "0&nbsp;(0%%&nbsp;%s&nbsp;%u)",
                Txt_of_PART_OF_A_TOTAL,MaxUsrs);

   /***** Draw bar with a with proportional to the number of clicks *****/
   fprintf (Gbl.F.Out,"<td class=\"DAT LEFT_TOP\" style=\"width:%upx;\">",
            Svy_MAX_BAR_WIDTH + 125);
   if (NumUsrs && MaxUsrs)
      BarWidth = (unsigned) ((((float) NumUsrs * (float) Svy_MAX_BAR_WIDTH) /
	                       (float) MaxUsrs) + 0.5);
   if (BarWidth < 2)
      BarWidth = 2;
   fprintf (Gbl.F.Out,"<img src=\"%s/c1x16.gif\""
	              " alt=\"%s\" title=\"%s\""
                      " class=\"LEFT_TOP\""
	              " style=\"width:%upx; height:20px;\" />"
	              "&nbsp;",
      Cfg_URL_ICON_PUBLIC,
      Gbl.Title,
      Gbl.Title,
      BarWidth);

   /***** Write the number of users *****/
   fprintf (Gbl.F.Out,"%s</td>"
	              "</tr>",
            Gbl.Title);
  }

/*****************************************************************************/
/********************* Put icon to remove one question ***********************/
/*****************************************************************************/

static void Svy_PutIconToRemoveOneQst (void)
  {
   Ico_PutContextualIconToRemove (ActReqRemSvyQst,Svy_PutParamsRemoveOneQst);
  }

/*****************************************************************************/
/****************** Put parameter to remove one question *********************/
/*****************************************************************************/

static void Svy_PutParamsRemoveOneQst (void)
  {
   Svy_PutParamSvyCod (Svy_CurrentSvyCod);
   Svy_PutParamQstCod (Svy_CurrentQstCod);
  }

/*****************************************************************************/
/********************** Request the removal of a question ********************/
/*****************************************************************************/

void Svy_RequestRemoveQst (void)
  {
   extern const char *Txt_Do_you_really_want_to_remove_the_question_X;
   extern const char *Txt_Remove_question;
   long SvyCod;
   struct SurveyQuestion SvyQst;

   /***** Initialize question to zero *****/
   Svy_InitQst (&SvyQst);

   /***** Get parameters from form *****/
   /* Get survey code */
   if ((SvyCod = Svy_GetParamSvyCod ()) == -1L)
      Lay_ShowErrorAndExit ("Code of survey is missing.");

   /* Get question code */
   if ((SvyQst.QstCod = Svy_GetParamQstCod ()) < 0)
      Lay_ShowErrorAndExit ("Wrong code of question.");

   /* Get question index */
   SvyQst.QstInd = Svy_GetQstIndFromQstCod (SvyQst.QstCod);

   /***** Show question and button to remove question *****/
   Svy_CurrentSvyCod    = SvyCod;
   Svy_CurrentQstCod = SvyQst.QstCod;
   Ale_ShowAlertAndButton (ActRemSvyQst,NULL,NULL,Svy_PutParamsRemoveOneQst,
			   Btn_REMOVE_BUTTON,Txt_Remove_question,
			   Ale_QUESTION,Txt_Do_you_really_want_to_remove_the_question_X,
	                   (unsigned long) (SvyQst.QstInd + 1));

   /***** Show current survey *****/
   Svy_ShowOneSurvey (SvyCod,&SvyQst,true);
  }

/*****************************************************************************/
/****************************** Remove a question ****************************/
/*****************************************************************************/

void Svy_RemoveQst (void)
  {
   extern const char *Txt_Question_removed;
   long SvyCod;
   struct SurveyQuestion SvyQst;

   /***** Initialize question to zero *****/
   Svy_InitQst (&SvyQst);

   /***** Get parameters from form *****/
   /* Get survey code */
   if ((SvyCod = Svy_GetParamSvyCod ()) == -1L)
      Lay_ShowErrorAndExit ("Code of survey is missing.");

   /* Get question code */
   if ((SvyQst.QstCod = Svy_GetParamQstCod ()) < 0)
      Lay_ShowErrorAndExit ("Wrong code of question.");

   /* Get question index */
   SvyQst.QstInd = Svy_GetQstIndFromQstCod (SvyQst.QstCod);

   /***** Remove the question from all the tables *****/
   /* Remove answers from this test question */
   Svy_RemAnswersOfAQuestion (SvyQst.QstCod);

   /* Remove the question itself */
   DB_QueryDELETE ("can not remove a question",
		   "DELETE FROM svy_questions WHERE QstCod=%ld",
		   SvyQst.QstCod);
   if (!mysql_affected_rows (&Gbl.mysql))
      Lay_ShowErrorAndExit ("The question to be removed does not exist.");

   /* Change index of questions greater than this */
   DB_QueryUPDATE ("can not update indexes of questions",
		   "UPDATE svy_questions SET QstInd=QstInd-1"
                   " WHERE SvyCod=%ld AND QstInd>%u",
		   SvyCod,SvyQst.QstInd);

   /***** Write message *****/
   Ale_ShowAlert (Ale_SUCCESS,Txt_Question_removed);

   /***** Show current survey *****/
   Svy_ShowOneSurvey (SvyCod,&SvyQst,true);
  }

/*****************************************************************************/
/************************ Receive answers of a survey ************************/
/*****************************************************************************/

void Svy_ReceiveSurveyAnswers (void)
  {
   extern const char *Txt_You_already_answered_this_survey_before;
   extern const char *Txt_Thanks_for_answering_the_survey;
   struct Survey Svy;
   struct SurveyQuestion SvyQst;

   /***** Initialize question to zero *****/
   Svy_InitQst (&SvyQst);

   /***** Get survey code *****/
   if ((Svy.SvyCod = Svy_GetParamSvyCod ()) == -1L)
      Lay_ShowErrorAndExit ("Code of survey is missing.");

   /***** Get data of the survey from database *****/
   Svy_GetDataOfSurveyByCod (&Svy);

   /***** Check if I have no answered this survey formerly *****/
   if (Svy.Status.IHaveAnswered)
      Ale_ShowAlert (Ale_WARNING,Txt_You_already_answered_this_survey_before);
   else
     {
      /***** Receive and store user's answers *****/
      Svy_ReceiveAndStoreUserAnswersToASurvey (Svy.SvyCod);
      Ale_ShowAlert (Ale_SUCCESS,Txt_Thanks_for_answering_the_survey);
     }

   /***** Show current survey *****/
   Svy_ShowOneSurvey (Svy.SvyCod,&SvyQst,true);
  }

/*****************************************************************************/
/**************** Get and store user's answers to a survey *******************/
/*****************************************************************************/

static void Svy_ReceiveAndStoreUserAnswersToASurvey (long SvyCod)
  {
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;
   unsigned NumQst;
   unsigned NumQsts;
   long QstCod;
   char ParamName[3 + 10 + 6 + 1];
   char StrAnswersIndexes[Svy_MAX_ANSWERS_PER_QUESTION * (10 + 1)];
   const char *Ptr;
   char UnsignedStr[10 + 1];
   unsigned AnsInd;

   /***** Get questions of this survey from database *****/
   NumQsts = (unsigned) DB_QuerySELECT (&mysql_res,"can not get questions"
						   " of a survey",
					"SELECT QstCod FROM svy_questions"
					" WHERE SvyCod=%ld ORDER BY QstCod",
					SvyCod);
   if (NumQsts)
     {
      // This survey has questions
      /***** Get questions *****/
      for (NumQst = 0;
	   NumQst < NumQsts;
	   NumQst++)
        {
         /* Get next answer */
         row = mysql_fetch_row (mysql_res);

         /* Get question code (row[0]) */
         if ((QstCod = Str_ConvertStrCodToLongCod (row[0])) <= 0)
            Lay_ShowErrorAndExit ("Error: wrong question code.");

         /* Get possible parameter with the user's answer */
         snprintf (ParamName,sizeof (ParamName),
                   "Ans%010u",
		   (unsigned) QstCod);
         // Lay_ShowAlert (Lay_INFO,ParamName);
         Par_GetParMultiToText (ParamName,StrAnswersIndexes,
                                Svy_MAX_ANSWERS_PER_QUESTION * (10 + 1));
         Ptr = StrAnswersIndexes;
         while (*Ptr)
           {
            Par_GetNextStrUntilSeparParamMult (&Ptr,UnsignedStr,10);
            if (sscanf (UnsignedStr,"%u",&AnsInd) == 1)
               // Parameter exists ==> user has checked this answer
               // 		   ==> store it in database
               Svy_IncreaseAnswerInDB (QstCod,AnsInd);
           }
        }
     }
   else		// This survey has no questions
      Lay_ShowErrorAndExit ("Error: this survey has no questions.");

   /***** Free structure that stores the query result *****/
   DB_FreeMySQLResult (&mysql_res);

   /***** Register that you have answered this survey *****/
   Svy_RegisterIHaveAnsweredSvy (SvyCod);
  }

/*****************************************************************************/
/************ Increase number of users who have marked one answer ************/
/*****************************************************************************/

static void Svy_IncreaseAnswerInDB (long QstCod,unsigned AnsInd)
  {
   /***** Increase number of users who have selected the answer AnsInd in the question QstCod *****/
   DB_QueryUPDATE ("can not register your answer to the survey",
		   "UPDATE svy_answers SET NumUsrs=NumUsrs+1"
                   " WHERE QstCod=%ld AND AnsInd=%u",
		   QstCod,AnsInd);
  }

/*****************************************************************************/
/***************** Register that I have answered this survey *****************/
/*****************************************************************************/

static void Svy_RegisterIHaveAnsweredSvy (long SvyCod)
  {
   DB_QueryINSERT ("can not register that you have answered the survey",
		   "INSERT INTO svy_users"
	           " (SvyCod,UsrCod)"
                   " VALUES"
                   " (%ld,%ld)",
		   SvyCod,Gbl.Usrs.Me.UsrDat.UsrCod);
  }

/*****************************************************************************/
/************** Register that you have answered this survey ******************/
/*****************************************************************************/

static bool Svy_CheckIfIHaveAnsweredSvy (long SvyCod)
  {
   /***** Get number of surveys with a field value from database *****/
   return (DB_QueryCOUNT ("can not check if you have answered a survey",
			  "SELECT COUNT(*) FROM svy_users"
			  " WHERE SvyCod=%ld AND UsrCod=%ld",
			  SvyCod,Gbl.Usrs.Me.UsrDat.UsrCod) != 0);
  }

/*****************************************************************************/
/*************** Register that you have answered this survey *****************/
/*****************************************************************************/

static unsigned Svy_GetNumUsrsWhoHaveAnsweredSvy (long SvyCod)
  {
   /***** Get number of surveys with a field value from database *****/
   return
   (unsigned) DB_QueryCOUNT ("can not get number of users"
			     " who have answered a survey",
			     "SELECT COUNT(*) FROM svy_users"
			     " WHERE SvyCod=%ld",
			     SvyCod);
  }

/*****************************************************************************/
/******************** Get number of courses with surveys *********************/
/*****************************************************************************/
// Returns the number of courses with surveys for courses
// in this location (all the platform, current degree or current course)

unsigned Svy_GetNumCoursesWithCrsSurveys (Hie_Level_t Scope)
  {
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;
   unsigned NumCourses;

   /***** Get number of courses with surveys from database *****/
   switch (Scope)
     {
      case Hie_SYS:
         DB_QuerySELECT (&mysql_res,"can not get number of courses"
				    " with surveys",
			 "SELECT COUNT(DISTINCT Cod)"
                         " FROM surveys"
                         " WHERE Scope='%s'",
			 Sco_GetDBStrFromScope (Hie_CRS));
         break;
      case Hie_CTY:
         DB_QuerySELECT (&mysql_res,"can not get number of courses"
				    " with surveys",
			 "SELECT COUNT(DISTINCT surveys.Cod)"
                         " FROM institutions,centres,degrees,courses,surveys"
			 " WHERE institutions.CtyCod=%ld"
			 " AND institutions.InsCod=centres.InsCod"
                         " AND centres.CtrCod=degrees.CtrCod"
                         " AND degrees.DegCod=courses.DegCod"
                         " AND courses.CrsCod=surveys.Cod"
                         " AND surveys.Scope='%s'",
			 Gbl.Hierarchy.Ins.InsCod,
			 Sco_GetDBStrFromScope (Hie_CRS));
         break;
      case Hie_INS:
         DB_QuerySELECT (&mysql_res,"can not get number of courses"
				    " with surveys",
			 "SELECT COUNT(DISTINCT surveys.Cod)"
                         " FROM centres,degrees,courses,surveys"
                         " WHERE centres.InsCod=%ld"
                         " AND centres.CtrCod=degrees.CtrCod"
                         " AND degrees.DegCod=courses.DegCod"
                         " AND courses.CrsCod=surveys.Cod"
                         " AND surveys.Scope='%s'",
			 Gbl.Hierarchy.Ins.InsCod,
			 Sco_GetDBStrFromScope (Hie_CRS));
         break;
      case Hie_CTR:
         DB_QuerySELECT (&mysql_res,"can not get number of courses"
				    " with surveys",
			 "SELECT COUNT(DISTINCT surveys.Cod)"
                         " FROM degrees,courses,surveys"
                         " WHERE degrees.CtrCod=%ld"
                         " AND degrees.DegCod=courses.DegCod"
                         " AND courses.CrsCod=surveys.Cod"
                         " AND surveys.Scope='%s'",
			 Gbl.Hierarchy.Ctr.CtrCod,
			 Sco_GetDBStrFromScope (Hie_CRS));
         break;
      case Hie_DEG:
         DB_QuerySELECT (&mysql_res,"can not get number of courses"
				    " with surveys",
			 "SELECT COUNT(DISTINCT surveys.Cod)"
                         " FROM courses,surveys"
                         " WHERE courses.DegCod=%ld"
                         " AND courses.CrsCod=surveys.Cod"
                         " AND surveys.Scope='%s'",
		 	 Gbl.Hierarchy.Deg.DegCod,
			 Sco_GetDBStrFromScope (Hie_CRS));
         break;
      case Hie_CRS:
         DB_QuerySELECT (&mysql_res,"can not get number of courses"
				    " with surveys",
			 "SELECT COUNT(DISTINCT Cod)"
			 " FROM surveys"
			 " WHERE Scope='%s' AND Cod=%ld",
			 Sco_GetDBStrFromScope (Hie_CRS),
			 Gbl.Hierarchy.Crs.CrsCod);
         break;
      default:
	 Lay_WrongScopeExit ();
	 break;
     }

   /***** Get number of surveys *****/
   row = mysql_fetch_row (mysql_res);
   if (sscanf (row[0],"%u",&NumCourses) != 1)
      Lay_ShowErrorAndExit ("Error when getting number of courses with surveys.");

   /***** Free structure that stores the query result *****/
   DB_FreeMySQLResult (&mysql_res);

   return NumCourses;
  }

/*****************************************************************************/
/******************** Get number of surveys for courses **********************/
/*****************************************************************************/
// Returns the number of surveys for courses
// in this location (all the platform, current degree or current course)

unsigned Svy_GetNumCrsSurveys (Hie_Level_t Scope,unsigned *NumNotif)
  {
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;
   unsigned NumSurveys;

   /***** Get number of surveys from database *****/
   switch (Scope)
     {
      case Hie_SYS:
         DB_QuerySELECT (&mysql_res,"can not get number of surveys",
			 "SELECT COUNT(*),SUM(NumNotif)"
                         " FROM surveys"
                         " WHERE Scope='%s'",
			 Sco_GetDBStrFromScope (Hie_CRS));
         break;
      case Hie_CTY:
         DB_QuerySELECT (&mysql_res,"can not get number of surveys",
			 "SELECT COUNT(*),SUM(surveys.NumNotif)"
                         " FROM institutions,centres,degrees,courses,surveys"
                         " WHERE institutions.CtyCod=%ld"
                         " AND institutions.InsCod=centres.InsCod"
                         " AND centres.CtrCod=degrees.CtrCod"
                         " AND degrees.DegCod=courses.DegCod"
                         " AND courses.CrsCod=surveys.Cod"
                         " AND surveys.Scope='%s'",
			 Gbl.Hierarchy.Cty.CtyCod,
			 Sco_GetDBStrFromScope (Hie_CRS));
         break;
      case Hie_INS:
         DB_QuerySELECT (&mysql_res,"can not get number of surveys",
			 "SELECT COUNT(*),SUM(surveys.NumNotif)"
                         " FROM centres,degrees,courses,surveys"
                         " WHERE centres.InsCod=%ld"
                         " AND centres.CtrCod=degrees.CtrCod"
                         " AND degrees.DegCod=courses.DegCod"
                         " AND courses.CrsCod=surveys.Cod"
                         " AND surveys.Scope='%s'",
			 Gbl.Hierarchy.Ins.InsCod,
			 Sco_GetDBStrFromScope (Hie_CRS));
         break;
      case Hie_CTR:
         DB_QuerySELECT (&mysql_res,"can not get number of surveys",
			 "SELECT COUNT(*),SUM(surveys.NumNotif)"
                         " FROM degrees,courses,surveys"
                         " WHERE degrees.CtrCod=%ld"
                         " AND degrees.DegCod=courses.DegCod"
                         " AND courses.CrsCod=surveys.Cod"
                         " AND surveys.Scope='%s'",
			 Gbl.Hierarchy.Ctr.CtrCod,
			 Sco_GetDBStrFromScope (Hie_CRS));
         break;
      case Hie_DEG:
         DB_QuerySELECT (&mysql_res,"can not get number of surveys",
			 "SELECT COUNT(*),SUM(surveys.NumNotif)"
                         " FROM courses,surveys"
                         " WHERE courses.DegCod=%ld"
                         " AND courses.CrsCod=surveys.Cod"
                         " AND surveys.Scope='%s'",
			 Gbl.Hierarchy.Deg.DegCod,
			 Sco_GetDBStrFromScope (Hie_CRS));
         break;
      case Hie_CRS:
         DB_QuerySELECT (&mysql_res,"can not get number of surveys",
			 "SELECT COUNT(*),SUM(NumNotif)"
                         " FROM surveys"
                         " WHERE surveys.Scope='%s'"
                         " AND CrsCod=%ld",
			 Sco_GetDBStrFromScope (Hie_CRS),
			 Gbl.Hierarchy.Crs.CrsCod);
         break;
      default:
	 Lay_WrongScopeExit ();
	 break;
     }

   /***** Get number of surveys *****/
   row = mysql_fetch_row (mysql_res);
   if (sscanf (row[0],"%u",&NumSurveys) != 1)
      Lay_ShowErrorAndExit ("Error when getting number of surveys.");

   /***** Get number of notifications by email *****/
   if (row[1])
     {
      if (sscanf (row[1],"%u",NumNotif) != 1)
         Lay_ShowErrorAndExit ("Error when getting number of notifications of surveys.");
     }
   else
      *NumNotif = 0;

   /***** Free structure that stores the query result *****/
   DB_FreeMySQLResult (&mysql_res);

   return NumSurveys;
  }

/*****************************************************************************/
/************ Get average number of questions per course survey **************/
/*****************************************************************************/

float Svy_GetNumQstsPerCrsSurvey (Hie_Level_t Scope)
  {
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;
   float NumQstsPerSurvey;

   /***** Get number of questions per survey from database *****/
   switch (Scope)
     {
      case Hie_SYS:
         DB_QuerySELECT (&mysql_res,"can not get number of questions"
				    " per survey",
			 "SELECT AVG(NumQsts) FROM"
                         " (SELECT COUNT(svy_questions.QstCod) AS NumQsts"
                         " FROM surveys,svy_questions"
                         " WHERE surveys.Scope='%s'"
                         " AND surveys.SvyCod=svy_questions.SvyCod"
                         " GROUP BY svy_questions.SvyCod) AS NumQstsTable",
			 Sco_GetDBStrFromScope (Hie_CRS));
         break;
      case Hie_CTY:
         DB_QuerySELECT (&mysql_res,"can not get number of questions"
				    " per survey",
			 "SELECT AVG(NumQsts) FROM"
                         " (SELECT COUNT(svy_questions.QstCod) AS NumQsts"
                         " FROM institutions,centres,degrees,courses,surveys,svy_questions"
                         " WHERE institutions.CtyCod=%ld"
                         " AND institutions.InsCod=centres.InsCod"
                         " AND centres.CtrCod=degrees.CtrCod"
                         " AND degrees.DegCod=courses.DegCod"
                         " AND courses.CrsCod=surveys.Cod"
                         " AND surveys.Scope='%s'"
                         " AND surveys.SvyCod=svy_questions.SvyCod"
                         " GROUP BY svy_questions.SvyCod) AS NumQstsTable",
			 Gbl.Hierarchy.Cty.CtyCod,
			 Sco_GetDBStrFromScope (Hie_CRS));
         break;
      case Hie_INS:
         DB_QuerySELECT (&mysql_res,"can not get number of questions"
				    " per survey",
			 "SELECT AVG(NumQsts) FROM"
                         " (SELECT COUNT(svy_questions.QstCod) AS NumQsts"
                         " FROM centres,degrees,courses,surveys,svy_questions"
                         " WHERE centres.InsCod=%ld"
                         " AND centres.CtrCod=degrees.CtrCod"
                         " AND degrees.DegCod=courses.DegCod"
                         " AND courses.CrsCod=surveys.Cod"
                         " AND surveys.Scope='%s'"
                         " AND surveys.SvyCod=svy_questions.SvyCod"
                         " GROUP BY svy_questions.SvyCod) AS NumQstsTable",
			 Gbl.Hierarchy.Ins.InsCod,
			 Sco_GetDBStrFromScope (Hie_CRS));
         break;
      case Hie_CTR:
         DB_QuerySELECT (&mysql_res,"can not get number of questions"
				    " per survey",
			 "SELECT AVG(NumQsts) FROM"
                         " (SELECT COUNT(svy_questions.QstCod) AS NumQsts"
                         " FROM degrees,courses,surveys,svy_questions"
                         " WHERE degrees.CtrCod=%ld"
                         " AND degrees.DegCod=courses.DegCod"
                         " AND courses.CrsCod=surveys.Cod"
                         " AND surveys.Scope='%s'"
                         " AND surveys.SvyCod=svy_questions.SvyCod"
                         " GROUP BY svy_questions.SvyCod) AS NumQstsTable",
			 Gbl.Hierarchy.Ctr.CtrCod,
			 Sco_GetDBStrFromScope (Hie_CRS));
         break;
      case Hie_DEG:
         DB_QuerySELECT (&mysql_res,"can not get number of questions"
				    " per survey",
			 "SELECT AVG(NumQsts) FROM"
                         " (SELECT COUNT(svy_questions.QstCod) AS NumQsts"
                         " FROM courses,surveys,svy_questions"
                         " WHERE courses.DegCod=%ld"
                         " AND courses.CrsCod=surveys.Cod"
                         " AND surveys.Scope='%s'"
                         " AND surveys.SvyCod=svy_questions.SvyCod"
                         " GROUP BY svy_questions.SvyCod) AS NumQstsTable",
			 Gbl.Hierarchy.Deg.DegCod,
			 Sco_GetDBStrFromScope (Hie_CRS));
         break;
      case Hie_CRS:
         DB_QuerySELECT (&mysql_res,"can not get number of questions"
				    " per survey",
			 "SELECT AVG(NumQsts) FROM"
                         " (SELECT COUNT(svy_questions.QstCod) AS NumQsts"
                         " FROM surveys,svy_questions"
                         " WHERE surveys.Scope='%s' AND surveys.Cod=%ld"
                         " AND surveys.SvyCod=svy_questions.SvyCod"
                         " GROUP BY svy_questions.SvyCod) AS NumQstsTable",
			 Sco_GetDBStrFromScope (Hie_CRS),Gbl.Hierarchy.Crs.CrsCod);
         break;
      default:
	 Lay_WrongScopeExit ();
	 break;
     }

   /***** Get number of courses *****/
   row = mysql_fetch_row (mysql_res);
   NumQstsPerSurvey = Str_GetFloatNumFromStr (row[0]);

   /***** Free structure that stores the query result *****/
   DB_FreeMySQLResult (&mysql_res);

   return NumQstsPerSurvey;
  }
