2020-08-07 12:23:59 +02:00
const { Op , QueryTypes } = require ( "sequelize" ) ; // pour certaines requêtes sql
const pug = require ( "pug" ) ;
2020-10-29 12:57:00 +01:00
const striptags = require ( "striptags" ) ;
2020-08-07 12:23:59 +02:00
const config = require ( "../config/main.js" ) ;
2020-10-29 12:57:00 +01:00
const configQuestionnaire = require ( "../config/questionnaires.js" ) ;
const ctrlQuestionnaire = require ( "./questionnaire" ) ;
2020-08-07 12:23:59 +02:00
const tool = require ( "../tools/main" ) ;
const toolError = require ( "../tools/error" ) ;
const toolFile = require ( "../tools/file" ) ;
const txtGeneral = require ( "../lang/" + config . adminLang + "/general" ) ;
const txtIllustration = require ( "../lang/" + config . adminLang + "/illustration" ) ;
2020-10-29 12:57:00 +01:00
const txtQuestionnaire = require ( "../lang/" + config . adminLang + "/questionnaire" ) ;
const txtTag = require ( "../lang/" + config . adminLang + "/tag" ) ;
2020-08-07 12:23:59 +02:00
// Actualise le classement d'un questionnaire suivant les tags envoyés
exports . checkTags = async ( req , res , next ) =>
{
try
{
if ( req . body . QuestionnaireId == undefined )
2020-10-29 12:57:00 +01:00
throw { message : txtTag . neededParams } ;
2020-08-07 12:23:59 +02:00
const tagsCurrent = await getUsedTagsQuestionnaire ( req . body . QuestionnaireId ) ;
if ( tagsCurrent === false )
2020-10-29 12:57:00 +01:00
throw { message : txtTag . tagsForQuestionnaireNotFound } ;
2020-08-07 12:23:59 +02:00
const tagsReceived = req . body . classification . split ( "," ) ; // ! peut être vide si pas/plus de classement souhaité
2020-11-04 10:02:04 +01:00
for ( let i in tagsReceived )
2020-08-07 12:23:59 +02:00
tagsReceived [ i ] = tagsReceived [ i ] . trim ( ) . toLowerCase ( ) ; // ! gestion de la casse différente pour JS, pas pour Mysql
// les tags jusqu'ici associés sont-ils toujours utilisés ?
let deleteLink ;
2020-11-04 10:02:04 +01:00
for ( let i in tagsCurrent )
2020-08-07 12:23:59 +02:00
{
2020-11-04 10:02:04 +01:00
if ( tagsReceived . indexOf ( tagsCurrent [ i ] . name . toLowerCase ( ) ) === - 1 )
2020-08-07 12:23:59 +02:00
deleteLink = await unlinkTagQuestionnaire ( tagsCurrent [ i ] . id , req . body . QuestionnaireId ) ;
}
// parmis les tags envoyés, certains sont-ils nouveaux pour ce questionnaire ?
let findTag = false , creaLink ;
for ( i in tagsReceived )
{
for ( j in tagsCurrent )
{
if ( tagsCurrent [ j ] . name . toLowerCase ( ) === tagsReceived [ i ] )
{
findTag = true ;
break ;
}
}
if ( ! findTag && tagsReceived [ i ] !== "" ) // revoir : remettre les majuscules lors de l'enregistrement ?
{
creaLink = await linkTagQuestionnaire ( tagsReceived [ i ] , req . body . QuestionnaireId ) ;
if ( creaLink !== true )
throw creaLink ; // nécessaire pour retourner les erreurs du modèle (mais uniquement "tag trop long" possible)
}
findTag = false ;
}
2020-10-29 12:57:00 +01:00
await ctrlQuestionnaire . creaQuestionnaireJson ( req . body . QuestionnaireId ) ; // attendre avant de répondre pour que cela soit pris en compte au réaffichage.
2020-08-07 12:23:59 +02:00
if ( req . method == "PUT" )
res . status ( 200 ) . json ( { message : txtGeneral . updateOkMessage } ) ;
else if ( req . method == "POST" )
res . status ( 201 ) . json ( { message : txtGeneral . addOkMessage , id : req . body . QuestionnaireId } ) ;
next ( ) ;
}
catch ( e )
{
const returnAPI = toolError . returnSequelize ( e ) ;
if ( returnAPI . messages )
{
res . status ( returnAPI . status ) . json ( { errors : returnAPI . messages } ) ;
next ( ) ;
}
else
next ( e ) ;
}
}
// Retourne la liste des tags déjà connus débutant par l'expression donnée :
exports . getTagsBeginningBy = async ( req , res , next ) =>
{
try
{
2020-10-29 12:57:00 +01:00
let tags = await getAllTags ( ) , tagsOk = [ ] , search = req . body . search . trim ( ) . toLowerCase ( ) ;
2020-08-07 12:23:59 +02:00
if ( search . length >= 2 )
{
2020-10-29 12:57:00 +01:00
for ( let i in tags )
2020-08-07 12:23:59 +02:00
{
2020-10-29 12:57:00 +01:00
let item = tags [ i ] . name . toLowerCase ( ) ; // il reste le problème des accents
2020-08-07 12:23:59 +02:00
if ( item . startsWith ( search ) )
tagsOk . push ( tags [ i ] ) ;
}
}
res . status ( 200 ) . json ( tagsOk ) ;
next ( ) ;
}
catch ( e )
{
next ( e ) ;
}
}
// Fais suite à la même fonction dans questionnaire.js
// Va récupérer la liste des tags utilisés pour classer au moins un questionnaire publié
// Puis regénère les fichiers HTML pour chacun de ces tags
exports . HTMLRegenerate = async ( req , res , next ) =>
{
try
{
const tagsUsed = await getUsedTags ( ) ;
for ( let i in tagsUsed )
2020-11-02 11:32:17 +01:00
await creaQuestionnairesTagJson ( tagsUsed [ i ] . id ) ; // Provoque la regénération du json + du html.
// + Page listant tous les tags actifs :
creaUsedTagsHTML ( tagsUsed ) ;
res . status ( 200 ) . json ( { message : res . messageToNext . replace ( "#NB3" , tagsUsed . length ) } ) ;
2020-08-07 12:23:59 +02:00
res . messageToNext = null ;
next ( ) ;
}
catch ( e )
{
next ( e ) ;
}
}
// UTILITAIRES
// Créer la liste de tous les tags présents dans la base de données
const creaAllTagsJson = async ( ) =>
{
const db = require ( "../models/index" ) ;
const tags = await db [ "Tag" ] . findAll ( ) ;
2020-10-29 12:57:00 +01:00
await toolFile . createJSON ( configQuestionnaire . dirCacheTags , "all" , tags ) ;
2020-08-07 12:23:59 +02:00
return tags ;
}
// Retourne la liste de tous les tags
const getAllTags = async ( ) =>
{
2020-10-29 12:57:00 +01:00
const tags = await toolFile . readJSON ( configQuestionnaire . dirCacheTags , "all" ) ;
2020-08-07 12:23:59 +02:00
if ( tags )
return tags ;
else
return await creaAllTagsJson ( ) ;
}
// Créer la liste de tous tags utilisés pour classer au moins un quiz publié
const creaUsedTagsJson = async ( ) =>
{
const db = require ( "../models/index" ) ;
const tags = await db . sequelize . query ( "SELECT DISTINCT `Tags`.`id`,`Tags`.`name`,`Tags`.`slug` FROM `Tags` INNER JOIN `QuestionnaireClassifications` INNER JOIN `Questionnaires` WHERE `Tags`.`id`=`QuestionnaireClassifications`.`TagId` AND `QuestionnaireClassifications`.`QuestionnaireId`=`Questionnaires`.`id` AND `Questionnaires`.`isPublished`=true ORDER BY `name` ASC" , { type : QueryTypes . SELECT , model : db [ "Tag" ] , mapToModel : true } ) ;
2020-10-29 12:57:00 +01:00
await toolFile . createJSON ( configQuestionnaire . dirCacheTags , "used" , tags ) ;
if ( tags . length !== 0 )
creaUsedTagsHTML ( tags ) ;
2020-08-07 12:23:59 +02:00
return tags ;
}
exports . creaUsedTagsJson = creaUsedTagsJson ; // utile pour actualiser en cas de publication/dépublication d'un quiz
2020-10-29 12:57:00 +01:00
// Créer la page HTML listant tous les tags
const creaUsedTagsHTML = async ( tags ) =>
{
// + la page listant les X derniers quizs + liste des tags
const compiledFunction = pug . compileFile ( "./views/" + config . theme + "/tagsList.pug" ) ;
const configTpl = require ( "../views/" + config . theme + "/config/" + config . availableLangs [ 0 ] + ".js" ) ;
let pageDatas =
{
config : config ,
2020-11-04 10:02:04 +01:00
configQuestionnaire : configQuestionnaire ,
2020-10-29 12:57:00 +01:00
configTpl : configTpl ,
tool : tool ,
pageLang : config . adminLang ,
metaDescription : configTpl . tagListMetaDesc ,
pageTitle : configTpl . tagListTitle ,
contentTitle : config . siteName ,
tags : tags ,
linkCanonical : config . siteUrl + "/" + configQuestionnaire . dirWebTags
}
html = await compiledFunction ( pageDatas ) ;
2020-11-04 10:02:04 +01:00
await toolFile . createHTML ( configQuestionnaire . dirHTMLTags , "themes" , html ) ;
2020-10-29 12:57:00 +01:00
return true ;
}
2020-08-07 12:23:59 +02:00
// Retourne la liste des tags utilisés
const getUsedTags = async ( ) =>
{
2020-10-29 12:57:00 +01:00
const tags = await toolFile . readJSON ( configQuestionnaire . dirCacheTags , "used" ) ;
2020-08-07 12:23:59 +02:00
if ( tags )
return tags ;
else
return await creaUsedTagsJson ( ) ;
}
exports . getUsedTags = getUsedTags ;
// Retourne la liste des tags d'un questionnaire avec leurs noms, slugs, etc.
const getUsedTagsQuestionnaire = async ( id ) =>
{
id = tool . trimIfNotNull ( id ) ;
2020-10-29 12:57:00 +01:00
if ( id === null )
2020-08-07 12:23:59 +02:00
return false ;
2020-10-29 12:57:00 +01:00
const questionnaire = await ctrlQuestionnaire . searchQuestionnaireById ( id ) ;
2020-08-07 12:23:59 +02:00
if ( ! questionnaire )
throw { message : txtQuestionnaire . notFound } ;
else
{
const allTags = await getAllTags ( ) ;
let tagsQuestionnaire = [ ] ;
2020-10-29 12:57:00 +01:00
for ( let i in questionnaire . Tags )
2020-08-07 12:23:59 +02:00
{
for ( j in allTags )
{
2020-10-29 12:57:00 +01:00
if ( allTags [ j ] . id === questionnaire . Tags [ i ] . TagId )
2020-08-07 12:23:59 +02:00
{
tagsQuestionnaire . push ( allTags [ j ] ) ;
break ;
}
}
}
return tagsQuestionnaire ;
}
}
exports . getTagsQuestionnaire = getUsedTagsQuestionnaire ;
// Créer la liste complète des questionnaires publiés, classés par un tag
const creaQuestionnairesTagJson = async ( id ) =>
{
id = tool . trimIfNotNull ( id ) ;
2020-10-29 12:57:00 +01:00
if ( id === null )
2020-08-07 12:23:59 +02:00
return false ;
const db = require ( "../models/index" ) ;
2020-09-09 16:19:37 +02:00
const questionnaires = await db . sequelize . query ( "SELECT id FROM `Questionnaires` INNER JOIN `QuestionnaireClassifications` ON `Questionnaires`.`id`=`QuestionnaireClassifications`.`QuestionnaireId` AND `Questionnaires`.`isPublished`=1 AND `QuestionnaireClassifications`.`TagId`=:id ORDER BY " + config . fieldNewQuestionnaires + " DESC" , { replacements : { id : id } , type : QueryTypes . SELECT , model : db [ "Questionnaire" ] , mapToModel : true } ) ;
2020-11-02 11:32:17 +01:00
await toolFile . createJSON ( configQuestionnaire . dirCacheTags , "liste-" + id , questionnaires ) ;
2020-08-07 12:23:59 +02:00
creaUsedTagsJson ( ) ;
creaQuestionnairesTagHTML ( id , questionnaires ) ; // pas await, car potentiellement long !
return questionnaires ;
}
exports . creaQuestionnairesTagJson = creaQuestionnairesTagJson ;
const creaQuestionnairesTagHTML = async ( id , Questionnaires ) =>
{
id = tool . trimIfNotNull ( id ) ;
2020-10-29 12:57:00 +01:00
if ( id === null || Questionnaires === null )
2020-11-04 10:02:04 +01:00
return false ;
2020-08-07 12:23:59 +02:00
const tag = await searchTagById ( id ) ;
2020-10-29 12:57:00 +01:00
if ( Questionnaires . length === 0 )
2020-08-07 12:23:59 +02:00
{ // plus aucun quiz classé ici. Il faudrait idéalement envoyer des erreurs 404/410, etc.
// revoir aussi l'intérêt ou non de supprimer les pages suivantes, sachant qu'elles ne sont pas indexables ? ou les rendre dynamiques ?
2020-10-29 12:57:00 +01:00
await toolFile . deleteFile ( configQuestionnaire . dirHTMLTags , tag . slug + ".html" ) ;
2020-08-07 12:23:59 +02:00
return true ;
}
const compiledFunction = pug . compileFile ( "./views/" + config . theme + "/tag.pug" ) ;
const configTpl = require ( "../views/" + config . theme + "/config/" + config . availableLangs [ 0 ] + ".js" ) ;
2020-10-29 12:57:00 +01:00
const pageDatas =
2020-08-07 12:23:59 +02:00
{
config : config ,
configTpl : configTpl ,
tool : tool ,
striptags : striptags ,
txtGeneral : txtGeneral ,
txtQuestionnaire : txtQuestionnaire ,
txtIllustration : txtIllustration ,
2020-11-04 10:02:04 +01:00
txtTag : txtTag ,
2020-08-07 12:23:59 +02:00
pageLang : config . adminLang ,
2020-11-04 10:02:04 +01:00
metaDescription : config . siteName + " - " + txtTag . tagMetaDescription + tag . name ,
2020-08-07 12:23:59 +02:00
pageTitle : config . siteName + " - " + tag . name ,
2020-11-04 10:02:04 +01:00
contentTitle : config . siteName + " : " + txtTag . tagMetaDescription + tag . name ,
2020-08-07 12:23:59 +02:00
tagInfos : tag ,
2020-10-29 12:57:00 +01:00
linkCanonical : config . siteUrl + "/" + configQuestionnaire . dirWebTags + "/" + tag . slug + ".html"
2020-08-07 12:23:59 +02:00
}
const nbPages = Math . ceil ( Questionnaires . length / configTpl . maxQuestionnairesByPage ) ;
pageDatas . nbPages = nbPages ;
2020-10-29 12:57:00 +01:00
let debut = 0 ;
2020-08-07 12:23:59 +02:00
for ( let i = 1 ; i <= nbPages ; i ++ )
{
2020-10-29 12:57:00 +01:00
const questionnairesPage = Questionnaires . slice ( debut , debut + configTpl . maxQuestionnairesByPage ) , questionnairesInfos = [ ] ;
2020-08-07 12:23:59 +02:00
for ( let j in questionnairesPage )
2020-10-29 12:57:00 +01:00
questionnairesInfos . push ( await ctrlQuestionnaire . searchQuestionnaireById ( questionnairesPage [ j ] . id ) ) ;
2020-08-07 12:23:59 +02:00
pageDatas . questionnaires = questionnairesInfos ;
pageDatas . page = i ;
2020-10-29 12:57:00 +01:00
let url = tag . slug ;
2020-08-07 12:23:59 +02:00
if ( i !== 1 )
{
url = url + "-" + i ;
pageDatas . metaRobots = "noindex,follow" ;
pageDatas . linkCanonical = null ;
}
2020-10-29 12:57:00 +01:00
const html = await compiledFunction ( pageDatas ) ;
await toolFile . createHTML ( configQuestionnaire . dirHTMLTags , url , html ) ;
2020-08-07 12:23:59 +02:00
debut += configTpl . maxQuestionnairesByPage ;
}
return true ;
}
// Retourne un tag, si il existe
const searchTagByName = async ( name ) =>
{
name = tool . trimIfNotNull ( name ) ;
if ( name === null )
return false ;
const db = require ( "../models/index" ) ;
const tag = await db [ "Tag" ] . findOne ( { where : { name : name } } ) ; // utile de faire en sql pour les problèmes de majuscules / minuscules
if ( tag )
return tag ;
else
return false ;
}
const searchTagById = async ( id ) =>
{
id = tool . trimIfNotNull ( id ) ;
if ( id === null )
return false ;
const db = require ( "../models/index" ) ;
const tag = await db [ "Tag" ] . findByPk ( id ) ;
if ( tag )
return tag ;
else
return false ;
}
// Supprime l'association entre un tag et un questionnaire + actualise la liste des questionnaires pour le tag concerné
// La mise à jour du json/HTML du questionnaire est appelée par le contrôleur appelant cette fonction
const unlinkTagQuestionnaire = async ( tagId , questionnaireId ) =>
{
const db = require ( "../models/index" ) ;
await db [ "QuestionnaireClassification" ] . destroy ( { where : { TagId : tagId , QuestionnaireId : questionnaireId } , limit : 1 } ) ;
creaQuestionnairesTagJson ( tagId ) ; // peut être lent !
return true ;
}
// Créer l'association entre un tag et un questionnaire + actualise la liste des questionnaires / tag
// La mise à jour du json/HTML du questionnaire est appelée par le contrôleur appelant cette fonction
const linkTagQuestionnaire = async ( tagName , questionnaireId ) =>
{
const db = require ( "../models/index" ) ;
// ce tag est-il nouveau ?
let tagLinked = await searchTagByName ( tagName ) ;
2020-10-29 12:57:00 +01:00
if ( tagLinked === false )
2020-08-07 12:23:59 +02:00
{
tagLinked = await db [ "Tag" ] . create ( { name : tagName , slug : null } , { fields : [ "name" , "slug" ] } ) ; // "slug : null" pour laisser le modèle le générer
creaAllTagsJson ( ) ;
}
if ( tagLinked )
{
await db [ "QuestionnaireClassification" ] . create ( { TagId : tagLinked . id , QuestionnaireId : questionnaireId } ) ;
creaQuestionnairesTagJson ( tagLinked . id ) ; // peut être lent !
}
return true ;
}