WikiLerni/controllers/questionnaire.js

874 lines
37 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { Op, QueryTypes } = require("sequelize");
const pug = require("pug");
const striptags = require("striptags");
const config = require("../config/main.js");
const configQuestionnaires = require("../config/questionnaires.js");
const configTags = require("../config/tags.js");
const configLinks = require("../config/links.js");
const configIllustrations = require("../config/illustrations.js");
const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js");
const tool = require("../tools/main");
const toolError = require("../tools/error");
const toolFile = require("../tools/file");
const toolMail = require("../tools/mail");
const groupCtrl = require("./group");
const questionCtrl = require("./question");
const illustrationCtrl = require("./illustration");
const tagCtrl = require("./tag");
const userCtrl = require("./user");
const txtGeneral = require("../lang/"+config.adminLang+"/general");
const txtQuestionnaire = require("../lang/"+config.adminLang+"/questionnaire");
const txtQuestionnaireAccess = require("../lang/"+config.adminLang+"/questionnaireaccess");
const txtIllustration = require("../lang/"+config.adminLang+"/illustration");
exports.create = async (req, res, next) =>
{
try
{
if(tool.isEmpty(req.body.GroupId) && !tool.isEmpty(req.body.rankInGroup))
res.status(400).json({ errors : [txtQuestionnaire.needGroupIfRank] });
else
{
const db = require("../models/index");
req.body.CreatorId=req.connectedUser.User.id;
if(!tool.isEmpty(req.body.GroupId) && tool.isEmpty(req.body.rankInGroup))
req.body.rankInGroup=1;
const questionnaire=await db["Questionnaire"].create({ ...req.body }, { fields: ["title", "slug", "introduction", "keywords", "publishingAt", "language", "estimatedTime", "CreatorId", "GroupId", "rankInGroup"] });
creaStatsQuestionnairesJson();
// Si un groupe a été sélectionné, il faut mettre à jour les fichiers du groupe :
if(!tool.isEmpty(req.body.GroupId))
{
const groupInfos = await groupCtrl.creaGroupJson(req.body.GroupId);
// Si le nouveau quiz est publiable, le fichier HTML de l'éventuel quiz/élément précédent du groupe doit être actualisé
// Si le nouveau n'est pas publiable, il ne sera pas listé dans groupInfos.Questionnaires donc pas d'incidence
if(groupInfos !== false)
{
for(let i in groupInfos.Questionnaires)
{
if(groupInfos.Questionnaires[i].id === questionnaire.id && i != 0)
{
let j=parseInt(i)-1;// !! "i" n'est pas un nombre !
if(groupInfos.Questionnaires[j] !== undefined)
await creaQuestionnaireHTML(groupInfos.Questionnaires[j].id);
}
}
}
}
// id utile au middleware suivant (classement tags) qui s'occupe aussi de retourner une réponse si ok :
req.body.QuestionnaireId=questionnaire.id;
next();
}
}
catch(e)
{
const returnAPI=toolError.returnSequelize(e);
if(returnAPI.messages)
{
res.status(returnAPI.status).json({ errors : returnAPI.messages });
next();
}
else
next(e);
}
}
exports.modify = async (req, res, next) =>
{
try
{
const db = require("../models/index");
const questionnaire=await searchQuestionnaireById(req.params.id);
if(!questionnaire)
{
const Err=new Error;
error.status=404;
error.message=txtQuestionnaire.notFound+" ("+req.params.id+")";
throw Err;
}
else if(req.connectedUser.User.status==="creator" && req.connectedUser.User.id!==questionnaire.CreatorId)
res.status(401).json({ errors: txtGeneral.notAllowed });
else if(tool.isEmpty(req.body.GroupId) && !tool.isEmpty(req.body.rankInGroup))
res.status(400).json({ errors : [txtQuestionnaire.needGroupIfRank] });
else
{
if(!tool.isEmpty(req.body.GroupId) && tool.isEmpty(req.body.rankInGroup))
req.body.rankInGroup=1;
await db["Questionnaire"].update({ ...req.body }, { where: { id : req.params.id } , fields: ["title", "slug", "introduction", "keywords", "publishingAt", "language", "estimatedTime", "GroupId", "rankInGroup"], limit:1 });
creaStatsQuestionnairesJson();// le nombre de quizs publiés peut avoir changé
// Si le questionnaire a été déplacé d'un groupe, il faut actualiser les fichiers de l'ancien groupe :
if(req.body.GroupId != questionnaire.Questionnaire.GroupId && !tool.isEmpty(questionnaire.Questionnaire.GroupId))
{ //(ne pas utiliser "!==" car types différents)
const groupInfos = await groupCtrl.creaGroupJson(questionnaire.Questionnaire.GroupId);
// Peut aussi avoir un impact sur les autres élement dans l'ancien groupe, notamment celui qui le précédait
if(groupInfos !== false)
{
for(let i in groupInfos.Questionnaires)
await creaQuestionnaireHTML(groupInfos.Questionnaires[i].id);
}
}
}
// id utile au middleware suivant (classement tags) qui s'occupe aussi de retourner une réponse si ok :
req.body.QuestionnaireId=req.params.id;
next();
}
catch(e)
{
const returnAPI=toolError.returnSequelize(e);
if(returnAPI.messages)
{
res.status(returnAPI.status).json({ errors : returnAPI.messages });
next();
}
else
next(e);
}
}
exports.delete = async (req, res, next) =>
{
try
{
const del=deleteQuestionnaire(req.params.id, req);
if(typeof del===Error)
throw Err;
else
res.status(200).json({ message: txtGeneral.deleteOkMessage });
next();
}
catch(e)
{
next(e);
}
}
exports.getOneQuestionnaireById = async (req, res, next) =>
{
try
{
const datas=await searchQuestionnaireById(req.params.id, true);
if(datas)
res.status(200).json(datas);
else
res.status(404).json({ errors:txtQuestionnaire.notFound });
next();
}
catch(e)
{
next(e);
}
}
exports.showOneQuestionnaireById = async (req, res, next) =>
{
try
{
// Seuls certains utilisateurs peuvent avoir accès à cette route :
const connectedUser=await userCtrl.checkTokenUser(req.params.token);
if(connectedUser === false)
res.status(403).json({ errors:txtGeneral.failAuthToken });
else
{
if(["admin", "manager", "creator"].indexOf(connectedUser.User.status) === -1)
res.status(403).json({ errors:txtGeneral.notAllowed });
else
{
const HTML=await creaQuestionnaireHTML(req.params.id, true);
if(HTML)
{
res.setHeader("Content-Type", "text/html");
res.send(HTML);
}
else
res.status(404).json({ errors:txtQuestionnaire.notFound });
}
}
next();
}
catch(e)
{
next(e);
}
}
// Recherche par mots-clés parmis tous les questionnaires publiés en filtrant parmi ceux auxquels l'abonné a déjà répondu ou pas
// Seul un certain nombre de résultats est renvoyé (pagination)
exports.searchQuestionnaires = async (req, res, next) =>
{
try
{
let search=tool.trimIfNotNull(req.body.searchQuestionnaires);
if(search === null || search === "" || search.length < config.minSearchQuestionnaires)
res.status(400).json(txtQuestionnaireAccess.searchIsNotLongEnough);
else
{
const db = require("../models/index");
let getQuestionnaires;
if(!tool.isEmpty(req.body.onlyAnswers) && !tool.isEmpty(req.connectedUser.User.id))
getQuestionnaires=await db.sequelize.query("SELECT DISTINCT `Questionnaires`.`id` FROM `Questionnaires` INNER JOIN `Answers` WHERE `Answers`.`QuestionnaireId`=`Questionnaires`.`id` AND `Answers`.`UserId`=:id AND (`title` LIKE :search OR `keywords` LIKE :search) AND `isPublished` = 1", { replacements: { id:req.connectedUser.User.id, search: "%"+search+"%" }, type: QueryTypes.SELECT });
else
getQuestionnaires=await db.sequelize.query("SELECT `id` FROM `Questionnaires` WHERE (`title` LIKE :search OR `keywords` LIKE :search) AND `isPublished` = 1", { replacements: { search: "%"+search+"%" }, type: QueryTypes.SELECT });
let begin=0, end, output="";
if(!tool.isEmpty(req.body.begin))
begin=parseInt(req.body.begin, 10);
end=begin+configTpl.nbQuestionnairesUserHomePage-1;// tableau commence à 0 !
if(req.body.output!==undefined)
output=req.body.output;
datas=await getListingsQuestionnairesOuput(getQuestionnaires, begin, end, output);
res.status(200).json(datas);
}
next();
}
catch(e)
{
next(e);
}
}
// Recherche aléatoire parmi tous les questionnaires publiés
// De nouveau on peut filtrer ou non ceux auxquels l'utilisateur a répondu
// Par contre, ici pas de pagination car on maîtrise le nombre de résultats envoyés
exports.getRandomQuestionnaires = async (req, res, next) =>
{
try
{
const db = require("../models/index");
let getQuestionnaires;
if(!tool.isEmpty(req.body.onlyAnswers) && !tool.isEmpty(req.connectedUser.User.id))
getQuestionnaires=await db.sequelize.query("SELECT DISTINCT `Questionnaires`.`id` FROM `Questionnaires` INNER JOIN `Answers` WHERE `Answers`.`QuestionnaireId`=`Questionnaires`.`id` AND `Answers`.`UserId`=:id AND `isPublished` = 1 ORDER BY RAND() LIMIT "+configQuestionnaires.nbRandomResults, { replacements: { id:req.connectedUser.User.id }, type: QueryTypes.SELECT });
else
getQuestionnaires=await db.sequelize.query("SELECT `id` FROM `Questionnaires` WHERE `isPublished` = 1 ORDER BY RAND() LIMIT "+configQuestionnaires.nbRandomResults, { type: QueryTypes.SELECT });
let begin=0, end;
end=begin+configTpl.nbQuestionnairesUserHomePage-1;
datas=await getListingsQuestionnairesOuput(getQuestionnaires, begin, end, "html");
res.status(200).json(datas);
next();
}
catch(e)
{
next(e);
}
}
// Recherche par mots-clés parmis tous les questionnaires, y compris ceux non publiés
exports.searchAdminQuestionnaires = async (req, res, next) =>
{
try
{
let search=tool.trimIfNotNull(req.body.searchQuestionnaires);
if(search === null || search === "" || search.length < config.minSearchQuestionnaires)
res.status(400).json(txtQuestionnaireAccess.searchIsNotLongEnough);
else
{
const db = require("../models/index");
const getQuestionnaires=await db.sequelize.query("SELECT `id`,`title` FROM `Questionnaires` WHERE (`title` LIKE :search OR `introduction` LIKE :search OR `keywords` LIKE :search) ORDER BY `title` ASC", { replacements: { search: "%"+search+"%" }, type: QueryTypes.SELECT });
res.status(200).json(getQuestionnaires);
}
next();
}
catch(e)
{
next(e);
}
}
// Retourne les statistiques concernant les questionnaires
exports.getStats = async(req, res, next) =>
{
try
{
const stats=await getStatsQuestionnaires();
res.status(200).json(stats);
}
catch(e)
{
next(e);
}
}
// Liste des prochains questionnaires devant être publiés.
// On vérifie à chaque fois si ils ont ce qu'il faut pour être publiables
// On en profite pour chercher la prochaine date sans questionnaire programmé
exports.getListNextQuestionnaires = async(req, res, next) =>
{
try
{
let questionnaires=await getNextQuestionnaires();
const dayWithoutPublication=[0,6];// à déclarer dans config + gérer cas où aucun jour n'est obligatoire
let dateNeeded="", questionnairesDatas, dateQuestionnaireTS, previousDayNeededTS, previousDayTS;
for(let i in questionnaires)
{
questionnairesDatas=await searchQuestionnaireById(questionnaires[i].id);
questionnaires[i].isPublishable=checkQuestionnaireIsPublishable(questionnairesDatas, false); // le questionnaire est-il complet ?
dateQuestionnaireTS=new Date(questionnaires[i].datePublishing).getTime();
if(dateNeeded==="")
{
// je commence par chercher le jour précédent pour lequel je dois avoir publié quelque chose :
previousDayTS=new Date(new Date(dateQuestionnaireTS-3600*1000*24));
previousDayNeededTS=0;
while (previousDayNeededTS===0)
{
if(dayWithoutPublication.indexOf(previousDayTS.getDay())===-1)
previousDayNeededTS=previousDayTS;
else
previousDayTS=new Date(previousDayTS.getTime()-3600*1000*24);
}
// si il n'y a pas de quiz précédent, cette date est celle que je cherche
if(!questionnaires[i-1])
{
if(previousDayNeededTS >= Date.now())
dateNeeded=previousDayNeededTS;
}
else
{ // sinon je compare la date du précédent quiz à celle pour laquelle j'ai besoin d'un quiz
if(new Date(questionnaires[i-1].datePublishing).getTime() < previousDayNeededTS)
dateNeeded=previousDayNeededTS;
}
}
}
if(questionnaires.length > 0 && dateNeeded==="")
dateNeeded=new Date(dateQuestionnaireTS+3600*1000*24);// le jour suivant celui du dernier questionnaire
else
dateNeeded=new Date(Date.now()+3600*1000*24);// mais il est possible que rien n'ai été publié ce jour, le quiz du jour étant absent de la liste traitée
res.status(200).json({questionnaires: questionnaires, dateNeeded: dateNeeded });
next();
}
catch(e)
{
next(e);
}
}
// Test si des questionnaires doivent être publiés puis (re)génère tous les fichiers HTML des questionnaires + les pages accueil + news
// La requête est ensuite passé aux tags qui font la même chose
exports.HTMLRegenerate= async (req, res, next) =>
{
try
{
await checkQuestionnairesNeedToBePublished();
const nb=await checkQuestionnairesPublishedHaveHTML(true);
await creaNewQuestionnairesJson();// provoque mise à jour du HTLM, RSS, etc.
res.messageToNext=txtQuestionnaire.haveBeenRegenerated.replace("#NB1", nb);
next();
}
catch(e)
{
next(e);
}
}
// CRONS
// Supprime fichiers json de questionnaires n'existant plus.
exports.deleteJsonFiles= async (req, res, next) =>
{
try
{
const db = require("../models/index");
const questionnaires=await db["Questionnaire"].findAll({ attributes: ["id"] });
let saveFiles=["last.json","stats.json"];// dans le même répertoir et à garder.
for(let i in questionnaires)
saveFiles.push(questionnaires[i].id+".json");
const deleteFiles = await toolFile.deleteFilesInDirectory(configQuestionnaires.dirCacheQuestionnaires, saveFiles);
res.status(200).json(deleteFiles);
next();
}
catch(e)
{
next(e);
}
}
// test si des questionnaires doivent être publiés
// + si des questionnaires publiés n'ont pas fichier html
// si fichier html créé il faut aussi actualiser la page d'accueil & co
exports.checkQuestionnairesNeedToBePublished= async (req, res, next) =>
{
try
{
await checkQuestionnairesNeedToBePublished();
const nb=await checkQuestionnairesPublishedHaveHTML();
if(nb > 0)
creaNewQuestionnairesJson();// provoque mise à jour du HTLM, RSS, etc.
res.status(200).json(txtQuestionnaire.haveBeenPublished.replace(":NB", nb));
next();
}
catch(e)
{
next(e);
}
}
// FONCTIONS PARTAGÉES
const creaQuestionnaireJson = async (id) =>
{
const db=require("../models/index");
const Questionnaire=await db["Questionnaire"].findByPk(id);
if(Questionnaire)
{
let datas={ Questionnaire };
const Tags=await db["QuestionnaireClassification"].findAll({ where: { QuestionnaireId: Questionnaire.id }, attributes: ["TagId"] });
if(Tags)
datas.Tags=Tags;
const Illustrations=await db["Illustration"].findAll({ where: { QuestionnaireId: Questionnaire.id } });
if(Illustrations)
datas.Illustrations=Illustrations;
const Links=await db["Link"].findAll({ where: { QuestionnaireId: Questionnaire.id } });
if(Links)
datas.Links=Links;
const Questions=await db["Question"].findAll({ where: { QuestionnaireId: Questionnaire.id }, order: [["rank", "ASC"], ["createdAt", "ASC"]], attributes: ["id"] });
if(Questions)
datas.Questions=Questions;
const wasPublished=datas.Questionnaire.isPublished;
datas.Questionnaire.isPublished=checkQuestionnaireIsPublishable(datas);
// Important d'écrire le fichier ici, car il est nécessaire aux fonctions appelées par la suite
await toolFile.createJSON(config.dirCacheQuestionnaires, id, datas);
if(datas.Questionnaire.isPublished !== wasPublished)
{
await db["Questionnaire"].update({ isPublished:datas.Questionnaire.isPublished }, { where: { id : id } , fields: ["isPublished"], limit:1 });
// + Peut impacter la liste des tags utilisés :
tagCtrl.creaUsedTagsJson();
// Et si le quiz était publié jusqu'ici, il me faut supprimer son fichier HTML.
if(wasPublished)
toolFile.deleteFile(config.dirHTMLQuestionnaires, Questionnaire.slug+".html");
}
// Peut impacter la liste des derniers quizs si des informations affichées ont changé
creaNewQuestionnairesJson();
// + les listes de quizs / tags :
for(let i in Tags)
tagCtrl.creaQuestionnairesTagJson(Tags[i].TagId) // ! Json + HTML, donc potentiellement long.
if(datas.Questionnaire.isPublished)
await creaQuestionnaireHTML(id);
// Si le questionnaire est l'élément d'un groupe, il faut actualiser les fichiers du groupe
// Il faut le faire ici et non dans le contrôleur de mise à jour, car des chgts des éléments annexes (liens, questions...) peuvent aussi impacter les fichiers du groupe ou avoir rendu le questionnaire publiable, etc.
if(!tool.isEmpty(Questionnaire.GroupId))
{
const groupInfos = await groupCtrl.creaGroupJson(Questionnaire.GroupId);
// Idem pour le HTML des éventuels autres quizs du groupe car title, slug, rank, etc. peuvent avoir changé
if(groupInfos !== false)
{
for(let i in groupInfos.Questionnaires)
await creaQuestionnaireHTML(groupInfos.Questionnaires[i].id);
}
}
return datas;
}
else
return false;
}
exports.creaQuestionnaireJson = creaQuestionnaireJson;
// Supprime un questionnaire et toutes ses dépendances
const deleteQuestionnaire = async (id, req) =>
{
const db = require("../models/index");
const questionnaire=await searchQuestionnaireById(id);
if(!questionnaire)
{
const Err=new Error;
error.status=404;
error.message=txtQuestionnaire.notFound+" ("+req.params.id+")";
return Err;
}
else if(req.connectedUser.User.status==="creator" && req.connectedUser.User.id!==questionnaire.CreatorId)
{
const Err=new Error;
error.status=401;
error.message=txtGeneral.notAllowed;
return Err;
}
else
{
// Suppression des fichiers associés en plus du sql. Inutile pour link qui n'a pas de fichier.
// À faire avant la suppression SQL du questionnaire qui entraîne des suppressions en cascade dans la base de données.
for(let i in questionnaire.Questions)
await questionCtrl.deleteQuestionById(questionnaire.Questions[i].id);
for(let i in questionnaire.Illustrations)
await illustrationCtrl.deleteIllustrationById(questionnaire.Illustrations[i].id);
const nb=await db["Questionnaire"].destroy( { where: { id : id }, limit:1 });
if(nb===1)// = json existant, bien que sql déjà supprimé ?
{
toolFile.deleteJSON(configQuestionnaires.dirCacheQuestionnaires, id);
// + HTML :
toolFile.deleteFile(configQuestionnaires.dirHTMLQuestionnaires, questionnaire.Questionnaire.slug+".html");
// Si ce questionnaire était l'élément d'un groupe, il faut actualiser ses fichiers :
if(!tool.isEmpty(questionnaire.Questionnaire.GroupId))
{
const groupInfos=await groupCtrl.creaGroupJson(questionnaire.Questionnaire.GroupId);
// Idem pour le HTML des éventuels autres quizs du groupe, notamment si un d'entre eux le précédait
if(groupInfos !== false)
{
for(let i in groupInfos.Questionnaires)
await creaQuestionnaireHTML(groupInfos.Questionnaires[i].id);
}
}
// Actualisation de liste des questionnaires pour les tags concernés.
// Ici au contraire, les enregistrements doivent être supprimés avant.
for(let i in questionnaire.Tags)
tagCtrl.creaQuestionnairesTagJson(questionnaire.Tags[i].TagId);
// La suppression peut éventuellement concerner un des derniers questionnaires, donc :
await creaNewQuestionnairesJson();
await creaStatsQuestionnairesJson();
return true;
}
}
}
exports.deleteQuestionnaire = deleteQuestionnaire;
const checkQuestionnaireIsPublishable = (datas, checkDate=true) =>
{
if(checkDate)
{
if(datas.Questionnaire.publishingAt===null)
return false;
else
{
const today=new Date();
today.setHours(0,0,0,0);// !! attention au décalage horaire du fait de l'enregistrement en UTC dans mysql
const publishingAt=new Date(datas.Questionnaire.publishingAt);
if (publishingAt.getTime() > today.getTime())
return false;
}
}
if(datas.Questions === undefined || datas.Questions.length < config.nbQuestionsMin)
return false;// le nombre de réponses mini étant contrôlé au moment de l'enregistrement de la question
if(datas.Tags === undefined || datas.Tags.length < config.nbTagsMin)
return false;
if(datas.Links === undefined || datas.Links.length < config.nbLinksMin)
return false;
if(datas.Illustrations === undefined || datas.Illustrations.length < config.nbIllustrationsMin)
return false;
return true;
}
const creaQuestionnaireHTML = async (id, preview=false) =>
{
// deux possibilités :
// -- si élément d'un groupe de quiz : juste le texte sans les questions
// -- si quiz automone : toutes les infos
const questionnaire=await searchQuestionnaireById(id, true);
if(!questionnaire)
return false;
if(questionnaire.Questionnaire.isPublished===false && preview===false)
return false;
if(!tool.isEmpty(questionnaire.Questionnaire.GroupId))
return creaQuestionnaireInGroupHTML(questionnaire, preview);
else
{
const compiledFunction = pug.compileFile("./views/"+config.theme+"/quiz.pug");
const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js");
const pageDatas =
{
config: config,
configTpl: configTpl,
tool: tool,
txtGeneral : txtGeneral,
txtQuestionnaire: txtQuestionnaire,
txtIllustration: txtIllustration,
pageLang: questionnaire.Questionnaire.language,
metaDescription: tool.shortenIfLongerThan(config.siteName+" : "+striptags(questionnaire.Questionnaire.introduction.replace("<br>", " ").replace("</p>", " ")), 200),
author: questionnaire.Questionnaire.CreatorName,
pageTitle: questionnaire.Questionnaire.title+" ("+txtQuestionnaire.questionnairesName+")",
contentTitle: questionnaire.Questionnaire.title,
questionnaire: questionnaire,
linkCanonical: config.siteUrl+"/"+config.dirWebQuestionnaires+"/"+questionnaire.Questionnaire.slug+".html"
}
const html=await compiledFunction(pageDatas);
if(preview===false)
{
await toolFile.createHTML(config.dirHTMLQuestionnaires, questionnaire.Questionnaire.slug, html);
return true;
}
else
return html;
}
}
exports.creaQuestionnaireHTML = creaQuestionnaireHTML;
const creaQuestionnaireInGroupHTML = async (questionnaire, preview=false) =>
{
if(questionnaire === undefined)
return false;
if(questionnaire.Questionnaire.isPublished === false && preview === false)
return false;
const compiledFunction = pug.compileFile("./views/"+config.theme+"/quiz-element.pug");
const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js");
const txtUser = require("../lang/"+config.adminLang+"/user");
// J'ai aussi besoin de certaines informations du groupe :
const groupInfos=await groupCtrl.searchGroupById(questionnaire.Questionnaire.GroupId, false);
if(!groupInfos)
return false;
// + Certaines infos de l'élément suivant du groupe, s'il existe :
let nextQuestionnaire=null;
for(let i in groupInfos.Questionnaires)
if(groupInfos.Questionnaires[i].id === questionnaire.Questionnaire.id)
{
let j=parseInt(i)+1;// !! "i" n'est pas un nombre !
if(groupInfos.Questionnaires[j] !== undefined)
nextQuestionnaire=await searchQuestionnaireById(groupInfos.Questionnaires[j].id, false);
}
const pageDatas=
{
config: config,
configQuestionnaires: configQuestionnaires,
configTpl: configTpl,
tool: tool,
txtGeneral : txtGeneral,
txtQuestionnaire: txtQuestionnaire,
txtIllustration: txtIllustration,
txtUser: txtUser,
pageLang: questionnaire.Questionnaire.language,
metaDescription: tool.shortenIfLongerThan(config.siteName+" : "+striptags(questionnaire.Questionnaire.introduction.replace("<br>", " ").replace("</p>", " ")), 200),
author: questionnaire.Questionnaire.CreatorName,
pageTitle: questionnaire.Questionnaire.title+" ("+txtQuestionnaire.questionnairesName+")",
contentTitle: questionnaire.Questionnaire.title,
questionnaire: questionnaire,
group: groupInfos,
nextQuestionnaire: nextQuestionnaire,
linkCanonical: config.siteUrl+"/"+config.dirWebQuestionnaires+"/"+questionnaire.Questionnaire.slug+".html"
}
const html=await compiledFunction(pageDatas);
if(preview===false)
{
await toolFile.createHTML(config.dirHTMLQuestionnaires, questionnaire.Questionnaire.slug, html);
return true;
}
else
return html;
}
const searchQuestionnaireById = async (id, reassemble=false) =>
{
let questionnaire=await toolFile.readJSON(config.dirCacheQuestionnaires, id);
if(!questionnaire)
questionnaire=await creaQuestionnaireJson(id);
if(!questionnaire)
return false;
if(reassemble)
{
const author=await userCtrl.searchUserById(questionnaire.Questionnaire.CreatorId);
if(author)
questionnaire.Questionnaire.CreatorName=author.User.name;
if(!tool.isEmpty(questionnaire.Questionnaire.GroupId))
questionnaire.Group=await groupCtrl.searchGroupById(questionnaire.Questionnaire.GroupId, false);// !! false, sinon on risque de tourner en rond !
let questionDatas; questionsDatas=[];
for(let i in questionnaire.Questions)
{
questionDatas=await questionCtrl.searchQuestionById(questionnaire.Questions[i].id);
if(questionDatas)
questionsDatas.push(questionDatas);
}
questionnaire.Questions=questionsDatas;
const tags=await tagCtrl.getTagsQuestionnaire(id);
if(tags)
questionnaire.Tags=tags;
}
return questionnaire;
}
exports.searchQuestionnaireById = searchQuestionnaireById;
// Cherche si il y a des questionnaires dont la date de publication est passée mais qui ne sont pas notés comme publiés
// Vérifie si ils sont publiables et si oui change leur statut et réactualise le cache json
const checkQuestionnairesNeedToBePublished = async () =>
{
const db = require("../models/index");
const questionnaires= await db.sequelize.query("SELECT `id` FROM `Questionnaires` WHERE `publishingAt` < NOW() AND `isPublished` = false", { type: QueryTypes.SELECT });
let questionnaireDatas;
for(let i in questionnaires)
{
questionnaireDatas=await searchQuestionnaireById(questionnaires[i].id, true);
if(checkQuestionnaireIsPublishable(questionnaireDatas))
{
await db["Questionnaire"].update({ isPublished:true }, { where: { id : questionnaires[i].id } , fields: ["isPublished"], limit:1 });
creaQuestionnaireJson(questionnaires[i].id);// provoque normalement la création du HTML
}
}
}
// Contrôle si tous les fichiers devant être publiés ont bien leur fichier HTML, sinon le génère
// Si regenerate true, tous les fichiers sont générés, même si ils existent déjà (évolution template...)
// Retourne le nombre de fichiers ayant été régénérés
const checkQuestionnairesPublishedHaveHTML = async (regenerate=false) =>
{
const db = require("../models/index");
const questionnaires= await db.sequelize.query("SELECT `id`,`slug` FROM `Questionnaires` WHERE `isPublished` = true", { type: QueryTypes.SELECT });
let nb=0;
for(let i in questionnaires)
{
if(regenerate)
{
await creaQuestionnaireHTML(questionnaires[i].id);
nb++;
}
else if(await toolFile.checkIfFileExist(config.dirHTMLQuestionnaires, questionnaires[i].slug+".html")===false)
{
await creaQuestionnaireHTML(questionnaires[i].id);
nb++;
}
}
return nb;
}
// Liste des derniers questionnaires publiés (utile pour page d'accueil, flux rss, etc.)
const creaNewQuestionnairesJson = async () =>
{
const db = require("../models/index");
const Questionnaires=await db["Questionnaire"].findAll({ where: { isPublished : true }, order: [[config.fieldNewQuestionnaires, "DESC"], ["id", "DESC"]], attributes: ["id"], limit: config.nbNewQuestionnaires });
if(Questionnaires)
{
await toolFile.createJSON(config.dirCacheQuestionnaires, "last", Questionnaires);
creaNewQuestionnairesHTML(Questionnaires);
return Questionnaires;
}
else
return false;
}
exports.creaNewQuestionnairesJson = creaNewQuestionnairesJson;
// Se limite à compter le nombre total de questionnaires et à le stocker pour éviter de lancer une requête sql à chaque fois
const creaStatsQuestionnairesJson = async () =>
{
const db = require("../models/index");
const Questionnaires=await db["Questionnaire"].findAll({ attributes: ["id"] });
const QuestionnairesPublished=await db["Questionnaire"].findAll({ where: { isPublished : true }, attributes: ["id"] });
if(Questionnaires && QuestionnairesPublished)
{
const stats =
{
nbTot : Questionnaires.length,
nbPublished : QuestionnairesPublished.length
}
await toolFile.createJSON(config.dirCacheQuestionnaires, "stats", stats);
return stats;
}
else
return false;
}
exports.creaStatsQuestionnairesJson = creaStatsQuestionnairesJson;
// Retourne les données créées par la fonction précédente
const getStatsQuestionnaires = async () =>
{
let stats=await toolFile.readJSON(config.dirCacheQuestionnaires, "stats");
if(!stats)
stats=await creaStatsQuestionnairesJson();
if(!stats)
return false;
else
return stats;
}
exports.getStatsQuestionnaires = getStatsQuestionnaires;
// Quelle est la prochaine date pour laquelle aucun questionnaire n'a été publié ?
const getDateNewQuestionnaireNeeded = async (maxDays) =>
{
const db = require("../models/index");
let dateNeed="", checkDate, addMin=0, addMax=1;
while(dateNeed==="")
{
checkDate = await db.sequelize.query("SELECT ADDDATE(CURDATE(), :addMin) as `dateNeeded` FROM `Questionnaires` WHERE `isPublished`=1 AND `publishingAt` > ADDDATE(CURDATE(), :addMin) AND `publishingAt`< ADDDATE(CURDATE(), :addMax) ORDER BY `publishingAt` LIMIT 1", { replacements: { addMin: addMin, addMax: addMax }, type: QueryTypes.SELECT });
if(checkDate && getSubscriptions24H.length>0)
dateNeed=checkDate.dateNeeded;
else
{
addMin++;
addMax++;
}
if(addMin>maxDays)
return false;
}
return dateNeed;
}
exports.getDateNewQuestionnaireNeeded = getDateNewQuestionnaireNeeded;
// Les prochains questionnaires devant être publiés (permet aux gestionnaires de voir les dates vides)
const getNextQuestionnaires = async () =>
{
const db = require("../models/index");
const questionnaires= await db.sequelize.query("SELECT `id`,`title`, `publishingAt` as `datePublishing` FROM `Questionnaires` WHERE `publishingAt` > NOW() order by `publishingAt`", { type: QueryTypes.SELECT });
return questionnaires;
}
exports.getNextQuestionnaires = getNextQuestionnaires;
const creaNewQuestionnairesHTML = async (Questionnaires) =>
{
// On regénère la page d'accueil avec le(s) dernier(s) questionnaire(s) mis en avant :
let compiledFunction = pug.compileFile("./views/"+config.theme+"/home.pug");
const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js");
let questionnaires=[];
for(let i in Questionnaires)
questionnaires.push(await searchQuestionnaireById(Questionnaires[i].id));
let pageDatas=
{
config: config,
configTpl: configTpl,
tool: tool,
striptags: striptags,
txtGeneral : txtGeneral,
txtQuestionnaire: txtQuestionnaire,
txtIllustration: txtIllustration,
pageLang: config.adminLang,
metaDescription: txtGeneral.siteMetaDescription,
pageTitle: txtGeneral.siteHTMLTitle,
contentTitle: config.siteName,
questionnaires: questionnaires,
linkCanonical: config.siteUrl
}
let html=await compiledFunction(pageDatas);
await toolFile.createHTML(config.dirHTML, "index", html);
// + la page listant les X derniers quizs + liste des tags
compiledFunction=pug.compileFile("./views/"+config.theme+"/newQuestionnaires.pug");
pageDatas.metaDescription=configTpl.newQuestionnairesIntro;
pageDatas.pageTitle=configTpl.newQuestionnairesTitle;
pageDatas.linkCanonical=config.siteUrl+"/"+configQuestionnaires.dirWebTags;
pageDatas.tags=await tagCtrl.getUsedTags();
html=await compiledFunction(pageDatas);
await toolFile.createHTML(configQuestionnaires.dirHTMLTags, "index", html);
return true;
}
// À partir d'une liste d'id questionnaires fournis, retourne la liste complète ou partielle (pagination) avec les infos de chaque questionnaire
// Retour en html ou json suivant les cas
const getListingsQuestionnairesOuput = async (Questionnaires, begin=0, end=null, output="") =>
{
const datas= { nbTot: Questionnaires.length, questionnaires: [], html: "" };
if(datas.nbTot!==0)
{
if(end===null)
end=datas.nbTot;
for (let i = begin; i <= end; i++)
{
if(Questionnaires[i]===undefined)
break;
else
datas.questionnaires.push(await searchQuestionnaireById(Questionnaires[i].id));
}
// Utiles pour savoir où j'en suis rendu dans le front :
datas.begin=begin;
datas.end=end;
if(output==="html")
{
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);
datas.questionnaires=null;// allège un peu le contenu retourné...
}
}
return datas;
}
exports.getListingsQuestionnairesOuput = getListingsQuestionnairesOuput;