Ajout de la possibilité de répondre à un groupe de questions.
This commit is contained in:
parent
fe713480a0
commit
ca4be71a89
@ -9,7 +9,6 @@ module.exports =
|
|||||||
previewQuestionnaireRoutes: "/preview",
|
previewQuestionnaireRoutes: "/preview",
|
||||||
publishedQuestionnaireRoutes: "/quiz/",
|
publishedQuestionnaireRoutes: "/quiz/",
|
||||||
regenerateHTML: "/htmlregenerated",
|
regenerateHTML: "/htmlregenerated",
|
||||||
saveAnswersRoute: "/answer/",
|
|
||||||
searchAdminQuestionnairesRoute : "/searchadmin",
|
searchAdminQuestionnairesRoute : "/searchadmin",
|
||||||
searchQuestionnairesRoute : "/search",
|
searchQuestionnairesRoute : "/search",
|
||||||
// -- groupes :
|
// -- groupes :
|
||||||
@ -24,8 +23,9 @@ module.exports =
|
|||||||
// -- answers :
|
// -- answers :
|
||||||
getAdminStats: "/getadminstats/",
|
getAdminStats: "/getadminstats/",
|
||||||
getPreviousAnswers: "/user/answers/",
|
getPreviousAnswers: "/user/answers/",
|
||||||
getQuestionnairesWithoutAnswer: "/withoutanswer/user/",
|
/// getQuestionnairesWithoutAnswer: "/withoutanswer/user/", -> ne sert plus ! à remplacer pour liste derniers quizs
|
||||||
getStatsAnswers : "/user/anwswers/stats/",
|
getStatsAnswers : "/user/anwswers/stats/",// fonctionne aussi pour les groupes
|
||||||
|
saveAnswersRoute: "/answer/",// idem
|
||||||
// forms : à compléter avec valeurs par défaut, etc. cf modèle
|
// forms : à compléter avec valeurs par défaut, etc. cf modèle
|
||||||
Questionnaire :
|
Questionnaire :
|
||||||
{
|
{
|
||||||
|
@ -6,10 +6,11 @@ const configTpl = require("../views/"+config.theme+"/config/"+config.availableLa
|
|||||||
const tool = require("../tools/main");
|
const tool = require("../tools/main");
|
||||||
const toolFile = require("../tools/file");
|
const toolFile = require("../tools/file");
|
||||||
|
|
||||||
const subscriptionCtrl = require("./subscription");
|
const groupCtrl = require("./group");
|
||||||
const questionnaireCtrl = require("./questionnaire");
|
const questionnaireCtrl = require("./questionnaire");
|
||||||
|
const subscriptionCtrl = require("./subscription");
|
||||||
|
|
||||||
const txt = require("../lang/"+config.adminLang+"/answer");
|
const txtAnswers = require("../lang/"+config.adminLang+"/answer");
|
||||||
const txtGeneral = require("../lang/"+config.adminLang+"/general");
|
const txtGeneral = require("../lang/"+config.adminLang+"/general");
|
||||||
|
|
||||||
// Enregistrement d'une réponse à un questionnaire
|
// Enregistrement d'une réponse à un questionnaire
|
||||||
@ -18,33 +19,39 @@ exports.create = async (req, res, next) =>
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
const db = require("../models/index");
|
const db = require("../models/index");
|
||||||
const checkQuestionnaireAccess=await subscriptionCtrl.checkQuestionnaireAccess(req.connectedUser.User.id, req.body.QuestionnaireId);
|
|
||||||
req.body.UserId=req.connectedUser.User.id;
|
req.body.UserId=req.connectedUser.User.id;
|
||||||
if(checkQuestionnaireAccess) // l'utilisateur a déjà accès à ce questionnaire
|
await saveAnswerToQuestionnaire(req.body);
|
||||||
await db["Answer"].create({ ...req.body }, { fields: ["nbQuestions", "nbCorrectAnswers", "duration", "QuestionnaireId", "UserId"] });
|
// J'en profite pour remettre les pendules à l'heure !
|
||||||
else
|
|
||||||
{
|
|
||||||
await Promise.all([
|
|
||||||
db["QuestionnaireAccess"].create({ ...req.body }, { fields: ["QuestionnaireId", "UserId"] }),
|
|
||||||
db["Answer"].create({ ...req.body }, { fields: ["nbQuestions", "nbCorrectAnswers", "duration", "QuestionnaireId", "UserId"] })
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
// j'en profite pour remettre les pendules à l'heure !
|
|
||||||
db["User"].update({ timeDifference: req.body.timeDifference }, { where: { id : req.connectedUser.User.id }, limit:1 });
|
db["User"].update({ timeDifference: req.body.timeDifference }, { where: { id : req.connectedUser.User.id }, limit:1 });
|
||||||
creaUserStatsAnwsersJson(req.body.UserId);
|
res.status(201).json({ message: txtAnswers.responseSavedMessage });
|
||||||
creaUserQuestionnairesWithoutAnswerJson(req.body.UserId);
|
|
||||||
creaUserAnswersJson(req.body.UserId);
|
|
||||||
res.status(201).json({ message: txt.responseSavedMessage });
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
catch(e)
|
catch(e)
|
||||||
{ // à priori, l'utilisateur ne peut pas avoir envoyé de données incorrectes, donc erreur application pour admin
|
{
|
||||||
|
next(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enregistrement d'une réponse à un groupe de questionnaires
|
||||||
|
exports.createInGroup = async (req, res, next) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const db = require("../models/index");
|
||||||
|
req.body.UserId=req.connectedUser.User.id;
|
||||||
|
await saveAnswerToGroup(req.body);
|
||||||
|
// J'en profite pour remettre les pendules à l'heure !
|
||||||
|
db["User"].update({ timeDifference: req.body.timeDifference }, { where: { id : req.connectedUser.User.id }, limit:1 });
|
||||||
|
res.status(201).json({ message: txtAnswers.responseSavedMessage });
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
next(e);
|
next(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retourne les réponses d'un utilisateur pour un questionnaire donné
|
// Retourne les réponses d'un utilisateur pour un questionnaire donné
|
||||||
// Si fichier réponses devient trop gros, passer par sql ?
|
|
||||||
exports.getAnswersByQuestionnaire = async(req, res, next) =>
|
exports.getAnswersByQuestionnaire = async(req, res, next) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -59,14 +66,30 @@ exports.getAnswersByQuestionnaire = async(req, res, next) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retourne les réponses d'un utilisateur pour un groupe de questionnaires donné
|
||||||
|
exports.getAnswersByGroup = async(req, res, next) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const answers=await getUserAnswersByGroup(req.params.userId, req.params.groupId);
|
||||||
|
res.status(200).json(answers);
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
next(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Retourne les statistiques de l'utilisateur
|
// Retourne les statistiques de l'utilisateur
|
||||||
exports.getStatsByUser = async(req, res, next) =>
|
exports.getStatsByUser = async(req, res, next) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const stats=await getUserStatsAnswers(req.params.userId);
|
const stats=await getUserStatsAnswers(req.params.userId);
|
||||||
// J'ajoute les stats générales des questionnaires pour comparaison :
|
// + stats générales des questionnaires et groupes pour comparaison :
|
||||||
stats.general=await questionnaireCtrl.getStatsQuestionnaires();
|
stats.questionnaires=await questionnaireCtrl.getStatsQuestionnaires();
|
||||||
|
stats.groups=await groupCtrl.getStatsGroups();
|
||||||
res.status(200).json(stats);
|
res.status(200).json(stats);
|
||||||
}
|
}
|
||||||
catch(e)
|
catch(e)
|
||||||
@ -75,71 +98,58 @@ exports.getStatsByUser = async(req, res, next) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retourne la liste des questionnaires auxquels un utilisateur a accès, mais n'a pas répondu
|
|
||||||
// Ils sont listés par ordre de fraîcheur, les + récents étant en début de liste
|
|
||||||
// Un questionnaire de début et un nombre de questionnaires à retourner doivent être fournis (pagination).
|
|
||||||
exports.getQuestionnairesWithouAnswerByUser = async(req, res, next) =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
let datas;
|
|
||||||
if(req.params.id===undefined || req.params.begin===undefined || req.params.nb===undefined)
|
|
||||||
{
|
|
||||||
const err=new Error;
|
|
||||||
err.message=txtGeneral.neededParams;
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
datas=await getUserQuestionnairesWithoutAnswer(req.params.id, req.params.begin, req.params.nb);
|
|
||||||
if(datas!==false)
|
|
||||||
{
|
|
||||||
if(req.params.output!==undefined && req.params.output=="html")
|
|
||||||
{
|
|
||||||
if(datas.questionnaires.length!=0)
|
|
||||||
{
|
|
||||||
const pug = require("pug");
|
|
||||||
const striptags = require("striptags");
|
|
||||||
const txtIllustration= require("../lang/"+config.adminLang+"/illustration");
|
|
||||||
const compiledFunction = pug.compileFile("./views/"+config.theme+"/includes/listing-questionnaires.pug");
|
|
||||||
const pageDatas=
|
|
||||||
{
|
|
||||||
tool: tool,
|
|
||||||
striptags: striptags,
|
|
||||||
txtGeneral: txtGeneral,
|
|
||||||
txtIllustration: txtIllustration,
|
|
||||||
questionnaires: datas.questionnaires,
|
|
||||||
nbQuestionnairesList:configTpl.nbQuestionnairesUserHomePage
|
|
||||||
}
|
|
||||||
datas.html=await compiledFunction(pageDatas);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
datas.html="";
|
|
||||||
res.status(200).json(datas);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
res.status(200).json(datas);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
res.status(404).json(txtQuestionnaire.notFound);
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
catch(e)
|
|
||||||
{
|
|
||||||
next(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// FONCTIONS UTILITAIRES
|
// FONCTIONS UTILITAIRES
|
||||||
|
|
||||||
|
// Enregistre la réponse à un questionnaire
|
||||||
|
const saveAnswerToQuestionnaire = async (req) =>
|
||||||
|
{
|
||||||
|
const db = require("../models/index");
|
||||||
|
const checkQuestionnaireAccess=await subscriptionCtrl.checkQuestionnaireAccess(req.UserId, req.QuestionnaireId);
|
||||||
|
if(checkQuestionnaireAccess) // L'utilisateur a déjà accès à ce questionnaire, j'enregistre juste sa réponse
|
||||||
|
await db["Answer"].create({ ...req }, { fields: ["nbQuestions", "nbCorrectAnswers", "duration", "QuestionnaireId", "UserId"] });
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Promise.all([
|
||||||
|
db["QuestionnaireAccess"].create({ ...req }, { fields: ["QuestionnaireId", "UserId"] }),
|
||||||
|
db["Answer"].create({ ...req }, { fields: ["nbQuestions", "nbCorrectAnswers", "duration", "QuestionnaireId", "UserId"] })
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
await creaUserStatsAnwsersJson(req.UserId);
|
||||||
|
await creaUserAnswersJson(req.UserId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
exports.saveAnswerToQuestionnaire = saveAnswerToQuestionnaire;
|
||||||
|
|
||||||
|
// Enregistre la réponse à un groupe de questionnaires
|
||||||
|
const saveAnswerToGroup = async (req) =>
|
||||||
|
{
|
||||||
|
const db = require("../models/index");
|
||||||
|
const answer = await db["Answer"].create({ ...req }, { fields: ["nbQuestions", "nbCorrectAnswers", "duration", "GroupId", "UserId"] });
|
||||||
|
const group = groupCtrl.searchGroupById(req.GroupId);
|
||||||
|
for(let i in group.Questionnaires)
|
||||||
|
{
|
||||||
|
if(await subscriptionCtrl.checkQuestionnaireAccess(req.UserId, group.Questionnaires[i].id) === false)
|
||||||
|
{
|
||||||
|
req.QuestionnaireId = group.Questionnaires[i].id;
|
||||||
|
await db["QuestionnaireAccess"].create({ ...req }, { fields: ["QuestionnaireId", "UserId"] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await creaUserStatsAnwsersJson(req.UserId);
|
||||||
|
await creaUserAnswersJson(req.UserId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
exports.saveAnswerToGroup = saveAnswerToGroup;
|
||||||
|
|
||||||
// Créer la liste des réponses d'un utilisateur
|
// Créer la liste des réponses d'un utilisateur
|
||||||
|
// !! à surveiller car fichier pouvant devenir gros ! mais utile pour future SVG côté client
|
||||||
const creaUserAnswersJson = async (UserId) =>
|
const creaUserAnswersJson = async (UserId) =>
|
||||||
{
|
{
|
||||||
const db = require("../models/index");
|
const db = require("../models/index");
|
||||||
const userAnswers=await db.sequelize.query("SELECT `QuestionnaireId`,`nbQuestions`,`nbCorrectAnswers`,`duration`,`createdAt` FROM `Answers` WHERE `UserId`=:id ORDER BY `QuestionnaireId` DESC, `createdAt` DESC", { replacements: { id: UserId }, type: QueryTypes.SELECT });
|
const userAnswers=await db.sequelize.query("SELECT `QuestionnaireId`, `GroupId`, `nbQuestions`, `nbCorrectAnswers`, `duration`, `createdAt` FROM `Answers` WHERE `UserId`=:id ORDER BY `GroupId` DESC, `QuestionnaireId` DESC, `createdAt` DESC", { replacements: { id: UserId }, type: QueryTypes.SELECT });
|
||||||
if(userAnswers)
|
if(userAnswers)
|
||||||
{
|
{
|
||||||
await toolFile.createJSON(config.dirCacheUsersAnswers, UserId, userAnswers);// à surveiller car fichier pouvant devenir gros ! mais utile pour SVG côté client
|
await toolFile.createJSON(config.dirCacheUsersAnswers, UserId, userAnswers);
|
||||||
return userAnswers;
|
return userAnswers;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -147,7 +157,7 @@ const creaUserAnswersJson = async (UserId) =>
|
|||||||
}
|
}
|
||||||
exports.creaUserAnswersJson = creaUserAnswersJson;
|
exports.creaUserAnswersJson = creaUserAnswersJson;
|
||||||
|
|
||||||
// Retourne les réponses d'un utilisateurs à un questionnaire
|
// Retourne les réponses d'un utilisateurs à un questionnaire simple.
|
||||||
const getUserAnswersByQuestionnaire = async (UserId, QuestionnaireId) =>
|
const getUserAnswersByQuestionnaire = async (UserId, QuestionnaireId) =>
|
||||||
{
|
{
|
||||||
let userAnswers=await toolFile.readJSON(config.dirCacheUsersAnswers, UserId);
|
let userAnswers=await toolFile.readJSON(config.dirCacheUsersAnswers, UserId);
|
||||||
@ -158,29 +168,49 @@ const getUserAnswersByQuestionnaire = async (UserId, QuestionnaireId) =>
|
|||||||
const answers=[];
|
const answers=[];
|
||||||
for(let i in userAnswers)
|
for(let i in userAnswers)
|
||||||
{
|
{
|
||||||
if(userAnswers[i].QuestionnaireId==QuestionnaireId)// pas "===" car type de données pouvant être différents
|
if(userAnswers[i].QuestionnaireId == QuestionnaireId) // pas "===", car type de données pouvant être différents
|
||||||
answers.push(userAnswers[i]);
|
answers.push(userAnswers[i]);
|
||||||
else if(answers.length!==0)// les réponses étant classées par QuestionnaireId, je peux sortir de la boucle
|
else if(answers.length !== 0) // les réponses étant classées par QuestionnaireId, je peux sortir de la boucle
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return answers;
|
return answers;
|
||||||
}
|
}
|
||||||
|
|
||||||
// À combien de questionnaire l'utilisateur a-t'il répondu, quelle est son résultat moyen ?
|
// Retourne les réponses d'un utilisateurs à un groupe de questionnaires.
|
||||||
|
const getUserAnswersByGroup = async (UserId, GroupId) =>
|
||||||
|
{
|
||||||
|
let userAnswers=await toolFile.readJSON(config.dirCacheUsersAnswers, UserId);
|
||||||
|
if(!userAnswers)
|
||||||
|
userAnswers=await creaUserAnswersJson(UserId);
|
||||||
|
if(!userAnswers)
|
||||||
|
return false;
|
||||||
|
const answers=[];
|
||||||
|
for(let i in userAnswers)
|
||||||
|
{
|
||||||
|
if(userAnswers[i].GroupId == GroupId)// pas "===" car type de données pouvant être différents
|
||||||
|
answers.push(userAnswers[i]);
|
||||||
|
else if(answers.length !== 0) // les réponses étant classées par GroupId, je peux sortir de la boucle
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return answers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// À combien de questionnaire l'utilisateur a-t-il répondu ? et quel est son résultat moyen ?
|
||||||
const creaUserStatsAnwsersJson = async (UserId) =>
|
const creaUserStatsAnwsersJson = async (UserId) =>
|
||||||
{
|
{
|
||||||
const db = require("../models/index");
|
const db = require("../models/index");
|
||||||
const getUserAnswers = await db["Answer"].findAll({ where: { UserId : UserId }, attributes: ["id"] });
|
const getUserAnswers = await db["Answer"].findAll({ where: { UserId : UserId }, attributes: ["id"] });
|
||||||
const getUserQuestionnaires = await db["Answer"].findAll({ attributes: [[db.sequelize.fn('DISTINCT', db.sequelize.col('QuestionnaireId')), 'id']], where: { UserId : UserId }});
|
const getUserQuestionnaires = await db.sequelize.query("SELECT DISTINCT QuestionnaireId FROM Answers WHERE UserId=:id AND QuestionnaireId IS NOT NULL", { replacements: { id: UserId }, type: QueryTypes.SELECT });
|
||||||
|
const getUserQuestionnairesGroup = await db.sequelize.query("SELECT DISTINCT GroupId FROM Answers WHERE UserId=:id AND GroupId IS NOT NULL", { replacements: { id: UserId }, type: QueryTypes.SELECT });
|
||||||
const getUserStats = await db.sequelize.query("SELECT ROUND(AVG(nbCorrectAnswers/nbQuestions) *100) as avgCorrectAnswers, ROUND(AVG(duration)) as avgDuration FROM Answers GROUP BY UserId HAVING UserId=:id", { replacements: { id: UserId }, type: QueryTypes.SELECT });
|
const getUserStats = await db.sequelize.query("SELECT ROUND(AVG(nbCorrectAnswers/nbQuestions) *100) as avgCorrectAnswers, ROUND(AVG(duration)) as avgDuration FROM Answers GROUP BY UserId HAVING UserId=:id", { replacements: { id: UserId }, type: QueryTypes.SELECT });
|
||||||
if(getUserAnswers && getUserQuestionnaires)
|
if(getUserAnswers && getUserQuestionnaires && getUserQuestionnairesGroup)
|
||||||
{
|
{
|
||||||
const stats =
|
const stats =
|
||||||
{
|
{
|
||||||
nbAnswers : getUserAnswers.length,
|
nbAnswers : getUserAnswers.length,
|
||||||
nbQuestionnaires : getUserQuestionnaires.length
|
nbQuestionnaires : getUserQuestionnaires.length+getUserQuestionnairesGroup.length
|
||||||
}
|
}
|
||||||
if(getUserStats && getUserAnswers.length!=0)
|
if(getUserStats && getUserAnswers.length !== 0)
|
||||||
{
|
{
|
||||||
stats.avgCorrectAnswers=getUserStats[0].avgCorrectAnswers;
|
stats.avgCorrectAnswers=getUserStats[0].avgCorrectAnswers;
|
||||||
stats.avgDuration=getUserStats[0].avgDuration;
|
stats.avgDuration=getUserStats[0].avgDuration;
|
||||||
@ -193,7 +223,7 @@ const creaUserStatsAnwsersJson = async (UserId) =>
|
|||||||
}
|
}
|
||||||
exports.creaUserStatsAnwsersJson = creaUserStatsAnwsersJson;
|
exports.creaUserStatsAnwsersJson = creaUserStatsAnwsersJson;
|
||||||
|
|
||||||
// Retourne les données créées par la fonction précédente
|
// Retourne les données créées par la fonction précédente :
|
||||||
const getUserStatsAnswers = async (UserId) =>
|
const getUserStatsAnswers = async (UserId) =>
|
||||||
{
|
{
|
||||||
let userStats=await toolFile.readJSON(config.dirCacheUsersAnswers, "stats"+UserId);
|
let userStats=await toolFile.readJSON(config.dirCacheUsersAnswers, "stats"+UserId);
|
||||||
@ -205,7 +235,7 @@ const getUserStatsAnswers = async (UserId) =>
|
|||||||
return userStats;
|
return userStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
// À combien de questionnaire les utilisateurs ont-ils répondu ces dernières 24 ? depuis le début ?
|
// À combien de questionnaire les utilisateurs ont-ils répondu ces dernières 24H ? depuis le début ?
|
||||||
const getStatsAnswers = async () =>
|
const getStatsAnswers = async () =>
|
||||||
{
|
{
|
||||||
const db = require("../models/index");
|
const db = require("../models/index");
|
||||||
@ -225,7 +255,9 @@ const getStatsAnswers = async () =>
|
|||||||
}
|
}
|
||||||
exports.getStatsAnswers = getStatsAnswers;
|
exports.getStatsAnswers = getStatsAnswers;
|
||||||
|
|
||||||
// Créer la liste des questionnaires proposés à l'utilisateur, mais auxquels il n'a pas encore répondu
|
/*
|
||||||
|
// Créer la liste des questionnaires/éléments de questionnaire proposés à l'utilisateur, mais auxquels il n'a pas encore répondu
|
||||||
|
// remplacer pour la liste des derniers éléments ou quizs envoyés ?
|
||||||
const creaUserQuestionnairesWithoutAnswerJson = async (UserId) =>
|
const creaUserQuestionnairesWithoutAnswerJson = async (UserId) =>
|
||||||
{
|
{
|
||||||
UserId=tool.trimIfNotNull(UserId);
|
UserId=tool.trimIfNotNull(UserId);
|
||||||
@ -271,3 +303,61 @@ const getUserQuestionnairesWithoutAnswer = async (UserId, begin=0, nb=10) =>
|
|||||||
return { nbTot: nbTot, questionnaires: Questionnaires };
|
return { nbTot: nbTot, questionnaires: Questionnaires };
|
||||||
}
|
}
|
||||||
exports.getUserQuestionnairesWithoutAnswer = getUserQuestionnairesWithoutAnswer;
|
exports.getUserQuestionnairesWithoutAnswer = getUserQuestionnairesWithoutAnswer;
|
||||||
|
* */
|
||||||
|
|
||||||
|
/// S'inspirer de la fonction ci-dessous pour la liste des questionnaires proposés à l'utilisateur.
|
||||||
|
/*
|
||||||
|
// Retourne la liste des questionnaires auxquels un utilisateur a accès, mais n'a pas répondu
|
||||||
|
// Ils sont listés par ordre de fraîcheur, les + récents étant en début de liste
|
||||||
|
// Un questionnaire de début et un nombre de questionnaires à retourner doivent être fournis (pagination).
|
||||||
|
exports.getQuestionnairesWithouAnswerByUser = async(req, res, next) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
let datas;
|
||||||
|
if(req.params.id === undefined || req.params.begin === undefined || req.params.nb === undefined)
|
||||||
|
{
|
||||||
|
const err=new Error;
|
||||||
|
err.message=txtGeneral.neededParams;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
datas=await getUserQuestionnairesWithoutAnswer(req.params.id, req.params.begin, req.params.nb);
|
||||||
|
if(datas!==false)
|
||||||
|
{
|
||||||
|
if(req.params.output!==undefined && req.params.output=="html")
|
||||||
|
{
|
||||||
|
if(datas.questionnaires.length!=0)
|
||||||
|
{
|
||||||
|
const pug = require("pug");
|
||||||
|
const striptags = require("striptags");
|
||||||
|
const txtIllustration= require("../lang/"+config.adminLang+"/illustration");
|
||||||
|
const compiledFunction = pug.compileFile("./views/"+config.theme+"/includes/listing-questionnaires.pug");
|
||||||
|
const pageDatas=
|
||||||
|
{
|
||||||
|
tool: tool,
|
||||||
|
striptags: striptags,
|
||||||
|
txtGeneral: txtGeneral,
|
||||||
|
txtIllustration: txtIllustration,
|
||||||
|
questionnaires: datas.questionnaires,
|
||||||
|
nbQuestionnairesList:configTpl.nbQuestionnairesUserHomePage
|
||||||
|
}
|
||||||
|
datas.html=await compiledFunction(pageDatas);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
datas.html="";
|
||||||
|
res.status(200).json(datas);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
res.status(200).json(datas);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
res.status(404).json(txtQuestionnaire.notFound);
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
next(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
* */
|
@ -62,7 +62,8 @@ exports.getGodfatherId = async (req, res, next) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Contrôleur traitant les données envoyées pour une inscription
|
// Contrôleur traitant les données envoyées pour une inscription
|
||||||
// Les CGU doivent être acceptées et une adresse e-mail envoyée. Le reste peut être adapté sur la page de validation.
|
// Les CGU doivent être acceptées et une adresse e-mail envoyée.
|
||||||
|
// Le reste peut être adapté sur la page de validation de l'inscription.
|
||||||
exports.signup = async (req, res, next) =>
|
exports.signup = async (req, res, next) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -76,7 +77,6 @@ exports.signup = async (req, res, next) =>
|
|||||||
{
|
{
|
||||||
// Un mot de passe temporaire et non communiqué est généré :
|
// Un mot de passe temporaire et non communiqué est généré :
|
||||||
req.body.passwordTemp=tool.getPassword(8, 12);
|
req.body.passwordTemp=tool.getPassword(8, 12);
|
||||||
console.log(req.body.passwordTemp);
|
|
||||||
req.body.password=await bcrypt.hash(req.body.passwordTemp, config.bcryptSaltRounds);
|
req.body.password=await bcrypt.hash(req.body.passwordTemp, config.bcryptSaltRounds);
|
||||||
// Un pseudo temporaire est créé en utilisant la partie de l'e-mail précédant le "@" :
|
// Un pseudo temporaire est créé en utilisant la partie de l'e-mail précédant le "@" :
|
||||||
const lastIndex=req.body.email.indexOf("@");
|
const lastIndex=req.body.email.indexOf("@");
|
||||||
@ -85,14 +85,11 @@ exports.signup = async (req, res, next) =>
|
|||||||
req.body.name=req.body.email.substring(0, lastIndex);
|
req.body.name=req.body.email.substring(0, lastIndex);
|
||||||
const user=await db["User"].create({ ...req.body }, { fields: ["name", "email", "password", "timeDifference"] });
|
const user=await db["User"].create({ ...req.body }, { fields: ["name", "email", "password", "timeDifference"] });
|
||||||
req.body.UserId=user.id;
|
req.body.UserId=user.id;
|
||||||
// si l'utilisateur a répondu à un quiz avant de créer son compte, on enregistre son résultat :
|
// Si l'utilisateur a répondu à un quiz (ou groupe de quizs) avant de créer son compte, on enregistre son résultat :
|
||||||
if(req.body.QuestionnaireId)
|
if(req.body.QuestionnaireId)
|
||||||
{
|
await answerCtrl.saveAnswerToQuestionnaire(req.body);
|
||||||
await Promise.all([
|
else if(req.body.GroupId)
|
||||||
db["QuestionnaireAccess"].create({ ...req.body }, { fields: ["QuestionnaireId", "UserId"] }),
|
await answerCtrl.saveAnswerToGroup(req.body);
|
||||||
db["Answer"].create({ ...req.body }, { fields: ["nbQuestions", "nbCorrectAnswers", "duration", "QuestionnaireId", "UserId"] })
|
|
||||||
]); // pas nécessaire ici de créer le cache JSON, car il sera créé lors de la première connexion au compte.
|
|
||||||
}
|
|
||||||
await sendValidationLink(user);
|
await sendValidationLink(user);
|
||||||
res.status(201).json({ message: txt.mailValidationMessage });
|
res.status(201).json({ message: txt.mailValidationMessage });
|
||||||
next();
|
next();
|
||||||
@ -312,23 +309,18 @@ exports.login = async (req, res, next) =>
|
|||||||
const now=new Date();
|
const now=new Date();
|
||||||
const timeDifference=req.body.timeDifference;
|
const timeDifference=req.body.timeDifference;
|
||||||
db["User"].update({ connectedAt: now, timeDifference: timeDifference }, { where: { id : user.id }, limit:1 });
|
db["User"].update({ connectedAt: now, timeDifference: timeDifference }, { where: { id : user.id }, limit:1 });
|
||||||
creaUserJson(user.id);
|
await creaUserJson(user.id);
|
||||||
// Connexion à rallonge uniquement possible pour utilisateur de base :
|
// Connexion à rallonge uniquement possible pour utilisateur de base :
|
||||||
let loginTime=config.tokenConnexionMinTimeInHours;
|
let loginTime=config.tokenConnexionMinTimeInHours;
|
||||||
if((req.body.keepConnected==="true") && (user.status==="user"))
|
if((req.body.keepConnected === "true") && (user.status === "user"))
|
||||||
loginTime=config.tokenConnexionMaxTimeInDays;
|
loginTime=config.tokenConnexionMaxTimeInDays;
|
||||||
// Si des données concernant un quiz ont été transmises, on les enregistre ici :
|
// Si l'utilisateur a répondu à un quiz (ou groupe de quizs) avant de se connecter, on enregistre son résultat.
|
||||||
|
// Uniquement pour les utilisateurs de base.
|
||||||
req.body.UserId=user.id;
|
req.body.UserId=user.id;
|
||||||
if(req.body.QuestionnaireId)
|
if(req.body.QuestionnaireId && user.status === "user")
|
||||||
{
|
await answerCtrl.saveAnswerToQuestionnaire(req.body);
|
||||||
const access=await subscriptionCtrl.checkQuestionnaireAccess(user.id, req.body.QuestionnaireId);
|
else if(req.body.GroupId && user.status === "user")
|
||||||
if(!access)
|
await answerCtrl.saveAnswerToGroup(req.body);
|
||||||
await db["QuestionnaireAccess"].create({ ...req.body }, { fields: ["QuestionnaireId", "UserId"] });
|
|
||||||
await db["Answer"].create({ ...req.body }, { fields: ["nbQuestions", "nbCorrectAnswers", "duration", "QuestionnaireId", "UserId"] });
|
|
||||||
answerCtrl.creaUserAnswersJson(req.body.UserId);
|
|
||||||
answerCtrl.creaUserQuestionnairesWithoutAnswerJson(req.body.UserId);
|
|
||||||
answerCtrl.creaUserStatsAnwsersJson(req.body.UserId);
|
|
||||||
}
|
|
||||||
res.status(200).json(
|
res.status(200).json(
|
||||||
{
|
{
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
@ -404,8 +396,8 @@ exports.checkLoginLink = async (req, res, next) =>
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
const db = require("../models/index");
|
const db = require("../models/index");
|
||||||
const userDatas= await checkTokenUser(req.body.t);
|
const userDatas = await checkTokenUser(req.body.t);
|
||||||
if(userDatas.User.status!=="user")
|
if(userDatas.User.status !== "user")
|
||||||
res.status(403).json({ errors: [txtGeneral.notAllowed] });
|
res.status(403).json({ errors: [txtGeneral.notAllowed] });
|
||||||
else if(userDatas.Subscription)
|
else if(userDatas.Subscription)
|
||||||
{
|
{
|
||||||
@ -416,18 +408,12 @@ exports.checkLoginLink = async (req, res, next) =>
|
|||||||
let loginTime=config.tokenConnexionMinTimeInHours;
|
let loginTime=config.tokenConnexionMinTimeInHours;
|
||||||
if(userDatas.decodedToken.keepConnected===true)
|
if(userDatas.decodedToken.keepConnected===true)
|
||||||
loginTime=config.tokenConnexionMaxTimeInDays;
|
loginTime=config.tokenConnexionMaxTimeInDays;
|
||||||
// si des données concernant un quiz ont été transmises, je les enregistre ici :
|
// Si l'utilisateur a répondu à un quiz (ou groupe de quizs) avant de se connecter, on enregistre son résultat :
|
||||||
if(req.body.QuestionnaireId)
|
|
||||||
{
|
|
||||||
req.body.UserId=userDatas.User.id;
|
req.body.UserId=userDatas.User.id;
|
||||||
const access=await subscriptionCtrl.checkQuestionnaireAccess(userDatas.User.id, req.body.QuestionnaireId);
|
if(req.body.QuestionnaireId)
|
||||||
if(!access)
|
await answerCtrl.saveAnswerToQuestionnaire(req.body);
|
||||||
await db["QuestionnaireAccess"].create({ ...req.body }, { fields: ["QuestionnaireId", "UserId"] });
|
else if(req.body.GroupId)
|
||||||
await db["Answer"].create({ ...req.body }, { fields: ["nbQuestions", "nbCorrectAnswers", "duration", "QuestionnaireId", "UserId"] });
|
await answerCtrl.saveAnswerToGroup(req.body);
|
||||||
answerCtrl.creaUserAnswersJson(req.body.UserId);
|
|
||||||
answerCtrl.creaUserStatsAnwsersJson(req.body.UserId);
|
|
||||||
answerCtrl.creaUserQuestionnairesWithoutAnswerJson(req.body.UserId);
|
|
||||||
}
|
|
||||||
res.status(200).json(
|
res.status(200).json(
|
||||||
{
|
{
|
||||||
userId: userDatas.User.id,
|
userId: userDatas.User.id,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// -- GESTION DU FORMULAIRE PERMETTANT D'AFFICHER ET RÉPONDRE À UN QUIZ
|
// -- GESTION DU FORMULAIRE PERMETTANT D'AFFICHER ET RÉPONDRE À UN GROUPE DE QUIZS
|
||||||
|
|
||||||
/// Il n'est pas nécessaire d'être connecté pour répondre au quiz et voir son résultat.
|
/// Il n'est pas nécessaire d'être connecté pour répondre au quiz et voir son résultat.
|
||||||
/// Mais si pas connecté, on propose à l'internaute de se connecter ou de créer un compte pour sauvegarder son résultat.
|
/// Mais si pas connecté, on propose à l'internaute de se connecter ou de créer un compte pour sauvegarder son résultat.
|
||||||
@ -8,7 +8,7 @@
|
|||||||
// Fichier de configuration tirés du backend :
|
// Fichier de configuration tirés du backend :
|
||||||
import { apiUrl, availableLangs, theme } from "../../config/instance.js";
|
import { apiUrl, availableLangs, theme } from "../../config/instance.js";
|
||||||
const lang=availableLangs[0];
|
const lang=availableLangs[0];
|
||||||
import { getPreviousAnswers, questionnaireRoutes, saveAnswersRoute } from "../../config/questionnaires.js";
|
import { getPreviousAnswers, groupRoutes, saveAnswersRoute } from "../../config/questionnaires.js";
|
||||||
const configTemplate = require("../../views/"+theme+"/config/"+lang+".js");
|
const configTemplate = require("../../views/"+theme+"/config/"+lang+".js");
|
||||||
|
|
||||||
import { checkAnswerOuput, saveAnswer } from "./tools/answers.js";
|
import { checkAnswerOuput, saveAnswer } from "./tools/answers.js";
|
||||||
@ -25,19 +25,28 @@ const { noPreviousAnswer, previousAnswersLine, previousAnswersStats, previousAns
|
|||||||
const { serverError } = require("../../lang/"+lang+"/general");
|
const { serverError } = require("../../lang/"+lang+"/general");
|
||||||
|
|
||||||
// Principaux éléments du DOM manipulés :
|
// Principaux éléments du DOM manipulés :
|
||||||
const myForm = document.getElementById("questionnaire");
|
|
||||||
const divResponse = document.getElementById("response");
|
|
||||||
const btnShow = document.getElementById("showQuestionnaire");
|
|
||||||
const btnSubmit = document.getElementById("checkResponses");
|
const btnSubmit = document.getElementById("checkResponses");
|
||||||
|
const divResponse = document.getElementById("response");
|
||||||
const explanationsTitle = document.getElementById("explanationsTitle");
|
const explanationsTitle = document.getElementById("explanationsTitle");
|
||||||
const explanationsContent = document.getElementById("explanationsContent");
|
const explanationsContent = document.getElementById("explanationsContent");
|
||||||
|
const myForm = document.getElementById("group");
|
||||||
|
|
||||||
|
// Affiche le bouton de soumission + déclenche le chronomètre mesurant la durée de la réponse.
|
||||||
|
let chronoBegin=0;
|
||||||
|
const beginAnswer = () =>
|
||||||
|
{
|
||||||
|
chronoBegin=Date.now();
|
||||||
|
btnSubmit.style.display="block";
|
||||||
|
const here=window.location;// window.location à ajouter pour ne pas quitter la page en mode "preview".
|
||||||
|
}
|
||||||
|
|
||||||
let isConnected, user;
|
let isConnected, user;
|
||||||
const initialise = async () =>
|
const initialise = async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
btnShow.style.display="inline";// bouton caché si JS inactif, car JS nécessaire pour vérifier les réponses
|
// Si JS activé, on affiche le bouton de soumission du formulaire :
|
||||||
|
beginAnswer();
|
||||||
isConnected=await checkSession(["user"]);// "user" car seuls les utilisateurs de base peuvent enregistrer leurs réponses aux quizs
|
isConnected=await checkSession(["user"]);// "user" car seuls les utilisateurs de base peuvent enregistrer leurs réponses aux quizs
|
||||||
// Si l'utilisateur est connecté et a déjà répondu à ce quiz, on affiche ses précédentes réponses à la place du texte servant à expliquer le topo aux nouveaux
|
// Si l'utilisateur est connecté et a déjà répondu à ce quiz, on affiche ses précédentes réponses à la place du texte servant à expliquer le topo aux nouveaux
|
||||||
if(isConnected)
|
if(isConnected)
|
||||||
@ -57,40 +66,6 @@ const initialise = async () =>
|
|||||||
initialise();
|
initialise();
|
||||||
helloDev();
|
helloDev();
|
||||||
|
|
||||||
// Affichage du questionnaire quand l'utilisateur clique sur le bouton ou si l'id du formulaire est passée par l'url.
|
|
||||||
// Déclenche en même temps le chronomètre mesurant la durée de la réponse aux questions.
|
|
||||||
const showQuestionnaire = () =>
|
|
||||||
{
|
|
||||||
chronoBegin=Date.now();
|
|
||||||
myForm.style.display="block";
|
|
||||||
btnShow.style.display="none";
|
|
||||||
const here=window.location;// window.location à ajouter pour ne pas quitter la page en mode "preview".
|
|
||||||
if(window.location.hash!=="")
|
|
||||||
{
|
|
||||||
window.location.hash="";// ! le "#" reste
|
|
||||||
window.location.assign(here+"questionnaire");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
window.location.assign(here+"#questionnaire");
|
|
||||||
}
|
|
||||||
let chronoBegin=0;
|
|
||||||
btnShow.addEventListener("click", function(e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
e.preventDefault();
|
|
||||||
showQuestionnaire();
|
|
||||||
}
|
|
||||||
catch(e)
|
|
||||||
{
|
|
||||||
addElement(divResponse, "p", serverError, "", ["error"]);
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Lien passé par mail pour voir directement le quiz
|
|
||||||
if(location.hash!="" && location.hash==="#questionnaire")
|
|
||||||
showQuestionnaire();
|
|
||||||
|
|
||||||
// Traitement de l'envoi de la réponse de l'utilisateur :
|
// Traitement de l'envoi de la réponse de l'utilisateur :
|
||||||
let answer = {};
|
let answer = {};
|
||||||
myForm.addEventListener("submit", function(e)
|
myForm.addEventListener("submit", function(e)
|
||||||
@ -99,12 +74,12 @@ myForm.addEventListener("submit", function(e)
|
|||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
btnSubmit.style.display="none";// seulement un envoi à la fois, SVP :)
|
btnSubmit.style.display="none";// seulement un envoi à la fois, SVP :)
|
||||||
divResponse.innerHTML="";// supprime les éventuels messages déjà affichés
|
divResponse.innerHTML="";// supprime les éventuels messages déjà affichés.
|
||||||
const userResponses=getDatasFromInputs(myForm);
|
const userResponses=getDatasFromInputs(myForm);
|
||||||
answer.duration=Math.round((Date.now()-chronoBegin)/1000);
|
answer.duration=Math.round((Date.now()-chronoBegin)/1000);
|
||||||
answer.nbQuestions=0;
|
answer.nbQuestions=0;
|
||||||
answer.nbCorrectAnswers=0;
|
answer.nbCorrectAnswers=0;
|
||||||
answer.QuestionnaireId=document.getElementById("questionnaireId").value;
|
answer.GroupId=document.getElementById("groupId").value;
|
||||||
// Les réponses sont regroupées par question, donc quand idQuestion change, on connaît le résultat pour la question précédente.
|
// Les réponses sont regroupées par question, donc quand idQuestion change, on connaît le résultat pour la question précédente.
|
||||||
// Pour qu'une réponse soit bonne, il faut cocher toutes les bonnes réponses (si QCM) à la question ET cocher aucune des mauvaises.
|
// Pour qu'une réponse soit bonne, il faut cocher toutes les bonnes réponses (si QCM) à la question ET cocher aucune des mauvaises.
|
||||||
let idChoice, idQuestion="", goodResponse=false;
|
let idChoice, idQuestion="", goodResponse=false;
|
||||||
@ -113,32 +88,31 @@ myForm.addEventListener("submit", function(e)
|
|||||||
if(item.startsWith("isCorrect_response_"))// = Nouvelle réponse possible.
|
if(item.startsWith("isCorrect_response_"))// = Nouvelle réponse possible.
|
||||||
{
|
{
|
||||||
idChoice = item.substring(item.lastIndexOf("_") + 1);
|
idChoice = item.substring(item.lastIndexOf("_") + 1);
|
||||||
// si on change de queston
|
if(userResponses["question_id_response_"+idChoice] != idQuestion) // = on commence à traiter une nouvelle question.
|
||||||
if(userResponses["question_id_response_"+idChoice]!=idQuestion) // on commence à traiter une nouvelle question
|
|
||||||
{
|
{
|
||||||
idQuestion=userResponses["question_id_response_"+idChoice];
|
idQuestion=userResponses["question_id_response_"+idChoice];
|
||||||
answer.nbQuestions++;
|
answer.nbQuestions++;
|
||||||
if(goodResponse) // résultat de la question précédente
|
if(goodResponse) // = pas d'erreur à la question précédente
|
||||||
answer.nbCorrectAnswers++;
|
answer.nbCorrectAnswers++;
|
||||||
goodResponse=true;// réponse bonne jusqu'à la première erreur...
|
goodResponse=true;// La réponse est considérée comme bonne, jusqu'à la première erreur...
|
||||||
}
|
}
|
||||||
if(userResponses[item]=="true")
|
if(userResponses[item] == "true")
|
||||||
{
|
{
|
||||||
document.getElementById("response_"+idChoice).parentNode.classList.add("isCorrect");
|
document.getElementById("response_"+idChoice).parentNode.classList.add("isCorrect");
|
||||||
if(userResponses["response_"+idChoice]===undefined)// une bonne réponse n'a pas été sélectionnée
|
if(userResponses["response_"+idChoice] === undefined)// = une bonne réponse n'a pas été sélectionnée
|
||||||
goodResponse=false;
|
goodResponse=false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(userResponses["response_"+idChoice]==="on")// réponse cochée ne faisant pas partie des bonnes
|
if(userResponses["response_"+idChoice] === "on")
|
||||||
{
|
{
|
||||||
goodResponse=false;
|
goodResponse=false; // = une mauvaise réponse a été sélectionnée
|
||||||
document.getElementById("response_"+idChoice).parentNode.classList.add("isNotCorrect");
|
document.getElementById("response_"+idChoice).parentNode.classList.add("isNotCorrect");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// si j'ai bien répondu à la dernière question, il faut le compter ici, car je suis sorti de la boucle :
|
// Si j'ai bien répondu à la dernière question, il faut le compter ici, car on est sorti de la boucle :
|
||||||
if(goodResponse)
|
if(goodResponse)
|
||||||
answer.nbCorrectAnswers++;
|
answer.nbCorrectAnswers++;
|
||||||
|
|
||||||
@ -146,9 +120,9 @@ myForm.addEventListener("submit", function(e)
|
|||||||
let getOuput=checkAnswerOuput(answer);
|
let getOuput=checkAnswerOuput(answer);
|
||||||
if(isConnected)
|
if(isConnected)
|
||||||
{
|
{
|
||||||
// Si l'utilisateur est connecté, on enregistre son résultat sur le serveur.
|
// Si l'utilisateur est connecté, on passe son résultat au serveur pour le sauvegarder.
|
||||||
const xhrSaveAnswer = new XMLHttpRequest();
|
const xhrSaveAnswer = new XMLHttpRequest();
|
||||||
xhrSaveAnswer.open("POST", apiUrl+questionnaireRoutes+saveAnswersRoute);
|
xhrSaveAnswer.open("POST", apiUrl+groupRoutes+saveAnswersRoute);
|
||||||
xhrSaveAnswer.onreadystatechange = function()
|
xhrSaveAnswer.onreadystatechange = function()
|
||||||
{
|
{
|
||||||
if (this.readyState == XMLHttpRequest.DONE)
|
if (this.readyState == XMLHttpRequest.DONE)
|
||||||
@ -161,7 +135,7 @@ myForm.addEventListener("submit", function(e)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
getOuput+="<br>"+responseSavedError.replace("#URL", configTemplate.userHomePage);
|
getOuput+="<br>"+responseSavedError.replace("#URL", configTemplate.userHomePage);
|
||||||
// on redirige vers le résultat
|
// Puis on le redirige vers son résultat :
|
||||||
window.location.hash="";
|
window.location.hash="";
|
||||||
const here=window.location;// window.location à ajouter pour ne pas quitter la page en mode "preview"...
|
const here=window.location;// window.location à ajouter pour ne pas quitter la page en mode "preview"...
|
||||||
window.location.assign(here+"explanations");
|
window.location.assign(here+"explanations");
|
||||||
@ -169,32 +143,30 @@ myForm.addEventListener("submit", function(e)
|
|||||||
}
|
}
|
||||||
xhrSaveAnswer.setRequestHeader("Authorization", "Bearer "+user.token);
|
xhrSaveAnswer.setRequestHeader("Authorization", "Bearer "+user.token);
|
||||||
xhrSaveAnswer.setRequestHeader("Content-Type", "application/json");
|
xhrSaveAnswer.setRequestHeader("Content-Type", "application/json");
|
||||||
answer.timeDifference=getTimeDifference();// on en profite pour mettre les pendules à l'heure.
|
answer.timeDifference=getTimeDifference();// On en profite pour mettre les pendules à l'heure.
|
||||||
xhrSaveAnswer.send(JSON.stringify(answer));
|
xhrSaveAnswer.send(JSON.stringify(answer));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // si pas connecté, on enregistre le résultat côté client pour permettre de le retrouver au moment de la création du compte ou de la connexion.
|
{ // Si internaute non connecté, on enregistre le résultat côté client pour permettre de le retrouver au moment de la création du compte ou de la connexion.
|
||||||
if(saveAnswer(answer))
|
if(saveAnswer(answer))
|
||||||
{
|
{
|
||||||
getOuput+="<br><br>"+wantToSaveResponses;
|
getOuput+="</p><p>"+wantToSaveResponses+"</p>";
|
||||||
addElement(divResponse, "p", getOuput, "", ["info"]);
|
addElement(divResponse, "p", getOuput, "", ["success"]);
|
||||||
document.querySelector(".subscribeBtns").style.display="block";
|
document.querySelector(".subscribeBtns").style.display="block";
|
||||||
}
|
}
|
||||||
else // inutile de proposer de créer un compte si le stockage local ne fonctionne pas
|
else // Mais inutile de proposer de créer un compte si le stockage local ne fonctionne pas
|
||||||
addElement(divResponse, "p", getOuput, "", ["info"]);
|
addElement(divResponse, "p", getOuput, "", ["success"]);
|
||||||
// on redirige vers le résultat
|
// Puis on le redirige vers son résultat :
|
||||||
window.location.hash="";
|
window.location.hash="";
|
||||||
const here=window.location;// window.location à ajouter pour ne pas quitter la page en mode "preview"...
|
const here=window.location;// window.location à ajouter pour ne pas quitter la page en mode "preview"...
|
||||||
window.location.assign(here+"response");
|
window.location.assign(here+"response");
|
||||||
}
|
}
|
||||||
// + affichage des textes d'explications pour chaque question
|
// + Affichage des textes d'explications pour chaque question
|
||||||
const explanations=document.querySelectorAll(".help");
|
const explanations=document.querySelectorAll(".help");
|
||||||
for(let i in explanations)
|
for(let i in explanations)
|
||||||
{
|
if(explanations[i].style !== undefined) // sinon, la console affiche une erreur "TypeError: explanations[i].style is undefined", bien que tout fonctionne (?)
|
||||||
if(explanations[i].style!=undefined) // sinon, la console affiche une erreur "TypeError: explanations[i].style is undefined", bien que tout fonctionne (?)
|
|
||||||
explanations[i].style.display="block";
|
explanations[i].style.display="block";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch(e)
|
catch(e)
|
||||||
{
|
{
|
||||||
addElement(divResponse, "p", serverError, "", ["error"]);
|
addElement(divResponse, "p", serverError, "", ["error"]);
|
||||||
@ -202,12 +174,12 @@ myForm.addEventListener("submit", function(e)
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Fonction vérifiant les précédentes réponses de l'utilisateur
|
// Fonction vérifiant les précédentes réponses de l'utilisateur.
|
||||||
// Utile si connecté lors du premier chargement de la page, puis après une nouvelle réponse
|
// Utile si connecté lors du premier chargement de la page, puis après une nouvelle réponse.
|
||||||
const checkPreviousResponses = (user) =>
|
const checkPreviousResponses = (user) =>
|
||||||
{
|
{
|
||||||
const xhrPreviousRes = new XMLHttpRequest();
|
const xhrPreviousRes = new XMLHttpRequest();
|
||||||
xhrPreviousRes.open("GET", apiUrl+questionnaireRoutes+getPreviousAnswers+user.id+"/"+document.getElementById("questionnaireId").value);
|
xhrPreviousRes.open("GET", apiUrl+groupRoutes+getPreviousAnswers+user.id+"/"+document.getElementById("groupId").value);
|
||||||
xhrPreviousRes.onreadystatechange = function()
|
xhrPreviousRes.onreadystatechange = function()
|
||||||
{
|
{
|
||||||
if (this.readyState == XMLHttpRequest.DONE)
|
if (this.readyState == XMLHttpRequest.DONE)
|
||||||
@ -247,7 +219,6 @@ const checkPreviousResponses = (user) =>
|
|||||||
addElement(explanationsContent, "ul", noPreviousAnswer);
|
addElement(explanationsContent, "ul", noPreviousAnswer);
|
||||||
// dans un cas comme dans l'autre, bouton pour revenir à l'accueil du compte
|
// dans un cas comme dans l'autre, bouton pour revenir à l'accueil du compte
|
||||||
addElement(explanationsContent, "p", "<a href=\"/"+configTemplate.userHomePage+"\" class=\"button cardboard\">"+configTemplate.userHomePageTxt+"</a>", "", ["btn"], "", false);
|
addElement(explanationsContent, "p", "<a href=\"/"+configTemplate.userHomePage+"\" class=\"button cardboard\">"+configTemplate.userHomePageTxt+"</a>", "", ["btn"], "", false);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,7 @@ const initialise = async () =>
|
|||||||
xhrStats.setRequestHeader("Authorization", "Bearer "+user.token);
|
xhrStats.setRequestHeader("Authorization", "Bearer "+user.token);
|
||||||
xhrStats.send();
|
xhrStats.send();
|
||||||
|
|
||||||
|
/*
|
||||||
// Par défaut, on affiche des derniers quizs proposés sans réponse :
|
// Par défaut, on affiche des derniers quizs proposés sans réponse :
|
||||||
const xhrLastQuizs = new XMLHttpRequest();
|
const xhrLastQuizs = new XMLHttpRequest();
|
||||||
xhrLastQuizs.open("GET", apiUrl+questionnaireRoutes+getQuestionnairesWithoutAnswer+""+user.id+"/"+0+"/"+configTemplate.nbQuestionnairesUserHomePage+"/html");
|
xhrLastQuizs.open("GET", apiUrl+questionnaireRoutes+getQuestionnairesWithoutAnswer+""+user.id+"/"+0+"/"+configTemplate.nbQuestionnairesUserHomePage+"/html");
|
||||||
@ -113,7 +114,7 @@ const initialise = async () =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
xhrLastQuizs.setRequestHeader("Authorization", "Bearer "+user.token);
|
xhrLastQuizs.setRequestHeader("Authorization", "Bearer "+user.token);
|
||||||
xhrLastQuizs.send();
|
xhrLastQuizs.send();*/
|
||||||
|
|
||||||
// Traitement du lancement d'une recherche
|
// Traitement du lancement d'une recherche
|
||||||
// La recherche peut être lancée via la bouton submit ou un lien de pagination
|
// La recherche peut être lancée via la bouton submit ou un lien de pagination
|
||||||
|
@ -8,7 +8,7 @@ const txt = require("../../../lang/"+configFrontEnd.lang+"/answer");
|
|||||||
// Enregistrement côté client du dernier résultat à un quiz en attendant d'être connecté
|
// Enregistrement côté client du dernier résultat à un quiz en attendant d'être connecté
|
||||||
export const saveAnswer = (answer) =>
|
export const saveAnswer = (answer) =>
|
||||||
{
|
{
|
||||||
if(!isEmpty(answer.duration) && !isEmpty(answer.nbCorrectAnswers) && !isEmpty(answer.nbQuestions) && !isEmpty(answer.QuestionnaireId))
|
if(!isEmpty(answer.duration) && !isEmpty(answer.nbCorrectAnswers) && !isEmpty(answer.nbQuestions) && (!isEmpty(answer.QuestionnaireId) || !isEmpty(answer.GroupId)))
|
||||||
{
|
{
|
||||||
saveLocaly("lastAnswer", answer);
|
saveLocaly("lastAnswer", answer);
|
||||||
return true;
|
return true;
|
||||||
@ -44,48 +44,3 @@ export const checkAnswerOuput = (answer) =>
|
|||||||
else
|
else
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
export const checkSession = async (config) =>
|
|
||||||
{
|
|
||||||
return new Promise((resolve, reject) =>
|
|
||||||
{
|
|
||||||
if(isEmpty(localStorage.getItem("user")))
|
|
||||||
resolve(false);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const user=JSON.parse(localStorage.getItem("user"));
|
|
||||||
if(user.duration===undefined || user.duration < Date.now())
|
|
||||||
{
|
|
||||||
localStorage.removeItem("user");
|
|
||||||
resolve(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
xhr.open("GET", configFrontEnd.apiUrl+config.userRoutes+config.checkLoginRoute+user.token);
|
|
||||||
xhr.onload = () =>
|
|
||||||
{
|
|
||||||
let response=JSON.parse(xhr.responseText);
|
|
||||||
if (xhr.status === 200 && response.isValid && response.id != undefined)
|
|
||||||
{
|
|
||||||
if(response.id===user.id)
|
|
||||||
resolve(true);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
localStorage.removeItem("user");
|
|
||||||
resolve(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
localStorage.removeItem("user");
|
|
||||||
resolve(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xhr.onerror = () => reject(xhr.statusText);
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}*/
|
|
@ -31,7 +31,7 @@ export const setSession = (userId, token, durationTS) =>
|
|||||||
saveLocaly("user", storageUser);
|
saveLocaly("user", storageUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérifie qu'il y a des données locales concernant le résultat d'un quiz
|
// Vérifie qu'il y a des données locales concernant le résultat d'un quiz ou d'un groupe de quizs
|
||||||
// Et les ajoute aux données envoyées par les formulaires d'inscription/connexion si c'est le cas
|
// Et les ajoute aux données envoyées par les formulaires d'inscription/connexion si c'est le cas
|
||||||
export const checkAnswerDatas = (datas) =>
|
export const checkAnswerDatas = (datas) =>
|
||||||
{
|
{
|
||||||
@ -39,12 +39,15 @@ export const checkAnswerDatas = (datas) =>
|
|||||||
if(!isEmpty(lastAnswer))
|
if(!isEmpty(lastAnswer))
|
||||||
{
|
{
|
||||||
const answer=JSON.parse(lastAnswer);
|
const answer=JSON.parse(lastAnswer);
|
||||||
if(!isEmpty(answer.duration) && !isEmpty(answer.nbCorrectAnswers) && !isEmpty(answer.QuestionnaireId) && !isEmpty(answer.nbQuestions))
|
if(!isEmpty(answer.duration) && !isEmpty(answer.nbCorrectAnswers) && !isEmpty(answer.nbQuestions) && (!isEmpty(answer.QuestionnaireId) || !isEmpty(answer.GroupId)))
|
||||||
{
|
{
|
||||||
datas.duration=answer.duration;
|
datas.duration=answer.duration;
|
||||||
datas.nbCorrectAnswers=answer.nbCorrectAnswers;
|
datas.nbCorrectAnswers=answer.nbCorrectAnswers;
|
||||||
datas.QuestionnaireId=answer.QuestionnaireId;
|
|
||||||
datas.nbQuestions=answer.nbQuestions;
|
datas.nbQuestions=answer.nbQuestions;
|
||||||
|
if(!isEmpty(answer.QuestionnaireId))
|
||||||
|
datas.QuestionnaireId=answer.QuestionnaireId;
|
||||||
|
else
|
||||||
|
datas.GroupId=answer.GroupId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return datas;
|
return datas;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
module.exports =
|
module.exports =
|
||||||
{
|
{
|
||||||
checkResponsesOuputFail : "Vous avez répondu en DURATION secondes et avez NBCORRECTANSWERS bonne(s) réponse(s) sur NBQUESTIONS questions. C'est certain, vous ferez mieux la prochaine fois !",
|
checkResponsesOuputFail : "Vous avez répondu en DURATION secondes et avez <u><b>NBCORRECTANSWERS bonne(s) réponse(s) sur NBQUESTIONS questions</b></u>. C'est certain, vous ferez mieux la prochaine fois !",
|
||||||
checkResponsesOuputMedium : "Vous avez répondu en DURATION secondes et avez NBCORRECTANSWERS bonne(s) réponse(s) sur NBQUESTIONS questions. C'est pas mal du tout !",
|
checkResponsesOuputMedium : "Vous avez répondu en DURATION secondes et avez <u><b>NBCORRECTANSWERS bonne(s) réponse(s) sur NBQUESTIONS questions</b></u>. C'est pas mal du tout !",
|
||||||
checkResponsesOuputSuccess : "Vous avez répondu en DURATION secondes et avez NBCORRECTANSWERS bonne(s) réponse(s) sur NBQUESTIONS questions. Bravo ! Rien ne vous échappe !",
|
checkResponsesOuputSuccess : "Vous avez répondu en DURATION secondes et avez <u><b>NBCORRECTANSWERS bonne(s) réponse(s) sur NBQUESTIONS questions</b></u>. Bravo ! Rien ne vous échappe !",
|
||||||
nbQuestionnaireWithoudAnswer: "Il y a #NB quizs qui vous ont été proposés et auxquels vous n'avez pas répondu. Voici les derniers :!",
|
nbQuestionnaireWithoudAnswer: "Il y a #NB quizs qui vous ont été proposés et auxquels vous n'avez pas répondu. Voici les derniers :!",
|
||||||
needIntegerNumberCorrectResponses : "Le nombre de réponses correctes doit être un nombre entier.",
|
needIntegerNumberCorrectResponses : "Le nombre de réponses correctes doit être un nombre entier.",
|
||||||
needIntegerNumberSecondesResponse : "La durée de la réponse doit être un nombre entier de secondes.",
|
needIntegerNumberSecondesResponse : "La durée de la réponse doit être un nombre entier de secondes.",
|
||||||
@ -22,5 +22,5 @@ module.exports =
|
|||||||
responseSavedError : "Cependant une erreur a été rencontrée durant l'enregistrement de votre résultat. <a href='/#URL'>Accèder à tous vos quizs</a>.",
|
responseSavedError : "Cependant une erreur a été rencontrée durant l'enregistrement de votre résultat. <a href='/#URL'>Accèder à tous vos quizs</a>.",
|
||||||
responseSavedMessage : "Votre résultat a été enregistré. <a href='/#URL'>Accèder à tous vos quizs</a>.",
|
responseSavedMessage : "Votre résultat a été enregistré. <a href='/#URL'>Accèder à tous vos quizs</a>.",
|
||||||
statsUser: "Vous avez enregistré NBANSWERS réponses à <b>NBQUESTIONNAIRES questionnaires différents</b> sur les NBTOTQUESTIONNAIRES proposés par le site.<br>En moyenne, vous avez mis AVGDURATION secondes à répondre et avez <b>correctement répondu à AVGCORRECTANSWERS % des questions</b>.",
|
statsUser: "Vous avez enregistré NBANSWERS réponses à <b>NBQUESTIONNAIRES questionnaires différents</b> sur les NBTOTQUESTIONNAIRES proposés par le site.<br>En moyenne, vous avez mis AVGDURATION secondes à répondre et avez <b>correctement répondu à AVGCORRECTANSWERS % des questions</b>.",
|
||||||
wantToSaveResponses: "Si vous le souhaitez, vous pouvez sauvegarder votre résultat <u>en créant votre compte ci-dessous</u>.<br><b>Cela vous permettra aussi de recevoir régulièrement de nouveaux quizs par e-mail</b>.",
|
wantToSaveResponses: "Si vous le souhaitez, vous pouvez <u><b>sauvegarder votre résultat</b></u> en créant votre compte ci-dessous. Cela vous permettra aussi de <u><b>recevoir régulièrement de nouvelles \"graines de culture\"</b></u> directement sur votre e-mail.",
|
||||||
};
|
};
|
@ -2,7 +2,7 @@ module.exports =
|
|||||||
{
|
{
|
||||||
btnSendResponse: "Testez vos réponses.",
|
btnSendResponse: "Testez vos réponses.",
|
||||||
btnShareQuizTxt: "Partager ce quiz sur ",
|
btnShareQuizTxt: "Partager ce quiz sur ",
|
||||||
commonIntroTxt: "Ce quiz vous permet tester ce que vous avez retenu des textes proposés à la lecture. Au besoin, cliquez sur le bouton suivant pour les relire :",
|
commonIntroTxt: "Ce quiz vous permet tester ce que vous avez retenu des textes proposés à la lecture. Au besoin, cliquez sur le bouton suivant pour les relire.",
|
||||||
correctAnswerTxt: "Bonne réponse",
|
correctAnswerTxt: "Bonne réponse",
|
||||||
groupsName: "Quiz",// nom d'un groupe pour l'affichage dans les vues
|
groupsName: "Quiz",// nom d'un groupe pour l'affichage dans les vues
|
||||||
haveBeenPublished: "#NB nouveaux groupes de quizs ont été publiés.",
|
haveBeenPublished: "#NB nouveaux groupes de quizs ont été publiés.",
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
const express = require("express");
|
const express = require("express");
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
const auth = require("../middleware/authAdmin");
|
const auth = require("../middleware/auth");
|
||||||
|
const authAdmin = require("../middleware/authAdmin");
|
||||||
|
|
||||||
|
const answerCtrl = require("../controllers/answer");
|
||||||
const groupCtrl = require("../controllers/group");
|
const groupCtrl = require("../controllers/group");
|
||||||
|
|
||||||
router.post("/search", auth, groupCtrl.searchGroups);
|
router.post("/search", authAdmin, groupCtrl.searchGroups);
|
||||||
router.post("/", auth, groupCtrl.create);
|
router.post("/", authAdmin, groupCtrl.create);
|
||||||
router.get("/stats", auth, groupCtrl.getStatsGroups);
|
router.get("/stats", authAdmin, groupCtrl.getStatsGroups);
|
||||||
router.put("/:id", auth, groupCtrl.modify);
|
router.put("/:id", authAdmin, groupCtrl.modify);
|
||||||
router.delete("/:id", auth, groupCtrl.delete);
|
router.delete("/:id", authAdmin, groupCtrl.delete);
|
||||||
router.get("/get/:id", auth, groupCtrl.getOneById);
|
router.get("/get/:id", authAdmin, groupCtrl.getOneById);
|
||||||
router.get("/preview/:id/:token", groupCtrl.showOneGroupById);// prévisualisation HTML, même si groupe "incomplet"
|
router.get("/preview/:id/:token", groupCtrl.showOneGroupById);// prévisualisation HTML, même si groupe "incomplet"
|
||||||
|
|
||||||
|
router.post("/answer/", auth, answerCtrl.createInGroup);
|
||||||
|
router.get("/user/answers/:userId/:groupId", auth, answerCtrl.getAnswersByGroup);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
@ -25,6 +25,6 @@ router.get("/preview/:id/:token", questionnaireCtrl.showOneQuestionnaireById);//
|
|||||||
router.post("/answer/", auth, answerCtrl.create);
|
router.post("/answer/", auth, answerCtrl.create);
|
||||||
router.get("/user/anwswers/stats/:userId", auth, answerCtrl.getStatsByUser);
|
router.get("/user/anwswers/stats/:userId", auth, answerCtrl.getStatsByUser);
|
||||||
router.get("/user/answers/:userId/:questionnaireId", auth, answerCtrl.getAnswersByQuestionnaire);
|
router.get("/user/answers/:userId/:questionnaireId", auth, answerCtrl.getAnswersByQuestionnaire);
|
||||||
router.get("/withoutanswer/user/:id/:begin/:nb/:output", auth, answerCtrl.getQuestionnairesWithouAnswerByUser);
|
//router.get("/withoutanswer/user/:id/:begin/:nb/:output", auth, answerCtrl.getQuestionnairesWithouAnswerByUser);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
@ -57,8 +57,8 @@ block content
|
|||||||
noscript
|
noscript
|
||||||
div
|
div
|
||||||
strong #{configTpl.noJSNotification}
|
strong #{configTpl.noJSNotification}
|
||||||
// à cacher si pas de JS !
|
|
||||||
form(id="group" method="POST" class="needJS")
|
form(id="group" method="POST")
|
||||||
h2 #{group.Group.title}
|
h2 #{group.Group.title}
|
||||||
div#response
|
div#response
|
||||||
div(class="subscribeBtns")
|
div(class="subscribeBtns")
|
||||||
@ -70,7 +70,7 @@ block content
|
|||||||
for question in questionnaire.Questions
|
for question in questionnaire.Questions
|
||||||
p(id="question_"+question.Question.id) #{question.Question.text}
|
p(id="question_"+question.Question.id) #{question.Question.text}
|
||||||
if(question.Question.explanation)
|
if(question.Question.explanation)
|
||||||
blockquote(class="help" id="help_"+question.Question.id cite=questionnaire.Links[0].url) #{txtexplanationBeforeTxt} #{question.Question.explanation}
|
blockquote(class="help" id="help_"+question.Question.id cite="/"+configQuestionnaires.dirWebQuestionnaires+"/"+questionnaire.Questionnaire.slug+".html") #{txtexplanationBeforeTxt} #{question.Question.explanation}
|
||||||
ul(class="checkbox_li")
|
ul(class="checkbox_li")
|
||||||
for response in question.Choices
|
for response in question.Choices
|
||||||
li(class="checkbox_li")
|
li(class="checkbox_li")
|
||||||
@ -85,9 +85,10 @@ block content
|
|||||||
input(type="hidden" name="isCorrect_response_"+response.id id="isCorrect_response_"+response.id value=""+response.isCorrect)
|
input(type="hidden" name="isCorrect_response_"+response.id id="isCorrect_response_"+response.id value=""+response.isCorrect)
|
||||||
input(type="hidden" name="question_id_response_"+response.id id="question_id_response_"+response.id value=question.Question.id)
|
input(type="hidden" name="question_id_response_"+response.id id="question_id_response_"+response.id value=question.Question.id)
|
||||||
input(name="groupId" id="groupId" value=group.Group.id type="hidden")
|
input(name="groupId" id="groupId" value=group.Group.id type="hidden")
|
||||||
|
// Bouton submit caché si pas de JS, car nécessaire au traitement de la réponse
|
||||||
p
|
p
|
||||||
span(class="input_wrapper")
|
span(class="input_wrapper")
|
||||||
input(id="checkResponses" type="submit" value=txtGroups.btnSendResponse class="cardboard" title=txtGroups.btnSendResponse)
|
input(id="checkResponses" type="submit" value=txtGroups.btnSendResponse class="cardboard needJS" title=txtGroups.btnSendResponse)
|
||||||
|
|
||||||
div#zerozozio
|
div#zerozozio
|
||||||
a(href="http://sharetodiaspora.github.io/?url="+linkCanonical+"&title="+group.Group.title rel="nofollow noopener" title=txtGroups.btnShareQuizTxt+" diaspora* ("+txtGeneral.alertNewWindow+")" target="_blank")
|
a(href="http://sharetodiaspora.github.io/?url="+linkCanonical+"&title="+group.Group.title rel="nofollow noopener" title=txtGroups.btnShareQuizTxt+" diaspora* ("+txtGeneral.alertNewWindow+")" target="_blank")
|
||||||
|
Loading…
Reference in New Issue
Block a user