Initial commit for publishing on gitlab

This commit is contained in:
Fabrice PENHOËT 2020-08-07 12:23:59 +02:00
commit 85aef66e35
228 changed files with 61897 additions and 0 deletions

26
.gitignore vendored Normal file
View File

@ -0,0 +1,26 @@
# BACKEND :
datas/
logs/
temp/
node_modules/
nodemon.json
*.env
!example.env
/config/instance.*
!/config/instance-example.js
# FRONT END :
/front/node_modules/
/front/webpack.config-*.js
!/front/webpack.config.js
/front/public/img/quizs/
/front/public/JS/*/
/front/public/quiz/
/front/public/quizs/
/front/public/index.html
/front/public/CGV-CGU.html
/front/public/mentions-legales.html
/front/public/robots-*.txt
/front/public/WikiLerni-pub.asc

5
.sequelizerc Normal file
View File

@ -0,0 +1,5 @@
const path = require('path');
module.exports = {
'config': path.resolve('config', 'database.js')
};

107
app.js Normal file
View File

@ -0,0 +1,107 @@
require("dotenv").config();
const express = require("express");
const bodyParser = require("body-parser");
const path = require("path");
const log4js = require("log4js");
const timeInitialise = require("./middleware/initialise");
const cronRoutes = require("./routes/cron");
const userRoutes = require("./routes/user");
const userPausesRoutes = require("./routes/pause");
const userPaymentsRoutes = require("./routes/payment");
const questionnairesRoutes = require("./routes/questionnaire");
const questionsRoutes = require("./routes/question");
const choicesRoutes = require("./routes/choice");
const illustrationRoutes = require("./routes/illustration");
const linkRoutes = require("./routes/link");
const tagRoutes = require("./routes/tag");
const config = require("./config/main");
const confLog4js=require("./config/log4js");
const txt = require("./lang/"+config.adminLang+"/general");
const tool = require("./tools/main");
const app = express();
app.use(timeInitialise);
app.use((req, res, next) =>
{
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content, Accept, Content-Type, Authorization");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
next();
});
//app.use(express.static(path.join(__dirname, "front/public")));
app.use(bodyParser.json());
app.use("/api/user", userRoutes);
app.use("/api/pause", userPausesRoutes);
app.use("/api/payment", userPaymentsRoutes);
app.use("/api/questionnaire", questionnairesRoutes);
app.use("/api/questionnaire", tagRoutes);
app.use("/api/question", questionsRoutes);
app.use("/api/question", choicesRoutes);
app.use("/api/illustration", illustrationRoutes);
app.use("/api/link", linkRoutes);
app.use("/api/cron", cronRoutes);
// Évalue de la durée de la réponse (!= durée script, car fonctions asynchrones continuent). Mettre next() après réponse des contrôleurs... à contrôler !
// Capture aussi les url inconnues en retournant une erreur 404.
// Je peux aussi recevoir des messages à afficher dans les logs venant des "cron".
app.use((req, res, next) =>
{
try
{
if(res.headersSent)
{
log4js.configure(confLog4js);// ici, car pas pris en compte si je le fais avant le middleware (?).
const myLogs = log4js.getLogger(config.env);
let timeEnd=Date.now(), maxTime=config.responseTimingAlertInSeconde;
if(req.url.startsWith("/api/cron"))
maxTime=config.cronTimingAlertInSeconde;
const mapMessage =
{
SCRIPT_TIMING: timeEnd-req.timeBegin,
SCRIPT_URL: req.url
};
if(config.env === "development")
{
myLogs.info(tool.replaceAll(txt.scriptTimingInfo, mapMessage));
if(res.alerte)
myLogs.warn(res.alerte);
}
else if((timeEnd-req.timeBegin) > maxTime*1000)
{
myLogs.warn(tool.replaceAll(txt.scriptTimingAlert, mapMessage));
if(res.message)
myLogs.info(res.message);
}
next();
}
else
{
const err = new Error(txt.badUrl);
err.status=404;
next(err);
}
}
catch(e)
{
next(e);
}
});
// Capture et traitement des erreurs
app.use((err, req, res, next) =>
{
const status = (err.status) ? err.status : 500;
if(!res.headersSent)
res.status(status).json({ errors: txt.serverError });
log4js.configure(confLog4js);// même remarque que + haut
const myLogs = log4js.getLogger(config.env);
myLogs.error(txt.serverErrorAdmin, { message : err.message, url: req.url });
});
log4js.shutdown();
module.exports = app;

29
config/database.js Normal file
View File

@ -0,0 +1,29 @@
require('dotenv').config();
module.exports =
{
"development":
{
"username": process.env.DB_USER,
"password": process.env.DB_PASS,
"database": process.env.DB_NAME,
"host": process.env.DB_HOST,
"dialect": "mysql"
},
"test":
{
"username": process.env.DB_USER,
"password": process.env.DB_PASS,
"database": process.env.DB_NAME,
"host": process.env.DB_HOST,
"dialect": "mysql"
},
"production":
{
"username": process.env.DB_USER,
"password": process.env.DB_PASS,
"database": process.env.DB_NAME,
"host": process.env.DB_HOST,
"dialect": "mysql"
}
};

18
config/illustrations.js Normal file
View File

@ -0,0 +1,18 @@
const instance = require("./instance");
module.exports =
{
// API'routes (after "apiUrl" defined in instance.js)
illustrationsRoute: "/illustration/",
// forms : à compléter avec valeurs par défaut, etc. cf modèle
Illustration :
{
alt: { maxlength: 255 },
title: { maxlength: 255 },
caption: { maxlength: 255 },
image: { required: true, accept: instance.mimeTypesForIllustration.join(",") }
},
// files upload tempory dir
dirIllustrationsTmp : "temp",
dirIllustrations: "front/public/img/quizs"
};

63
config/instance-demo.js Normal file
View File

@ -0,0 +1,63 @@
const users = require("./users");
const questionnaires = require("./questionnaires");
module.exports =
{
apiUrl: "https://apitest.wikilerni.com/api",
siteUrl: "https://test.wikilerni.com",
adminName: "Fabrice",
adminEmail: "dev@wikilerni.com",
senderName: "WikiLerni (démo)",
senderEmail: "bonjour@wikilerni.com",
adminLang: "fr",
theme: "wikilerni", // le thème utilisé (dans /views) pour générer les pages HTML. Contient ses propres fichiers de configuration.
availableLangs: ["fr"],// Languages in which the site is available. The first one is the default one.
siteName: "WikiLerni (démo)",
beginCodeGodfather: "WL", // case-sensitive and can't contain "@" !
cronTimingAlertInSeconde: 120, // for logs
responseTimingAlertInSeconde: 3, // idem
tokenSignupValidationTimeInHours: "48h", // https://github.com/zeit/ms
tokenLoginLinkTimeInHours: "1h",
tokenConnexionMinTimeInHours: "24h",
tokenConnexionMaxTimeInDays: "180 days",
tokenLoginChangingTimeInHours: "1h",// for email & password changing
tokenDeleteUserTimeInHours: "1h",
tokenUnsubscribeLinkTimeInDays: "7 days", // token send with subscription's emails
freeAccountTimingInDays: 15,
freeAccountExpirationNotificationInDays: 2,
accountExpirationFirstNotificationInDays: 15,
accountExpirationSecondNotificationInDays: 3,
inactiveAccountTimeToDeleteInDays: 180,
// Questionnaires:
nbQuestionsMin: 1, // minimum number of questions for the questionnaire to be publishable
nbQuestionsMax: 0, // if 0 = not maximum
nbChoicesMax: 10,
nbNewQuestionnaires: 10,// for RSS, etc.
hourGiveNewQuestionnaireBegin: 3, // in user local time
hourGiveNewQuestionnaireEnd: 8, // idem
numberNewQuestionnaireAtSameTime: 50, // for mass mailing
minSearchQuestionnaires: 3,
// Illustrations:
nbIllustrationsMin: 0,
nbIllustrationsMax: 1,
maxIllustrationSizeinOctet: 1000000,// pas contrôlé pour l'instant. À revoir.
mimeTypesForIllustration: [ "image/jpg", "image/jpeg", "image/png", "image/gif", "image/png" ],
// -- Upload and resize:
illustrationsWidthMaxInPx: 400,
illustrationsMiniaturesWidthMaxInPx: 200,
// Links:
nbLinksMin: 1,
nbLinksMax: 1,
// à supprimer quand tous les "require" à jour:
nbQuestionsMin: questionnaires.nbQuestionsMin,
nbQuestionsMax: questionnaires.nbQuestionsMax,
nbChoicesMax: questionnaires.nbChoicesMax,
passwordMinLength: users.password.minlength,
dirCacheUsers: users.dirCacheUsers,
dirCacheUsersAnswers: users.dirCacheUsersAnswers,
dirCacheQuestionnaires: questionnaires.dirCacheQuestionnaires,
dirCacheQuestions: questionnaires.dirCacheQuestions,
dirCacheUsersQuestionnaires: questionnaires.dirCacheUsersQuestionnaires,
dirHTMLQuestionnaire: questionnaires.dirHTMLQuestionnaire,
dirWebQuestionnaire: questionnaires.dirWebQuestionnaire
};

View File

@ -0,0 +1,65 @@
/// À ADAPTER ET RENOMMER : instance.js.
const users = require("./users");
const questionnaires = require("./questionnaires");
module.exports =
{
apiUrl: "https://...",
siteUrl: "https://...",
adminName: "bob",
adminEmail: "bob@example.tld",
senderName: "bob",
senderEmail: "bob@example.tld",
adminLang: "fr",
theme: "wikilerni", // le thème utilisé (dans /views) pour générer les pages HTML. Contient ses propres fichiers de configuration.
availableLangs: ["fr"],// Languages in which the site is available. The first one is the default one.
siteName: "WikiLerni",
beginCodeGodfather: "WL", // case-sensitive and can't contain "@" !
cronTimingAlertInSeconde: 120, // for logs
responseTimingAlertInSeconde: 3, // idem
tokenSignupValidationTimeInHours: "48h", // see : https://github.com/zeit/ms
tokenLoginLinkTimeInHours: "1h",
tokenConnexionMinTimeInHours: "24h",
tokenConnexionMaxTimeInDays: "180 days",
tokenLoginChangingTimeInHours: "1h",// for email & password changing
tokenDeleteUserTimeInHours: "1h",
tokenUnsubscribeLinkTimeInDays: "7 days", // token send with subscription's emails
freeAccountTimingInDays: 15,
freeAccountExpirationNotificationInDays: 3,
accountExpirationFirstNotificationInDays: 10,
accountExpirationSecondNotificationInDays: 3,
inactiveAccountTimeToDeleteInDays: 180,
// Questionnaires:
nbQuestionsMin: 1, // minimum number of questions for the questionnaire to be publishable
nbQuestionsMax: 0, // if 0 = not maximum
nbChoicesMax: 10,
nbNewQuestionnaires: 10, // for RSS, etc.
hourGiveNewQuestionnaireBegin: 3, // in user local time
hourGiveNewQuestionnaireEnd: 8, // idem
numberNewQuestionnaireAtSameTime: 50, // for mass mailing sending new quiz
minSearchQuestionnaires: 3,
// Illustrations:
nbIllustrationsMin: 0,
nbIllustrationsMax: 1,
maxIllustrationSizeinOctet: 1000000,// Not checked yet. To be continued.
mimeTypesForIllustration: [ "image/jpg", "image/jpeg", "image/png", "image/gif", "image/png" ],
// -- Upload and resize:
illustrationsWidthMaxInPx: 400,
illustrationsMiniaturesWidthMaxInPx: 200,
// Links:
nbLinksMin: 1,
nbLinksMax: 1,
// à supprimer quand tous les "require" à jour:
nbQuestionsMin: questionnaires.nbQuestionsMin,
nbQuestionsMax: questionnaires.nbQuestionsMax,
nbChoicesMax: questionnaires.nbChoicesMax,
passwordMinLength: users.password.minlength,
dirCacheUsers: users.dirCacheUsers,
dirCacheUsersAnswers: users.dirCacheUsersAnswers,
dirCacheQuestionnaires: questionnaires.dirCacheQuestionnaires,
dirCacheQuestions: questionnaires.dirCacheQuestions,
dirCacheUsersQuestionnaires: questionnaires.dirCacheUsersQuestionnaires,
dirHTMLQuestionnaire: questionnaires.dirHTMLQuestionnaire,
dirWebQuestionnaire: questionnaires.dirWebQuestionnaire
};

63
config/instance-prod.js Normal file
View File

@ -0,0 +1,63 @@
const users = require("./users");
const questionnaires = require("./questionnaires");
module.exports =
{
apiUrl: "https://api.wikilerni.com/api",
siteUrl: "https://www.wikilerni.com",
adminName: "Fab",
adminEmail: "dev@wikilerni.com",
senderName: "WikiLerni",
senderEmail: "bonjour@wikilerni.com",
adminLang: "fr",
theme: "wikilerni", // le thème utilisé (dans /views) pour générer les pages HTML. Contient ses propres fichiers de configuration.
availableLangs: ["fr"],// Languages in which the site is available. The first one is the default one.
siteName: "WikiLerni",
beginCodeGodfather: "WL", // case-sensitive and can't contain "@" !
cronTimingAlertInSeconde: 120, // for logs
responseTimingAlertInSeconde: 3, // idem
tokenSignupValidationTimeInHours: "48h", // https://github.com/zeit/ms
tokenLoginLinkTimeInHours: "1h",
tokenConnexionMinTimeInHours: "24h",
tokenConnexionMaxTimeInDays: "180 days",
tokenLoginChangingTimeInHours: "1h",// for email & password changing
tokenDeleteUserTimeInHours: "1h",
tokenUnsubscribeLinkTimeInDays: "7 days", // token send with subscription's emails
freeAccountTimingInDays: 15,
freeAccountExpirationNotificationInDays: 3,
accountExpirationFirstNotificationInDays: 10,
accountExpirationSecondNotificationInDays: 3,
inactiveAccountTimeToDeleteInDays: 180,
// Questionnaires:
nbQuestionsMin: 1, // minimum number of questions for the questionnaire to be publishable
nbQuestionsMax: 0, // if 0 = not maximum
nbChoicesMax: 10,
nbNewQuestionnaires: 10,// for RSS, etc.
hourGiveNewQuestionnaireBegin: 3, // in user local time
hourGiveNewQuestionnaireEnd: 8, // idem
numberNewQuestionnaireAtSameTime: 50, // for mass mailing
minSearchQuestionnaires: 3,
// Illustrations:
nbIllustrationsMin: 0,
nbIllustrationsMax: 1,
maxIllustrationSizeinOctet: 1000000,// pas contrôlé pour l'instant. À revoir.
mimeTypesForIllustration: [ "image/jpg", "image/jpeg", "image/png", "image/gif", "image/png" ],
// -- Upload and resize:
illustrationsWidthMaxInPx: 400,
illustrationsMiniaturesWidthMaxInPx: 200,
// Links:
nbLinksMin: 1,
nbLinksMax: 1,
// à supprimer quand tous les "require" à jour:
nbQuestionsMin: questionnaires.nbQuestionsMin,
nbQuestionsMax: questionnaires.nbQuestionsMax,
nbChoicesMax: questionnaires.nbChoicesMax,
passwordMinLength: users.password.minlength,
dirCacheUsers: users.dirCacheUsers,
dirCacheUsersAnswers: users.dirCacheUsersAnswers,
dirCacheQuestionnaires: questionnaires.dirCacheQuestionnaires,
dirCacheQuestions: questionnaires.dirCacheQuestions,
dirCacheUsersQuestionnaires: questionnaires.dirCacheUsersQuestionnaires,
dirHTMLQuestionnaire: questionnaires.dirHTMLQuestionnaire,
dirWebQuestionnaire: questionnaires.dirWebQuestionnaire
};

11
config/links.js Normal file
View File

@ -0,0 +1,11 @@
module.exports =
{
// API'routes (after "apiUrl" defined in instance.js)
linksRoute: "/link/",
// forms : à compléter avec valeurs par défaut, etc. cf modèle
Link :
{
url: { maxlength: 255, required: true },
anchor: { maxlength: 150, required: true }
}
};

20
config/log4js.js Normal file
View File

@ -0,0 +1,20 @@
module.exports =
{
"appenders":
{
"fileLogs":
{
"type": "dateFile",
"filename": "logs/day.log",
"alwaysIncludePattern" : true,
"numBackups": 7,
"keepFileExt": true
},
"console": { "type": "console" }
},
"categories":
{
"production": { "appenders": ["fileLogs"], "level": "trace" },
"default": { "appenders": [ "console" ], "level": "trace" }
}
}

20
config/mail.js Normal file
View File

@ -0,0 +1,20 @@
require('dotenv').config();
const instance = require("./instance");
module.exports =
{
"SMTP" :
{
"names": process.env.SMTP_NAMES.split(","),
"hosts": process.env.SMTP_HOSTS.split(","),
"ports": process.env.SMTP_PORTS.split(","),
"secures" : process.env.SMTP_SECURES.split(","),
"logins" : process.env.SMTP_LOGINS.split(","),
"passwords" : process.env.SMTP_PASSWORDS.split(",")
},
"SENDER" :
{
"name" : instance.senderName,
"email" : instance.senderEmail
}
};

16
config/main.js Normal file
View File

@ -0,0 +1,16 @@
require('dotenv').config();
const instance = require("./instance");
instance.env=process.env.NODE_ENV;
instance.bcryptSaltRounds=parseInt(process.env.BCRYPT_SALT_ROUNDS,10);
instance.cronToken=process.env.CRON_TOKEN;
instance.tokenPrivateKey=process.env.TOKEN_PRIVATE_KEY;
instance.maxLoginFail=parseInt(process.env.MAX_LOGIN_FAILS,10);
instance.loginFailTimeInMinutes=parseInt(process.env.LOGIN_FAIL_TIME_IN_MINUTES,10);
instance.dirCache="datas";
instance.dirHTML="front/public";
instance.dirTmp="datas/tmp";
instance.dirTmpLogin="datas/tmp/logins";
module.exports = instance;

56
config/questionnaires.js Normal file
View File

@ -0,0 +1,56 @@
module.exports =
{
// API'routes (after "apiUrl" defined in instance.js)
questionnaireRoutes: "/questionnaire",
getQuestionnaireRoutes: "/get",
previewQuestionnaireRoutes: "/preview",
publishedQuestionnaireRoutes: "/quiz/",
saveAnswersRoute: "/answer/",
getStatsQuestionnaires : "/stats/",
searchQuestionnairesRoute : "/search",
getRandomQuestionnairesRoute : "/getrandom",
searchAdminQuestionnairesRoute : "/searchadmin",
getListNextQuestionnaires: "/getlistnextquestionnaires/",
regenerateHTML: "/htmlregenerated",
// -- questions & choices :
questionsRoute: "/question/",
// -- tags :
tagsSearchRoute: "/tags/search/",
// -- answers :
getQuestionnairesWithoutAnswer: "/withoutanswer/user/",
getPreviousAnswers: "/user/answers/",
getStatsAnswers : "/user/anwswers/stats/",
getAdminStats: "/getadminstats/",
// forms : à compléter avec valeurs par défaut, etc. cf modèle
Questionnaire :
{
title: { maxlength: 255, required: true },
slug: { maxlength: 150 }, // champ requis mais calculé à partir du titre qd vide
introduction: { required: true }
},
searchQuestionnaires : { minlength: 3, required: true },
Question :
{
text: { maxlength: 255, required: true },
rank: { required: true, min:1, defaultValue:1 }
},
Choice :
{
text: { maxlength: 255, required: true }
},
nbQuestionsMin: 1,
nbQuestionsMax: 0,
nbChoicesMax: 10,
nbTagsMin: 0,
nbTagsMax: 0, // 0 = not max
// JSON and HTML dir
dirCacheQuestionnaires : "datas/questionnaires",
dirCacheQuestions : "datas/questionnaires/questions",
dirCacheUsersQuestionnaires : "datas/users/questionnaires",
dirCacheTags : "datas/questionnaires/tags",
dirHTMLQuestionnaire : "front/public/quiz",
dirHTMLTags : "front/public/quizs",
dirWebQuestionnaire : "quiz",//pour url page
dirWebTags : "quizs",// idem
nbRandomResults : 3// limite les résultat du moteur de recherche quand demande de résultats au hasard
};

11
config/tags.js Normal file
View File

@ -0,0 +1,11 @@
// fichier à supprimer une fois tous les "require" ok
const questionnaires = require("./questionnaires");
module.exports =
{
dirCacheTags : questionnaires.dirCacheTags,
dirHTMLTags : questionnaires.dirHTMLTags,
dirWebTags : questionnaires.dirWebTags,
nbTagsMin: questionnaires.nbTagsMin,
nbTagsMax: questionnaires.nbTagsMax
};

36
config/users.js Normal file
View File

@ -0,0 +1,36 @@
module.exports =
{
// API'routes (after "apiUrl" defined in instance.js)
userRoutes: "/user",
subscribeRoute: "/signup",
getGodfatherRoute: "/getgodfatherid",
checkIfIsEmailfreeRoute: "/isemailfree",
checkSubscribeTokenRoute: "/validation/",
checkLoginRoute: "/checklogin/",
connectionRoute: "/login",
getLoginLinkRoute: "/getloginlink",
connectionWithLinkRoute: "/checkloginlink",
getUserInfos: "/get/",
createUserRoute: "/create",
validateUserRoute: "/validate/",
updateUserInfos: "/modify/",
searchUserRoute: "/search/",
getGodChilds: "/getgodchilds/",
checkNewLoginLinkRoute: "/confirmnewlogin/",
checkDeleteLinkRoute: "/confirmdelete/",
getPayments: "/payment/getforoneuser/",
unsubscribeRoute: "/subscription/stop/",
getAdminStats: "/getadminstats/",
// forms : à compléter avec valeurs par défaut, etc. cf modèle
name: { maxlength: 70, required: true },
email: { maxlength: 255, required: true },
password: { minlength: 8, maxlength:72, required: true }, // https://www.npmjs.com/package/bcrypt#security-issues-and-concerns
newPassword: { minlength: 8, maxlength:72 },
codeGodfather: { maxlength: 255 },
cguOk: { value: "true", required: true },
timeDifferenceMin: -720,
timeDifferenceMax:840,
// JSON dir
dirCacheUsers : "datas/users",
dirCacheUsersAnswers : "datas/users/questionnaires/answers"
};

273
controllers/answer.js Normal file
View File

@ -0,0 +1,273 @@
const { QueryTypes } = require("sequelize");
const config = require("../config/main.js");
const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js");
const tool = require("../tools/main");
const toolFile = require("../tools/file");
const subscriptionCtrl = require("./subscription");
const questionnaireCtrl = require("./questionnaire");
const txt = require("../lang/"+config.adminLang+"/answer");
const txtGeneral = require("../lang/"+config.adminLang+"/general");
// Enregistrement d'une réponse à un questionnaire
exports.create = async (req, res, next) =>
{
try
{
const db = require("../models/index");
const checkQuestionnaireAccess=await subscriptionCtrl.checkQuestionnaireAccess(req.connectedUser.User.id, req.body.QuestionnaireId);
req.body.UserId=req.connectedUser.User.id;
if(checkQuestionnaireAccess) // l'utilisateur a déjà accès à ce questionnaire
await db["Answer"].create({ ...req.body }, { fields: ["nbQuestions", "nbCorrectAnswers", "duration", "QuestionnaireId", "UserId"] });
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 });
creaUserStatsAnwsersJson(req.body.UserId);
creaUserQuestionnairesWithoutAnswerJson(req.body.UserId);
creaUserAnswersJson(req.body.UserId);
res.status(201).json({ message: txt.responseSavedMessage });
next();
}
catch(e)
{ // à priori, l'utilisateur ne peut pas avoir envoyé de données incorrectes, donc erreur application pour admin
next(e);
}
}
// 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) =>
{
try
{
const answers=await getUserAnswersByQuestionnaire(req.params.userId, req.params.questionnaireId);
res.status(200).json(answers);
next();
}
catch(e)
{
next(e);
}
}
// Retourne les statistiques de l'utilisateur
exports.getStatsByUser = async(req, res, next) =>
{
try
{
const stats=await getUserStatsAnswers(req.params.userId);
// J'ajoute les stats générales des questionnaires pour comparaison :
stats.general=await questionnaireCtrl.getStatsQuestionnaires();
res.status(200).json(stats);
}
catch(e)
{
next(e);
}
}
// 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
// Créer la liste des réponses d'un utilisateur
const creaUserAnswersJson = async (UserId) =>
{
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 });
if(userAnswers)
{
await toolFile.createJSON(config.dirCacheUsersAnswers, UserId, userAnswers);// à surveiller car fichier pouvant devenir gros ! mais utile pour SVG côté client
return userAnswers;
}
else
return false;
}
exports.creaUserAnswersJson = creaUserAnswersJson;
// Retourne les réponses d'un utilisateurs à un questionnaire
const getUserAnswersByQuestionnaire = async (UserId, QuestionnaireId) =>
{
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].QuestionnaireId==QuestionnaireId)// 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 QuestionnaireId, je peux sortir de la boucle
break;
}
return answers;
}
// À combien de questionnaire l'utilisateur a-t'il répondu, quelle est son résultat moyen ?
const creaUserStatsAnwsersJson = async (UserId) =>
{
const db = require("../models/index");
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 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)
{
const stats =
{
nbAnswers : getUserAnswers.length,
nbQuestionnaires : getUserQuestionnaires.length
}
if(getUserStats && getUserAnswers.length!=0)
{
stats.avgCorrectAnswers=getUserStats[0].avgCorrectAnswers;
stats.avgDuration=getUserStats[0].avgDuration;
}
await toolFile.createJSON(config.dirCacheUsersAnswers, "stats"+UserId, stats);
return stats;
}
else
return false;
}
exports.creaUserStatsAnwsersJson = creaUserStatsAnwsersJson;
// Retourne les données créées par la fonction précédente
const getUserStatsAnswers = async (UserId) =>
{
let userStats=await toolFile.readJSON(config.dirCacheUsersAnswers, "stats"+UserId);
if(!userStats)
userStats=await creaUserStatsAnwsersJson(UserId);
if(!userStats)
return false;
else
return userStats;
}
// À combien de questionnaire les utilisateurs ont-ils répondu ces dernières 24 ? depuis le début ?
const getStatsAnswers = async () =>
{
const db = require("../models/index");
const getAnswers24H = await db.sequelize.query("SELECT `id` FROM `Answers` WHERE `createdAt` > ADDDATE(NOW(), -1)", { type: QueryTypes.SELECT });
const getAnswersTot = await db.sequelize.query("SELECT `id` FROM `Answers`", { type: QueryTypes.SELECT });
if(getAnswers24H && getAnswersTot)
{
const stats =
{
nbAnswers24H : getAnswers24H.length,
nbAnswersTot : getAnswersTot.length
}
return stats;
}
else
return false;
}
exports.getStatsAnswers = getStatsAnswers;
// Créer la liste des questionnaires proposés à l'utilisateur, mais auxquels il n'a pas encore répondu
const creaUserQuestionnairesWithoutAnswerJson = async (UserId) =>
{
UserId=tool.trimIfNotNull(UserId);
if(UserId===null)
return false;
const db = require("../models/index");
const userQuestionnaires=await db.sequelize.query("SELECT `QuestionnaireId` FROM `QuestionnaireAccesses` WHERE `UserId`=:id AND `QuestionnaireId` NOT IN (SELECT DISTINCT `QuestionnaireId` FROM Answers WHERE `UserId`=:id) ORDER BY `createdAt` DESC ", { replacements: { id: UserId }, type: QueryTypes.SELECT });
if(userQuestionnaires)
{
const questionnairesId=[];// les ids suffisent et allègent le fichier
for(i in userQuestionnaires)
questionnairesId.push(userQuestionnaires[i].QuestionnaireId);
await toolFile.createJSON(config.dirCacheUsersQuestionnaires+"/without", UserId, { ids: questionnairesId });
return { ids: questionnairesId };
}
else
return false;
}
exports.creaUserQuestionnairesWithoutAnswerJson = creaUserQuestionnairesWithoutAnswerJson;
// Retourne les données créées par la fonction précédente
const getUserQuestionnairesWithoutAnswer = async (UserId, begin=0, nb=10) =>
{
UserId=tool.trimIfNotNull(UserId);
if(UserId===null)
return false;
let userQuestionnaires=await toolFile.readJSON(config.dirCacheUsersQuestionnaires+"/without", UserId);
if(!userQuestionnaires)
userQuestionnaires=await creaUserQuestionnairesWithoutAnswerJson(UserId);
if(!userQuestionnaires)
return false;
let questionnaire, Questionnaires=[], i=begin;
const nbTot=userQuestionnaires.ids.length;
if(nb===0)
nb=nbTot;// peut être = 0 si l'utilisateur est à jour
while(i<nb && userQuestionnaires.ids[i])
{
questionnaire=await questionnaireCtrl.searchQuestionnaireById(userQuestionnaires.ids[i]);
if(questionnaire)
Questionnaires.push(questionnaire);
i++;
}
return { nbTot: nbTot, questionnaires: Questionnaires };
}
exports.getUserQuestionnairesWithoutAnswer = getUserQuestionnairesWithoutAnswer;

142
controllers/choice.js Normal file
View File

@ -0,0 +1,142 @@
const config = require("../config/main.js");
const tool = require("../tools/main");
const toolError = require("../tools/error");
const questionCtrl = require("./question");
const questionnaireCtrl = require("./questionnaire");
const txt = require("../lang/"+config.adminLang+"/choice");
const txtQuestion = require("../lang/"+config.adminLang+"/question");
// J'arrive aux deux contrôleurs suivants après être passé par les contrôleurs de "question" qui leur passe la main via next()
exports.create = async (req, res, next) =>
{
try
{
const db = require("../models/index");
let question=await questionCtrl.searchQuestionById(req.body.QuestionId);
if(!question)
throw { message: txt.needQuestionForChoices+req.body.QuestionId };
let choices=[], i=0, oneIsCorrect=false;
while(!tool.isEmpty(req.body["choiceText"+i]))
{
if(req.body["choiceIsCorrect"+i]=="true")
{
isCorrect=true;
oneIsCorrect=true;
}
else
isCorrect=false;
choices.push({ text:req.body["choiceText"+i], isCorrect:isCorrect, QuestionId:req.body.QuestionId });
i++;
}
if(!oneIsCorrect)
{
questionCtrl.deleteQuestionById(req.body.QuestionId);
res.status(400).json({ errors: [txt.needOneGoodChoice] });
}
else if(choices.length < 2)
{
questionCtrl.deleteQuestionById(req.body.QuestionId);
res.status(400).json({ errors: [txt.needMinChoicesForQuestion] });
}
else if(config.nbChoicesMax!==0 && choices.length>config.nbChoicesMax)
{
questionCtrl.deleteQuestionById(req.body.QuestionId);
res.status(400).json({ errors: [txt.needMaxChoicesForQuestion+config.nbChoicesMax] });
}
else
{
for(let i in choices)
await db["Choice"].create(choices[i], { fields: ["text", "isCorrect", "QuestionId"] });
question=await questionCtrl.creaQuestionJson(req.body.QuestionId);// besoin de ces données pour la réponse
await questionnaireCtrl.creaQuestionnaireJson(req.body.QuestionnaireId,true);// pour le cache + HTML
questionnaire=await questionnaireCtrl.searchQuestionnaireById(req.body.QuestionnaireId,true);// nécessaire réaffichage après ajout
res.status(201).json({ message: txtQuestion.addOkMessage , questionnaire: questionnaire });
}
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");
let question=await questionCtrl.searchQuestionById(req.params.id);
if(!question)
throw { message: txt.needQuestionForChoices+req.params.id };
let choicesUpdated=[], choicesAdded=[], i=0, isCorrect, oneIsCorrect=false;
while(!tool.isEmpty(req.body["choiceText"+i]))
{
if(req.body["choiceIsCorrect"+i]=="true")
{
isCorrect=true;
oneIsCorrect=true;
}
else
isCorrect=false;
if(!tool.isEmpty(req.body["idChoice"+i]))
choicesUpdated.push({ text:req.body["choiceText"+i], isCorrect:isCorrect, id:req.body["idChoice"+i] });
else
choicesAdded.push({ text:req.body["choiceText"+i], isCorrect:isCorrect, QuestionId:req.params.id });
i++;
}
if(!oneIsCorrect)
res.status(400).json({ errors: [txt.needOneGoodChoice] });
else if(i<2)
res.status(400).json({ errors: [txt.needMinChoicesForQuestion] });
else if(config.nbChoicesMax!==0 && i>config.nbChoicesMax)
res.status(400).json({ errors: [txt.needMaxChoicesForQuestion+config.nbChoicesMax] });
else
{
let finded=false;
for(let i in question.Choices)// = les réponses actuellement enregistrées
{
for(let j in choicesUpdated)
{
if(choicesUpdated[j].id==question.Choices[i].id)
{
finded=true;
break;
}
}
if(!finded)
await db["Choice"].destroy( { where: { id : question.Choices[i].id }, limit:1 }); // ce choix n'a pas été gardé
finded=false;
}
for(let i in choicesUpdated)
await db["Choice"].update(choicesUpdated[i], { where: { id: choicesUpdated[i].id } , fields: ["text", "isCorrect"], limit:1 });
for(let i in choicesAdded)
await db["Choice"].create(choicesAdded[i], { fields: ["text", "isCorrect", "QuestionId"] });
question=await questionCtrl.creaQuestionJson(req.params.id);// attendre pour pouvoir tout retourner
await questionnaireCtrl.creaQuestionnaireJson(req.body.QuestionnaireId,true);// pour le cache + HTML
questionnaire=await questionnaireCtrl.searchQuestionnaireById(req.body.QuestionnaireId, true);// nécessaire réaffichage après enregistrement
res.status(200).json({ message: txtQuestion.updateOkMessage , questionnaire: questionnaire });
}
next();
}
catch(e)
{
const returnAPI=toolError.returnSequelize(e);
if(returnAPI.messages)
{
res.status(returnAPI.status).json({ errors : returnAPI.messages });
next();
}
else
next(e);
}
}

237
controllers/illustration.js Normal file
View File

@ -0,0 +1,237 @@
const sharp = require("sharp");
const config = require("../config/main.js");
const configIllustrations = require("../config/illustrations.js");
const tool = require("../tools/main");
const toolError = require("../tools/error");
const toolFile = require("../tools/file");
const questionnaireCtrl = require("./questionnaire");
const txt = require("../lang/"+config.adminLang+"/illustration");
const txtGeneral = require("../lang/"+config.adminLang+"/general");
exports.create = async (req, res, next) =>
{
try
{
const db = require("../models/index");
const illustrationDatas = await checkHasFile(req);
// Le fichier peut avoir été bloqué par multer :
if(!illustrationDatas.url)
res.status(400).json({ errors: [txt.needGoodFile] });
else
{
const questionnaire=await questionnaireCtrl.searchQuestionnaireById(illustrationDatas.QuestionnaireId);
if(!questionnaire)
{
toolFile.deleteFile(configIllustrations.dirIllustrations, illustrationDatas.url);
throw { message: txt.needQuestionnaireForIllustration };
}
else if(configIllustrations.nbIllustrationsMax!==0 && questionnaire.Illustrations.length >= configIllustrations.nbIllustrationsMax)
{
toolFile.deleteFile(configIllustrations.dirIllustrations, illustrationDatas.url);
res.status(400).json({ errors: [txt.needMaxIllustrationsForQuestionnaire] });
}
else
{
const illustration=await db["Illustration"].create({ ...illustrationDatas }, { fields: ["url", "alt", "title", "caption", "QuestionnaireId"] });
const questionnaireDatas=await questionnaireCtrl.creaQuestionnaireJson(illustrationDatas.QuestionnaireId);// me permet de retourner en réponse les infos actualisées pour les afficher
res.status(201).json({ message: txt.addedOkMessage, questionnaire: questionnaireDatas });
}
}
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 illustration=await searchIllustrationById(req.params.id);
if(!illustration)
res.status(404).json({ errors: txt.notFound });
else
{
const questionnaire=await questionnaireCtrl.searchQuestionnaireById(illustration.QuestionnaireId);
if(!questionnaire)
{
if(illustrationDatas.url)
toolFile.deleteFile(configIllustrations.dirIllustrations, illustrationDatas.url);
throw { message: txt.needQuestionnaireForIllustration };
}
else if(req.connectedUser.User.status==="creator" && req.connectedUser.User.id!==questionnaire.Questionnaire.CreatorId)
{
if(illustrationDatas.url)
toolFile.deleteFile(configIllustrations.dirIllustrations, illustrationDatas.url);
res.status(401).json({ errors: txtGeneral.notAllowed });
}
else
{
if(!tool.isEmpty(req.body.fileError))
res.status(400).json({ errors: [req.body.fileError] });
else
{
const illustrationDatas = await checkHasFile(req);
// Lors d'une mise à jour, un nouveau fichier n'a pas forcément été envoyé ou peut avoir été bloqué par multer
// Mais si c'est le cas, on supprime l'ancien fichier :
if(illustrationDatas.url)
{
toolFile.deleteFile(configIllustrations.dirIllustrations+"/min", illustration.url);
}
await db["Illustration"].update({ ...illustrationDatas }, { where: { id : req.params.id } , fields: ["url", "alt", "title", "caption"], limit:1 });
const questionnaireDatas=await questionnaireCtrl.creaQuestionnaireJson(illustrationDatas.QuestionnaireId);// me permet de retourner en réponse les infos actualisées pour les afficher
res.status(200).json({ message: txt.updatedOkMessage, questionnaire: questionnaireDatas });
}
}
}
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 db = require("../models/index");
const illustration=await searchIllustrationById(req.params.id);
if(!illustration)
throw { message: txt.notFound+req.params.id };
else
{
const questionnaire=await questionnaireCtrl.searchQuestionnaireById(illustration.QuestionnaireId);
if(!questionnaire)
throw { message: txt.needQuestionnaireForIllustration };
else if(req.connectedUser.User.status==="creator" && req.connectedUser.User.id!==questionnaire.Questionnaire.CreatorId)
res.status(401).json({ errors: txtGeneral.notAllowed });
else
{
const delIllus=await deleteIllustrationById(req.params.id);
if(delIllus)
{
const questionnaireDatas=await questionnaireCtrl.creaQuestionnaireJson(illustration.QuestionnaireId);// me permet de retourner en réponse les infos actualisées pour les afficher
res.status(200).json({ message: txt.deletedOkMessage, questionnaire: questionnaireDatas });
}
else
res.status(400).json({ errors: [txtGeneral.serverError] });
}
}
next();
}
catch(e)
{
next(e);
}
}
exports.getOneIllustrationById = async (req, res, next) =>
{
try
{
const illustration=await searchIllustrationById(req.params.id);
if(illustration)
res.status(200).json(illustration);
else
res.status(404).json(null);
}
catch(e)
{
next(e);
}
}
// cron de nettoyage des fichiers illustrations n'existant plus dans la bd
exports.deleteOldFiles= async (req, res, next) =>
{
try
{
const db = require("../models/index");
const illustrations=await db["Illustration"].findAll({ attributes: ["url"] });
let saveFiles=[];
for(let i in illustrations)
saveFiles.push(illustrations[i].url);
await toolFile.deleteFilesInDirectory(configIllustrations.dirIllustrations, saveFiles);
await toolFile.deleteFilesInDirectory(configIllustrations.dirIllustrations+"/min", saveFiles);
// + le répertoire temporaire où rien ne devrait traîner :
const fileExpiration=new Date().getTime()-1000;
await toolFile.deleteOldFilesInDirectory(configIllustrations.dirIllustrationsTmp, fileExpiration);
res.status(200).json(deleteFiles);
next();
}
catch(e)
{
next(e);
}
}
// FONCTIONS UTILITAIRES
// Gère le redimensionnement de l'image si un fichier est envoyé.
// c'est multer qui vérifie dans le middleware précédent que l'image a le bon format, puis lui donne un nom (filename) si c'est ok
const checkHasFile = async (req) =>
{
if(req.file)
{ // à revoir ? : là l'image est aggrandie si + petite que demandé
await sharp(req.file.path).resize(configIllustrations.illustrationsWidthMaxInPx).toFile(configIllustrations.dirIllustrations+"/"+req.file.filename);
await sharp(req.file.path).resize(configIllustrations.illustrationsMiniaturesWidthMaxInPx).toFile(configIllustrations.dirIllustrations+"/min/"+req.file.filename);
await toolFile.deleteFile(configIllustrations.dirIllustrationsTmp, req.file.filename);
}
// La gestion du téléchargement du fichier de l'illustration fait que les données sont envoyées sous forme de chaîne de caractères (form-data), qu'il faut transformer en json
const datas=req.file ? { ...req.body, url: req.file.filename } : { ...req.body };
return datas;
}
const searchIllustrationById = async (id) =>
{
const db = require("../models/index");
const illustration=await db["Illustration"].findByPk(id);
if(illustration)
return illustration;
else
return false;
}
const deleteIllustrationById = async (id) =>
{
const db = require("../models/index");
const illustration=await searchIllustrationById(id);
if(!illustration)
throw { message: txt.notFound+id };
else
{
const nb=await db["Illustration"].destroy( { where: { id : id }, limit:1 });
if(nb===1)
{
toolFile.deleteFile(configIllustrations.dirIllustrations, illustration.url);
toolFile.deleteFile(configIllustrations.dirIllustrations+"/min", illustration.url);
questionnaireCtrl.creaQuestionnaireJson(illustration.QuestionnaireId);
}
return true;
}
}
exports.deleteIllustrationById = deleteIllustrationById;

144
controllers/link.js Normal file
View File

@ -0,0 +1,144 @@
const config = require("../config/main.js");
const configLinks = require("../config/links.js");
const tool = require("../tools/main");
const toolError = require("../tools/error");
const questionnaireCtrl = require("./questionnaire");
const txt = require("../lang/"+config.adminLang+"/link");
const txtGeneral = require("../lang/"+config.adminLang+"/general");
exports.create = async (req, res, next) =>
{
try
{
const db = require("../models/index");
const questionnaire=await questionnaireCtrl.searchQuestionnaireById(req.body.QuestionnaireId);
if(!questionnaire)
throw { message: txt.needQuestionnaire };
else if(configLinks.nbLinksMax!==0 && questionnaire.Links.length>=configLinks.nbLinksMax)
res.status(400).json({ errors: txt.needMaxLinksForQuestionnaire });
else
{
const link=await db["Link"].create({ ...req.body }, { fields: ["url","anchor", "QuestionnaireId"] });
questionnaireCtrl.creaQuestionnaireJson(req.body.QuestionnaireId);
const questionnaireDatas=await questionnaireCtrl.creaQuestionnaireJson(req.body.QuestionnaireId);// me permet de retourner en réponse les infos actualisées pour les afficher
res.status(201).json({ message: txt.addedOkMessage, questionnaire: questionnaireDatas });
}
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 link=await searchLinkById(req.params.id);
if(!link)
res.status(404).json({ errors: txt.notFound });
else
{
const questionnaire=await questionnaireCtrl.searchQuestionnaireById(link.QuestionnaireId);
if(!questionnaire)
throw { message: txt.needQuestionnaire };
else if(req.connectedUser.User.status==="creator" && req.connectedUser.User.id!==questionnaire.Questionnaire.CreatorId)
res.status(401).json({ errors: txtGeneral.notAllowed });
else
{
await db["Link"].update({ ...req.body }, { where: { id : req.params.id } , fields: ["url","anchor"], limit:1 });
const questionnaireDatas=await questionnaireCtrl.creaQuestionnaireJson(link.QuestionnaireId);// me permet de retourner en réponse les infos actualisées pour les afficher
res.status(200).json({ message: txt.updatedOkMessage, questionnaire: questionnaireDatas });
}
}
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 db = require("../models/index");
const link=await searchLinkById(req.params.id);
if(!link)
res.status(404).json({ errors: txt.notFound });
else
{
const questionnaire=await questionnaireCtrl.searchQuestionnaireById(link.QuestionnaireId);
if(!questionnaire)
throw { message: txt.needQuestionnaire };
else if(req.connectedUser.User.status==="creator" && req.connectedUser.User.id!==questionnaire.Questionnaire.CreatorId)
res.status(401).json({ errors: txtGeneral.notAllowed });
else
{
const nb=await db["Link"].destroy( { where: { id : req.params.id }, limit:1 });
if(nb===1)
{
const questionnaireDatas=await questionnaireCtrl.creaQuestionnaireJson(link.QuestionnaireId);
res.status(200).json({ message: txt.deletedOkMessage, questionnaire: questionnaireDatas });
}
else // ne devrait pas être possible, car déjà testé + haut !
throw { message: txt.needQuestionnaire };
}
}
next();
}
catch(e)
{
next(e);
}
}
exports.getOneById = async (req, res, next) =>
{
try
{
const link=await searchLinkById(req.params.id);
if(link)
res.status(200).json(link);
else
res.status(404).json(null);
next();
}
catch(e)
{
next(e);
}
}
// FONCTIONS UTILITAIRES
const searchLinkById = async (id) =>
{
const db = require("../models/index");
const link = await db["Link"].findByPk(id);
if(link)
return link;
else
return false;
}

129
controllers/pause.js Normal file
View File

@ -0,0 +1,129 @@
const { QueryTypes } = require("sequelize");
const config = require("../config/main.js");
const tool = require("../tools/main");
const toolError = require("../tools/error");
const userCtrl=require("./user");
const txt = require("../lang/"+config.adminLang+"/pause");
const txtGeneral = require("../lang/"+config.adminLang+"/general");
exports.create = async (req, res, next) =>
{
try
{
const db = require("../models/index");
const connectedUser=req.connectedUser;
req.body.SubscriptionId=connectedUser.Subscription.id;
await db["Pause"].create({ ...req.body });
userCtrl.creaUserJson(connectedUser.User.id);
res.status(201).json({ message: txt.createdOkMessage });
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 connectedUser=req.connectedUser;
if(!checkPauseIsOk(req.params.id, connectedUser))
res.status(404).json({ errors: txtGeneral.serverError });
else
{
await db["Pause"].update({ ...req.body }, { where: { id : req.params.id } , fields: ["name", "startingAt", "endingAt"], limit:1 }),
userCtrl.creaUserJson(connectedUser.User.id);
res.status(201).json({ message: txt.updatedOkMessage });
}
next();
}
catch(e)
{
const returnAPI=toolError.returnSequelize(e);
if(returnAPI.length!==0)
{
res.status(returnAPI.status).json({ errors : returnAPI.messages });
next();
}
else
next(e);
}
}
exports.delete = async (req, res, next) =>
{
try
{
const db = require("../models/index");
const connectedUser=req.connectedUser;
if(!checkPauseIsOk(req.params.id, connectedUser))
res.status(404).json({ errors: txtGeneral.serverError });
else
{
await db["Pause"].destroy({ where: { id : req.params.id }, limit:1 });
userCtrl.creaUserJson(connectedUser.User.id);
res.status(200).json({ message: txt.deletedOkMessage });
}
next();
}
catch(e)
{
next(e);
}
}
// Cron
exports.deleteOldPauses= async(req, res, next) =>
{
try
{
const db = require("../models/index");
// on laisse deux jours de rab pour les décalages horaires & co
const userPauses=await db.sequelize.query("SELECT DISTINCT `Subscriptions`.`UserId` FROM `Subscriptions` INNER JOIN `Pauses` ON `Subscriptions`.`id`=`Pauses`.`SubscriptionId` WHERE ADDDATE(`endingAt`, 2) < NOW()", { type: QueryTypes.SELECT });
if(userPauses.length!==0)
{
await db.sequelize.query("DELETE FROM `Pauses` WHERE ADDDATE(`endingAt`, 2) < NOW()");
for(i in userPauses)
await userCtrl.creaUserJson(userPauses[i].UserId);
}
res.status(200).json(true);
next();
}
catch(e)
{
next(e);
}
}
// FONCTIONS UTILITAIRES
// Vérifie si la période de pause appartient bien à cet utilisateur
const checkPauseIsOk = (idPause, user) =>
{
if(!user.Pauses)
return false;
let PauseIsOk=false, i=0;
while (!PauseIsOk && user.Pauses[i])
{
if(user.Pauses[i].id == idPause)// ! n'ont pas forcément le même type
PauseIsOk=true;
else
i++;
}
return PauseIsOk;
}

132
controllers/payment.js Normal file
View File

@ -0,0 +1,132 @@
const config = require("../config/main.js");
const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js");
const tool = require("../tools/main");
const toolMail = require("../tools/mail");
const txt = require("../lang/"+config.adminLang+"/payment");
const txtUser = require("../lang/"+config.adminLang+"/user");
const txtGeneral = require("../lang/"+config.adminLang+"/general");
const userCtrl = require("./user");
exports.getOneUserPayments = async (req, res, next) =>
{
try
{
const connectedUser=req.connectedUser;
if(connectedUser===false || ["admin","manager"].indexOf(connectedUser.User.status) === -1)
res.status(403).json({ errors: txtGeneral.notAllowed });
else
{
const db = require("../models/index");
const Payments=await db["Payment"].findAll({ where: { UserId: req.params.id }, order: [["createdAt", "DESC"]] });
res.status(200).json(Payments);
}
next();
}
catch(e)
{
next(e);
}
}
exports.saveUserPaymentInfos = async (req, res, next) =>
{
// exemple d'url : WP-infos.html?dom=NOM_DE_DOMAINE_DU_BOUTON&ref=ID_DU_USER&mt=MONTANT_TTC&cmd=CODE_COMMANDE_WEBPORTAGE&cl=NOM+DU+CLIENT&hKey=le_hash_en_md5
// dom=wikilerni.com&ref=5&mt=24&cmd=de11de&cl=monsieur+dugenoux&hKey=998dccdef52bd27dc0a674941c0e9340
try
{
require('dotenv').config();
const db = require("../models/index");
const md5 = require("md5");
const montantsAbonnement=["12","24", "60", "120"];
// !! attention req.query enlève les caractères spéciaux comme les "+" des paramètres de l'url. Il vaut donc mieux utiliser req.url pour comparer avec le hash au reste de la chaîne.
const testUrl=req.url.slice(req.url.indexOf("?")+1,req.url.lastIndexOf("&"));
console.log(testUrl);
console.log(md5(testUrl+process.env.MD5_WP));
if(md5(testUrl+process.env.MD5_WP)!==req.query.hKey) // le hashage est effectué après le remplacement des caractères spéciaux dans l'url.
throw { message: txt.paymentUrlFail+testUrl };
else if(req.query.ref==="" || montantsAbonnement.indexOf(req.query.mt) === -1)
throw { message: txt.paymentDatasFail+testUrl };
else
{
const client=await userCtrl.searchUserById(req.query.ref);
if(!client)
throw { message: txt.paymentUserNotFound+testUrl };
else
{
// Si cet utilisateur a un parrain on le remercie et lui ajoute 30 jours d'abonnement
// Cela impacte aussi la durée de l'abonnement commandé par l'utilisateur
let numberOfDays=365;
if(client.User.GodfatherId)
{
const parrain=await userCtrl.searchUserById(client.User.GodfatherId);
if(parrain)
{
parrain.Subscription.numberOfDays+=30;
numberOfDays+=30;
await db["Subscription"].update({ ...parrain.Subscription }, { where: { UserId : client.User.GodfatherId }, fields: ["numberOfDays"], limit:1 });
userCtrl.creaUserJson(client.User.GodfatherId);
const mapMail =
{
USER_NAME: parrain.User.name
};
const mailDatas=
{
mailSubject: txt.mailPaymentThankGodfatherSubject,
mailPreheader: txt.mailPaymentThankGodfatherSubject,
mailTitle: txt.mailPaymentThankGodfatherSubject,
mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.userHomePage,
mailHeaderLinkTxt: txt.mailPaymentLinkTxt,
mailMainContent: tool.replaceAll(txt.mailPaymentThankGodfatherBodyHTML, mapMail),
linksCTA: [{ url:config.siteUrl+"/"+configTpl.userHomePage, txt:txt.mailPaymentLinkTxt }],
mailRecipientAddress: parrain.User.email
}
await toolMail.sendMail(parrain.User.smtp, parrain.User.email, txt.mailPaymentThankGodfatherSubject, tool.replaceAll(txt.mailPaymentThankGodfatherBodyTxt, mapMail), "", mailDatas);
}
else
res.alerte=txt.paymentGodfatherNotFound+client.User.GodfatherId;
}
const infosClient=
{
clientName: req.query.cl,
amount: req.query.mt,
codeCommande: req.query.cmd,
UserId: client.User.id,
numberOfDays: client.Subscription.numberOfDays+numberOfDays
};
await db["Payment"].create({ ...infosClient }, { fields: ["clientName", "amount", "codeCommande", "UserId"] });
await db["Subscription"].update({ ...infosClient }, { where: { UserId : infosClient.UserId }, fields: ["numberOfDays"], limit:1 });
userCtrl.creaUserJson(infosClient.UserId);
// mail remerciement abonné
const mapMail2 =
{
SITE_NAME: config.siteName,
USER_NAME: client.User.name,
NBDAYS: numberOfDays
};
const mailDatas2 =
{
mailSubject: txt.mailPaymentThankSubject,
mailPreheader: txt.mailPaymentThankSubject,
mailTitle: txt.mailPaymentThankSubject,
mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.userHomePage,
mailHeaderLinkTxt: txt.mailPaymentLinkTxt,
mailMainContent: tool.replaceAll(txt.mailPaymentThankBodyHTML, mapMail2),
linksCTA: [{ url:config.siteUrl+"/"+configTpl.userHomePage, txt:txt.mailPaymentLinkTxt }],
mailRecipientAddress: client.User.email
}
await toolMail.sendMail(client.User.smtp, client.User.email, txt.mailPaymentThankSubject, tool.replaceAll(txt.mailPaymentThankBodyTxt, mapMail2), "", mailDatas2);
// + info admin site
await toolMail.sendMail(0, config.adminEmail, txt.mailPaymentAdminNoticeSubject, txt.mailPaymentAdminNoticeBodyTxt.replace("EMAIL", client.User.email), txt.mailPaymentAdminNoticeBodyHTML.replace("EMAIL", client.User.email));
res.status(200).json(true);
}
}
next();
}
catch(e)
{
next(e);
}
}

179
controllers/question.js Normal file
View File

@ -0,0 +1,179 @@
const { Op, QueryTypes } = require("sequelize");
const config = require("../config/main.js");
const tool = require("../tools/main");
const toolError = require("../tools/error");
const toolFile = require("../tools/file");
const questionnaireCtrl = require("./questionnaire");
const txt = require("../lang/"+config.adminLang+"/question");
const txtGeneral = require("../lang/"+config.adminLang+"/general");
exports.create = async (req, res, next) =>
{
try
{
const db = require("../models/index");
const questionnaire=await questionnaireCtrl.searchQuestionnaireById(req.body.QuestionnaireId);
if(!questionnaire)
throw txt.needQuestionnaire;
else if(config.nbQuestionsMax!==0 && questionnaire.Questions.length>=config.nbQuestionsMax)
res.status(400).json({ errors: [txt.needMaxQuestions+config.nbQuestionsMax] });
else
{
const question=await db["Question"].create({ ...req.body }, { fields: ["text", "explanation", "rank", "QuestionnaireId"] });
questionnaireCtrl.creaQuestionnaireJson(req.body.QuestionnaireId);
req.body.QuestionId=question.id;
next();// je passe la main au contrôleur qui gère les réponses possibles saisies pour cette question
return true;
}
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 question=await searchQuestionById(req.params.id);
if(!question)
throw txt.notFound;
const questionnaire=questionnaireCtrl.searchQuestionnaireById(question.Question.QuestionnaireId);
if(!questionnaire)
throw txt.needQuestionnaire;
if(req.connectedUser.User.status==="creator" && req.connectedUser.User.id!==questionnaire.CreatorId)
res.status(401).json({ errors: txtGeneral.notAllowed });
else
{
await db["Question"].update({ ...req.body }, { where: { id : req.params.id } , fields: ["text", "explanation", "rank"], limit:1 });
next();// je passe la main au contrôleur qui gère les réponses possibles saisies pour cette question
return true;
}
}
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 question=await searchQuestionById(req.params.id);
if(!question)
throw txt.notFound;
let questionnaire=questionnaireCtrl.searchQuestionnaireById(question.Question.QuestionnaireId);
if(!questionnaire)
throw txt.needQuestionnaire;
if(req.connectedUser.User.status==="creator" && req.connectedUser.User.id!==questionnaire.CreatorId)
res.status(401).json({ errors: txtGeneral.notAllowed });
else
{
if(await deleteQuestionById(req.params.id))
{
questionnaire=await questionnaireCtrl.searchQuestionnaireById(req.body.QuestionnaireId,true);// nécessaire réaffichage après suppression
res.status(200).json({ message: txt.deleteOkMessage, questionnaire: questionnaire });
}
else
res.status(400).json({ errors: txtGeneral.serverError });
}
next();// pour middleware mesurant la durée de la réponse
}
catch(e)
{
next(e);
}
}
// CRON supprimant les fichiers Json obsolètes
exports.deleteJsonFiles= async (req, res, next) =>
{
try
{
const db = require("../models/index");
const questions=await db["Question"].findAll({ attributes: ["id"] });
let saveFiles=[];
for(let i in questions)
saveFiles.push(questions[i].id+".json");
const deleteFiles = await toolFile.deleteFilesInDirectory(config.dirCacheQuestions, saveFiles);
res.status(200).json(deleteFiles);
next();
}
catch(e)
{
next(e);
}
}
// FONCTIONS UTILITAIRES
// Création du fichier des données relatives à une question (avec les réponses proposées)
const creaQuestionJson = async (id) =>
{
const db = require("../models/index");
const Question=await db["Question"].findByPk(id);
if(Question)
{
let datas={ Question };
const Choices=await db["Choice"].findAll({ where: { QuestionId: Question.id }});
if(Choices)
datas.Choices=Choices;
await toolFile.createJSON(config.dirCacheQuestions, id, datas);
return datas;
}
else
return false;
}
exports.creaQuestionJson = creaQuestionJson;
// Retourne les données d'une question
const searchQuestionById = async (id) =>
{
const question=await toolFile.readJSON(config.dirCacheQuestions, id);
if(question)
return question;
else
return await creaQuestionJson(id);
}
exports.searchQuestionById = searchQuestionById;
// Supprime une question et ses dépendances
const deleteQuestionById = async (id) =>
{
const db = require("../models/index");
const question=await searchQuestionById(id);
if(!question)
throw txt.notFound;
const nb=await db["Question"].destroy( { where: { id : id }, limit:1 });
if(nb===1)
{
toolFile.deleteJSON(config.dirCacheQuestions, id);
questionnaireCtrl.creaQuestionnaireJson(question.Question.QuestionnaireId);
return true;
}
else
throw txt.notFound;
}
exports.deleteQuestionById = deleteQuestionById;

View File

@ -0,0 +1,728 @@
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 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
{
const db = require("../models/index");
req.body.CreatorId=req.connectedUser.User.id;
const questionnaire=await db["Questionnaire"].create({ ...req.body }, { fields: ["title", "slug", "introduction", "keywords", "publishingAt", "language", "estimatedTime", "CreatorId"] });
creaStatsQuestionnairesJson();
//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
{
await db["Questionnaire"].update({ ...req.body }, { where: { id : req.params.id } , fields: ["title", "slug", "introduction", "keywords", "publishingAt", "language", "estimatedTime"], limit:1 }); // voir si admin aura le droit de changer le créateur ? et ajouter une gestion des redirection si slug change ?
creaStatsQuestionnairesJson();// le nombre de quizs publiés peut avoir changé
}
//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 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
{
// Permet de supprimer les fichiers associés en plus du sql. Inutile pour link qui n'a pas de fichier.
// À faire avant la suppression SQL du questionnaire entraînant la suppression en cascade du reste.
for(i in questionnaire.Questions)
await questionCtrl.deleteQuestionById(questionnaire.Questions[i].id);
for(i in questionnaire.Illustrations)
await illustrationCtrl.deleteIllustrationById(questionnaire.Illustrations[i].id);
const nb=await db["Questionnaire"].destroy( { where: { id : req.params.id }, limit:1 });
if(nb===1)
{
await toolFile.deleteJSON(config.dirCacheQuestionnaires, req.params.id);
res.status(200).json({ message: txtGeneral.deleteOkMessage });
// 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 :
creaNewQuestionnairesJson();
creaStatsQuestionnairesJson();
// Éventuellement regénérer les caches listant les réponses/quizs des users ayant accès à ce questionnaire ?
// ++ HTML
}
else
{
const Err=new Error;
error.status=404;
error.message=txtQuestionnaire.notFound+" ("+req.params.id+")";
throw Err;
}
}
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 page
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 `introduction` 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 `introduction` 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();
let dateNeeded="", questionnairesDatas, dateSearch, dateQuestionnaire;
for(let i in questionnaires)
{
questionnairesDatas=await searchQuestionnaireById(questionnaires[i].id);
questionnaires[i].isPublishable=checkQuestionnaireIsPublishable(questionnairesDatas, false); // le questionnaire est-il complet ?
if(dateNeeded==="") // si il y a plus de 24H entre deux dates de publication, c'est mal !
{
if(i==0)
dateSearch=new Date(Date.now()+3600*1000*24);
else
dateSearch=new Date(questionnaires[i-1].datePublishing).getTime()+3600*1000*24;
dateQuestionnaire=new Date(questionnaires[i].datePublishing).getTime();
if(dateQuestionnaire > dateSearch)
dateNeeded=dateSearch;
}
}
if(questionnaires.length > 0 && dateNeeded==="")
dateNeeded=new Date(dateQuestionnaire+3600*1000*24);// le jour suivant celui du dernier questionnaire
else
dateNeeded=new Date();// rien pour ce jour
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);
creaNewQuestionnairesJson();// provoque mise à jour du HTLM, RSS, etc.
res.messageToNext=txtQuestionnaire.haveBeenRegenerated.replace("#NB1", nb);
next();
}
catch(e)
{
next(e);
}
}
// CRONS
exports.deleteJsonFiles= async (req, res, next) =>
{
// ajouter le suppression des fichiers HTML ? -> plutôt le faire manuellement lors de la suppression du questionnaire
try
{
const db = require("../models/index");
const questionnaires=await db["Questionnaire"].findAll({ attributes: ["id"] });
let saveFiles=[];
for(let i in questionnaires)
saveFiles.push(questionnaires[i].id+".json");
const deleteFiles = await toolFile.deleteFilesInDirectory(config.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));
console.log(txtQuestionnaire.haveBeenPublished.replace(":NB", nb));
await toolMail.sendMail(0, config.adminEmail, "Publication articles", txtQuestionnaire.haveBeenPublished.replace(":NB", nb), "<p>"+txtQuestionnaire.haveBeenPublished.replace(":NB", nb)+"</p>");
next();
}
catch(e)
{
next(e);
}
}
// FONCTIONS UTILITAIRES
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 autres fonctions appelées ci-dessous
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 });
// + liste des tags utilisés :
tagCtrl.creaUsedTagsJson();
// si le quiz était publié jusqu'ici, il me faut supprimer son fichier HTML (revenir pour réactiver)
/*if(wasPublished)
toolFile.deleteFile(config.dirHTMLQuestionnaire, Questionnaire.slug+".html");*/
}
// peut impacter la liste des derniers si des informations affichées ont changé
creaNewQuestionnairesJson();// peut avoir été impacté
// + 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);
return datas;
}
else
return false;
}
exports.creaQuestionnaireJson = creaQuestionnaireJson;
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) // le nombre de réponses mini étant contrôlé au moment de l'enregistrement de la question
return false;
if(datas.Tags==undefined || datas.Tags.length < configTags.nbTagsMin)
return false;
if(datas.Links==undefined || datas.Links.length < configLinks.nbLinksMin)
return false;
if(datas.Illustrations==undefined || datas.Illustrations.length < configIllustrations.nbIllustrationsMin)
return false;
return true;
}
const creaQuestionnaireHTML = async (id, preview=false) =>
{
// besoin de toutes les infos concernant le questionnaire pour les transmettre à la vue
// à terme : séparer la création de la partie body pouvant être retournée pour recharger page, de la génération complète pour créer le fichier html
const questionnaire=await searchQuestionnaireById(id, true);
if(!questionnaire)
return false;
if(questionnaire.Questionnaire.isPublished===false && preview===false)
return false;
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.dirHTMLQuestionnaire+"/"+questionnaire.Questionnaire.slug+".html"
}
const html=await compiledFunction(pageDatas);
if(preview===false)
{
await toolFile.createHTML(config.dirHTMLQuestionnaire, 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)
{
let question; Questions=[];
const author=await userCtrl.searchUserById(questionnaire.Questionnaire.CreatorId);
if(author)
questionnaire.Questionnaire.CreatorName=author.User.name;
for(i in questionnaire.Questions)
{
question=await questionCtrl.searchQuestionById(questionnaire.Questions[i].id);
if(question)
Questions.push(question);
}
questionnaire.Questions=Questions;
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.dirHTMLQuestionnaire, questionnaires[i].slug+".html")===false)
{
await creaQuestionnaireHTML(questionnaires[i].id);
console.log("je viens de publier le questionnaire :"+questionnaires[i].slug);
await toolMail.sendMail(0, config.adminEmail, "Publication d'un article", "Je viens de publier le quiz : "+config.siteUrl+"/quizs/"+questionnaires[i].slug+".html", "<p>Je viens de publier le quiz : "+config.siteUrl+"/quizs/"+questionnaires[i].slug+".html</p>");
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: [["updatedAt", "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;

391
controllers/subscription.js Normal file
View File

@ -0,0 +1,391 @@
// Contrôleurs gérant l'abonnement de l'utilisateur mais aussi les questionnaires auxquels il a accès
const { Op, QueryTypes } = require("sequelize");// pour certaines requêtes sql
const jwt = require("jsonwebtoken");
const config = require("../config/main");
const configQuestionnaires = require("../config/questionnaires");
const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js");
const tool = require("../tools/main");
const toolFile = require("../tools/file");
const toolMail = require("../tools/mail");
const txt = require("../lang/"+config.adminLang+"/subscription");
const txtQuestionnaire= require("../lang/"+config.adminLang+"/questionnaire");
const txtQuestionnaireAccess= require("../lang/"+config.adminLang+"/questionnaireaccess");
const txtGeneral= require("../lang/"+config.adminLang+"/general");
const answerCtrl = require("./answer");
const questionnaireCtrl = require("./questionnaire");
const userCtrl = require("./user");
// Clic sur lien de désabonnement aux alertes email
exports.unsubscribeLink = async (req, res, next) =>
{
try
{
const db = require("../models/index");
const userDatas= await userCtrl.checkTokenUser(req.params.token);
await db["User"].update({ newsletterOk: false }, { where: { id : userDatas.User.id }, limit:1 });
await db["Subscription"].update({ noticeOk: false }, { where: { UserId : userDatas.User.id }, limit:1 });
userCtrl.creaUserJson(userDatas.User.id);
res.status(200).json({ message: txt.unsubscriptionOk });
next();
}
catch(e)
{
next(e);
}
}
// CRONS
// + une autre notification le jour "J" ??? vérifier si l'utilisateur actif les dernières 48H et laisser les autres tranquilles ?
exports.notifyExpirationFreeAccount= async(req, res, next) =>
{
try
{
const db = require("../models/index");
const dateExpirationMin=new Date(new Date().getTime()-(config.freeAccountTimingInDays-config.freeAccountExpirationNotificationInDays+1)*24*3600*1000);
const dateExpirationMax=new Date(new Date().getTime()-(config.freeAccountTimingInDays-config.freeAccountExpirationNotificationInDays)*24*3600*1000);
console.log("je cherche les utilisateurs freemium dont l'abonnement a été créé entre le "+dateExpirationMin+" et le "+dateExpirationMax+".");
const users=await db["Subscription"].findAll(
{
where:
{
[Op.and]:
[
{ numberOfDays: config.freeAccountTimingInDays },
{ createdAt: { [Op.gte]: dateExpirationMin } },
{ createdAt: { [Op.lt]: dateExpirationMax } }
]
},
attributes: ["UserId"]
});
const sendNotification = async (user) =>
{
let userInfos=await userCtrl.searchUserById(user.UserId);
if(userInfos && userInfos.User.status=="user")
{
//const token=jwt.sign({ userId: userInfos.User.id }, config.tokenPrivateKey, { expiresIn: config.tokenLoginLinkTimeInHours });
const mapMail =
{
SITE_NAME: config.siteName,
USER_NAME: userInfos.User.name,
LINK_URL : config.siteUrl+"/"+configTpl.updateAccountPage+"#abo"
};
const mailDatas=
{
mailSubject: txt.mailEndFreeTimeSubject,
mailPreheader: txt.mailEndFreeTimeSubject,
mailTitle: txt.mailEndFreeTimeSubject,
mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.updateAccountPage+"#abo",
mailHeaderLinkTxt: txt.mailEndFreeTimeLinkTxt,
mailMainContent: tool.replaceAll(txt.mailEndFreeTimeBodyHTML, mapMail),
linksCTA: [{ url:config.siteUrl+"/"+configTpl.updateAccountPage+"#abo", txt:txt.mailEndFreeTimeLinkTxt }],
mailRecipientAddress: userInfos.User.email
}
await toolMail.sendMail(userInfos.User.smtp, userInfos.User.email, txt.mailEndFreeTimeSubject, tool.replaceAll(txt.mailEndFreeTimeBodyTxt, mapMail), "", mailDatas);
}
}
for(let i in users)
sendNotification(users[i]);
if(res.message)
res.message+="\n"+users.length+txt.mailEndFreeTimeMessage;
else
res.message=users.length+txt.mailEndFreeTimeMessage;
res.status(200).json(true);
console.log(users.length+txt.mailEndFreeTimeMessage);
next();
}
catch(e)
{
next(e);
}
}
exports.notifyExpirationAccount= async(req, res, next) =>
{ // utiliser la vue puis des recherches en sql natif ou ajouter un modèle : https://stackoverflow.com/questions/48407329/cant-able-to-create-views-in-mysql-using-sequelize-orm
try
{
const db = require("../models/index");
const getUsers= async (nbDays) =>
{
const dateExpirationMin=new Date(new Date().getTime()+nbDays*24*3600*1000);
const dateExpirationMax=new Date(new Date().getTime()+(nbDays+1)*24*3600*1000);
const users=await db["Subscription"].findAll(
{
where:
{
[Op.and]:
[
{ numberOfDays: { [Op.gt]: config.freeAccountTimingInDays } },
db.sequelize.where(db.sequelize.fn('ADDDATE', db.sequelize.col('createdAt'), db.sequelize.literal('INTERVAL `numberOfDays` DAY')), { [Op.gte]: dateExpirationMin }),
db.sequelize.where(db.sequelize.fn('ADDDATE', db.sequelize.col('createdAt'), db.sequelize.literal('INTERVAL `numberOfDays` DAY')), { [Op.lt]: dateExpirationMax })
]
},
attributes: ["UserId"],
});
return users;
}
const sendNotification= async (user, mail) => // utiliser la paramètre "mail" pour adapter le texte du mail !
{
let userInfos=await searchUserById(user.UserId);
if(userInfos && userInfos.User.status=="user")
{
//const token=jwt.sign({ userId: userInfos.User.id }, config.tokenPrivateKey, { expiresIn: config.tokenLoginLinkTimeInHours });
const mapMail =
{
SITE_NAME: config.siteName,
USER_NAME: userInfos.User.name,
LINK_URL : config.siteUrl+"/"+configTpl.updateAccountPage+"#abo"
};
const mailDatas=
{
mailSubject: txt.mailExpirationSubject,
mailPreheader: txt.mailExpirationSubject,
mailTitle: txt.mailExpirationSubject,
mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.updateAccountPage+"#abo",
mailHeaderLinkTxt: txt.mailExpirationLinkTxt,
mailMainContent: tool.replaceAll(txt.mailExpirationBodyHTML, mapMail),
linksCTA: [{ url:config.siteUrl+"/"+configTpl.updateAccountPage+"#abo", txt:txt.mailExpirationLinkTxt }],
mailRecipientAddress: userInfos.User.email
}
await toolMail.sendMail(userInfos.User.smtp, userInfos.User.email, txt.mailExpirationSubject, tool.replaceAll(txt.mailExpirationBodyTxt, mapMail), "", mailDatas);
}
}
// première salve :
let users1=await getUsers(config.accountExpirationFirstNotificationInDays);
for(i in users1)
sendNotification(users1[i], "first");
// deuxième relance :
let users2=await getUsers(config.accountExpirationSecondNotificationInDays);
for(i in users2)
sendNotification(users2[i], "second");
if(res.message)
res.message+="\n"+tool.replaceAll(txt.mailExpirationMessage, { FIRST:users1.length , SECOND:users2.length });
else
res.message=tool.replaceAll(txt.mailExpirationMessage, { FIRST:users1.length , SECOND:users2.length });
res.status(200).json(true);
next();
}
catch(e)
{
next(e);
}
}
// CRON donnant accès à l'utilisateur à nouveau quiz suivant son choix
exports.addNewQuestionnaireUsers = async(req, res, next) =>
{
try
{
const db = require("../models/index");
// Utilisateurs dont l'abonnement est toujours actif et souhaitant recevoir un nouveau quiz le jour de l'appel de cette méthode.
// Le tout en heure locale et en ignorant ceux qui ont déjà été servis ce jour.
const subscriptionsOk = await db.sequelize.query("SELECT `Subscriptions`.`id` as SubscriptionId, `UserId`, `name`, `email`, `smtp`, `language`, `noticeOk`, `receiptDays`, ADDDATE(UTC_TIMESTAMP, INTERVAL `timeDifference` MINUTE) AS localDate FROM `Subscriptions` INNER JOIN `Users` ON `Subscriptions`.`UserId`=`Users`.`id` WHERE `status`='user' AND ADDDATE(`Subscriptions`.`createdAt`, `numberOfDays`) > UTC_TIMESTAMP HAVING HOUR(localDate) > "+config.hourGiveNewQuestionnaireBegin+" AND HOUR(localDate) < "+config.hourGiveNewQuestionnaireEnd+" AND LOCATE(DAYOFWEEK(localDate),receiptDays)!=0 AND SubscriptionId NOT IN (SELECT DISTINCT `SubscriptionId` FROM `Pauses` WHERE ADDDATE(`startingAt`, INTERVAL `timeDifference` MINUTE) <= localDate AND ADDDATE(`endingAT`, INTERVAL `timeDifference` MINUTE) > localDate) AND `UserId` NOT IN (SELECT DISTINCT `UserId` FROM `QuestionnaireAccesses` WHERE DATEDIFF(NOW(),`createdAt`) < 1 AND `selfCreatedOk` = false) LIMIT "+config.numberNewQuestionnaireAtSameTime, { type: QueryTypes.SELECT });
if(subscriptionsOk.length===0)
{
res.status(200).json({ message: txt.allSubscriptionProcessed });
//console.log(txt.allSubscriptionProcessed);
}
else
{
await toolMail.sendMail(0, config.adminEmail, "Abonnements à traiter", subscriptionsOk.length+" abonnements à traiter.", "<p>"+subscriptionsOk.length+" abonnements à traiter.</p>");
let newQuestionnaire, access, questionnaire, token;
for (let i in subscriptionsOk)
{
newQuestionnaire=await db.sequelize.query("SELECT `Questionnaires`.`id`, `title`, `slug`, `introduction`, `url`, `anchor`,`estimatedTime` FROM `Questionnaires` INNER JOIN `Links` ON `Links`.`QuestionnaireId`=`Questionnaires`.`id` WHERE `isPublished`=1 AND `language`='"+subscriptionsOk[i].language+"' AND `Questionnaires`.`id` NOT IN (SELECT `QuestionnaireId` FROM `QuestionnaireAccesses` WHERE `UserId`="+subscriptionsOk[i].UserId+") ORDER BY `id` LIMIT 1", { type: QueryTypes.SELECT });
if(newQuestionnaire.length!==0)
{
access=await db["QuestionnaireAccess"].create({ QuestionnaireId: newQuestionnaire[0].id, UserId: subscriptionsOk[i].UserId, selfCreatedOk: false });
if(access)
{
answerCtrl.creaUserQuestionnairesWithoutAnswerJson(subscriptionsOk[i].UserId);
if(subscriptionsOk[i].noticeOk)
{
token=jwt.sign({ userId: subscriptionsOk[i].UserId }, config.tokenPrivateKey, { expiresIn: config.tokenUnsubscribeLinkTimeInDays });
const mapMail =
{
USER_NAME: subscriptionsOk[i].name,
QUESTIONNAIRE_URL: config.siteUrl+"/"+configQuestionnaires.dirWebQuestionnaire+"/"+newQuestionnaire[0].slug+".html#questionnaire",
UNSUBSCRIBE_URL: config.siteUrl+"/"+configTpl.stopMailPage+token
};
const mailDatas=
{
mailSubject: newQuestionnaire[0].title,
mailPreheader: newQuestionnaire[0].title,
mailTitle: newQuestionnaire[0].title,
mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.stopMailPage+token,
mailHeaderLinkTxt: txt.mailStopMailLinkTxt,
mailMainContent: newQuestionnaire[0].introduction+"<p><b>"+txtQuestionnaire.estimatedTime+"</b> "+txtQuestionnaire.estimatedTimeOption[newQuestionnaire[0].estimatedTime]+".</p>",
linksCTA: [{ url:newQuestionnaire[0].url, txt:newQuestionnaire[0].anchor }, { url:config.siteUrl+"/"+configQuestionnaires.dirWebQuestionnaire+"/"+newQuestionnaire[0].slug+".html#questionnaire", txt:txtQuestionnaire.btnShowQuestionnaire }],
mailRecipientAddress: subscriptionsOk[i].email
}
toolMail.sendMail(subscriptionsOk[i].smtp, subscriptionsOk[i].email, newQuestionnaire[0].title, tool.replaceAll(txt.mailNewQuestionnaireBodyTxt, mapMail), "", mailDatas);
}
}
}
else
{
if(res.alerte)
res.alerte+="\n"+txt.noNewQuestionnaireForUser+subscriptionsOk[i].email;
else
res.alerte=txt.noNewQuestionnaireForUser+subscriptionsOk[i].UserId;
await toolMail.sendMail(0, config.adminEmail, "Abonné sans nouveau quiz", txt.noNewQuestionnaireForUser+subscriptionsOk[i].email, "<p>"+txt.noNewQuestionnaireForUser+subscriptionsOk[i].email+"</p>");
}
}
res.status(200).json(subscriptionsOk);
console.log(subscriptionsOk);
}
next();
}
catch(e)
{
next(e);
}
}
// FONCTIONS UTILITAIRES
// Retourne un booléen suivant si l'utilisateur a accès ou non à un questionnaire
const checkQuestionnaireAccess = async (UserId, QuestionnaireId) =>
{
const db = require("../models/index");
const checkQuestionnaireAccess=await db["QuestionnaireAccess"].findOne({ where: { UserId : UserId, QuestionnaireId : QuestionnaireId }, attributes: ["createdAt"] });
if(checkQuestionnaireAccess)
return true;
else
return false;
}
exports.checkQuestionnaireAccess = checkQuestionnaireAccess;
// Combien d'abonnements ont été enregistrés ces dernières 24 H ? depuis le début ? combien sont en premium ?
// Revoir : chercher les dates de paiement WP pour avoir stats 24 H ?
const getStatsSubscriptions = async () =>
{
const db = require("../models/index");
const getSubscriptions24H = await db.sequelize.query("SELECT `id` FROM `Subscriptions` WHERE `createdAt` > ADDDATE(NOW(), -1)", { type: QueryTypes.SELECT });
const getSubscriptionsTot = await db.sequelize.query("SELECT `id` FROM `Subscriptions`", { type: QueryTypes.SELECT });
const getSubscriptionsPremium = await db.sequelize.query("SELECT `id` FROM `Subscriptions` WHERE `numberOfDays` > :nb", { replacements: { nb: config.freeAccountTimingInDays }, type: QueryTypes.SELECT });
if(getSubscriptions24H && getSubscriptionsTot && getSubscriptionsPremium)
{
const stats =
{
nbSubscriptions24H : getSubscriptions24H.length,
nbSubscriptionsTot : getSubscriptionsTot.length,
nbSubscriptionsPremium : getSubscriptionsPremium.length
}
return stats;
}
else
return false;
}
exports.getStatsSubscriptions = getStatsSubscriptions;
/* Abandonné pour l'instant
*
// Recherche par mots-clés parmis les questionnaires auxquels l'utilisateur a accès !! où ceux auxquels il a répondu ? est-ce que cela a un intérêt ?
// 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");
const getQuestionnaires=await db.sequelize.query("SELECT `Questionnaires`.`id` FROM `Questionnaires` INNER JOIN `QuestionnaireAccesses` WHERE `Questionnaires`.`id`=`QuestionnaireAccesses`.`QuestionnaireId` AND `QuestionnaireAccesses`.`UserId`=:id AND `Questionnaires`.`isPublished` = 1 AND (`Questionnaires`.`title` LIKE :search OR `Questionnaires`.`introduction` LIKE :search)", { replacements: { id: req.connectedUser.User.id, search: "%"+search+"%" }, type: QueryTypes.SELECT });
let begin=0, end, output="";
if(!tool.isEmpty(req.body.begin))
begin=req.body.begin;
end=begin+configTpl.maxQuestionnairesByPage-1;// commence à 0 !
if(req.body.output!==undefined)
output=req.body.output;
datas=await questionnaireCtrl.getListingsQuestionnairesOuput(getQuestionnaires, begin, end, output);
res.status(200).json(datas);
}
next();
}
catch(e)
{
next(e);
}
}
// Retourne un questionnaire au hasard parmi ceux auxquels l'utilisateur a accès
exports.getOneRandomQuestionnaire = async(req, res, next) =>
{
try
{
const questionnaires=await getUserQuestionnaires(req.params.id, false);
if(!questionnaires || questionnaires.ids.length===0)
res.status(404).json({ txtCode: "notFound" });
else
{
const randomInt=tool.getRandomInt(0, questionnaires.ids.length);
const questionnaire=await questionnaireCtrl.searchQuestionnaireById(questionnaires.ids[randomInt]);
if(questionnaire)
res.status(200).json(questionnaire);
else
throw { message : txtQuestionnaireAccess.notFound+questionnaires.ids[randomInt], status : 404 };
}
next();
}
catch(e)
{
next(e);
}
}
// Retourne la liste des questionnaires auxquels un utilisateur a accès.
// Ils sont listés par ordre de fraîcheur, les + récents étant en début de liste
// Si un questionnaire de début et un nombre de questionnaires sont fournis, l'ensemble des informations du questionnaire sont retournées
// Sinon, seulement les ids.
exports.getQuestionnairesByUser = async(req, res, next) =>
{
try
{
let datas;
if(req.params.begin && req.params.nb)
datas=await getUserQuestionnaires(req.params.id, true, req.params.begin, req.params.nb);
else
datas=await getUserQuestionnaires(req.params.id, false);
if(datas)
res.status(200).json(datas);
else
res.status(404).json(txtQuestionnaire.notFound);
next();
}
catch(e)
{
next(e);
}
}
*
// Création du fichier des identifiants des questionnaires d'un utilisateur
// Les premiers étant les derniers
const creaUserQuestionnairesJson = async (UserId) =>
{
UserId=tool.trimIfNotNull(UserId);
if(UserId===null)
return false;
const db = require("../models/index");
const userQuestionnaires=await db.sequelize.query("SELECT `QuestionnaireId` FROM `QuestionnaireAccesses` WHERE `UserId`=:id ORDER BY `createdAt` DESC", { replacements: { id: UserId }, type: QueryTypes.SELECT });
if(userQuestionnaires)
{
const questionnairesId=[];// les ids suffisent et allègent le fichier
for(i in userQuestionnaires)
questionnairesId.push(userQuestionnaires[i].QuestionnaireId);
await toolFile.createJSON(config.dirCacheUsersQuestionnaires, UserId, { ids: questionnairesId });
return { ids: questionnairesId };
}
else
return false;
}
exports.creaUserQuestionnairesJson = creaUserQuestionnairesJson;
* */

321
controllers/tag.js Normal file
View File

@ -0,0 +1,321 @@
const { Op, QueryTypes } = require("sequelize");// pour certaines requêtes sql
const pug = require("pug");
var striptags = require("striptags");
const config = require("../config/main.js");
const configTags = require("../config/tags.js");
const tool = require("../tools/main");
const toolError = require("../tools/error");
const toolFile = require("../tools/file");
const questionnaireCtrl = require("./questionnaire");
const txt = require("../lang/"+config.adminLang+"/tag");
const txtGeneral = require("../lang/"+config.adminLang+"/general");
const txtQuestionnaire = require("../lang/"+config.adminLang+"/questionnaire");
const txtIllustration = require("../lang/"+config.adminLang+"/illustration");
// Actualise le classement d'un questionnaire suivant les tags envoyés
exports.checkTags = async (req, res, next) =>
{
try
{
if(req.body.QuestionnaireId==undefined)
throw { message: txt.neededParams };
const tagsCurrent=await getUsedTagsQuestionnaire(req.body.QuestionnaireId);
if(tagsCurrent===false)
throw { message: txt.tagsForQuestionnaireNotFound };
const tagsReceived=req.body.classification.split(",");// ! peut être vide si pas/plus de classement souhaité
for(i in tagsReceived)
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;
for (i in tagsCurrent)
{
if(tagsReceived.indexOf(tagsCurrent[i].name.toLowerCase())===-1)
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;
}
await questionnaireCtrl.creaQuestionnaireJson(req.body.QuestionnaireId);// attendre avant de répondre pour que cela pris en compte au réaffichage
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
{
let tags=await getAllTags(), tagsOk=[], search=req.body.search.trim().toLowerCase(), item;
if(search.length >= 2)
{
for(i in tags)
{
item=tags[i].name.toLowerCase();// restera le problème des accents
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)
await creaQuestionnairesTagJson(tagsUsed[i].id);// provoque la regénération du json + du html
res.status(200).json({ message: res.messageToNext.replace("#NB2", tagsUsed.length) });
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();
await toolFile.createJSON(configTags.dirCacheTags, "all", tags);
return tags;
}
// Retourne la liste de tous les tags
const getAllTags = async () =>
{
const tags=await toolFile.readJSON(configTags.dirCacheTags, "all");
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 });
await toolFile.createJSON(configTags.dirCacheTags, "used", tags);
return tags;
}
exports.creaUsedTagsJson = creaUsedTagsJson;// utile pour actualiser en cas de publication/dépublication d'un quiz
// Retourne la liste des tags utilisés
const getUsedTags = async () =>
{
const tags=await toolFile.readJSON(configTags.dirCacheTags, "used");
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);
if(id===null)
return false;
const questionnaire=await questionnaireCtrl.searchQuestionnaireById(id);
if(!questionnaire)
throw { message: txtQuestionnaire.notFound };
else
{
const allTags=await getAllTags();
let tagsQuestionnaire=[];
for(i in questionnaire.Tags)
{
for(j in allTags)
{
if(allTags[j].id===questionnaire.Tags[i].TagId)
{
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);
if(id===null)
return false;
const db = require("../models/index");
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 `Questionnaires`.`publishingAt`", { replacements: { id: id }, type: QueryTypes.SELECT , model: db["Questionnaire"], mapToModel: true });
await toolFile.createJSON(configTags.dirCacheTags, "liste-"+id, questionnaires);// le supprimer si liste vide ?
creaUsedTagsJson();
creaQuestionnairesTagHTML(id, questionnaires);// pas await, car potentiellement long !
return questionnaires;
}
exports.creaQuestionnairesTagJson = creaQuestionnairesTagJson;
const creaQuestionnairesTagHTML = async (id, Questionnaires) =>
{
id=tool.trimIfNotNull(id);
if(id===null || Questionnaires===null)
return false;
const tag=await searchTagById(id);
if(Questionnaires.length===0)
{ // 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 ?
await toolFile.deleteFile(configTags.dirHTMLTags, tag.slug+".html");
return true;
}
const compiledFunction = pug.compileFile("./views/"+config.theme+"/tag.pug");
const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js");
const pageDatas=
{
config: config,
configTpl: configTpl,
tool: tool,
striptags: striptags,
txtGeneral : txtGeneral,
txtQuestionnaire: txtQuestionnaire,
txtIllustration: txtIllustration,
pageLang: config.adminLang,
metaDescription: config.siteName+" : "+txt.tagMetaDescription+tag.name,
pageTitle: config.siteName+" - "+tag.name,
contentTitle: config.siteName+" - "+tag.name,
tagInfos: tag,
linkCanonical: config.siteUrl+"/"+configTags.dirWebTags+"/"+tag.slug+".html"
}
const nbPages=Math.ceil(Questionnaires.length / configTpl.maxQuestionnairesByPage);
pageDatas.nbPages=nbPages;
let debut=0, fin, questionnairesPage, questionnairesInfos=[], html, url;
for (let i = 1; i <= nbPages; i++)
{
let questionnairesPage = Questionnaires.slice(debut, debut+configTpl.maxQuestionnairesByPage);
for(let j in questionnairesPage)
questionnairesInfos.push(await questionnaireCtrl.searchQuestionnaireById(questionnairesPage[j].id));
pageDatas.questionnaires=questionnairesInfos;
pageDatas.page=i;
url=tag.slug;
if(i!==1)
{
url=url+"-"+i;
pageDatas.metaRobots="noindex,follow";
pageDatas.linkCanonical=null;
}
html=await compiledFunction(pageDatas);
await toolFile.createHTML(configTags.dirHTMLTags, url, html);
debut+=configTpl.maxQuestionnairesByPage;
questionnairesInfos=[];
}
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);
if(tagLinked===false)
{
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;
}

1184
controllers/user.js Normal file

File diff suppressed because it is too large Load Diff

27
example.env Normal file
View File

@ -0,0 +1,27 @@
# COMPLÉTER LES VARIABLES D'ENVIRONNEMENT CI-DESSOUS, SUPPRIMER TOUTES LES LIGNES DE COMMENTAIRES SANS LAISSER D'ESPACES. PUIS ENREGISTRER LE FICHIER : .env
# development/production ?
NODE_ENV=
# DATABASE LOGIN DETAILS :
DB_HOST=
DB_USER=
DB_PASS=
DB_NAME=
# INFORMATION FOR CONNECTING TO THE STMP SERVER(S). IF THERE ARE SEVERAL, SEPARATE THE INFORMATION WITH A COMMA WITHOUT SPACES.
SMTP_NAMES=
SMTP_HOSTS=
SMTP_PORTS=
SMTP_SECURES=
SMTP_LOGINS=
SMTP_PASSWORDS=
# AN INTEGER NUMBER USED FOR PASSWORD ENCRYPTION (see : https://www.npmjs.com/package/bcrypt) :
BCRYPT_SALT_ROUNDS=
# STRING USED BY THE CONNECTION TOKENS (see : https://www.npmjs.com/package/jsonwebtoken) :
TOKEN_PRIVATE_KEY=
# STRING USED BY THE CRONJOBS TOKENS :
CRON_TOKEN=
# NUMBER OF LOGIN ERRORS BEFORE THE USER IS BLOCKED :
MAX_LOGIN_FAILS=
# TIME DURING WHICH THE USER CAN NO LONGER LOG IN :
LOGIN_FAIL_TIME_IN_MINUTES=Time during which the user can no longer log in.
# ONLY USEFUL IF YOU USE THE WEBPORTAGE PAYMENT SOLUTION :
MD5_WP=

6672
front/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

31
front/package.json Normal file
View File

@ -0,0 +1,31 @@
{
"name": "wikilerni",
"version": "0.1.1",
"description": "Front-end of WikiLerni web application",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack-dev-server"
},
"keywords": [
"quiz",
"wikipédia",
"questionnaire",
"e-learning"
],
"author": "Fabrice PENHOËT",
"license": "GPL-3.0-or-later",
"devDependencies": {
"@babel/core": "^7.11.0",
"@babel/preset-env": "^7.11.0",
"babel-loader": "^8.1.0",
"babel-polyfill": "^6.26.0",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
},
"browserslist": [
"> 1%"
]
}

50
front/public/.htaccess Normal file
View File

@ -0,0 +1,50 @@
# Activation de la génération des en-têtes Expires
ExpiresActive On
# Les documents HTML, JS, JSON, CSS, XML expirent dès leur modification
ExpiresByType text/html M0
ExpiresByType text/javascript M0
ExpiresByType application/javascript M0
ExpiresByType application/json M0
ExpiresByType text/css M0
ExpiresByType application/xml M0
# Compression de certains fichiers
SetOutputFilter DEFLATE
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
AddOutputFilterByType DEFLATE application/x-font
AddOutputFilterByType DEFLATE application/x-font-opentype
AddOutputFilterByType DEFLATE application/x-font-otf
AddOutputFilterByType DEFLATE application/x-font-truetype
AddOutputFilterByType DEFLATE application/x-font-ttf
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE font/opentype
AddOutputFilterByType DEFLATE font/otf
AddOutputFilterByType DEFLATE font/ttf
AddOutputFilterByType DEFLATE image/png
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE image/x-icon
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE text/xml
# Bloquer ETag
Header unset ETag
FileETag none
# Protection de la lecture des répertoires
Options -Indexes
# protection du htaccess
<files .htaccess>
order allow,deny
deny from all
</files>
# erreur 404
ErrorDocument 404 /404.html

56
front/public/404.html Normal file
View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="L'erreur est humaine !">
<meta name="robots" content="noindex">
<title>WikiLerni : page non trouvée</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/index.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/wikilerni/css/common.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/common-mobile.css" media="screen and (max-width: 969px)">
<link rel="stylesheet" href="/themes/wikilerni/css/pages.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/pages-mobile.css" media="screen and (max-width: 969px)">
</head>
<body class="cardboard">
<!-- En tête -->
<header class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-128.png" alt="WikiLerni (logo)" title="Accéder à la page d'accueil de WikiLerni" /></a>
<ul id="headLinks">
<li><a href="/contact.html" rel="nofollow">Contact</a></li>
<li><a href="/quizs/" id="indexHeadLink" title="Les derniers quizs">Parcourir</a></li>
<li><a href="/connexion.html" id="accountHeadLink">Mon compte</a></li>
<li><a href="/a-propos.html">À propos</a></li>
</ul>
</header>
<div id="prompt" class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-512.png" alt="Logo WikiLerni" title="W I K I L E R N I" /></a>
<p class="cardboard">Cultivons notre jardin !</p>
</div>
<div id="page" class="cardboard">
<div class="framed">
<h2>Page non trouvée !</h2>
<p class="engraved">La page que vous demandez n'a pas été trouvée !<br>Peut-être a-t'elle été supprimée ou déplacée ?</p>
<p class="engraved">
<figure>
<img alt="Illustration : page non trouvée" src="/img/404-notfound.png">
<figCaption>Crédit illustration : <a title="coursetakers.ae / CC BY-SA (https://creativecommons.org/licenses/by-sa/4.0)" href="https://commons.wikimedia.org/wiki/File:Found_nothing.png">CC BY-SA coursetakers.ae</a></figCaption>
</figure></p>
</div>
</div>
<footer class="cardboard">
<ul id="footLinks">
<li><a href="https://framasphere.org/people/7e54b7a0b53201389eef2a0000053625" title="Blog WikiLerni sur diaspora*">Blog</a></li>
<li><a href="/credits.html">Crédits</a></li>
<li><a href="/mentions-legales.html" rel="nofollow">Mentions légales</a></li>
<li><a href="/donnees.html">Données personnelles</a></li>
<li><a href="/CGV-CGU.html" rel="nofollow">CGV &amp; CGU</a></li>
</ul>
</footer>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1104
front/public/JS/quiz.app.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

138
front/public/a-propos.html Normal file
View File

@ -0,0 +1,138 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Foire Aux Questions WikiLerni, pour tout savoir sur le site.">
<title>Tout savoir sur WikiLerni</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/index.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/wikilerni/css/common.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/common-mobile.css" media="screen and (max-width: 969px)">
<link rel="stylesheet" href="/themes/wikilerni/css/pages.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/pages-mobile.css" media="screen and (max-width: 969px)">
<link rel="canonical" href="https://www.wililerni.com/a-propos.html">
</head>
<body class="cardboard">
<!-- En tête -->
<header class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-128.png" alt="WikiLerni (logo)" title="Accéder à la page d'accueil de WikiLerni" /></a>
<ul id="headLinks">
<li><a href="/contact.html" rel="nofollow">Contact</a></li>
<li><a href="/quizs/" id="indexHeadLink" title="Les derniers quizs">Parcourir</a></li>
<li><a href="/connexion.html" id="accountHeadLink">Mon compte</a></li>
<li><a href="/a-propos.html">À propos</a></li>
</ul>
</header>
<div id="prompt" class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-512.png" alt="Logo WikiLerni" title="W I K I L E R N I" /></a>
<p class="cardboard">Cultivons notre jardin !</p>
</div>
<div id="page" class="cardboard">
<div class="framed">
<h2>Qu'est-ce que WikiLerni ?</h2>
<p class="engraved">WikiLerni vous propose de lire une sélection d'articles de Wikipédia.<br>
Suite à chaque lecture, une série de questions vous permet de tester ce que vous avez retenu.<br>
Toutes les réponses à ce quiz se trouvent dans l'article proposé à la lecture.<br>
Vous pouvez sauvegarder vos résultats au quiz en créant un compte WikiLerni.<br>
Vous pourrez ensuite répondre de nouveau aux mêmes questions plusieurs semaines, mois... après avoir lu l'article.<br>
De quoi tester votre mémoire à plus en moins long terme !</p>
<h2>Que signifie "WikiLerni" ?</h2>
<p class="engraved">Le nom "WikiLerni" est composé de deux parties :
<ul>
<li>« Wiki » fait évidemment référence à l'encyclopédie en ligne Wikipédia. Comme je vous sais curieux, sachez que le terme « wiki » vient lui-même du mot hawaïen « wikiwiki » signifiant « rapide », « vite » ou « informel ».</li>
<li>« Lerni » est un verbe espéranto signifiant « apprendre » ou encore « étudier ». Vous pouvez penser à « learn » en anglais ou encore « lernen » en allemand.</li>
</ul></p>
<p>Bref, WikiLerni vous invite à <b>apprendre de nouvelles choses avec Wikipédia !</b><br>
Et en lisant ces quelques lignes, vous venez peut-être déjà d'apprendre quelque chose :-)</p>
<h2>À qui s'adresse WikiLerni ?</h2>
<p class="engraved">À toute personne curieuse aimant apprendre de nouvelles choses !<br>
Contrairement à d'autres sites de quiz testant votre culture générale, WikiLerni ne vous demande pas d'être déjà très "savants", même si vous l'êtes forcément, au moins dans certains domaines. Si ! Si !<br>
Vous êtes ici pour apprendre et toutes les réponses aux questions des quizs se trouvent dans l'article qui vous est proposé à la lecture.<br>
C'est donc une façon ludique d'apprendre de nouvelles choses sur des sujets auxquels vous ne vous seriez peut-être jamais intéressé par vous-même.</p>
<h2>Est-il nécessaire de créer un compte pour utiliser WikiLerni ?</h2>
<p class="engraved">Non, vous pouvez parcourir librement WikiLerni sans avoir besoin d'être connecté au site.<br>
Votre compte vous permettra de :
<ul>
<li>Sauvegarder vos résultats aux quizs</li>
<li>Recevoir des suggestions de nouvelles lectures, directement par e-mail</li>
<li>Faciliter votre navigation via un moteur de recherche interne à WikiLerni</li>
<li> + d'autres fonctionnalités à venir...</li>
</ul></p>
<h2>Comment sont sélectionnés les articles proposés par WikiLerni ?</h2>
<p class="engraved">Tout comme Wikipédia, WikiLerni se veut éclectique.<br>
Donc il vous sera proposé des articles sur des sujets très variés : sciences, arts, histoire, littérature, mythologie, géographie, culture populaire, etc.<br>
Il n'y aucun sujet qui ne soit pas digne d'intérêt à priori. Seront évités des articles indiqués par Wikipédia comme de faible qualité ou encore sujets à polémique.</p>
<p class="engraved">Les articles proposés à la lecture peuvent être de longueurs variées.<br>
Une estimation de la durée de lecture vous est indiquée sur la page du quiz ou encore dans les e-mails que vous recevez si vous êtes abonné.<br>
Vous pourrez ainsi choisir de laisser de côté un article un peu long pour y revenir quand vous aurez plus de temps.</p>
<p class="engraved">Pour l'instant, les articles proposés sont uniquement francophones.<br>
Mais suivant le succès du site, des versions dans d'autres langues pourront être envisagées.<br>
Si vous souhaitez vous-même lancer une version dans une autre langue, mais n'avez pas les compétences techniques pour le faire, n'hésitez <a href="/contact.hml">à me contacter</a> pour un éventuel partenariat.</p>
<h2>Quel lien entre WikiLerni et Wikipédia ?</h2>
<p class="engraved">Puisque toutes les suggestions de lecture de WikiLerni vous amènent sur Wikipédia, le lien est évident.<br>
Pour autant, WikiLerni n'est pas un projet porté par la fondation Wikipédia, ni par une de ses associations locales, mais est un projet indépendant.<br>
Au-delà de ce lien pratique, WikiLerni se veut très proche de l'esprit de Wikipédia en partageant son attrait pour la culture libre et le "libre" en général.
L'application faisant fonctionner WikiLerni est un logiciel libre, c'est-à-dire que toute personne peut l'utiliser, étudier son code, le modifier, le distribuer selon son souhait.<br>Ceci est également vrai pour le graphisme du site et les quizs eux-mêmes : texte d'introduction, questions/réponses, illustrations... Sauf exceptions indiquées.</p>
<h2>Est-ce que WikiLerni est gratuit ?</h2>
<p class="engraved">Oui.. et non ! Réponse de normand venant d'un breton ? :-)<br>Vous pouvez tout à fait parcourir WikiLerni, lire les articles proposés et répondre aux quizs.<br>
Tout cela se fait sans avoir besoin de vous abonner et donc gratuitement.<br>Par contre, si vous souhaitez garder vos résultats, recevoir par mail de nouvelles suggestions de lectures... vous devrez vous abonner au site.<br>Vous pourrez alors tester gratuitement l'abonnement pendant une période de découverte.<br>Ensuite, vous serez invité à souscrire à un abonnement, mais à un prix "libre", c'est-à-dire que différentes possibilités vous seront proposées.<br>Tout le monde n'a pas les mêmes moyens, ni le même intérêt pour le WikiLerni, donc à vous de choisir en conscience.<br>
Vous aimez WikiLerni, mais ne pouvez vraiment pas payer pour ce service ? <a href="/contact.html">Contactez-moi</a>. Vous n'avez pas à vous justifier.<br>
De même, si aucune des options ne vous convient ou encore si vous préférez un autre moyen de paiement (chèque, virement...), <a href="/contact.html">contactez-moi</a> pour me dire ce que vous souhaitez.<br>Des abonnements de groupe (famille, écoles, associations...) ou autres formules peuvent aussi être envisagées, dans la mesure où cela sera techniquement possible.<br>Bref, <b>nous sommes ici plus dans l'esprit d'un financement participatif que dans celui d'un abonnement classique</b>.<br>En souscrivant à un abonnement, vous permettez à WikiLerni d'exister.</p>
<h2>À quoi va servir l'argent de mon abonnement ?</h2>
<p class="engraved">Cet argent va principalement servir à :
<ul>
<li>Payer les frais techniques liés au site : serveurs, routeurs e-mail, paiement en ligne...</li>
<li>Développer le logiciel libre permettant à WikiLerni de fonctionner.</li>
<li>Consacrer du temps à sélectionner les articles Wikipédia et préparer les questions.</li>
<li>Gérer le site au quotidien : répondre à vos courriels, communiquer, etc.</li>
</ul></p>
<p class="engraved"><b>Tout cela sans vous imposer de publicités ou faire commerce de vos données personnelles.</b><br>
Par ailleurs, une partie des bénéfices nets de WikiLerni seront distribués sous forme de dons à l'<a href="https://www.wikimedia.fr/" target="_blank">association Wikimédia France</a> et aux développeurs des logiciels libres utilisés par WikiLerni.<br>Je parle de bénéfices "nets", car au-delà des frais de fonctionnement, mon activité étant déclarée, il faut y soustraire TVA, cotisations, etc.<br><br>
<b>Pas de publicité, respect de vos données personnelles, activité déclarée en France... cela change, non ?</b></p>
<h2>Comment puis-je aider WikiLerni ?</h2>
<p class="engraved">Tout d'abord en l'utilisant et <a href="/contact.html">me retournant</a> vos éventuelles remarques/suggestions.<br>
Un bug ? Une erreur d'orthographe ? Une suggestion de fonctionnalité ? Quelque chose que vous ne comprenez pas ? N'hésitez pas à me le dire, cela m'intéresse.<br>
Ensuite, si vous avez les moyens, vous pouvez souscrire un abonnement payant pour permettre au projet de perdurer.<br>
Et WikiLerni n'ayant pas les moyens des grandes sociétés pour communiquer, vous pouvez aussi en parler autour de vous, en ligne ou dans la vie de tous les jours. <b>
Vous le savez sans doute, rien ne vaut le bouche à oreille !</b><br>
Un système de parrainage est d'ailleurs prévu pour vous récompenser : à chaque fois qu'une personne inscrite en vous désignant comme "parrain" souscrit un abonnement payant, votre propre abonnement se trouve prolongé de 30 jours.</p>
<h2><a class="button cardboard" href="/inscription.html">Tester gratuitement</a></h2>
</div>
</div>
<footer class="cardboard">
<ul id="footLinks">
<li><a href="https://framasphere.org/people/7e54b7a0b53201389eef2a0000053625" title="Blog WikiLerni sur diaspora*">Blog</a></li>
<li><a href="/credits.html">Crédits</a></li>
<li><a href="/mentions-legales.html" rel="nofollow">Mentions légales</a></li>
<li><a href="/donnees.html">Données personnelles</a></li>
<li><a href="/CGV-CGU.html" rel="nofollow">CGV &amp; CGU</a></li>
</ul>
</footer>
</body>
</html>

79
front/public/accueil.html Normal file
View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Page d'accueil de l'abonné.">
<meta name="robots" content="noindex">
<title>Mon WikiLerni</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/homeUser.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/wikilerni/css/common.css" media="screen and (min-width: 871px)">
<link rel="stylesheet" href="/themes/wikilerni/css/common-mobile.css" media="screen and (max-width: 870px)">
<link rel="stylesheet" href="/themes/wikilerni/css/home.css" media="screen and (min-width: 751px)">
<link rel="stylesheet" href="/themes/wikilerni/css/home-mobile.css" media="screen and (max-width: 750px)">
<link rel="stylesheet" href="/themes/wikilerni/css/menu.css" media="screen and (min-width: 751px)">
<link rel="stylesheet" href="/themes/wikilerni/css/menu-mobile.css" media="screen and (max-width: 750px)">
</head>
<body class="cardboard">
<!-- En tête -->
<header class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-128.png" alt="WikiLerni (logo)" title="Accéder à la page d'accueil de WikiLerni" /></a>
<ul id="headLinks">
<li><a href="/contact.html" rel="nofollow">Contact</a></li>
<li><a href="/quizs/" id="indexHeadLink" title="Les derniers quizs">Parcourir</a></li>
<li><a href="/connexion.html" id="accountHeadLink">Mon compte</a></li>
<li><a href="/a-propos.html">À propos</a></li>
</ul>
</header>
<div id="crash"></div>
<section id="main-content" class="needJS">
<ul id="menu" class="cardboard">
<li><a href="/accueil.html">MON WIKILERNI</a></li>
<li><a href="/compte.html#infos" title="Email, mot de passe">Mes informations</a></li>
<li><a href="/compte.html#subscribe" class="pure-menu-link">Mon abonnement</a></li>
<li><a href="/sortie.html" class="pure-menu-link">Me déconnecter</a></li>
</ul>
<div id="home" class="cardboard">
<img id="logo" src="/themes/wikilerni/img/wikilerni-purple-2-512.png" alt="Logo WikiLerni" />
<div id="message" class="cardboard"></div>
<h2>Chercher un quiz</h2>
<form id="search" method="POST">
<input id="searchQuestionnaires" type="text" name="searchQuestionnaires" placeholder="Votre recherche" class="cardboard" />
<input id="begin" type="hidden" name="begin" value="0">
<input type="submit" value="Chercher" class="cardboard" />
<div class="line"><input type="checkbox" id="onlyAnswers" name="onlyAnswers" /><div class="checkbox_override"></div> Parmi mes réponses.</div>
<div class="line"><button type="button" id="random" class="button cardboard">Au hasard !</button></div>
</form>
<h2 id="quizsTitle">Les quizs attendant votre réponse</h2>
<div id="quizsIntro"></div>
</div>
<div id="quizs">
<div id="quizsList"></div>
<nav id="pagination"><div id="previous"></div><div id="next"></div></nav>
</div>
</section>
<footer class="cardboard">
<ul id="footLinks">
<li><a href="https://framasphere.org/people/7e54b7a0b53201389eef2a0000053625" title="Blog WikiLerni sur diaspora*">Blog</a></li>
<li><a href="/credits.html">Crédits</a></li>
<li><a href="/mentions-legales.html" rel="nofollow">Mentions légales</a></li>
<li><a href="/donnees.html">Données personnelles</a></li>
<li><a href="/CGV-CGU.html" rel="nofollow">CGV &amp; CGU</a></li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex">
<title>Validation de la suppression de votre compte</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/deleteValidation.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/wikilerni/css/common.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/common-mobile.css" media="screen and (max-width: 969px)">
<link rel="stylesheet" href="/themes/wikilerni/css/links-page.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/links-page-mobile.css" media="screen and (max-width: 969px)">
</head>
<body class="cardboard">
<!-- En tête -->
<header class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-128.png" alt="WikiLerni (logo)" title="Accéder à la page d'accueil de WikiLerni" /></a>
<ul id="headLinks">
<li><a href="/contact.html" rel="nofollow">Contact</a></li>
<li><a href="/quizs/" id="indexHeadLink" title="Les derniers quizs">Parcourir</a></li>
<li><a href="/connexion.html" id="accountHeadLink">Mon compte</a></li>
<li><a href="/a-propos.html">À propos</a></li>
</ul>
</header>
<div id="prompt" class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-512.png" alt="Logo WikiLerni" title="W I K I L E R N I" /></a>
<p class="cardboard">Cultivons notre jardin !</p>
<h1 class="cardboard">Validation de la suppression de votre compte</h1>
<div id="response"><p class="error">Si vous voyez ce message, c'est que votre lien de validation n'est pas valide ou a expiré. Vous pouvez <a href="/compte.html">en demander un nouveau en cliquant ici</a>.</p></div>
</div>
<footer class="cardboard">
<ul id="footLinks">
<li><a href="https://framasphere.org/people/7e54b7a0b53201389eef2a0000053625" title="Blog WikiLerni sur diaspora*">Blog</a></li>
<li><a href="/credits.html">Crédits</a></li>
<li><a href="/mentions-legales.html" rel="nofollow">Mentions légales</a></li>
<li><a href="/donnees.html">Données personnelles</a></li>
<li><a href="/CGV-CGU.html" rel="nofollow">CGV &amp; CGU</a></li>
</ul>
</footer>
</body>
</html>

168
front/public/compte.html Normal file
View File

@ -0,0 +1,168 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex">
<title>Mon compte WikiLerni</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/accountUser.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/wikilerni/css/common.css" media="screen and (min-width: 871px)">
<link rel="stylesheet" href="/themes/wikilerni/css/common-mobile.css" media="screen and (max-width: 870px)">
<link rel="stylesheet" href="/themes/wikilerni/css/account.css" media="screen and (min-width: 990px)">
<link rel="stylesheet" href="/themes/wikilerni/css/account-mobile.css" media="screen and (max-width: 989px)">
<link rel="stylesheet" href="/themes/wikilerni/css/menu.css" media="screen and (min-width: 751px)">
<link rel="stylesheet" href="/themes/wikilerni/css/menu-mobile.css" media="screen and (max-width: 750px)">
</head>
<body class="cardboard">
<!-- En tête -->
<header class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-128.png" alt="WikiLerni (logo)" title="Accéder à la page d'accueil de WikiLerni" /></a>
<ul id="headLinks">
<li><a href="/contact.html" rel="nofollow">Contact</a></li>
<li><a href="/quizs/" id="indexHeadLink" title="Les derniers quizs">Parcourir</a></li>
<li><a href="/connexion.html" id="accountHeadLink">Mon compte</a></li>
<li><a href="/a-propos.html">À propos</a></li>
</ul>
</header>
<div id="crash"></div>
<section id="main-content" class="needJS">
<ul id="menu" class="cardboard">
<li><a href="/accueil.html">MON WIKILERNI</a></li>
<li><a href="/compte.html#infos" title="Email, mot de passe">Mes informations</a></li>
<li><a href="/compte.html#subscribe" class="pure-menu-link">Mon abonnement</a></li>
<li><a href="/sortie.html" class="pure-menu-link">Me déconnecter</a></li>
</ul>
<div id="account" class="cardboard">
<h1 class="cardboard" id="infos">Votre compte WikiLerni</h1>
<div id="message"></div>
<form id="accountUpdate" method="POST">
<fieldset><label for="name">Nom ou pseudo</label><input id="name" type="text" name="name" class="cardboard"></fieldset>
<fieldset><label for="email">Email</label><input id="email" type="email" name="email" class="cardboard">
<div id="emailMessage"></div></fieldset>
<fieldset><label for="newPassword">Nouveau mot de passe</label><input id="newPassword" type="password" name="newPassword" class="cardboard">
<div id="newPasswordMessage"><span class="info">Laisser vide sauf si vous souhaitez le changer.</span></div></fieldset>
<div class="framed">
<p>Par défaut un nouveau quiz vous est proposé tous les jours. Vous pouvez préciser ci-dessous les jours souhaités.</p>
<ul class="checkbox_list">
<li class="checkbox_li">
<label for="d2" class="check"><input type="checkbox" id="d2" name="d2" value="true" /><div class="checkbox_override"></div> Lundi.</label>
</li>
<li class="checkbox_li">
<label for="d3" class="check"><input type="checkbox" id="d3" name="d3" value="true" /><div class="checkbox_override"></div> Mardi.</label>
</li>
<li class="checkbox_li">
<label for="d4" class="check"><input type="checkbox" id="d4" name="d4" value="true" /><div class="checkbox_override"></div> Mercredi.</label>
</li>
<li class="checkbox_li">
<label for="d5" class="check"><input type="checkbox" id="d5" name="d5" value="true" /><div class="checkbox_override"></div> Jeudi.</label>
</li>
<li class="checkbox_li">
<label for="d6" class="check"><input type="checkbox" id="d6" name="d6" value="true" /><div class="checkbox_override"></div> Vendredi.</label>
</li>
<li class="checkbox_li">
<label for="d7" class="check"><input type="checkbox" id="d7" name="d7" value="true" /><div class="checkbox_override"></div> Samedi.</label>
</li>
<li class="checkbox_li">
<label for="d1" class="check"><input type="checkbox" id="d1" name="d1" value="true" /><div class="checkbox_override"></div> Dimanche.</label>
</li>
</ul>
</div>
<ul class="checkbox_list">
<li class="checkbox_li">
<label for="noticeOk" class="check"><input type="checkbox" id="noticeOk" name="noticeOk" value="true" /><div class="checkbox_override"></div> Je souhaite recevoir les nouveaux quizs par email.</label>
</li>
<li class="checkbox_list">
<label for="newsletterOk" class="check"><input type="checkbox" id="newsletterOk" name="newsletterOk" value="true" /><div class="checkbox_override"></div> J'accepte de recevoir d'autres messages ponctuels de WikiLerni (nouvelles fonctionnalités ...).</label>
</li>
<li class="checkbox_list">
<label for="deleteOk" class="check"><input type="checkbox" id="deleteOk" name="deleteOk" value="true" /><div class="checkbox_override"></div> <span class="error">Je souhaite supprimer mon compte utilisateur.</span></label>
</li>
</ul>
<div class="input_wrapper"><input type="submit" value="Valider" class="cardboard" id="submitDatas" /></div>
</form>
<div id="response"></div>
<h1 class="cardboard" id="subscribe">Votre abonnement</h1>
<div id="subscribeIntro"></div>
<!--<div class="info">Pour l'instant le paiement en ligne n'est pas encore possible.</div>-->
<ul class="checkbox_list">
<li class="checkbox_li">
<label for="abo12" class="check"><input type="checkbox" id="abo12" name="abo12" value="true" /><div class="checkbox_override"></div> J'accepte de payer <b>1 € TTC/mois</b>, soit 12 € TTC.</label>
</li>
<li class="checkbox_li">
<label for="abo24" class="check"><input type="checkbox" id="abo24" name="abo24" value="true" /><div class="checkbox_override"></div> J'accepte de payer <b>2 € TTC/mois</b>, soit 24 € TTC.</label>
</li>
<li class="checkbox_li">
<label for="abo60" class="check"><input type="checkbox" id="abo60" name="abo60" value="true" /><div class="checkbox_override"></div> J'accepte de payer <b>5 € TTC/mois</b>, soit 60 € TTC.</label>
</li>
<li class="checkbox_li">
<label for="abo120" class="check"><input type="checkbox" id="abo120" name="abo120" value="true" /><div class="checkbox_override"></div> J'accepte de payer <b>10 € TTC/mois</b>, soit 120 € TTC.</label>
</li>
<li class="checkbox_li">
<label for="CGVOk" class="check" title="Vous devez d'abord choisir le montant de votre abonnement."><input type="checkbox" id="CGVOk" name="CGVOk" value="true" /><div class="checkbox_override"></div> <span class="info">J'ai lu et accepte les <a href="/CGV-CGU.html" target="_blank" rel="noopener">Condition Générales de Vente</a> (obligatoire).</span></label>
</li>
</ul>
<div id="WPBtns">
<script type="text/javascript">
const userWP=JSON.parse(localStorage.getItem("user"));
var paiement_ref = ""+userWP.id;// d'après test, doit être une chaîne pour que cela fonctionne.
</script>
<div id="WPBtn12" class="needJS">
<h4>Paiement de votre abonnement annuel prémium à 12 € TTC/an.</h4>
<script type="text/javascript" src="https://paiementsecurise.info/S-pan64po51vmnscc5-pBVw3HZ84GScLeOJ.js"></script>
</div>
<div id="WPBtn24" class="needJS">
<h4>Paiement de votre abonnement annuel prémium à 24 € TTC/an.</h4>
<script type="text/javascript" src="https://paiementsecurise.info/S-pan64po51vmnscc5-uKIw8rfTVVuEbBJI.js"></script>
</div>
<div id="WPBtn60" class="needJS">
<h4>Paiement de votre abonnement annuel prémium à 60 € TTC/an.</h4>
<script type="text/javascript" src="https://paiementsecurise.info/S-pan64po51vmnscc5-f5NsL6A4p1tGBC1G.js"></script>
</div>
<div id="WPBtn120" class="needJS">
<h4>Paiement de votre abonnement annuel prémium à 120 € TTC/an.</h4>
<script type="text/javascript" src="https://paiementsecurise.info/S-pan64po51vmnscc5-xDfE3jccOP4bwQFL.js"></script>
</div>
<p class="success">En cliquant sur le bouton de paiement, vous serez dirigé vers l'outil de facturation et de paiement en ligne.<br>Lors de votre premier abonnement, <b>vous devrez y créer un compte client qui est distinct de votre compte utilisateur WikiLerni</b>. Vous pouvez y utiliser un mot de passe différent. <br>Les années suivantes, lors de vos renouvellements, vous pourrez vous reconnecter à ce compte client.</p>
</div>
<div class="info">
<h3>Prix libre ?</h3>
<p>WikiLerni pratique le "prix libre", c'est-à-dire que <b>vous pouvez choisir quel montant vous êtes prêt à payer pour continuer à utiliser WikiLerni</b>.</p>
<p>Cependant, <b>il ne s'agit pas vraiment d'un don</b>, car sans ce financement participatif, <b>le site WikiLerni cessera son activité</b> et vous ne pourrez donc plus l'utiliser.</p>
<p>Vous pouvez donc choisir en conscience ce que vous pouvez et souhaitez payer cette année pour WikiLerni, sachant que <b>ce choix ne vous engagera pas pour les futurs renouvellements</b>.</p>
<p>Une fois sélectionné le montant qui vous convient, il vous faudra <b>cocher la case de validation des Conditions Générales de Vente</b>, pour voir apparaître un bouton de paiement qui vous mènera <b>sur l'outil de facturation et paiement en ligne de la société WebPortage</b>.</p>
<p>Vous aimez WikiLerni, mais ne pouvez vraiment pas payer ? Vous préférez un autre montant ou un autre moyen de paiement (chèque, virement) ? Ou encore vous avez besoin d'explications ? Alors <a href="/contact.html">contactez-moi</a>. <b>Je me ferai un plaisir de vous répondre et d'essayer de m'adapter à chaque situation</b>.</p>
</div>
<h1 class="cardboard" id="godfather">Les utilisateurs que vous avez parrainés</h1>
<div class="engraved framed">
<p>Vous pouvez parrainer d'autres utilisateurs. Pour ce faire, demandez-leur de saisir lors de l'inscription votre adresse e-mail <strong id="godfatherEmail"></strong> ou encore le code suivant : <strong id="godfatherCode"></strong>.</p>
<p>À chaque fois qu'un utilisateur que vous avez parrainé <b>souscrit ou renouvelle un abonnement payant</b>, son abonnement comme le vôtre <b>se trouve prolongé gratuitement de 30 jours</b>. Cet avantage restera valable tant que cet utilisateur et vous-mêmes garderez votre compte WikiLerni.</p>
</div>
<p id="godchilds"><b>Pour l'instant, aucune personne ne s'est inscrite, en vous désignant comme "parrain".</b></p>
</div>
</section>
<footer class="cardboard">
<ul id="footLinks">
<li><a href="https://framasphere.org/people/7e54b7a0b53201389eef2a0000053625" title="Blog WikiLerni sur diaspora*">Blog</a></li>
<li><a href="/credits.html">Crédits</a></li>
<li><a href="/mentions-legales.html" rel="nofollow">Mentions légales</a></li>
<li><a href="/donnees.html">Données personnelles</a></li>
<li><a href="/CGV-CGU.html" rel="nofollow">CGV &amp; CGU</a></li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Formulaire de connexion à WikiLerni.">
<title>Se connecter à WikiLerni</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/connection.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/wikilerni/css/common.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/common-mobile.css" media="screen and (max-width: 969px)">
<link rel="stylesheet" href="/themes/wikilerni/css/login.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/login-mobile.css" media="screen and (max-width: 969px)">
<link rel="canonical" href="https://www.wililerni.com/connexion.html">
</head>
<body class="cardboard">
<!-- En tête -->
<header class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-128.png" alt="WikiLerni (logo)" title="Accéder à la page d'accueil de WikiLerni" /></a>
<ul id="headLinks">
<li><a href="/contact.html" rel="nofollow">Contact</a></li>
<li><a href="/quizs/" id="indexHeadLink" title="Les derniers quizs">Parcourir</a></li>
<li><a href="/connexion.html" id="accountHeadLink">Mon compte</a></li>
<li><a href="/a-propos.html">À propos</a></li>
</ul>
</header>
<div id="prompt" class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-512.png" alt="Logo WikiLerni" title="W I K I L E R N I" /></a>
<p class="cardboard">Cultivons notre jardin !</p>
</div>
<div id="login" class="cardboard">
<h1 class="cardboard">Connectez-vous à WikiLerni</h1>
<noscript>Désolé, mais pour l'instant, l'utilisation de WikiLerni nécessite l'activation du JavaScript.</noscript>
<p class="engraved framed">Pas de compte WikiLerni ? <a href="/inscription.html">créez-le ici</a>.</p>
<div id="message"></div>
<form class="needJS" id="connection" method="POST">
<fieldset>
<label for="email">Email :</label><input id="email" type="email" name="email" placeholder="Adresse e-mail utilisée pour ce site" class="cardboard"required>
</fieldset>
<fieldset>
<label for="password">Mot de passe :</label>
<input id="password" type="password" name="password" placeholder="Votre mot de passe" class="cardboard">
<div id="passwordMessage" class="cardboard"><i>Oublié ? Alors laissez vide et cochez la case ci-dessous.</i></div>
</fieldset>
<ul class="checkbox_li">
<li class="checkbox_li">
<label for="getLoginLink" class="check"><input type="checkbox" id="getLoginLink" name="getLoginLink" value="true" /><div class="checkbox_override"></div> Je préfère recevoir un lien de connexion par e-mail.</label>
</li>
<li class="checkbox_li">
<label for="keepConnected" class="check"><input type="checkbox" id="keepConnected" name="keepConnected" value="true" /><div class="checkbox_override"></div> Je souhaite ne pas avoir à me connecter à chaque fois.</label>
</li>
</ul>
<div class="input_wrapper">
<input id="submitDatas" type="submit" value="Valider" class="cardboard" />
</div>
</form>
<div id="response"></div>
<div class="framed">
<h2>Besoin d'aide?</h2>
<p class="engraved">Si vous avez oublié votre mot de passe, il vous suffit de cocher la case "Je souhaite recevoir un lien de connexion par e-mail". Un lien valide pendant une courte durée vous permettra de vous connecter au site.</p>
<p class="engraved">Si vous ne vous souvenez pas non plus de l'adresse e-mail utilisée sur ce site ou que vous n'y avez plus accès, vous pouvez <a href="/contact.html">nous contacter</a> en fournissant des informations permettant de vous identifier.</p>
<p class="engraved">La case "Je souhaite ne pas avoir à me connecter à chaque fois." vous permettra de rester connecté.e jusqu'à 6 mois, pour peu que vous utilisiez le même navigateur internet.</p>
</div>
</div>
<footer class="cardboard">
<ul id="footLinks">
<li><a href="https://framasphere.org/people/7e54b7a0b53201389eef2a0000053625" title="Blog WikiLerni sur diaspora*">Blog</a></li>
<li><a href="/credits.html">Crédits</a></li>
<li><a href="/mentions-legales.html" rel="nofollow">Mentions légales</a></li>
<li><a href="/donnees.html">Données personnelles</a></li>
<li><a href="/CGV-CGU.html" rel="nofollow">CGV &amp; CGU</a></li>
</ul>
</footer>
</body>
</html>

53
front/public/contact.html Normal file
View File

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex">
<title>WikiLerni : page de contact</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/index.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/wikilerni/css/common.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/common-mobile.css" media="screen and (max-width: 969px)">
<link rel="stylesheet" href="/themes/wikilerni/css/pages.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/pages-mobile.css" media="screen and (max-width: 969px)">
</head>
<body class="cardboard">
<!-- En tête -->
<header class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-128.png" alt="WikiLerni (logo)" title="Accéder à la page d'accueil de WikiLerni" /></a>
<ul id="headLinks">
<li><a href="/contact.html" rel="nofollow">Contact</a></li>
<li><a href="/quizs/" id="indexHeadLink" title="Les derniers quizs">Parcourir</a></li>
<li><a href="/connexion.html" id="accountHeadLink">Mon compte</a></li>
<li><a href="/a-propos.html">À propos</a></li>
</ul>
</header>
<div id="prompt" class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-512.png" alt="Logo WikiLerni" title="W I K I L E R N I" /></a>
<p class="cardboard">Cultivons notre jardin !</p>
</div>
<div id="page" class="cardboard">
<div class="framed">
<p>Pour me contacter, le plus simple est de m'écrire sur l'adresse suivante : <a href="mailto:bonjour@wikilerni.com">bonjour@wikilerni.com</a><br>J'essayerai de vous répondre au plus tôt.</p>
<p>Je vous conseille d'ailleurs <b>d'ajouter cette adresse à votre carnet d'adresse</b>.<br>Cela limitera le risque que les messages de WikiLerni finissent dans votre dossier "spam", voire soient complètement bloqués ...</p>
<p>Si vous souhaitez échanger par téléphone, merci d'indiquer vos coordonnées téléphoniques, ainsi que les créneaux horaires où vous êtes généralement disponibles.</p>
<p>D'une manière générale, <b>merci de préciser votre demande dans votre message !</b> Ma boule de cristal fonctionne fort mal :)</p>
<p>Si vous êtes adepte du chiffrement de e-mails, vous pouvez <a href="/WikiLerni-pub.asc">télécharger ma clé OpenPGP publique</a>.<br>Si vous ne savez pas de quoi il s'agit, ne vous en préoccupez pas, mais je sais que les intéressés apprécieront :)</p>
</div>
</div>
<footer class="cardboard">
<ul id="footLinks">
<li><a href="https://framasphere.org/people/7e54b7a0b53201389eef2a0000053625" title="Blog WikiLerni sur diaspora*">Blog</a></li>
<li><a href="/credits.html">Crédits</a></li>
<li><a href="/mentions-legales.html" rel="nofollow">Mentions légales</a></li>
<li><a href="/donnees.html">Données personnelles</a></li>
<li><a href="/CGV-CGU.html" rel="nofollow">CGV &amp; CGU</a></li>
</ul>
</footer>
</body>
</html>

59
front/public/credits.html Normal file
View File

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Qui est à l'origine de WikiLerni et de son contenu ? Quels sont vos droits d'utilisation ?">
<title>Qui sont les créateurs de WikiLerni et avec quelles licences ?</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/index.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/wikilerni/css/common.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/common-mobile.css" media="screen and (max-width: 969px)">
<link rel="stylesheet" href="/themes/wikilerni/css/pages.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/pages-mobile.css" media="screen and (max-width: 969px)">
<link rel="canonical" href="https://www.wililerni.com/credits.html">
</head>
<body class="cardboard">
<!-- En tête -->
<header class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-128.png" alt="WikiLerni (logo)" title="Accéder à la page d'accueil de WikiLerni" /></a>
<ul id="headLinks">
<li><a href="/contact.html" rel="nofollow">Contact</a></li>
<li><a href="/quizs/" id="indexHeadLink" title="Les derniers quizs">Parcourir</a></li>
<li><a href="/connexion.html" id="accountHeadLink">Mon compte</a></li>
<li><a href="/a-propos.html">À propos</a></li>
</ul>
</header>
<div id="prompt" class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-512.png" alt="Logo WikiLerni" title="W I K I L E R N I" /></a>
<p class="cardboard">Cultivons notre jardin !</p>
</div>
<div id="page" class="cardboard">
<div class="framed engraved">
<p>Le logiciel servant à faire fonctionner WikiLerni est un logiciel libre. J'en suis le créateur et pour l'instant le principal développeur. Je me nomme Fabrice PENHOËT et pour en savoir plus sur moi, vous pouvez <a href="https://cv.le-fab-lab.com/">consulter mon CV en ligne</a>. La licence utilisée est la GNU GENERAL PUBLIC LICENSE (GNU/GPLv3) dont vous pouvez lire <a href="https://www.gnu.org/licenses/gpl-3.0.en.html" rel="licence">la version officielle</a> (en anglais). Pour des explications en français, évidemment, <a href="https://fr.wikipedia.org/wiki/Licence_publique_g%C3%A9n%C3%A9rale_GNU">Wikipédia y consacre un article</a> !</p>
<p>Le graphisme du site est la création de <a href="https://denissalem.tuxfamily.org/">Denis SALEM</a>. Les éléments graphiques sont soumis à la licence <a href="https://creativecommons.org/licenses/by-sa/2.0/fr" rel="licence">CC-By-SA</a>. La partie logicielle (CSS...) étant de nouveau soumise à la licence GNU/GPLv3.</p>
<p>Les textes du site, et notamment les quizs et leur introduction sont eux-mêmes libres. Cette fois, la licence utilisée est la <a href="https://creativecommons.org/licenses/by-sa/3.0/deed.fr" rel="licence">CC BY-SA 3.0</a>, par cohérence avec celle utilisée par Wikipédia.
<p>Les illustrations du site, notamment celles des quizs viennent principalement de Wikipédia. Les licences d'utilisation sont variables et un lien est généralement fourni. Les extraits des articles Wikipédia sont eux soumis à la licence <a href="https://creativecommons.org/licenses/by-sa/3.0/deed.fr" rel="licence">CC BY-SA 3.0</a>.</p>
<p>Si vous avez des doutes sur ce que vous pouvez faire ou pas du contenu de WikiLerni, n'hésitez pas <a href="/contact.html">à me demander</a>.<br>De même, si le logiciel utilisé par WikiLerni vous intéresse pour vos propres projets, mais que vous avez besoin d'aide pour le prendre en main, je peux vous fournir un devis sur demande.</p>
</div>
</div>
<footer class="cardboard">
<ul id="footLinks">
<li><a href="https://framasphere.org/people/7e54b7a0b53201389eef2a0000053625" title="Blog WikiLerni sur diaspora*">Blog</a></li>
<li><a href="/credits.html">Crédits</a></li>
<li><a href="/mentions-legales.html" rel="nofollow">Mentions légales</a></li>
<li><a href="/donnees.html">Données personnelles</a></li>
<li><a href="/CGV-CGU.html" rel="nofollow">CGV &amp; CGU</a></li>
</ul>
</footer>
</body>
</html>

101
front/public/donnees.html Normal file
View File

@ -0,0 +1,101 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Tout savoir sur ce que WikiLerni fait de vos données personnelles : abonnements, sauvegardes, paiement en ligne, etc.">
<title>WikiLerni et vos données personnelles</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/index.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/wikilerni/css/common.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/common-mobile.css" media="screen and (max-width: 969px)">
<link rel="stylesheet" href="/themes/wikilerni/css/pages.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/pages-mobile.css" media="screen and (max-width: 969px)">
<link rel="canonical" href="https://www.wililerni.com/donnees.html">
</head>
<body class="cardboard">
<!-- En tête -->
<header class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-128.png" alt="WikiLerni (logo)" title="Accéder à la page d'accueil de WikiLerni" /></a>
<ul id="headLinks">
<li><a href="/contact.html" rel="nofollow">Contact</a></li>
<li><a href="/quizs/" id="indexHeadLink" title="Les derniers quizs">Parcourir</a></li>
<li><a href="/connexion.html" id="accountHeadLink">Mon compte</a></li>
<li><a href="/a-propos.html">À propos</a></li>
</ul>
</header>
<div id="prompt" class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-512.png" alt="Logo WikiLerni" title="W I K I L E R N I" /></a>
<p class="cardboard">Cultivons notre jardin !</p>
</div>
<div id="page" class="cardboard">
<div class="framed">
<p class="engraved">CNIL, RGPD... aujourd'hui toutes les organisations souhaitent montrer patte blanche quant au bon usage qu'elles font de vos données personnelles.<br>Cela avec plus ou moins de sincérité et de pédagogie. Ici, je vous explique quelles données sont traitées par WikiLerni et pourquoi.<br>Néanmoins, pour la responsabilité légale du site, merci de vous reporter <a href="/CGV-CGU.html">aux CGU &amp; CGV du site</a> qui sont les seuls textes valables.</p>
<h2>Les données liées à votre abonnement</h2>
<p class="engraved">Lorsque vous créez votre compte, vous devez choisir <b>un pseudonyme</b> qui est complètement libre, donc vous n'êtes pas obligé de saisir votre vrai nom.</p>
<p class="engraved">De même, <b>votre adresse e-mail</b> est libre. Vous devez juste y avoir accès pour pouvoir valider la création de votre compte.<br>
Pour l'instant, WikiLerni ne bloque pas les adresses e-mail jetables. Toutefois, sachez qu'elles peuvent fragiliser la sécurité de votre compte.<br>
Par ailleurs, certains fournisseurs d'adresses e-mail rejettent sytématiquement les courriels venant de sites internet et vous ne pourrez donc pas valider votre compte, etc. Ce n'est pas un choix de WikiLerni.</p>
<p class="engraved">Le <b>mot de passe</b> que vous choisissez est évidemment chiffré. Personne n'y a accès et je ne pourrai donc vous le redonner si vous l'avez oublié.<br>
Mais vous pouvez vous connecter sans mot de passe en demandant à recevoir sur votre adresse e-mail un lien valable pendant une courte durée.</p>
<p class="engraved">En résumé, je peux donc accéder à votre e-mail, le pseudonyme que vous avez choisi, ainsi que quelques informations telles que les dates de création/modification de vos informations ou encore la date de votre dernière connexion au site. Ceci est utile au bon fonctionnement du site.</p>
<p class="engraved">J'ai aussi évidemment accès aux informations concernant votre abonnement (durée de votre abonnement, jours où vous souhaitez recevoir de nouveaux quizs, période de pauses, etc.). De nouveau, il s'agit d'informations nécessaires au site et que vous pouvez modifier vous-mêmes.</p>
<p class="engraved"><b>Lorsque que vous supprimez votre compte, toutes les données sont supprimées immédiatement et définitivement.</b><br>
De même, si vous n'avez plus d'abonnement actif et que vous ne vous connectez pendant un certain nombre de mois, votre compte et ses informations sont supprimés définitivement.</p>
<p class="engraved">En fait, vos données peuvent toujours apparaître un certain temps dans les sauvegardes du site, mais ne comptez pas dessus pour récupérer votre compte après une suppression. Ses sauvegardes existent pour permettre de remettre en route l'ensemble du site en cas d'incident.</p>
<p class="engraved">Il existe quelques informations vous concernant auxquelles vous n'avez pas directement accès.<br>Le <b>décalage horaire de votre compte</b> qui est détecté périodiquement lorsque vous vous connectez au site. En effet, pour WikiLerni vous envoi en général ces messages durant la nuit, mais l'heure n'est pas la même suivant si vous habitez en Nouvelle-Calédonie ou en métropole.<br>Un champ mémo me permet denregistrer des données libres concernant votre compte. Il est vide dans 99 % des cas et ne contient rien de personnel. Mais vous pouvez me demander cette information si vous le souhaitez.</p>
<h2>Les prestataires</h2>
<p class="engraved">Le site WikiLerni est hébergé sur un serveur situé en France et appartenant à une entreprise elle-même française (alwaysdata).</p>
<p class="engraved">Pour envoyer ses e-mails en essayant d'éviter de se retrouver en "spam", WikiLerni utilise les services d'un routeur e-mail.<br>
De nouveau, il s'agit d'une société française (Spirion), ses serveurs étant eux-mêmes situés en France (OVH) au moment où j'écris ce texte. Je l'ai choisie pour ça.<br>
Donc, en théorie, ce prestataire peut avoir accès à votre adresse e-mail, mais à aucune autre information.<br>
Il n'y aucun pistage des affichages ou clics des messages envoyés par WikiLerni, ce qui est chose rare !</p>
<h2>Les données de facturation</h2>
<p class="engraved">Si vous optez pour un abonnement prémium, vous devrez passer par le site de la société WebPortage/Nodalys qui est une société de portage salarial me permettant d'avoir une activité déclarée en France. Les employés de Nodalys ont donc accès à vos données de facturation et paiement. Pour en savoir plus sur ce sujet <a href="https://www.webportage.com/protection-donnees-cookies">cliquez-ici</a>.</p>
<p class="engraved">Ce logiciel de facturation est séparé de WikiLerni, tout comme ceux de Paypal ou Paybox qui vous permettrons de payer votre abonnement en ligne.
Si vous souhaitez éviter de passer par Paypal ou Paybox, vous pouvez me contacter pour demander un paiement par chèque ou virement.</p>
<h2>Les statistiques de visite</h2>
<p class="engraved">Lorsque l'on gère un site internet, il est difficile de naviguer complètement à vue et avoir certaines informations sur les visites de son site est utile.
Pour ce faire WikiLerni privilégie en toute cohérence un logiciel libre nommé Matomo.<br>
Ce logiciel est configuré conformément aux préconisations de la CNIL, c'est-à-dire entre autres pour ne pas collecter votre adresse IP, qui est une information permettant de vous identifier sur internet.<br>
Par ailleurs, Matomo est inactif lorsque vous êtes connecté à votre compte WikiLerni.<br>
Cet outil est installé sur un serveur internet situé en France et je suis la seule personne à avoir accès à ces informations.</p>
<p class="engraved">Si vous souhaitez vous opposer à ce suivi statistique, vous pouvez utiliser le lien suivant :<br>
<iframe src="https://stats.le-fab-lab.com/index.php?module=CoreAdminHome&amp;action=optOut&amp;language=fr" style="border: 0; height: 200px; width: 600px;"></iframe></p>
<p class="engraved"><b>Pour toutes questions/réclamations concernant vos données personnelles sur WikiLerni, <a href="/contact.html">n'hésitez pas à me contacter</a></b>.</p>
</div>
</div>
<footer class="cardboard">
<ul id="footLinks">
<li><a href="https://framasphere.org/people/7e54b7a0b53201389eef2a0000053625" title="Blog WikiLerni sur diaspora*">Blog</a></li>
<li><a href="/credits.html">Crédits</a></li>
<li><a href="/mentions-legales.html" rel="nofollow">Mentions légales</a></li>
<li><a href="/donnees.html">Données personnelles</a></li>
<li><a href="/CGV-CGU.html" rel="nofollow">CGV &amp; CGU</a></li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,261 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Saisie et mise à jour des quizs">
<meta name="robots" content="noindex">
<title>Les quizs</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/manageQuestionnaires.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
<link rel="canonical" href="https://www.wililerni.com/gestion-quizs.html">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content" class="needJS">
<div class="pure-menu pure-menu-horizontal">
<a href="/gestion.html" class="pure-menu-heading pure-menu-link">Gestion WikiLerni</a>
<ul id="classement" class="pure-menu-list">
<li class="pure-menu-item"><a href="/gestion-quizs.html" title="Publication des quizs" class="pure-menu-link" >Les quizs</a></li>
<li class="pure-menu-item"><a href="/gestion-utilisateurs.html" title="Les comptes utilisateurs" class="pure-menu-link">Les abonné(e)s</a></li>
<li class="pure-menu-item"><a href="/sortie.html" title="Sortie des artistes !" class="pure-menu-link">Me déconnecter</a></li>
</ul>
</div>
<div class="content">
<h2 class="content-head is-center" id="infos">Les quizs</h2>
<form class="pure-form pure-u-1" id="search" method="POST">
<fieldset>
<legend>Chercher un quiz</legend>
<input id="searchQuestionnaires" type="txt" name="searchQuestionnaires" placeholder="Votre recherche">
<button type="submit" class="pure-button pure-button-primary">Chercher</button>
<div id="searchResult"></div>
</fieldset>
</form>
<div class="pure-g">
<div class="l-box-lrg pure-u-1">
<div id="message"></div>
<form class="pure-form pure-form-aligned" id="questionnaires" method="POST">
<fieldset>
<legend>Informations du quiz</legend>
<div class="pure-control-group">
<label for="title">Titre</label>
<input id="title" type="text" name="title">
<span class="pure-form-message-inline"></span>
</div>
<div class="pure-control-group">
<label for="slug">Page html</label>
<input id="slug" type="text" name="slug">.html
</div>
<div class="pure-control-group">
<label for="introduction">Introduction</label>
<textarea id="introduction" name="introduction" rows="5"></textarea>
</div>
<div class="pure-control-group">
<label for="keywords">Mots-clés</label>
<textarea id="keywords" name="keywords" rows="5"></textarea>
</div>
<div class="pure-control-group">
<label for="publishingAt">Date de publication</label>
<input id="publishingAt" type="date" name="publishingAt"> <span class="information" id="helpPublishingAt"></span>
</div>
<div class="pure-control-group">
<label for="estimatedTime">Durée de lecture estimée</label>
<select name="estimatedTime" id="estimatedTime">
<option value="short">Court</option>
<option value="medium">Moyen</option>
<option value="long">Long</option>
</select>
</div>
<div class="pure-control-group">
<label for="classification">Catégories de classement</label>
<input id="classification" type="text" name="classification"> <span class="information" id="helpClassification">Séparer les rubriques par des virgules</span>
</div>
<div class="pure-controls">
<label for="deleteOk" class="pure-checkbox error" id="deleteOkLabel">
<input type="checkbox" id="deleteOk" name="deleteOk" value="true" /> Je souhaite supprimer ce quiz
</label>
<input type="hidden" name="id" id="id" value="">
<button type="submit" class="pure-button pure-button-primary" id="submitDatas">Valider</button>
<a href="#questionnaires" class="pure-button pure-button-primary needJS" id="wantNewQuestionnaire">Nouveau questionnaire</a>
<a href="#questionnaires" class="pure-button pure-button-primary needJS" id="previewQuestionnaire" target=="_blank">Voir le quiz</a>
</div>
<div id="response"></div>
<div id="linksList" class="needJS"></div>
<div id="illustrationsList" class="needJS"></div>
<div id="questionsList" class="needJS"></div>
</fieldset>
</form>
<form class="pure-form pure-form-aligned" id="links" method="POST">
<fieldset>
<legend>Informations du lien</legend>
<div class="pure-control-group">
<label for="url">Url</label>
<input id="url" type="url" name="url">
<span class="pure-form-message-inline"></span>
</div>
<div class="pure-control-group">
<label for="anchor">Texte du lien</label>
<input id="anchor" type="text" name="anchor" value="Lire l'article sur Wikipédia.">
</div>
<div class="pure-controls">
<input type="hidden" name="id" id="idLink" value="">
<input type="hidden" name="QuestionnaireId" id="QuestionnaireIdLink" value="">
<input type="hidden" name="deleteOk" id="deleteOkLink" value="">
<button type="submit" class="pure-button pure-button-primary">Valider</button>
</div>
<div id="responseLink"></div>
</fieldset>
</form>
<form class="pure-form pure-form-aligned" id="illustrations" method="POST" enctype="multipart/form-data">
<fieldset>
<legend>Informations de l'illustration</legend>
<div class="pure-control-group">
<label for="image">Sélectionnez le fichier à utiliser</label>
<input type="file" id="image" name="image">
</div>
<div class="pure-control-group">
<label for="alt">Propriété "alt"</label>
<input id="alt" type="text" name="alt">
</div>
<div class="pure-control-group">
<label for="title">Propriété "title"</label>
<input id="title" type="text" name="title">
</div>
<div class="pure-control-group">
<label for="caption">Légende</label>
<input id="caption" type="text" name="caption">
</div>
<div class="pure-controls">
<input type="hidden" name="id" id="idIllustration" value="">
<input type="hidden" name="QuestionnaireId" id="QuestionnaireIdIllustration" value="">
<input type="hidden" name="deleteOk" id="deleteOkIllustration" value="">
<button type="submit" class="pure-button pure-button-primary">Valider</button>
</div>
<div id="responseIllustration"></div>
</fieldset>
</form>
<form class="pure-form pure-form-aligned" id="questions" method="POST">
<fieldset>
<legend>La question et les réponses proposées</legend>
<div class="pure-control-group">
<label for="text">Question</label>
<input id="text" type="text" name="text">
<span class="pure-form-message-inline"></span>
</div>
<div class="pure-control-group">
<label for="rank">Rang</label>
<input id="rank" type="number" name="rank" value="1"> <span class="information">Permet de fixer l'ordre d'affichage des questions.</span>
</div>
<div class="pure-control-group">
<label for="explanation">Explications</label>
<textarea id="explanation" name="explanation" rows="5"></textarea>
</div>
<legend>Réponses proposées</legend>
<div class="pure-control-group">
<input id="choiceText0" type="text" name="choiceText0" maxlength="255" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect0" name="choiceIsCorrect0" value="true" /> Réponse correcte
<input type="hidden" name="idChoice0" id="idChoice0" value="">
</div>
<div class="pure-control-group">
<input id="choiceText1" type="text" name="choiceText1" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect1" name="choiceIsCorrect1" value="true" /> Réponse correcte
<input type="hidden" name="idChoice1" id="idChoice1" value="">
</div>
<div class="pure-control-group">
<input id="choiceText2" type="text" name="choiceText2" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect2" name="choiceIsCorrect2" value="true" /> Réponse correcte
<input type="hidden" name="idChoice2" id="idChoice2" value="">
</div>
<div class="pure-control-group">
<input id="choiceText3" type="text" name="choiceText3" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect3" name="choiceIsCorrect3" value="true" /> Réponse correcte
<input type="hidden" name="idChoice3" id="idChoice3" value="">
</div>
<div class="pure-control-group">
<input id="choiceText4" type="text" name="choiceText4" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect4" name="choiceIsCorrect4" value="true" /> Réponse correcte
<input type="hidden" name="idChoice4" id="idChoice4" value="">
</div>
<div class="pure-control-group">
<input id="choiceText5" type="text" name="choiceText5" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect5" name="choiceIsCorrect5" value="true" /> Réponse correcte
<input type="hidden" name="idChoice5" id="idChoice5" value="">
</div>
<div class="pure-control-group">
<input id="choiceText6" type="text" name="choiceText6" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect6" name="choiceIsCorrect6" value="true" /> Réponse correcte
<input type="hidden" name="idChoice6" id="idChoice6" value="">
</div>
<div class="pure-control-group">
<input id="choiceText7" type="text" name="choiceText7" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect7" name="choiceIsCorrect7" value="true" /> Réponse correcte
<input type="hidden" name="idChoice7" id="idChoice7" value="">
</div>
<div class="pure-control-group">
<input id="choiceText8" type="text" name="choiceText8" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect8" name="choiceIsCorrect8" value="true" /> Réponse correcte
<input type="hidden" name="idChoice8" id="idChoice8" value="">
</div>
<div class="pure-control-group">
<input id="choiceText9" type="text" name="choiceText9" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect9" name="choiceIsCorrect9" value="true" /> Réponse correcte
<input type="hidden" name="idChoice9" id="idChoice9" value="">
</div>
<div class="pure-controls">
<input type="hidden" name="id" id="idQuestion" value="">
<input type="hidden" name="QuestionnaireId" id="QuestionnaireIdQuestion" value="">
<input type="hidden" name="deleteOk" id="deleteOkQuestion" value="">
<button type="submit" class="pure-button pure-button-primary">Valider</button>
</div>
<div id="responseQuestion"></div>
</fieldset>
</form>
<div class="l-box-lrg pure-u-1" id="questionnairesList"></div>
</div>
</div>
</div>
</section>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Crédits</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/donnees.html">Données personnelles</a>
</li>
<li><a href="/CGV-CGU.html">CGV &amp; CGU</a></li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,206 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Gestion des abonnés">
<meta name="robots" content="noindex">
<title>Les abonnés</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/manageUsers.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
<link rel="canonical" href="https://www.wililerni.com/gestion-utilisateurs.html">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content" class="needJS">
<div class="pure-menu pure-menu-horizontal">
<a href="/gestion.html" class="pure-menu-heading pure-menu-link">Gestion WikiLerni</a>
<ul id="classement" class="pure-menu-list">
<li class="pure-menu-item"><a href="/gestion-quizs.html" title="Publication des quizs" class="pure-menu-link" >Les quizs</a></li>
<li class="pure-menu-item"><a href="/gestion-utilisateurs.html" title="Les comptes utilisateurs" class="pure-menu-link">Les abonné(e)s</a></li>
<li class="pure-menu-item"><a href="/sortie.html" title="Sortie des artistes !" class="pure-menu-link">Me déconnecter</a></li>
</ul>
</div>
<div class="content">
<h2 class="content-head is-center" id="infos">Les abonnés</h2>
<form class="pure-form pure-u-1" id="search" method="POST">
<fieldset>
<legend>Chercher un utilisateur</legend>
<input id="search" type="txt" name="search" placeholder="Votre recherche">
<button type="submit" class="pure-button pure-button-primary">Chercher</button>
<div id="searchResult"></div>
</fieldset>
</form>
<div class="pure-g">
<div class="l-box-lrg pure-u-1">
<div id="message"></div>
<form class="pure-form pure-form-aligned" id="users" method="POST">
<fieldset>
<legend>Informations de l'abonné</legend>
<div id="subscribeIntro"></div>
<div class="pure-control-group">
<label for="name">Nom ou pseudo</label>
<input id="name" type="text" name="name">
<span class="pure-form-message-inline"></span>
</div>
<div class="pure-control-group">
<label for="email">Email</label>
<input id="email" type="email" name="email">
<span class="pure-form-message-inline" id="emailMessage"></span>
</div>
<div class="pure-control-group">
<label for="status">Statut</label>
<select id="status" name="status">
<option value="user">Utilisateur</option>
<option value="creator">Créateur</option>
<option value="manager">Gestionnaire</option>
<option value="admin">Administrateur</option>
</select>
</div>
<div class="pure-control-group">
<label for="smtp">SMTP</label>
<select id="smtp" name="smtp">
<option value="0">Spirion</option>
<option value="1">Mailjet</option>
</select>
</div>
<div class="pure-control-group">
<label for="timeDifference">Décalage horaire</label>
<input id="timeDifference" type="number" name="timeDifference" readonly>
</div>
<div class="pure-control-group">
<label for="newPassword">Nouveau mot de passe</label>
<input id="newPassword" type="password" name="newPassword">
<span class="pure-form-message-inline" id="newPasswordMessage"><b class="information">Laisser vide sauf si vous souhaitez le changer.</b></span>
</div>
<div class="pure-control-group">
<label for="adminComments">Commentaires</label>
<textarea id="adminComments" name="adminComments" rows="5"></textarea>
</div>
<div class="pure-control-group">
<label for="numberOfDays">Durée totale de l'abonnement</label>
<input id="numberOfDays" type="number" name="numberOfDays"><span class="pure-form-message-inline"><i class="information">Depuis la création de son compte.</i></span>
</div>
<div class="pure-controls">
Jours valables pour l'abonnement
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-7"><!-- grille ne fonctionne pas ?! -->
<label for="d2">
<input type="checkbox" id="d2" name="d2" value="true" /> Lundi
</label>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d3">
<input type="checkbox" id="d3" name="d3" value="true" /> Mardi
</label>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d4">
<input type="checkbox" id="d4" name="d4" value="true" /> Mercredi
</label>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d5">
<input type="checkbox" id="d5" name="d5" value="true" /> Jeudi
</label>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d6">
<input type="checkbox" id="d6" name="d6" value="true" /> Vendredi
</label>
</li>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d7">
<input type="checkbox" id="d7" name="d7" value="true" /> Samedi
</label>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d1">
<input type="checkbox" id="d1" name="d1" value="true" /> Dimanche
</label>
</li>
</ul>
</div>
<div class="pure-controls">
<label for="noticeOk" class="pure-checkbox">
<input type="checkbox" id="noticeOk" name="noticeOk" value="true" /> Reçoit les quizs par email.
</label>
<label for="newsletterOk" class="pure-checkbox">
<input type="checkbox" id="newsletterOk" name="newsletterOk" value="true" /> Reçoit les autres actus par email.
</label>
<label for="validationOk" class="pure-checkbox success" id="validationOkLabel">
<input type="checkbox" id="validationOk" name="validationOk" /> Valider le compte.
</label>
<label for="deleteOk" class="pure-checkbox error" id="deleteOkLabel">
<input type="checkbox" id="deleteOk" name="deleteOk" /> Supprimer le compte
</label>
<input type="hidden" name="id" id="id" value="">
<button type="submit" class="pure-button pure-button-primary" id="submitDatas">Valider</button>
<a href="#users" class="pure-button pure-button-primary" id="wantNewUser">Vider</a>
</div>
<div id="response"></div>
<div id="infosPayments" class="needJS"><h4>Payments reçus via l'API WebPortage</h4></div>
<div id="infosGodchilds" class="needJS"><h4>Parrainages</h4></div>
</fieldset>
</form>
</div>
</div>
</div>
</section>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Crédits</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/donnees.html">Données personnelles</a>
</li>
<li><a href="/CGV-CGU.html">CGV &amp; CGU</a></li>
</ul>
</footer>
</body>
</html>

78
front/public/gestion.html Normal file
View File

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Page d'accueil du gestionnaire.">
<meta name="robots" content="noindex">
<title>WikiLerni - gestion du site</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/homeManager.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
<link rel="canonical" href="https://www.wililerni.com/gestion.html">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content" class="needJS">
<div class="pure-menu pure-menu-horizontal">
<a href="/gestion.html" class="pure-menu-heading pure-menu-link">Gestion WikiLerni</a>
<ul id="classement" class="pure-menu-list">
<li class="pure-menu-item"><a href="/gestion-quizs.html" title="Publication des quizs" class="pure-menu-link" >Les quizs</a></li>
<li class="pure-menu-item"><a href="/gestion-utilisateurs.html" title="Les comptes utilisateurs" class="pure-menu-link">Les abonné(e)s</a></li>
<li class="pure-menu-item"><a href="/sortie.html" title="Sortie des artistes !" class="pure-menu-link">Me déconnecter</a></li>
</ul>
</div>
<div class="content">
<h2 class="content-head is-center">WikiLerni</h2>
<div class="pure-g">
<div class="l-box-lrg pure-u-1" id="message"></div>
<div class="l-box-lrg pure-u-1" id="questionnaires"></div>
</div>
<p><a href="#" class="pure-button pure-button-primary" id="wantRegenerate">Régénérer le HTML.</a></p>
</div>
</section>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Crédits</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/donnees.html">Données personnelles</a>
</li>
<li><a href="/CGV-CGU.html">CGV &amp; CGU</a></li>
</ul>
</footer>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
front/public/img/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,94 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Formulaire d'inscription à WikiLerni.">
<title>S'inscrire à WikiLerni</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/subscribe.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/wikilerni/css/common.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/common-mobile.css" media="screen and (max-width: 969px)">
<link rel="stylesheet" href="/themes/wikilerni/css/signup.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/signup-mobile.css" media="screen and (max-width: 969px)">
<link rel="canonical" href="https://www.wililerni.com/inscription.html">
</head>
<body class="cardboard">
<!-- En tête -->
<header class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-128.png" alt="WikiLerni (logo)" title="Accéder à la page d'accueil de WikiLerni" /></a>
<ul id="headLinks">
<li><a href="/contact.html" rel="nofollow">Contact</a></li>
<li><a href="/quizs/" id="indexHeadLink" title="Les derniers quizs">Parcourir</a></li>
<li><a href="/connexion.html" id="accountHeadLink">Mon compte</a></li>
<li><a href="/a-propos.html">À propos</a></li>
</ul>
</header>
<div id="prompt" class="cardboard">
<img src="/themes/wikilerni/img/wikilerni-purple-2-512.png" alt="Logo WikiLerni" title="W I K I L E R N I" />
<p class="cardboard">Cultivons notre jardin !</p>
</div>
<div id="signup" class="cardboard">
<h1 class="cardboard">Créer votre compte WIKILERNI</h1>
<noscript>Désolé, mais pour l'instant, l'utilisation de WikiLerni nécessite l'activation du JavaScript.</noscript>
<form class="needJS" id="subscription" method="POST">
<fieldset>
<label for="name">Nom ou pseudo: </label><input id="name" type="text" name="name" placeholder="Pseudo de votre choix" class="cardboard" >
</fieldset>
<fieldset>
<label for="email">Email: </label><input id="email" type="email" name="email" placeholder="Adresse e-mail" class="cardboard">
</fieldset>
<div id="emailMessage" class="cardboard"></div>
<fieldset>
<label for="password">Mot de passe: </label><input id="password" type="password" name="password" placeholder="Mot de passe de votre choix" class="cardboard">
</fieldset>
<div id="passwordMessage" class="cardboard"><i>Au moins 8 caractères. <a href="#password" id="getPassword">Générer un mot de passe</a>.</i></div>
<fieldset>
<label for="codeGodfather">Code/e-mail parrainage: </label><input id="codeGodfather" type="text" name="codeGodfather" placeholder="Code ou email de votre parrain." class="cardboard">
</fieldset>
<div id="codeGodfatherMessage" class="cardboard"><i>Facultatif.</i></div>
<ul class="checkbox_li">
<li class="checkbox_li">
<label for="cguOk" class="check"><input type="checkbox" id="cguOk" name="cguOk" value="true" /><div class="checkbox_override"></div> J'accepte <a href="/CGV-CGU.html">les Conditions Générale d'Utilisation</a> du site (requis).</label>
</li>
<li class="checkbox_li">
<label for="newsletterOk" class="check"><input type="checkbox" id="newsletterOk" name="newsletterOk" value="true" /><div class="checkbox_override"></div> J'accepte de recevoir les nouveaux quizs WikiLerni par email (facultatif).</label>
</li>
</ul>
<div class="input_wrapper">
<input id="submitDatas" type="submit" value="Valider" class="cardboard" />
</div>
</form>
<div id="response"></div>
<div class="framed">
<h2>Besoin d'aide ?</h2>
<p class="engraved">La saisie d'un pseudonyme, d'une adresse e-mail et d'un mot de passe <b>est obligatoire</b> et <b>vous devez accepter les Conditions Générales d'Utilisation</b>.</p>
<p class="engraved">Votre pseudonyme est complétement libre, mais servira à personnaliser certains affichages.</p>
<p class="engraved"><b>Vous recevrez un lien sur l'adresse e-mail saisie</b> sur lequel vous devrez cliquer pour valider la création de votre compte.</p>
<p class="engraved"><b>Votre mot de passe doit compter au moins 8 caractères.</b> Si vous manquez d'inspiration, cliquez sur le lien "Générer un mot de passe" pour en obtenir un !</p>
<p class="engraved">Si vous connaissez une personne déjà inscrite à WikiLerni, <b>vous pouvez fournir son code parrain ou encore son adresse e-mail</b>. Dans le cas où vous optez ensuite pour un abonnement prémium, il sera alors allongé de 30 jours grâce à ce parrainage.</p>
</div>
</div>
<footer class="cardboard">
<ul id="footLinks">
<li><a href="https://framasphere.org/people/7e54b7a0b53201389eef2a0000053625" title="Blog WikiLerni sur diaspora*">Blog</a></li>
<li><a href="/credits.html">Crédits</a></li>
<li><a href="/mentions-legales.html" rel="nofollow">Mentions légales</a></li>
<li><a href="/donnees.html">Données personnelles</a></li>
<li><a href="/CGV-CGU.html" rel="nofollow">CGV &amp; CGU</a></li>
</ul>
</footer>
</body>
</html>

45
front/public/login.html Normal file
View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex">
<title>Tester votre lien de connexion</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/loginLink.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/wikilerni/css/common.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/common-mobile.css" media="screen and (max-width: 969px)">
<link rel="stylesheet" href="/themes/wikilerni/css/links-page.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/links-page-mobile.css" media="screen and (max-width: 969px)">
</head>
<body class="cardboard">
<!-- En tête -->
<header class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-128.png" alt="WikiLerni (logo)" title="Accéder à la page d'accueil de WikiLerni" /></a>
<ul id="headLinks">
<li><a href="/contact.html" rel="nofollow">Contact</a></li>
<li><a href="/quizs/" id="indexHeadLink" title="Les derniers quizs">Parcourir</a></li>
<li><a href="/connexion.html" id="accountHeadLink">Mon compte</a></li>
<li><a href="/a-propos.html">À propos</a></li>
</ul>
</header>
<div id="prompt" class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-512.png" alt="Logo WikiLerni" title="W I K I L E R N I" /></a>
<p class="cardboard">Cultivons notre jardin !</p>
<div id="response" class="error">Si vous voyez ce message, c'est que votre lien de connexion n'est pas valide. Vous pouvez <a href="/connexion.html">en demander un nouveau en cliquant ici</a>.</div>
</div>
<footer class="cardboard">
<ul id="footLinks">
<li><a href="https://framasphere.org/people/7e54b7a0b53201389eef2a0000053625" title="Blog WikiLerni sur diaspora*">Blog</a></li>
<li><a href="/credits.html">Crédits</a></li>
<li><a href="/mentions-legales.html" rel="nofollow">Mentions légales</a></li>
<li><a href="/donnees.html">Données personnelles</a></li>
<li><a href="/CGV-CGU.html" rel="nofollow">CGV &amp; CGU</a></li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex">
<title>Lien pour changer vos informations de connexion</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/newLoginValidation.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/wikilerni/css/common.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/common-mobile.css" media="screen and (max-width: 969px)">
<link rel="stylesheet" href="/themes/wikilerni/css/links-page.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/links-page-mobile.css" media="screen and (max-width: 969px)">
</head>
<body class="cardboard">
<!-- En tête -->
<header class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-128.png" alt="WikiLerni (logo)" title="Accéder à la page d'accueil de WikiLerni" /></a>
<ul id="headLinks">
<li><a href="/contact.html" rel="nofollow">Contact</a></li>
<li><a href="/quizs/" id="indexHeadLink" title="Les derniers quizs">Parcourir</a></li>
<li><a href="/connexion.html" id="accountHeadLink">Mon compte</a></li>
<li><a href="/a-propos.html">À propos</a></li>
</ul>
</header>
<div id="prompt" class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-512.png" alt="Logo WikiLerni" title="W I K I L E R N I" /></a>
<p class="cardboard">Cultivons notre jardin !</p>
<div id="response" class="error">Si vous voyez ce message, c'est que votre lien de validation n'est pas valide ou a expiré. Vous pouvez <a href="/compte.html">en demander un nouveau en cliquant ici</a>.</div>
</div>
<footer class="cardboard">
<ul id="footLinks">
<li><a href="https://framasphere.org/people/7e54b7a0b53201389eef2a0000053625" title="Blog WikiLerni sur diaspora*">Blog</a></li>
<li><a href="/credits.html">Crédits</a></li>
<li><a href="/mentions-legales.html" rel="nofollow">Mentions légales</a></li>
<li><a href="/donnees.html">Données personnelles</a></li>
<li><a href="/CGV-CGU.html" rel="nofollow">CGV &amp; CGU</a></li>
</ul>
</footer>
</body>
</html>

2
front/public/robots.txt Normal file
View File

@ -0,0 +1,2 @@
User-agent: *
Disallow: /

46
front/public/sortie.html Normal file
View File

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex">
<title>Déconnexion WikiLerni</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/deconnection.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/wikilerni/css/common.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/common-mobile.css" media="screen and (max-width: 969px)">
<link rel="stylesheet" href="/themes/wikilerni/css/links-page.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/links-page-mobile.css" media="screen and (max-width: 969px)">
</head>
<body class="cardboard">
<!-- En tête -->
<header class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-128.png" alt="WikiLerni (logo)" title="Accéder à la page d'accueil de WikiLerni" /></a>
<ul id="headLinks">
<li><a href="/contact.html" rel="nofollow">Contact</a></li>
<li><a href="/quizs/" id="indexHeadLink" title="Les derniers quizs">Parcourir</a></li>
<li><a href="/connexion.html" id="accountHeadLink">Mon compte</a></li>
<li><a href="/a-propos.html">À propos</a></li>
</ul>
</header>
<div id="prompt" class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-512.png" alt="Logo WikiLerni" title="W I K I L E R N I" /></a>
<p class="cardboard">Cultivons notre jardin !</p>
<h1 class="cardboard">Au revoir !</h1>
<div id="response"><p class="error">Si vous voyez ce message, c'est qu'un problème a été rencontré durant la déconnexion.<br>N'hésitez pas <a href="/contact.html">à nous prévenir</a> si le problème persiste.</p></div>
</div>
<footer class="cardboard">
<ul id="footLinks">
<li><a href="https://framasphere.org/people/7e54b7a0b53201389eef2a0000053625" title="Blog WikiLerni sur diaspora*">Blog</a></li>
<li><a href="/credits.html">Crédits</a></li>
<li><a href="/mentions-legales.html" rel="nofollow">Mentions légales</a></li>
<li><a href="/donnees.html">Données personnelles</a></li>
<li><a href="/CGV-CGU.html" rel="nofollow">CGV &amp; CGU</a></li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex">
<title>Ne plus recevoir d'e-mail de WikiLerni</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/unsubscribe.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/wikilerni/css/common.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/common-mobile.css" media="screen and (max-width: 969px)">
<link rel="stylesheet" href="/themes/wikilerni/css/links-page.css" media="screen and (min-width: 970px)">
<link rel="stylesheet" href="/themes/wikilerni/css/links-page-mobile.css" media="screen and (max-width: 969px)">
</head>
<body class="cardboard">
<!-- En tête -->
<header class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-128.png" alt="WikiLerni (logo)" title="Accéder à la page d'accueil de WikiLerni" /></a>
<ul id="headLinks">
<li><a href="/contact.html" rel="nofollow">Contact</a></li>
<li><a href="/quizs/" id="indexHeadLink" title="Les derniers quizs">Parcourir</a></li>
<li><a href="/connexion.html" id="accountHeadLink">Mon compte</a></li>
<li><a href="/a-propos.html">À propos</a></li>
</ul>
</header>
<div id="prompt" class="cardboard">
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-512.png" alt="Logo WikiLerni" title="W I K I L E R N I" /></a>
<p class="cardboard">Cultivons notre jardin !</p>
<div id="response" class="error">Si vous voyez ce message, c'est que votre lien de désabonnement ne fonctionne pas. Vous pouvez <a href="/compte.html">accéder à votre compte</a> pour désactiver les envois.</div>
</div>
<footer class="cardboard">
<ul id="footLinks">
<li><a href="https://framasphere.org/people/7e54b7a0b53201389eef2a0000053625" title="Blog WikiLerni sur diaspora*">Blog</a></li>
<li><a href="/credits.html">Crédits</a></li>
<li><a href="/mentions-legales.html" rel="nofollow">Mentions légales</a></li>
<li><a href="/donnees.html">Données personnelles</a></li>
<li><a href="/CGV-CGU.html" rel="nofollow">CGV &amp; CGU</a></li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="L'erreur est humaine !">
<meta name="robots" content="noindex">
<title>WikiLerni : page non trouvée</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/index.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content">
<header id="main-header">
<h1>WikiLerni</h1>
<p>Cultivons notre jardin !</p>
</header>
<article class="content">
<div class="pure-g">
<div class="l-box pure-u-1">
<h3 class="content-subhead">Page non trouvée !</h3>
<p>La page que vous demandez n'a pas été trouvée !<br>
Peut-être a-t'elle été supprimée ou déplacée ?</p>
</div>
<div class="l-box pure-u-1"><a href="/" class="pure-button pure-button-primary">Cliquez-ici pour accéder à la page d'accueil de WikiLerni.</a></div>
<div class="l-box pure-u-1">
<figure>
<img width="512" alt="Illustration : page non trouvée" src="/img/404-notfound.png">
<figCaption>Crédit illustration : <a title="coursetakers.ae / CC BY-SA (https://creativecommons.org/licenses/by-sa/4.0)" href="https://commons.wikimedia.org/wiki/File:Found_nothing.png">CC BY-SA coursetakers.ae</a></figCaption>
</figure>
</div>
</div>
</article>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-4">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-4">
<a href="/donnees.html">Données personnelles</a>
</li>
<li class="pure-u-1 pure-u-lg-1-4">
<a href="/cgu.html">C.G.U.</a>
</li>
<li class="pure-u-1 pure-u-lg-1-4">
<a href="/cgv.html">C.G.V.</a>
</li>
</ul>
</footer>
</section>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,286 @@
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
/*
* -- BASE STYLES --
* Most of these are inherited from Base, but I want to change a few.
*/
body {
line-height: 1.7em;
color: #C1B654;
font-size: 13px;
}
h1,
h2,
h3,
h4,
h5,
h6,
label {
color: #34495e;
}
.pure-img-responsive {
max-width: 100%;
height: auto;
}
/*
* -- LAYOUT STYLES --
* These are some useful classes which I will need
*/
.l-box {
padding: 1em;
}
.l-box-lrg {
padding: 2em;
border-bottom: 1px solid rgba(0,0,0,0.1);
}
.is-center {
text-align: center;
}
/*
* -- PURE FORM STYLES --
* Style the form inputs and labels
*/
.pure-form label {
margin: 1em 0 0;
font-weight: bold;
font-size: 100%;
}
.pure-form input[type] {
border: 2px solid #ddd;
box-shadow: none;
font-size: 100%;
width: 100%;
margin-bottom: 1em;
}
/*
* -- PURE BUTTON STYLES --
* I want my pure-button elements to look a little different
*/
.pure-button {
background-color: #1f8dd6;
color: white;
padding: 0.5em 2em;
border-radius: 5px;
}
a.pure-button-primary {
background: white;
color: #8D847B;
border-radius: 5px;
font-size: 130%;
}
/*
* -- MENU STYLES --
* I want to customize how my .pure-menu looks at the top of the page
*/
.home-menu {
padding: 0.5em;
text-align: center;
box-shadow: 0 1px 1px rgba(0,0,0, 0.10);
}
.home-menu {
background: #D8AD32;
}
.pure-menu.pure-menu-fixed {
/* Fixed menus normally have a border at the bottom. */
border-bottom: none;
/* I need a higher z-index here because of the scroll-over effect. */
z-index: 4;
}
.home-menu .pure-menu-heading {
color: white;
font-weight: 400;
font-size: 120%;
}
.home-menu .pure-menu-selected a {
color: white;
}
.home-menu a {
color: #8F543B;
}
.home-menu li a:hover,
.home-menu li a:focus {
background: none;
border: none;
color: #8D847B;
}
/*
* -- SPLASH STYLES --
* This is the blue top section that appears on the page.
*/
.splash-container {
background: #FCF6EC;
z-index: 1;
overflow: hidden;
/* The following styles are required for the "scroll-over" effect */
width: 100%;
height: 40%;
top: 0;
left: 0;
position: fixed !important;
}
.splash {
/* absolute center .splash within .splash-container */
width: 80%;
height: 80%;
margin: auto;
position: absolute;
top: 100px; left: 0; bottom: 0; right: 0;
text-align: center;
text-transform: uppercase;
}
/* This is the main heading that appears on the blue section */
.splash-head {
font-size: 20px;
font-weight: bold;
color: #DDE023;
border: 3px solid #DDE023;
padding: 1em 1.6em;
font-weight: 100;
border-radius: 5px;
line-height: 0.5em;
}
/* This is the subheading that appears on the blue section */
.splash-subhead {
color: #8F543B;
letter-spacing: 0.05em;
opacity: 0.8;
}
/*
* -- CONTENT STYLES --
* This represents the content area (everything below the blue section)
*/
.content-wrapper {
/* These styles are required for the "scroll-over" effect */
position: absolute;
top: 39%;
width: 100%;
min-height: 12%;
z-index: 2;
background: white;
}
/* We want to give the content area some more padding */
.content {
padding: 1em 1em 3em;
}
/* This is the class used for the main content headers (<h2>) */
.content-head {
font-weight: 400;
text-transform: uppercase;
letter-spacing: 0.1em;
margin: 2em 0 1em;
}
/* This is a modifier class used when the content-head is inside a ribbon */
.content-head-ribbon {
color: white;
}
/* This is the class used for the content sub-headers (<h3>) */
.content-subhead {
color: #8F543B;
}
.content-subhead i {
margin-right: 7px;
}
/* This is the class used for the dark-background areas. */
.ribbon {
background: #2d3e50;
color: #aaa;
}
/* This is the class used for the footer */
.footer {
background: #111;
position: fixed;
bottom: 0;
width: 100%;
}
/*
* -- TABLET (AND UP) MEDIA QUERIES --
* On tablets and other medium-sized devices, we want to customize some
* of the mobile styles.
*/
@media (min-width: 48em) {
/* We increase the body font size */
body {
font-size: 16px;
}
/* We can align the menu header to the left, but float the
menu items to the right. */
.home-menu {
text-align: left;
}
.home-menu ul {
float: right;
}
/* We increase the height of the splash-container */
/* .splash-container {
height: 500px;
}*/
/* We decrease the width of the .splash, since we have more width
to work with */
.splash {
width: 50%;
height: 50%;
}
.splash-head {
font-size: 250%;
}
/* We remove the border-separator assigned to .l-box-lrg */
.l-box-lrg {
border: none;
}
}
/*
* -- DESKTOP (AND UP) MEDIA QUERIES --
* On desktops and other large devices, we want to over-ride some
* of the mobile and tablet styles.
*/
@media (min-width: 78em) {
/* We increase the header font size even more */
.splash-head {
font-size: 300%;
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,352 @@
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
/*
* -- BASE STYLES --
* Most of these are inherited from Base, but I want to change a few.
*/
body
{
line-height: 1.7em;
color: #C1B654;
font-size: 13px;
}
h1,h2,h3,h4,h5,h6,label
{
color: #34495e;
}
.pure-img-responsive
{
max-width: 100%;
height: auto;
}
/*
* -- LAYOUT STYLES --
* These are some useful classes which I will need
*/
.l-box
{
padding: 1em;
}
.l-box-lrg
{
padding: 2em;
border-bottom: 1px solid rgba(0,0,0,0.1);
}
.is-center
{
text-align: center;
}
/*
* -- PURE BUTTON STYLES --
* I want my pure-button elements to look a little different
*/
.pure-button
{
background-color: #1f8dd6;
color: white;
padding: 0.5em 2em;
border-radius: 5px;
}
a.pure-button-primary
{
background: white;
color: #8D847B;
border-radius: 5px;
font-size: 130%;
}
/*
* -- MENU STYLES --
* I want to customize how my .pure-menu looks at the top of the page
*/
.menu
{
padding: 0.5em;
margin-bottom:0;
text-align: center;
box-shadow: 0 1px 1px rgba(0,0,0, 0.10);
background: #D8AD32;
}
.menu .menu-heading
{
padding: 0.5em 0 0.5em 0;
}
.menu .pure-menu-heading
{
color: white;
font-weight: 400;
font-size: 120%;
}
.menu .pure-menu-selected a
{
color: white;
}
.menu a
{
color: #8F543B;
}
.menu li a:hover, .menu li a:focus
{
background: none;
border: none;
color: #8D847B;
}
/* Header */
#main-header
{
background: #FCF6EC;
min-height: 20%;
padding-top:2em;
text-align: center;
}
#main-header h1
{
width:80%;
margin:auto;
border: 3px solid #DDE023;
border-radius: 5px;
padding: 1em 1.6em;
font-size:20px;
font-weight: bold;
color: #DDE023;
font-weight: 100;
line-height: 0.5em;
}
#main-header p
{
color: #8F543B;
letter-spacing: 0.05em;
opacity: 0.8;
}
/*
* -- PURE BUTTON STYLES --
* I want my pure-button elements to look a little different
*/
.pure-button {
background-color: #1f8dd6;
color: white;
padding: 0.5em 2em;
border-radius: 5px;
}
a.pure-button-primary {
background: white;
color: #8D847B;
border-radius: 5px;
font-size: 130%;
}
#quizsPagination a
{
background: #E7DEDE;
color: #7F7F7F;
border-radius: 5px;
font-size: 130%;
margin: 0.5em;
}
/* We want to give the content area some more padding */
.content
{
padding: 1em 1em 3em;
}
/* This is the class used for the main content headers (<h2>) */
.content-head
{
font-weight: 400;
text-transform: uppercase;
letter-spacing: 0.1em;
margin: 2em 0 1em;
}
/* This is a modifier class used when the content-head is inside a ribbon */
.content-head-questionnaire
{
color: white;
}
/* This is the class used for the dark-background areas. */
.questionnaire-listed, .questionnaire-intro
{
background: #2d3e50;
color: #aaa;
}
.questionnaire-intro img
{
max-height:500px;
width:auto;
}
.questionnaire-listed img
{
max-height:200px;
width:auto;
}
nav#pagination
{
clear:both;
}
/*
* -- PURE FORM STYLES --
* Style the form inputs and labels
*/
.pure-form
{
width:90%;
margin-left:1em;
}
.pure-form label
{
font-weight: bold;
font-size: 100%;
}
.pure-form input[type]
{
border: 2px solid #ddd;
box-shadow: none;
font-size: 100%;
margin-bottom: 1em;
}
.error
{
color: rgb(202, 60, 60);
}
.success
{
color: rgb(28, 184, 65);
}
.information
{
color: rgb(66, 184, 221);
}
#helpClassification a
{
margin-right:1em;
}
.needJS
{
display:none;
}
#links
{
margin:1em 0;
}
#response
{
margin:0.5em 1em;
padding:0.5em;
}
/* Bouton permettant de demander l'affichage du quiz */
#showQuestionnaire
{
display:none;
}
/* Boutons inscription/connexion suite réponse quiz */
.subcribeBtns
{
display:none;
}
.subcribeBtns .pure-button
{
background-color: #1f8dd6;
color: white;
font-size: 1.1em;
margin:0 0.5em;
}
.accountBtns .pure-button
{
background-color: #1f8dd6;
color: white;
font-size: 1.1em;
margin:0 0.5em;
}
.quizs, .help
{
display: none;
}
#main-content
{
padding-bottom:3em;
}
.footer
{
background: #111;
width: 100%;
}
.footer a
{
color:white;
font-weight:normal;
}
.footer ul
{
margin:0;
padding:0;
}
.fab, .fas, .far
{
margin-right:0.5em;
}
/*
* -- TABLET (AND UP) MEDIA QUERIES --
* On tablets and other medium-sized devices, we want to customize some
* of the mobile styles.
*/
@media (min-width: 48em)
{
body
{
font-size: 16px;
}
.menu
{
text-align: left;
}
.menu ul
{
float: right;
}
/* We remove the border-separator assigned to .l-box-lrg */
.l-box-lrg
{
border: none;
}
.footer
{
position: fixed;
bottom: 0;
height:3em;
}
}

View File

@ -0,0 +1,161 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Chaque jour, testez vos connaissances et apprenez de nouvelles choses avec WikiLerni.">
<title>Tout savoir sur WikiLerni</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/index.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
<link rel="canonical" href="https://www.wililerni.com/a-propos.html">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content">
</section>
<header id="main-header">
<h1>WikiLerni</h1>
<p>Cultivons notre jardin !</p>
<p>
<a class="pure-button pure-button-primary" href="/inscription.html">Tester gratuitement</a>
</p>
</header>
<article class="content">
<div class="pure-g">
<div class="l-box pure-u-1">
<h3 class="content-subhead">Qu'est-ce que WikiLerni ?</h3>
<p>WikiLerni vous propose de lire une sélection d'articles de Wikipédia.<br>
Suite à chaque lecture, une série de questions vous permet de tester ce que vous avez retenu.<br>
Toutes les réponses à ce quiz se trouvent dans l'article proposé à la lecture.<br>
Vous pouvez sauvegarder vos résultats au quiz en créant un compte WikiLerni.<br>
Vous pourrez ensuite répondre de nouveau aux mêmes questions plusieurs semaines, mois... après avoir lu l'article.<br>
De quoi tester votre mémoire à plus en moins long terme !</p>
<h3 class="content-subhead">Que signifie "WikiLerni" ?</h3>
<p>Le nom "WikiLerni" est composé de deux parties :
<ul>
<li>« Wiki » fait évidemment référence à l'encyclopédie en ligne Wikipédia. Comme je vous sais curieux, sachez que le terme « wiki » vient lui-même du mot hawaïen « wikiwiki » signifiant « rapide », « vite » ou « informel ».</li>
<li>« Lerni » est un verbe espéranto signifiant « apprendre » ou encore « étudier ». Vous pouvez penser à « learn » en anglais ou encore « lernen » en allemand.</li>
</ul>
Bref, WikiLerni vous invite à <b>apprendre de nouvelles choses avec Wikipédia !</b><br>
Et en lisant ces quelques lignes, vous venez peut-être déjà d'apprendre quelque chose :-)</p>
<h3 class="content-subhead">À qui s'adresse WikiLerni ?</h3>
<p>À toute personne curieuse aimant apprendre de nouvelles choses !<br>
Contrairement à d'autres sites de quiz testant votre culture générale, WikiLerni ne vous demande pas d'être déjà très "savants", même si vous l'êtes forcément, au moins dans certains domaines. Si ! Si !<br>
Vous êtes ici pour apprendre et toutes les réponses aux questions des quizs se trouvent dans l'article qui vous est proposé à la lecture.<br>
C'est donc une façon ludique d'apprendre de nouvelles choses sur des sujets auxquels vous ne vous seriez peut-être jamais intéressé par vous-même.</p>
<h3 class="content-subhead">Est-il nécessaire de créer un compte pour utiliser WikiLerni ?</h3>
<p>Non, vous pouvez parcourir librement WikiLerni sans avoir besoin d'être connecté au site.<br>
Votre compte vous permettra de :
<ul>
<li>sauvegarder vos résultats aux quizs</li>
<li>recevoir des suggestions de nouvelles lectures, directement par e-mail</li>
<li>faciliter votre navigation via un moteur de recherche interne à WikiLerni</li>
<li>et d'autres fonctionnalités à venir...</li>
</ul></p>
<h3 class="content-subhead">Comment sont sélectionnés les articles proposés par WikiLerni ?</h3>
<p>Tout comme Wikipédia, WikiLerni se veut éclectique.<br>
Donc il vous sera proposé des articles sur des sujets très variés : sciences, arts, histoire, littérature, mythologie, géographie, culture populaire, etc.<br>
Il n'y aucun sujet qui ne soit pas digne d'intérêt à priori. Seront évités des articles indiqués par Wikipédia comme de faible qualité ou encore sujets à polémique.</p>
<p>Les articles proposés à la lecture peuvent être de longueurs variées.<br>
Une estimation de la durée de lecture vous est indiquée sur la page du quiz ou encore dans les e-mails que vous recevez si vous êtes abonné.<br>
Vous pourrez ainsi choisir de laisser de côté un article un peu long pour y revenir quand vous aurez plus de temps.</p>
<p>Pour l'instant, les articles proposés sont uniquement francophones.<br>
Mais suivant le succès du site, des versions dans d'autres langues pourront être envisagées.<br>
Si vous souhaitez vous-même lancer une version dans une autre langue, mais n'avez pas les compétences techniques pour le faire, n'hésitez <a href="/contact.hml">à me contacter</a> pour un éventuel partenariat.</p>
<h3 class="content-subhead">Quel lien entre WikiLerni et Wikipédia ?</h3>
<p>Puisque toutes les suggestions de lecture de WikiLerni vous amènent sur Wikipédia, le lien est évident.<br>
Pour autant, WikiLerni n'est pas un projet porté par la fondation Wikipédia, ni par une de ses associations locales, mais est un projet indépendant.<br>
Au-delà de ce lien pratique, WikiLerni se veut très proche de l'esprit de Wikipédia en partageant son attrait pour la culture libre et le "libre" en général.
L'application faisant fonctionner WikiLerni est un logiciel libre, c'est-à-dire que toute personne peut l'utiliser, étudier son code, le modifier, le distribuer selon son souhait.<br>Ceci est également vrai pour le graphisme du site et les quizs eux-mêmes : texte d'introduction, questions/réponses, illustrations... Sauf exceptions indiquées.</p>
<h3 class="content-subhead">Est-ce que WikiLerni est gratuit ?</h3>
<p>Oui.. et non ! Réponse de normand venant d'un breton ? :-)<br>Vous pouvez tout à fait parcourir WikiLerni, lire les articles proposés et répondre aux quizs.<br>
Tout cela se fait sans avoir besoin de vous abonner et donc gratuitement.<br>Par contre, si vous souhaitez garder vos résultats, recevoir par mail de nouvelles suggestions de lectures... vous devrez vous abonner au site.<br>Vous pourrez alors tester gratuitement l'abonnement pendant une période de découverte.<br>Ensuite, vous serez invité à souscrire à un abonnement, mais à un prix "libre", c'est-à-dire que différentes possibilités vous seront proposées.<br>Tout le monde n'a pas les mêmes moyens, ni le même intérêt pour le WikiLerni, donc à vous de choisir en conscience.<br>
Vous aimez WikiLerni, mais ne pouvez vraiment pas payer pour ce service ? <a href="/contact.html">Contactez-moi</a>. Vous n'avez pas à vous justifier.<br>
De même, si aucune des options ne vous convient ou encore si vous préférez un autre moyen de paiement (chèque, virement...), <a href="/contact.html">contactez-moi</a> pour me dire ce que vous souhaitez.<br>Des abonnements de groupe (famille, écoles, associations...) ou autres formules peuvent aussi être envisagées, dans la mesure où cela sera techniquement possible.<br>Bref, <b>nous sommes ici plus dans l'esprit d'un financement participatif que dans celui d'un abonnement classique</b>.<br>En souscrivant à un abonnement, vous permettez à WikiLerni d'exister.</p>
<h3 class="content-subhead">À quoi va servir l'argent de mon abonnement ?</h3>
<p>Cet argent va principalement servir à :<ul>
<li>payer les frais techniques liés au site : serveurs, routeurs e-mail, paiement en ligne...</li>
<li>développer le logiciel permettant à WikiLerni de fonctionner.</li>
<li>consacrer du temps à sélectionner les articles Wikipédia et préparer les questions.</li>
<li>gérer le site au quotidien : répondre à vos courriels, communiquer, etc.</li>
</ul>Tout cela sans vous imposer de publicités ou faire commerce de vos données personnelles.</b><br>
Par ailleurs, une partie des bénéfices nets de WikiLerni seront distribués sous forme de dons à l'<a href="https://www.wikimedia.fr/" target="_blank">association Wikimédia France</a> et aux développeurs des logiciels libres utilisés par WikiLerni.<br>Je parle de bénéfices "nets", car au-delà des frais de fonctionnement, mon activité étant déclarée, il faut y soustraire TVA, cotisations, etc.<br><br>
<b>Pas de publicité, respect de vos données personnelles, activité déclarée en France... un autre internet serait possible ?</b></p>
<h3 class="content-subhead">Comment puis-je aider WikiLerni ?</h3>
<p>Tout d'abord en l'utilisant et <a href="/contact.html">me retournant</a> vos éventuelles remarques/suggestions.<br>
Un bug ? Une erreur d'orthographe ? Une suggestion de fonctionnalité ? Quelque chose que vous ne comprenez pas ? N'hésitez pas à me le dire, cela m'intéresse.<br>
Ensuite, si vous avez les moyens, vous pouvez souscrire un abonnement payant pour permettre au projet de perdurer.<br>
Et WikiLerni n'ayant pas les moyens des grandes sociétés pour communiquer, vous pouvez aussi en parler autour de vous, en ligne ou dans la vie de tous les jours. <b>
Vous le savez sans doute, rien ne vaut le bouche à oreille !</b><br>
Un système de parrainage est d'ailleurs prévu pour vous récompenser : à chaque fois qu'une personne inscrite en vous désignant comme "parrain" souscrit un abonnement payant, votre propre abonnement se trouve prolongé de 30 jours.</p>
</div>
<div class="l-box pure-u-1"><a href="/inscription.html" class="pure-button pure-button-primary">Tester gratuitement.</a></div>
</article>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-4">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-4">
<a href="/donnees.html">Données personnelles</a>
</li>
<li class="pure-u-1 pure-u-lg-1-4">
<a href="/cgu.html">C.G.U.</a>
</li>
<li class="pure-u-1 pure-u-lg-1-4">
<a href="/cgv.html">C.G.V.</a>
</li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,100 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Page d'accueil de l'abonné.">
<meta name="robots" content="noindex">
<title>WikiLerni</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/homeUser.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
<link rel="canonical" href="https://www.wililerni.com/accueil.html">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content" class="needJS">
<div class="pure-menu pure-menu-horizontal">
<a href="/accueil.html" class="pure-menu-heading pure-menu-link">Mon WikiLerni</a>
<ul id="classement" class="pure-menu-list">
<li class="pure-menu-item"><a class="pure-menu-link" href="/compte.html#infos" title="Email, mot de passe">Mes informations</a></li>
<li class="pure-menu-item"><a href="/compte.html#subscribe" class="pure-menu-link">Mon abonnement</a></li>
<li class="pure-menu-item"><a href="/sortie.html" class="pure-menu-link">Me déconnecter</a></li>
</ul>
</div>
<div class="content">
<h2 class="content-head is-center">WikiLerni</h2>
<div class="pure-g">
<div class="l-box-lrg pure-u-1" id="message"></div>
<form class="pure-form pure-u-1" id="search" method="POST">
<fieldset>
<legend>Chercher un quiz</legend>
<input id="searchQuestionnaires" type="txt" name="searchQuestionnaires" placeholder="Votre recherche">
<input type="checkbox" id="onlyAnswers" name="onlyAnswers" /> Parmi mes réponses.
<input id="begin" type="hidden" name="begin" value="0">
<button type="submit" class="pure-button pure-button-primary">Chercher</button>
<button type="button" id="random" class="pure-button pure-button-primary">Au hasard !</button>
</fieldset>
</form>
<div class="l-box-lrg pure-u-1" id="quizs">
<h2 id="quizsTitle">Les quizs attendant votre réponse</h2>
<div id="quizsIntro"></div>
<div id="quizsList"></div>
<div id="quizsPagination"></div>
</div>
</div>
</div>
</section>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Crédits</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/donnees.html">Données personnelles</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgu.html">C.G.U.</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgv.html">C.G.V.</a>
</li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Valider la suppression de votre compte.">
<meta name="robots" content="noindex">
<title>Valider la suppression de votre compte</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/deleteValidation.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
<link rel="canonical" href="https://www.wililerni.com/aurevoir.html">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content">
<header id="main-header">
<h1>WikiLerni</h1>
<p>Cultivons notre jardin !</p>
</header>
<div class="content">
<h2 class="content-head is-center">Valider la suppression de votre compte</h2>
<div class="pure-g">
<div class="l-box-lrg pure-u-1">
<noscript>Désolé, mais pour l'instant, l'utilisation de WikiLerni nécessite l'activation du JavaScript. Nous travaillons à changer cela.</noscript>
<div id="response">Si vous voyez ce message, c'est que votre lien de validation n'est pas valide ou a expiré. Vous pouvez <a href="/compte.html">en demander un nouveau en cliquant ici</a>.</div>
</div>
</div>
</div>
</section>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Crédits</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/donnees.html">Données personnelles</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgu.html">C.G.U.</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgv.html">C.G.V.</a>
</li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,176 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Mettre à jour votre compte, votre abonnement...">
<meta name="robots" content="noindex">
<title>Mon compte</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/accountUser.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
<link rel="canonical" href="https://www.wililerni.com/compte.html">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content" class="needJS">
<div class="pure-menu pure-menu-horizontal">
<a href="/accueil.html" class="pure-menu-heading pure-menu-link">Mon WikiLerni</a>
<ul id="classement" class="pure-menu-list">
<li class="pure-menu-item"><a class="pure-menu-link" href="/compte.html#infos" title="Email, mot de passe">Mes informations</a></li>
<li class="pure-menu-item"><a href="/compte.html#subscribe" class="pure-menu-link">Mon abonnement</a></li>
<li class="pure-menu-item"><a href="/sortie.html" class="pure-menu-link">Me déconnecter</a></li>
</ul>
</div>
<div class="content">
<h2 class="content-head is-center" id="infos">Votre compte WikiLerni</h2>
<div class="pure-g">
<div class="l-box-lrg pure-u-1">
<div id="message"></div>
<form class="pure-form pure-form-aligned" id="accountUpdate" method="POST">
<fieldset>
<div class="pure-control-group">
<label for="name">Nom ou pseudo</label>
<input id="name" type="text" name="name">
<span class="pure-form-message-inline"></span>
</div>
<div class="pure-control-group">
<label for="email">Email</label>
<input id="email" type="email" name="email">
<span class="pure-form-message-inline" id="emailMessage"></span>
</div>
<div class="pure-control-group">
<label for="newPassword">Nouveau mot de passe</label>
<input id="newPassword" type="password" name="newPassword">
<span class="pure-form-message-inline" id="newPasswordMessage"><b class="information">Laisser vide sauf si vous souhaitez le changer.</b></span>
</div>
<div class="pure-controls">
Par défaut un nouveau quiz vous est proposé tous les jours. Vous pouvez préciser ci-dessous les jours souhaités :
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-7"><!-- grille ne fonctionne pas ?! -->
<label for="d2">
<input type="checkbox" id="d2" name="d2" value="true" /> Lundi
</label>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d3">
<input type="checkbox" id="d3" name="d3" value="true" /> Mardi
</label>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d4">
<input type="checkbox" id="d4" name="d4" value="true" /> Mercredi
</label>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d5">
<input type="checkbox" id="d5" name="d5" value="true" /> Jeudi
</label>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d6">
<input type="checkbox" id="d6" name="d6" value="true" /> Vendredi
</label>
</li>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d7">
<input type="checkbox" id="d7" name="d7" value="true" /> Samedi
</label>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d1">
<input type="checkbox" id="d1" name="d1" value="true" /> Dimanche
</label>
</li>
</ul>
</div>
<div class="pure-controls">
<label for="noticeOk" class="pure-checkbox">
<input type="checkbox" id="noticeOk" name="noticeOk" value="true" /> Je souhaite recevoir les nouveaux quizs par email.
</label>
<label for="newsletterOk" class="pure-checkbox">
<input type="checkbox" id="newsletterOk" name="newsletterOk" value="true" /> J'accepte de recevoir d'autres messages ponctuels de WikiLerni (nouvelles fonctionnalités ...).
</label>
<label for="deleteOk" class="pure-checkbox error">
<input type="checkbox" id="deleteOk" name="deleteOk" value="true" /> Je souhaite supprimer mon compte utilisateur
</label>
<button type="submit" class="pure-button pure-button-primary" id="submitDatas">Valider</button>
</div>
</fieldset>
</form>
<div id="response"></div>
<h3 id="subscribe">Votre abonnement</h3>
<div id="subscribeIntro"></div>
<p>Le coût de l'abonnement dépend de la durée pour laquelle vous vous engagez.</p>
<p>Merci de sélectionnez l'offre qui vous convient et de valider les C.G.V.<br>Un bouton vous permettra ensuite d'effectuer votre paiement.</p>
<ul>
<li><input type="checkbox" id="180Ok" name="180Ok" value="true" /> Je m'engage pour 180 jours</li>
<li><input type="checkbox" id="365Ok" name="365Ok" value="true" /> Je m'engage pour 365 jours</li>
<li><input type="checkbox" id="CGVOk" name="CGVOk" value="true" /> J'ai lu et accepte les <a href="/cgv.html">Conditions Générales de Vente</a>.</li>
</ul>
<div id="WPBtn180" class="needJS"><script>document.write("ici apparaîtra un bouton de paiement pour les 180 jours")</script></div>
<div id="WPBtn365" class="needJS"><script>document.write("ici apparaîtra un bouton de paiement pour les 365 jours")</script></div>
<div id="WPNone" class="needJS"><p><b>Merci de sélectionné l'offre qui vous convient.</b></p></div>
<h3 id="godfather">Les utilisateurs que vous avez parrainés</h3>
<p>Vous pouvez parrainer d'autres utilisateurs. Pour ce faire, demandez-leur de saisir lors de l'inscription votre adresse e-mail <strong id="godfatherEmail"></strong> ou encore le code suivant : <strong id="godfatherCode"></strong> (en gardant les majuscules/minuscules).</p>
<p>À chaque fois qu'un utilisateur que vous avez parrainé <b>souscrit un abonnement payant</b>, son abonnement comme le vôtre <b>se trouve prolongé gratuitement de 30 jours</b>. Ceci reste valable tant que cet utilisateur garde son compte WikiLerni.</p>
<p id="godchilds"><b>Pour l'instant aucune personne ne s'est inscrite en vous désignant comme "parrain".</b></p>
</div>
</div>
</div>
</section>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Crédits</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/donnees.html">Données personnelles</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgu.html">C.G.U.</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgv.html">C.G.V.</a>
</li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,108 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Formulaire de connexion à WikiLerni.">
<title>Se connecter à WikiLerni</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/connection.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
<link rel="canonical" href="https://www.wililerni.com/inscription.html">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content">
<header id="main-header">
<h1>WikiLerni</h1>
<p>Cultivons notre jardin !</p>
</header>
<div class="content">
<h2 class="content-head is-center">Connectez-vous à WikiLerni</h2>
<div class="pure-g">
<div class="l-box-lrg pure-u-1">
<noscript>Désolé, mais pour l'instant, l'utilisation de WikiLerni nécessite l'activation du JavaScript. Nous travaillons à changer cela.</noscript>
<div id="message"></div>
<form class="pure-form pure-form-aligned needJS" id="connection" method="POST">
<fieldset>
<p>Pas de compte WikiLerni ? <a href="/inscription.html">créez-le ici</a>.</p>
<div class="pure-control-group">
<label for="email">Email</label>
<input id="email" type="email" name="email" placeholder="Adresse e-mail utilisée pour ce site" required>
<span class="pure-form-message-inline" id="emailMessage"></span>
</div>
<div class="pure-control-group">
<label for="password">Mot de passe</label>
<input id="password" type="password" name="password" placeholder="Votre mot de passe">
<span class="pure-form-message-inline" id="passwordMessage"><i class="information">Oublié ? Alors laissez vide et cochez la case ci-dessous.</i></span>
</div>
<div class="pure-controls">
<label for="getLoginLink" class="pure-checkbox">
<input type="checkbox" id="getLoginLink" name="getLoginLink" value="true" /> Je souhaite recevoir un lien de connexion par e-mail.
</label>
<label for="keepConnected" class="pure-checkbox">
<input type="checkbox" id="keepConnected" name="keepConnected" value="true" /> Je souhaite ne pas avoir à me connecter à chaque fois.
</label>
<button type="submit" class="pure-button pure-button-primary">Valider</button>
</div>
</fieldset>
</form>
<div id="response"></div>
</div>
<div class="l-box-lrg pure-u-1">
<h4>Besoin d'aide ?</h4>
<p>Si vous avez oublié votre mot de passe, il vous suffit de cocher la case <i>"Je souhaite recevoir un lien de connexion par e-mail."</i>. Un lien valide pendant une courte durée vous permettra de vous connecter au site.<br>Si vous ne vous souvenez pas non plus de l'adresse e-mail utilisée sur ce site ou que vous n'y avez plus accès, vous pouvez <a href="/contact.html">nous contacter</a> en fournissant des informations permettant de vous identifier.</p><p>La case <i>"Je souhaite ne pas avoir à me connecter à chaque fois."</i> vous permettra de rester connecté(e) jusqu'à 6 mois, pour peu que vous utilisiez le même navigateur internet.</p>
</div>
</div>
</div>
</section>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Crédits</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/donnees.html">Données personnelles</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgu.html">C.G.U.</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgv.html">C.G.V.</a>
</li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,266 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Saisie et mise à jour des quizs">
<meta name="robots" content="noindex">
<title>Les quizs</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/manageQuestionnaires.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
<link rel="canonical" href="https://www.wililerni.com/gestion-quizs.html">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content" class="needJS">
<div class="pure-menu pure-menu-horizontal">
<a href="/gestion.html" class="pure-menu-heading pure-menu-link">Gestion WikiLerni</a>
<ul id="classement" class="pure-menu-list">
<li class="pure-menu-item"><a href="/gestion-quizs.html" title="Publication des quizs" class="pure-menu-link" >Les quizs</a></li>
<li class="pure-menu-item"><a href="/gestion-utilisateurs.html" title="Les comptes utilisateurs" class="pure-menu-link">Les abonné(e)s</a></li>
<li class="pure-menu-item"><a href="/sortie.html" title="Sortie des artistes !" class="pure-menu-link">Me déconnecter</a></li>
</ul>
</div>
<div class="content">
<h2 class="content-head is-center" id="infos">Les quizs</h2>
<form class="pure-form pure-u-1" id="search" method="POST">
<fieldset>
<legend>Chercher un quiz</legend>
<input id="searchQuestionnaires" type="txt" name="searchQuestionnaires" placeholder="Votre recherche">
<button type="submit" class="pure-button pure-button-primary">Chercher</button>
<div id="searchResult"></div>
</fieldset>
</form>
<div class="pure-g">
<div class="l-box-lrg pure-u-1">
<div id="message"></div>
<form class="pure-form pure-form-aligned" id="questionnaires" method="POST">
<fieldset>
<legend>Informations du quiz</legend>
<div class="pure-control-group">
<label for="title">Titre</label>
<input id="title" type="text" name="title">
<span class="pure-form-message-inline"></span>
</div>
<div class="pure-control-group">
<label for="slug">Page html</label>
<input id="slug" type="text" name="slug">.html
</div>
<div class="pure-control-group">
<label for="introduction">Introduction</label>
<textarea id="introduction" name="introduction" rows="5"></textarea>
</div>
<div class="pure-control-group">
<label for="keywords">Mots-clés</label>
<textarea id="keywords" name="keywords" rows="5"></textarea>
</div>
<div class="pure-control-group">
<label for="publishingAt">Date de publication</label>
<input id="publishingAt" type="date" name="publishingAt"> <span class="information" id="helpPublishingAt"></span>
</div>
<div class="pure-control-group">
<label for="estimatedTime">Durée de lecture estimée</label>
<select name="estimatedTime" id="estimatedTime">
<option value="short">Court</option>
<option value="medium">Moyen</option>
<option value="long">Long</option>
</select>
</div>
<div class="pure-control-group">
<label for="classification">Catégories de classement</label>
<input id="classification" type="text" name="classification"> <span class="information" id="helpClassification">Séparer les rubriques par des virgules</span>
</div>
<div class="pure-controls">
<label for="deleteOk" class="pure-checkbox error" id="deleteOkLabel">
<input type="checkbox" id="deleteOk" name="deleteOk" value="true" /> Je souhaite supprimer ce quiz
</label>
<input type="hidden" name="id" id="id" value="">
<button type="submit" class="pure-button pure-button-primary" id="submitDatas">Valider</button>
<a href="#questionnaires" class="pure-button pure-button-primary needJS" id="wantNewQuestionnaire">Nouveau questionnaire</a>
<a href="#questionnaires" class="pure-button pure-button-primary needJS" id="previewQuestionnaire" target=="_blank">Voir le quiz</a>
</div>
<div id="response"></div>
<div id="linksList" class="needJS"></div>
<div id="illustrationsList" class="needJS"></div>
<div id="questionsList" class="needJS"></div>
</fieldset>
</form>
<form class="pure-form pure-form-aligned" id="links" method="POST">
<fieldset>
<legend>Informations du lien</legend>
<div class="pure-control-group">
<label for="url">Url</label>
<input id="url" type="url" name="url">
<span class="pure-form-message-inline"></span>
</div>
<div class="pure-control-group">
<label for="anchor">Texte du lien</label>
<input id="anchor" type="text" name="anchor" value="Lire l'article sur Wikipédia.">
</div>
<div class="pure-controls">
<input type="hidden" name="id" id="idLink" value="">
<input type="hidden" name="QuestionnaireId" id="QuestionnaireIdLink" value="">
<input type="hidden" name="deleteOk" id="deleteOkLink" value="">
<button type="submit" class="pure-button pure-button-primary">Valider</button>
</div>
<div id="responseLink"></div>
</fieldset>
</form>
<form class="pure-form pure-form-aligned" id="illustrations" method="POST" enctype="multipart/form-data">
<fieldset>
<legend>Informations de l'illustration</legend>
<div class="pure-control-group">
<label for="image">Sélectionnez le fichier à utiliser</label>
<input type="file" id="image" name="image">
</div>
<div class="pure-control-group">
<label for="alt">Propriété "alt"</label>
<input id="alt" type="text" name="alt">
</div>
<div class="pure-control-group">
<label for="title">Propriété "title"</label>
<input id="title" type="text" name="title">
</div>
<div class="pure-control-group">
<label for="caption">Légende</label>
<input id="caption" type="text" name="caption">
</div>
<div class="pure-controls">
<input type="hidden" name="id" id="idIllustration" value="">
<input type="hidden" name="QuestionnaireId" id="QuestionnaireIdIllustration" value="">
<input type="hidden" name="deleteOk" id="deleteOkIllustration" value="">
<button type="submit" class="pure-button pure-button-primary">Valider</button>
</div>
<div id="responseIllustration"></div>
</fieldset>
</form>
<form class="pure-form pure-form-aligned" id="questions" method="POST">
<fieldset>
<legend>La question et les réponses proposées</legend>
<div class="pure-control-group">
<label for="text">Question</label>
<input id="text" type="text" name="text">
<span class="pure-form-message-inline"></span>
</div>
<div class="pure-control-group">
<label for="rank">Rang</label>
<input id="rank" type="number" name="rank" value="1"> <span class="information">Permet de fixer l'ordre d'affichage des questions.</span>
</div>
<div class="pure-control-group">
<label for="explanation">Explications</label>
<textarea id="explanation" name="explanation" rows="5"></textarea>
</div>
<legend>Réponses proposées</legend>
<div class="pure-control-group">
<input id="choiceText0" type="text" name="choiceText0" maxlength="255" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect0" name="choiceIsCorrect0" value="true" /> Réponse correcte
<input type="hidden" name="idChoice0" id="idChoice0" value="">
</div>
<div class="pure-control-group">
<input id="choiceText1" type="text" name="choiceText1" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect1" name="choiceIsCorrect1" value="true" /> Réponse correcte
<input type="hidden" name="idChoice1" id="idChoice1" value="">
</div>
<div class="pure-control-group">
<input id="choiceText2" type="text" name="choiceText2" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect2" name="choiceIsCorrect2" value="true" /> Réponse correcte
<input type="hidden" name="idChoice2" id="idChoice2" value="">
</div>
<div class="pure-control-group">
<input id="choiceText3" type="text" name="choiceText3" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect3" name="choiceIsCorrect3" value="true" /> Réponse correcte
<input type="hidden" name="idChoice3" id="idChoice3" value="">
</div>
<div class="pure-control-group">
<input id="choiceText4" type="text" name="choiceText4" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect4" name="choiceIsCorrect4" value="true" /> Réponse correcte
<input type="hidden" name="idChoice4" id="idChoice4" value="">
</div>
<div class="pure-control-group">
<input id="choiceText5" type="text" name="choiceText5" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect5" name="choiceIsCorrect5" value="true" /> Réponse correcte
<input type="hidden" name="idChoice5" id="idChoice5" value="">
</div>
<div class="pure-control-group">
<input id="choiceText6" type="text" name="choiceText6" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect6" name="choiceIsCorrect6" value="true" /> Réponse correcte
<input type="hidden" name="idChoice6" id="idChoice6" value="">
</div>
<div class="pure-control-group">
<input id="choiceText7" type="text" name="choiceText7" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect7" name="choiceIsCorrect7" value="true" /> Réponse correcte
<input type="hidden" name="idChoice7" id="idChoice7" value="">
</div>
<div class="pure-control-group">
<input id="choiceText8" type="text" name="choiceText8" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect8" name="choiceIsCorrect8" value="true" /> Réponse correcte
<input type="hidden" name="idChoice8" id="idChoice8" value="">
</div>
<div class="pure-control-group">
<input id="choiceText9" type="text" name="choiceText9" maxlength="255">&nbsp;<input type="checkbox" id="choiceIsCorrect9" name="choiceIsCorrect9" value="true" /> Réponse correcte
<input type="hidden" name="idChoice9" id="idChoice9" value="">
</div>
<div class="pure-controls">
<input type="hidden" name="id" id="idQuestion" value="">
<input type="hidden" name="QuestionnaireId" id="QuestionnaireIdQuestion" value="">
<input type="hidden" name="deleteOk" id="deleteOkQuestion" value="">
<button type="submit" class="pure-button pure-button-primary">Valider</button>
</div>
<div id="responseQuestion"></div>
</fieldset>
</form>
<div class="l-box-lrg pure-u-1" id="questionnairesList"></div>
</div>
</div>
</div>
</section>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Crédits</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/donnees.html">Données personnelles</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgu.html">C.G.U.</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgv.html">C.G.V.</a>
</li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,211 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Gestion des abonnés">
<meta name="robots" content="noindex">
<title>Les abonnés</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/manageUsers.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
<link rel="canonical" href="https://www.wililerni.com/gestion-utilisateurs.html">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content" class="needJS">
<div class="pure-menu pure-menu-horizontal">
<a href="/gestion.html" class="pure-menu-heading pure-menu-link">Gestion WikiLerni</a>
<ul id="classement" class="pure-menu-list">
<li class="pure-menu-item"><a href="/gestion-quizs.html" title="Publication des quizs" class="pure-menu-link" >Les quizs</a></li>
<li class="pure-menu-item"><a href="/gestion-utilisateurs.html" title="Les comptes utilisateurs" class="pure-menu-link">Les abonné(e)s</a></li>
<li class="pure-menu-item"><a href="/sortie.html" title="Sortie des artistes !" class="pure-menu-link">Me déconnecter</a></li>
</ul>
</div>
<div class="content">
<h2 class="content-head is-center" id="infos">Les abonnés</h2>
<form class="pure-form pure-u-1" id="search" method="POST">
<fieldset>
<legend>Chercher un utilisateur</legend>
<input id="search" type="txt" name="search" placeholder="Votre recherche">
<button type="submit" class="pure-button pure-button-primary">Chercher</button>
<div id="searchResult"></div>
</fieldset>
</form>
<div class="pure-g">
<div class="l-box-lrg pure-u-1">
<div id="message"></div>
<form class="pure-form pure-form-aligned" id="users" method="POST">
<fieldset>
<legend>Informations de l'abonné</legend>
<div id="subscribeIntro"></div>
<div class="pure-control-group">
<label for="name">Nom ou pseudo</label>
<input id="name" type="text" name="name">
<span class="pure-form-message-inline"></span>
</div>
<div class="pure-control-group">
<label for="email">Email</label>
<input id="email" type="email" name="email">
<span class="pure-form-message-inline" id="emailMessage"></span>
</div>
<div class="pure-control-group">
<label for="status">Statut</label>
<select id="status" name="status">
<option value="user">Utilisateur</option>
<option value="creator">Créateur</option>
<option value="manager">Gestionnaire</option>
<option value="admin">Administrateur</option>
</select>
</div>
<div class="pure-control-group">
<label for="smtp">SMTP</label>
<select id="smtp" name="smtp">
<option value="0">Spirion</option>
<option value="1">Mailjet</option>
</select>
</div>
<div class="pure-control-group">
<label for="timeDifference">Décalage horaire</label>
<input id="timeDifference" type="number" name="timeDifference" readonly>
</div>
<div class="pure-control-group">
<label for="newPassword">Nouveau mot de passe</label>
<input id="newPassword" type="password" name="newPassword">
<span class="pure-form-message-inline" id="newPasswordMessage"><b class="information">Laisser vide sauf si vous souhaitez le changer.</b></span>
</div>
<div class="pure-control-group">
<label for="adminComments">Commentaires</label>
<textarea id="adminComments" name="adminComments" rows="5"></textarea>
</div>
<div class="pure-control-group">
<label for="numberOfDays">Durée totale de l'abonnement</label>
<input id="numberOfDays" type="number" name="numberOfDays"><span class="pure-form-message-inline"><i class="information">Depuis la création de son compte.</i></span>
</div>
<div class="pure-controls">
Jours valables pour l'abonnement
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-7"><!-- grille ne fonctionne pas ?! -->
<label for="d2">
<input type="checkbox" id="d2" name="d2" value="true" /> Lundi
</label>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d3">
<input type="checkbox" id="d3" name="d3" value="true" /> Mardi
</label>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d4">
<input type="checkbox" id="d4" name="d4" value="true" /> Mercredi
</label>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d5">
<input type="checkbox" id="d5" name="d5" value="true" /> Jeudi
</label>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d6">
<input type="checkbox" id="d6" name="d6" value="true" /> Vendredi
</label>
</li>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d7">
<input type="checkbox" id="d7" name="d7" value="true" /> Samedi
</label>
</li>
<li class="pure-u-1 pure-u-lg-1-7">
<label for="d1">
<input type="checkbox" id="d1" name="d1" value="true" /> Dimanche
</label>
</li>
</ul>
</div>
<div class="pure-controls">
<label for="noticeOk" class="pure-checkbox">
<input type="checkbox" id="noticeOk" name="noticeOk" value="true" /> Reçoit les quizs par email.
</label>
<label for="newsletterOk" class="pure-checkbox">
<input type="checkbox" id="newsletterOk" name="newsletterOk" value="true" /> Reçoit les autres actus par email.
</label>
<label for="validationOk" class="pure-checkbox success" id="validationOkLabel">
<input type="checkbox" id="validationOk" name="validationOk" /> Valider le compte.
</label>
<label for="deleteOk" class="pure-checkbox error" id="deleteOkLabel">
<input type="checkbox" id="deleteOk" name="deleteOk" /> Supprimer le compte
</label>
<input type="hidden" name="id" id="id" value="">
<button type="submit" class="pure-button pure-button-primary" id="submitDatas">Valider</button>
<a href="#users" class="pure-button pure-button-primary" id="wantNewUser">Vider</a>
</div>
<div id="response"></div>
<div id="infosPayments" class="needJS"><h4>Payments reçus via l'API WebPortage</h4></div>
<div id="infosGodchilds" class="needJS"><h4>Parrainages</h4></div>
</fieldset>
</form>
</div>
</div>
</div>
</section>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Crédits</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/donnees.html">Données personnelles</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgu.html">C.G.U.</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgv.html">C.G.V.</a>
</li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Page d'accueil du gestionnaire.">
<meta name="robots" content="noindex">
<title>WikiLerni - gestion du site</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/homeManager.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
<link rel="canonical" href="https://www.wililerni.com/gestion.html">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content" class="needJS">
<div class="pure-menu pure-menu-horizontal">
<a href="/gestion.html" class="pure-menu-heading pure-menu-link">Gestion WikiLerni</a>
<ul id="classement" class="pure-menu-list">
<li class="pure-menu-item"><a href="/gestion-quizs.html" title="Publication des quizs" class="pure-menu-link" >Les quizs</a></li>
<li class="pure-menu-item"><a href="/gestion-utilisateurs.html" title="Les comptes utilisateurs" class="pure-menu-link">Les abonné(e)s</a></li>
<li class="pure-menu-item"><a href="/sortie.html" title="Sortie des artistes !" class="pure-menu-link">Me déconnecter</a></li>
</ul>
</div>
<div class="content">
<h2 class="content-head is-center">WikiLerni</h2>
<div class="pure-g">
<div class="l-box-lrg pure-u-1" id="message"></div>
<div class="l-box-lrg pure-u-1" id="questionnaires"></div>
</div>
<p><a href="#" class="pure-button pure-button-primary" id="wantRegenerate">Régénérer le HTML.</a></p>
</div>
</section>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Crédits</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/donnees.html">Données personnelles</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgu.html">C.G.U.</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgv.html">C.G.V.</a>
</li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1 @@
<!DOCTYPE html><html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="description" content="Chaque jour, testez vos connaissances et apprenez de nouvelles choses avec WikiLerni."><title>WikiLerni : qu'allez-vous apprendre aujourd'hui ?</title><base href="http://localhost:8080"><script src="/JS/polyfill.app.js" defer></script><script src="/JS/index.app.js" defer></script><link rel="stylesheet" href="/themes/default/CSS/pure-min.css"><link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css"><link rel="stylesheet" href="/themes/default/CSS/wikilerni.css"><link rel="shortcut icon" href="/img/favicon.ico"><link rel="apple-touch-icon" sizes="57x57" href="/img/apple-icon-57x57.png"><link rel="icon" type="image/png" sizes="192x192" href="/img/android-icon-192x192.png"><link rel="icon" type="image/png" sizes="32x32" href="/img/favicon-32x32.png"><link rel="canonical" href="http://localhost:8080"></head><body><header class="pure-g menu"><div class="pure-u-1 pure-u-lg-1-8 menu-heading"><a class="pure-menu-heading" href="/">WikiLerni</a></div><div class="pure-u-1 pure-u-lg-7-8"><ul class="pure-g"><li class="pure-menu-item pure-u-1 pure-u-lg-1-4"><a class="pure-menu-link" href="/">Accueil</a></li><li class="pure-menu-item pure-u-1 pure-u-lg-1-4"><a class="pure-menu-link" href="/connexion.hmt" id="accountHeadLink">Mon compte</a></li><li class="pure-menu-item pure-u-1 pure-u-lg-1-4"><a class="pure-menu-link" href="/a-propos.html">À propos</a></li><li class="pure-menu-item pure-u-1 pure-u-lg-1-4"><a class="pure-menu-link" href="/contact.html">Contact</a></li></ul></div></header><section id="main-content"><header id="main-header"><h1>WikiLerni</h1><p></p><p><a class="pure-button pure-button-primary" href="/inscription.html">Inscription</a></p></header><article class="content"><div class="pure-g"><div class="l-box pure-u-1"><h3 class="content-subhead"><em>De nature curieuse ?</em></h3><p>Avec WikiLerni vous apprenez chaque jour de nouvelles choses.<br>Des articles de Wikipédia sont sélectionnés pour vous et sont suivis d'un quiz vous permettant de tester ce que vous en avez retenu.<br>De jour en jour de nouvelles graines de savoir sont ainsi semées dans votre "jardin".</p></div><div class="l-box pure-u-1"><h3 class="content-subhead"><em>La culture en liberté</em></h3><p>Tout comme sur Wikipédia (*), le logiciel et le contenu partagé sur WikiLerni sont libres.<br>Vous pouvez les utiliser, les modifier et les diffuser selon votre souhait.<br>Sur WikiLerni, pas de publicité, ni de commercialisation de vos données personnelles.<br>Vous pouvez venir y "cultiver votre jardin" en toute tranquillité.<br><br><small><em>(*) Bien que partageant ses valeurs, WikiLerni est un projet indépendant de la fondation Wikipédia.</em></small></p></div></div><div class="l-box pure-u-1"><a class="pure-button pure-button-primary" href="/a-propos.html">En savoir plus sur WikiLerni.</a></div><div class="l-box pure-u-1"><a class="pure-button pure-button-primary" href="/inscription.html">Tester WikiLerni gratuitement.</a></div></article><div class="questionnaire-listed l-box-lrg pure-g"><div class="l-box-lrg is-center pure-u-1 pure-u-md-1-2 pure-u-lg-2-5"><img class="pure-img-responsive" src="/img/quizs/bonhomme_de_neige01_377_600-1589208854134.jpg" alt="sans fichier !" title="juste les infos"></div><div class="pure-u-1 pure-u-md-1-2 pure-u-lg-3-5"><h2 class="content-head content-head-questionnaire">Que savez-vous des éditions Gallimard ?</h2><div id="intro">Les éditions Gallimard, appelées jusquen 1919 les éditions de la Nouvelle Revue française et jusquen 1961 la librairie Gallimard, sont un groupe d'édition français. La maison d'édition est fondée par Gaston Gallimard en 1911. Le groupe Gallimard est actuellement dirigé par Antoine Gallimard.Con...</div><a class="pure-button pure-button-primary" href="/quiz/editions-gallimard.html">Allez au quiz !</a></div></div></section><footer class="footer l-box is-center"><ul class="pure-g"><li class="pure-u-1 pure-u-lg-1-5"><a href="/credits.html">Crédits</a></li><li class="pure-u-1 pure-u-lg-1-5"><a href="/mentions-legales.html" rel="nofollow">Mentions légales</a></li><li class="pure-u-1 pure-u-lg-1-5"><a href="/donnees.html">Données personnelles</a></li><li class="pure-u-1 pure-u-lg-1-5"><a href="/cgu.html" rel="nofollow">C.G.U.</a></li><li class="pure-u-1 pure-u-lg-1-5"><a href="/cgv.html" rel="nofollow">C.G.V.</a></li></ul></footer></body></html>

View File

@ -0,0 +1,115 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Formulaire d'inscription à WikiLerni.">
<title>S'inscrire à WikiLerni</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/subscribe.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
<link rel="canonical" href="https://www.wililerni.com/inscription.html">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content">
<header id="main-header">
<h1>WikiLerni</h1>
<p>Cultivons notre jardin !</p>
</header>
<div class="content">
<h2 class="content-head is-center">Créer votre compte WikiLerni</h2>
<div class="pure-g">
<div class="l-box-lrg pure-u-1">
<noscript>Désolé, mais pour l'instant, l'utilisation de WikiLerni nécessite l'activation du JavaScript. Nous travaillons à changer cela.</noscript>
<form class="pure-form pure-form-aligned needJS" id="subscription" method="POST">
<fieldset>
<div class="pure-control-group">
<label for="name">Nom ou pseudo</label>
<input id="name" type="text" name="name" placeholder="Pseudo de votre choix">
<span class="pure-form-message-inline"></span>
</div>
<div class="pure-control-group">
<label for="email">Email</label>
<input id="email" type="email" name="email" placeholder="Adresse e-mail">
<span class="pure-form-message-inline" id="emailMessage"></span>
</div>
<div class="pure-control-group">
<label for="password">Mot de passe</label>
<input id="password" type="password" name="password" placeholder="Mot de passe de votre choix">
<span class="pure-form-message-inline" id="passwordMessage"><i class="information">Votre mot de passe doit compter au moins 8 caractères. <a href="#password" id="getPassword">Générer un mot de passe</a>.</i></span>
</div>
<div class="pure-control-group">
<label for="codeGodfather">Code parrainage</label>
<input id="codeGodfather" name="codeGodfather" placeholder="Code ou email de votre parrain.">
<span class="pure-form-message-inline" id="codeGodfatherMessage"><i class="information">Facultatif.</i></span>
</div>
<div class="pure-controls">
<label for="cguOk" class="pure-checkbox">
<input type="checkbox" id="cguOk" name="cguOk" value="true" /> J'accepte <a href="/cgu.html">les Conditions Générales d'Utilisation du site</a> (requis).
</label>
<label for="newsletterOk" class="pure-checkbox">
<input type="checkbox" id="newsletterOk" name="newsletterOk" value="true" /> J'accepte de recevoir les nouveaux quizs WikiLerni par email (facultatif).
</label>
<button type="submit" class="pure-button pure-button-primary" id="submitDatas">Valider</button>
</div>
</fieldset>
</form>
<div id="response"></div>
</div>
<div class="l-box-lrg pure-u-1">
<h4>Pourquoi s'inscrire ?</h4>
<p>Cela prend quelques instants et n'engage à rien, car c'est totalement gratuit les quinze premiers jours. Libre à vous ensuite de vous abonner pour quelques euros par an. Sinon, il vous suffira de ne pas donner suite à la notification vous proposant de vous abonner.</p>
</div>
</div>
</div>
</section>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Crédits</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/donnees.html">Données personnelles</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgu.html">C.G.U.</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgv.html">C.G.V.</a>
</li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Validation création compte WikiLerni.">
<meta name="robots" content="noindex">
<title>Tester votre lien de connexion</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/loginLink.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
<link rel="canonical" href="https://www.wililerni.com/login.html">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content">
<header id="main-header">
<h1>WikiLerni</h1>
<p>Cultivons notre jardin !</p>
</header>
<div class="content">
<h2 class="content-head is-center">Test de votre lien de connexion</h2>
<div class="pure-g">
<div class="l-box-lrg pure-u-1">
<noscript>Désolé, mais pour l'instant, l'utilisation de WikiLerni nécessite l'activation du JavaScript. Nous travaillons à changer cela.</noscript>
<div id="response">Si vous voyez ce message, c'est que votre lien de connexion n'est pas valide. Vous pouvez <a href="/connexion.html">en demander un nouveau en cliquant ici</a>.</div>
</div>
</div>
</div>
</section>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Crédits</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/donnees.html">Données personnelles</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgu.html">C.G.U.</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgv.html">C.G.V.</a>
</li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Validation de vos nouveaux identifiants.">
<meta name="robots" content="noindex">
<title>Valider vos nouveaux identifiants</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/newLoginValidation.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
<link rel="canonical" href="https://www.wililerni.com/newlogin.html">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content">
<header id="main-header">
<h1>WikiLerni</h1>
<p>Cultivons notre jardin !</p>
</header>
<div class="content">
<h2 class="content-head is-center">Validation de vos nouveaux identifiants</h2>
<div class="pure-g">
<div class="l-box-lrg pure-u-1">
<noscript>Désolé, mais pour l'instant, l'utilisation de WikiLerni nécessite l'activation du JavaScript. Nous travaillons à changer cela.</noscript>
<div id="response">Si vous voyez ce message, c'est que votre lien de validation n'est pas valide ou a expiré. Vous pouvez <a href="/compte.html">en demander un nouveau en cliquant ici</a>.</div>
</div>
</div>
</div>
</section>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Crédits</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/donnees.html">Données personnelles</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgu.html">C.G.U.</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgv.html">C.G.V.</a>
</li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Page de déconnexion.">
<meta name="robots" content="noindex">
<title>Déconnexion</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/deconnection.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
<link rel="canonical" href="https://www.wililerni.com/deconnection.html">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content">
<header id="main-header">
<h1>WikiLerni</h1>
<p>Cultivons notre jardin !</p>
</header>
<div class="content">
<h2 class="content-head is-center">Au revoir !</h2>
<div class="pure-g">
<div class="l-box-lrg pure-u-1">
<div id="response">Si vous voyez ce message, c'est qu'un problème a été rencontré durant la déconnexion.<br>N'hésitez pas à nous prévenir si le problème persiste.</div>
</div>
</div>
</div>
</section>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Crédits</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/donnees.html">Données personnelles</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgu.html">C.G.U.</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgv.html">C.G.V.</a>
</li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Stopper les envois d'e-mail venant de WikiLerni.">
<meta name="robots" content="noindex">
<title>Ne plus recevoir d'e-mail</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/unsubscribe.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
<link rel="canonical" href="https://www.wililerni.com/stop-mail.html">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content">
<header id="main-header">
<h1>WikiLerni</h1>
<p>Cultivons notre jardin !</p>
</header>
<div class="content">
<h2 class="content-head is-center">Stopper les e-mail de WikiLerni</h2>
<div class="pure-g">
<div class="l-box-lrg pure-u-1">
<div id="response">Si vous voyez ce message, c'est que votre lien de désabonnement ne fonctionne pas. Vous pouvez <a href="/compte.html">accéder à votre compte</a> pour désactiver les envois.</div>
</div>
</div>
</div>
</section>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Crédits</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/donnees.html">Données personnelles</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgu.html">C.G.U.</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgv.html">C.G.V.</a>
</li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Validation création compte WikiLerni.">
<meta name="robots" content="noindex">
<title>Valider votre compte WikiLerni</title>
<script src="/JS/polyfill.app.js" defer></script>
<script src="/JS/subscribeValidation.app.js" defer></script>
<link rel="shortcut icon" href="/img/favicon.ico">
<link rel="stylesheet" href="/themes/default/CSS/pure-min.css">
<link rel="stylesheet" href="/themes/default/CSS/grids-responsive-min.css">
<link rel="stylesheet" href="/themes/default/CSS/wikilerni.css">
<link rel="canonical" href="https://www.wililerni.com/validation.html">
</head>
<body>
<header class="pure-g menu">
<div class="pure-u-1 pure-u-lg-1-8 menu-heading">
<a class="pure-menu-heading" href="/">WikiLerni</a>
</div>
<div class="pure-u-1 pure-u-lg-7-8">
<ul class="pure-g">
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/">Accueil</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/connexion.html" id="accountHeadLink">Mon compte</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/a-propos.html">À propos</a>
</li>
<li class="pure-menu-item pure-u-1 pure-u-lg-1-4">
<a class="pure-menu-link" href="/contact.html">Contact</a>
</li>
</ul>
</div>
</header>
<section id="main-content">
<header id="main-header">
<h1>WikiLerni</h1>
<p>Cultivons notre jardin !</p>
</header>
<div class="content">
<h2 class="content-head is-center">Validation de votre compte WikiLerni</h2>
<div class="pure-g">
<div class="l-box-lrg pure-u-1">
<noscript>Désolé, mais pour l'instant, l'utilisation de WikiLerni nécessite l'activation du JavaScript.</noscript>
<div id="response">Si vous voyez ce message, c'est que votre lien de validation n'est pas valide ou a expiré. Vous pouvez <a href="/connexion.html">en demander un nouveau en cliquant ici</a>.</div>
</div>
</div>
</div>
</section>
<footer class="footer l-box is-center">
<ul class="pure-g">
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Crédits</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/mentions-legales.html">Mentions légales</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/donnees.html">Données personnelles</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgu.html">C.G.U.</a>
</li>
<li class="pure-u-1 pure-u-lg-1-5">
<a href="/cgv.html">C.G.V.</a>
</li>
</ul>
</footer>
</body>
</html>

View File

@ -0,0 +1,67 @@
#account fieldset
{
width: 80%;
margin: auto;
}
#account label
{
width: 49%;
margin-right: 1%;
text-align: right;
display: inline-block;
}
#account label.check
{
text-align: left;
width:100%;
display: inline;
}
#account
{background-color: #FF8800;
width: 85%;
margin: auto;
padding-top: 2.5em;
margin-top: -2.5em;
box-shadow: 0px 0px 5px black;
padding-bottom: 2.5em;
margin-bottom: -2.5em;
text-align: center;}
#account h1
{width: 105%;
background-color: #FF8800;
box-shadow: 0px 0px 5px black;
margin-left: -2.5%;
text-align: center;
border-radius: 5px;
padding-top: 0.5em;
padding-bottom: 0.5em;
border-top: 1px solid rgba(255,255,255,0.5);
border-bottom: 1px solid rgba(0, 0, 0, 0.5);}
#account p,
#account ul
{margin: 1em;
text-align: justify;}
#account .error, #account .info, #account .success
{
margin: 1em;
width: auto;
border-radius: 5px;
}
@media screen and (max-width: 660px) {
#account label
{width: 100%;
text-align: center;
display: inline-block;}
#account fieldset input
{display: block;
margin: auto;}
}

View File

@ -0,0 +1,49 @@
#account fieldset { width: 80%; }
#account label
{
width: 49%;
margin-right: 1%;
text-align: right;
display: inline-block;
}
#account label.check
{
text-align: left;
width:100%;
display: inline;
}
#account
{background-color: #FF8800;
width: 50%;
margin: auto;
padding-top: 2.5em;
margin-top: -2.5em;
box-shadow: 0px 0px 5px black;
padding-bottom: 2.5em;
margin-bottom: -2.5em;
text-align: center;}
#account h1
{width: 105%;
background-color: #FF8800;
box-shadow: 0px 0px 5px black;
margin-left: -2.5%;
text-align: center;
border-radius: 5px;
padding-top: 0.5em;
padding-bottom: 0.5em;
border-top: 1px solid rgba(255,255,255,0.5);
border-bottom: 1px solid rgba(0, 0, 0, 0.5);}
#account p,
#account ul
{margin: 1em;
text-align: justify;}
#account .error, #account .info, #account .success
{
margin: 1em;
width: auto;
border-radius: 5px;
}

View File

@ -0,0 +1,308 @@
@font-face {
font-family: Millimetre;
src: url(../webfonts/Light/Millimetre-Light_web.woff);
font-weight: lighter;
}
@font-face {
font-family: Millimetre;
src: url(../webfonts/Regular/Millimetre-Regular_web.woff);
font-weight: regular;
}
@font-face {
font-family: Millimetre;
src: url(../webfonts/Bold/Millimetre-Bold_web.woff);
font-weight: bold;
}
body
{margin: 0px;
overflow-x: hidden;
background-color: #8c599c;}
body,
a,
input
{font-family: Millimetre;
font-weight: regular;
color: rgba(255,255,255,1);
text-shadow: 0px 1px 0px rgba(0, 0, 0, 0.75);}
input, #headLinks a, #footLinks a, a.button,
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a
{
text-decoration: none;
outline: none;
}
a:hover
{
color: rgba(0,0,0,0.66);
text-shadow: 0px 1px 0px rgba(255,255,255,0.75);
}
a.button:hover
{
color:white;
text-shadow:none;
}
header,
footer
{background-color: #FF8800;
border-bottom: 1px solid rgba(0,0,0,0.75);
box-shadow: 0px 0px 10px black;
font-size: 1.5em;
width: 100%;
height:100px;
margin-top: 1em;
transform: rotate(0.66deg);}
footer
{font-size: 1em;}
/*
header img
{max-height:80px;
margin-left:10px;
margin-top: 10px;
display: inline-block;}*/
header img
{
display:none;
}
header ul
{
width:100%;
}
header ul, footer ul
{list-style-type: none;
display: inline-block;
float: right;
line-height: 50px;}
header ul li
{float: right;
display: block;}
footer ul li
{margin-top: 10px;
display:inline-block;}
footer ul
{width: 100%;}
header ul li::after,
footer ul li::after
{width: 1px;
height: 80px;
display: block;
float: right;
content: '';
margin-left: 1em;
margin-top: -20px;
background: linear-gradient(to top, rgba(255,255,255,0.25), rgba(255,255,255,0.0));}
header ul li::before,
footer ul li::before
{width: 1px;
height: 80px;
display: block;
float: right;
content: '';
margin-top: -20px;
margin-right: 1em;
background: linear-gradient(to top, rgba(0,0,0,0.25), rgba(0,0,0,0.0));}
header ul li:first-child::after,
footer ul li:last-child::after
{display: none;}
header ul li:first-child::before,
footer ul li:last-child::before
{display: none;}
header ul li:first-child,
footer ul li:first-child
{margin-right: 1em;}
footer
{transform: rotate(-0.66deg);
clear: both;
text-align: center;}
fieldset
{border: 0px;
text-align: left;}
.needJS
{
display: none;
}
label
{width: 50%;}
.engraved
{
color: white;
text-shadow: 0px -1px 0px rgba(0, 0, 0, 1);
font-family: verdana;
}
.framed
{border: 1px solid rgba(0, 0, 0, 0.33);
border-radius: 5px;
box-shadow: 0px 1px 0px rgba(255,255,255,0.5), inset 0px 1px 0px rgba(255,255,255,0.33);
display: inline-block;
margin: 1em;}
.cardboard
{
background-opacity: 0.75;
background-image: url('../img/background-texture.png');
}
#explanations
{
padding: 0.7em;
}
#explanations p
{
margin:1.3em;
text-align:left;
}
/* This allow the motion of the button while overing without displacing the content of the page */
.input_wrapper
{
height: 40px;
display: inline-block;
margin-top: 1em;
}
input[type=submit], input[type=text], input[type=email], input[type=password], .button
{
background-color: #8c599c;
padding: 10px;
border: 0px;
border-radius: 3px;
margin-top:-10px;
color: white;
font-family: sans;
font-weight: bold;
font-size: 0.6em;
position: relative;
transition: 0.125s ease all;
margin-top: 0px;
box-shadow: inset 0px 1px 0px rgba(255,255,255,0.66), inset 0px -1px 0px rgba(0, 0, 0,0.66), 0px 3px 8px rgba(0,0,0,0.66);
}
input[type=submit]:hover,
.button:hover
{margin-top: 2px;
box-shadow: inset 0px 1px 0px rgba(255,255,255,0.66), inset 0px -1px 0px rgba(0, 0, 0,0.66), 0px 0px 5px rgba(0,0,0,0.66);}
input[type=text], input[type=password], input[type=email] {
box-shadow: inset 0px 1px 0px rgba(0,0,0,0.66), inset 0px -1px 0px rgba(255,255,255,0.66), inset 0px 3px 8px rgba(0,0,0,0.66);}
input[type=checkbox]
{width:15px;
height:15px;
margin-left: 0px;
margin-right: -15px;
opacity:0.0;}
/* The following is a trick to force browser to let me redefine checkbox style */
.checkbox_override
{width: 15px;
height: 15px;
border-radius: 2px;
box-shadow: inset 0px 2px 2px rgba(0, 0, 0, 0.5);
background-color: rgba(0, 0, 0, 0.25);
border-top: 1px solid rgba(0,0,0,0.75);
border-bottom: 1px solid rgba(255,255,255,0.5);
display: inline-block;}
input:hover ~ .checkbox_override
{background-color: rgba(0, 0, 0, 0.125);}
input:checked ~ .checkbox_override {
border-bottom: 1px solid rgba(0,0,0,0.75);
border-top: 1px solid rgba(255,255,255,0.5);
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.5);
background-color: rgba(255, 255, 255, 0.25);
}
/*#ERROR:target, #SUCCESS:target, #INFO:target,*/
p.error, p.success, p.info,
div.error, div.success, div.info
{
width: calc(100% - 20px);
height: auto;
padding: 1em;
opacity: 1;
margin-left: 0px;
line-height: 1.25em;
background-image: url('../img/background-texture.png');
}
.info {background-color: #8c599c;}
.success {background-color: #00c684;}
.error {background-color: red;}
@media screen and (max-width: 570px) {
header ul
{line-height: 66px;}
header
{font-size: 1em;}
}
@media screen and (max-width: 600px) {
footer
{font-size: 0.75em;}
header ul li::after,
footer ul li::after
{display: none;}
header ul li,
footer ul li
{display: inline-block;
margin-left: 0.5em;
margin-right: 0.5em;}
header ul li::before,
footer ul li::before
{display: none;}
header
{
height:60px;
}
}
@media screen and (max-width: 400px) {
header
{font-size: 0.75em;}
footer ul
{line-height: 25px;}
header ul
{line-height: 75px;}
}
#zerozozio
{
margin:1em;
text-align:right;
}
#zerozozio a, span
{
padding:0 0.3em;
}

View File

@ -0,0 +1,247 @@
@font-face {
font-family: Millimetre;
src: url(../webfonts/Light/Millimetre-Light_web.woff);
font-weight: lighter;
}
@font-face {
font-family: Millimetre;
src: url(../webfonts/Regular/Millimetre-Regular_web.woff);
font-weight: regular;
}
@font-face {
font-family: Millimetre;
src: url(../webfonts/Bold/Millimetre-Bold_web.woff);
font-weight: bold;
}
body
{margin: 0px;
overflow-x: hidden;
background-color: #8c599c;}
body, a, input
{
font-family: Millimetre;
font-weight: regular;
color: rgba(255,255,255,1);
text-shadow: 0px 1px 0px rgba(0, 0, 0, 0.75);
}
input, #headLinks a, #footLinks a, a.button,
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a
{
text-decoration: none;
outline: none;
}
a:hover
{
color: rgba(0,0,0,0.66);
text-shadow: 0px 1px 0px rgba(255,255,255,0.75);
}
a.button:hover
{
color:white;
text-shadow:none;
}
header,
footer
{background-color: #FF8800;
border-bottom: 1px solid rgba(0,0,0,0.75);
box-shadow: 0px 0px 10px black;
font-size: 1.5em;
width: 100%;
height:100px;
margin-top: 1em;
transform: rotate(0.66deg);}
header img
{max-height:80px;
margin-left:10px;
margin-top: 10px;
display: inline-block;}
header ul, footer ul
{list-style-type: none;
display: inline-block;
float: right;
line-height: 50px;}
header ul li
{float: right;
display: block;}
footer ul li
{display:inline-block;}
footer ul
{width: 100%;}
header ul li::after,
footer ul li::after
{width: 1px;
height: 80px;
display: block;
float: right;
content: '';
margin-left: 1em;
margin-top: -20px;
background: linear-gradient(to top, rgba(255,255,255,0.25), rgba(255,255,255,0.0));}
header ul li::before,
footer ul li::before
{width: 1px;
height: 80px;
display: block;
float: right;
content: '';
margin-top: -20px;
margin-right: 1em;
background: linear-gradient(to top, rgba(0,0,0,0.25), rgba(0,0,0,0.0));}
header ul li:first-child::after,
footer ul li:last-child::after
{display: none;}
header ul li:first-child::before,
footer ul li:last-child::before
{display: none;}
header ul li:first-child,
footer ul li:first-child
{margin-right: 1em;}
footer
{transform: rotate(-0.66deg);
clear: both;
text-align: center;}
fieldset
{border: 0px;}
.needJS
{
display: none;
}
.engraved
{
color: white;
text-shadow: 0px -1px 0px rgba(0, 0, 0, 1);
font-family: verdana;
}
.framed
{
border: 1px solid rgba(0, 0, 0, 0.33);
border-radius: 5px;
padding-top: 0.5em;
padding-bottom: 0.5em;
box-shadow: 0px 1px 0px rgba(255,255,255,0.5), inset 0px 1px 0px rgba(255,255,255,1);
background-image: url('../img/background-texture.png');
display: inline-block;
margin: 1em;
}
#explanations
{
padding: 1em;
}
#explanations p
{
margin:1.3em;
}
.cardboard, .error, .success, .info
{
background-opacity: 0.75;
background-image: url('../img/background-texture.png');
}
/* This allow the motion of the button while overing without displacing the content of the page */
.input_wrapper
{
height: 40px;
display: inline-block;
margin-bottom: 1em;
}
input[type=submit], input[type=text], input[type=email], input[type=password], .button
{background-color: #8c599c;
padding: 10px;
border: 0px;
border-radius: 3px;
margin-top:-10px;
color: white;
font-family: sans;
font-weight: bold;
position: relative;
transition: 0.125s ease all;
margin-top: 0px;
box-shadow: inset 0px 1px 0px rgba(255,255,255,0.66), inset 0px -1px 0px rgba(0, 0, 0,0.66), 0px 3px 8px rgba(0,0,0,0.66);}
input[type=submit]:hover, .button:hover
{
margin-top: 2px;
box-shadow: inset 0px 1px 0px rgba(255,255,255,0.66), inset 0px -1px 0px rgba(0, 0, 0,0.66), 0px 0px 5px rgba(0,0,0,0.66);
}
input[type=text], input[type=password], input[type=email] {
box-shadow: inset 0px 1px 0px rgba(0,0,0,0.66), inset 0px -1px 0px rgba(255,255,255,0.66), inset 0px 3px 8px rgba(0,0,0,0.66);}
input[type=checkbox]
{width:15px;
height:15px;
margin-left: 0px;
margin-right: -16px;
opacity:0.0;}
/* The following is a trick to force browser to let me redefine checkbox style */
.checkbox_override
{width: 15px;
height: 15px;
border-radius: 2px;
box-shadow: inset 0px 2px 2px rgba(0, 0, 0, 0.5);
background-color: rgba(0, 0, 0, 0.25);
border-top: 1px solid rgba(0,0,0,0.75);
border-bottom: 1px solid rgba(255,255,255,0.5);
display: inline-block;}
input:hover ~ .checkbox_override
{background-color: rgba(0, 0, 0, 0.125);}
input:checked ~ .checkbox_override {
border-bottom: 1px solid rgba(0,0,0,0.75);
border-top: 1px solid rgba(255,255,255,0.5);
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.5);
background-color: rgba(255, 255, 255, 0.25);
}
p.error, p.success, p.info,
div.error, div.success, div.info
{
width: calc(100% - 20px);
height: auto;
padding: 1em;
opacity: 1;
margin-left: 0px;
line-height: 1.4em;
}
.error {background-color: red;}
.info {background-color: #8c599c;}
.success {background-color: #00c684;}
#zerozozio
{
margin:1em;
text-align:right;
}
#zerozozio a, span
{
padding:0 0.3em;
}

View File

@ -0,0 +1,160 @@
#home
{width: 80%;
margin: auto;
border-radius: 5px;
box-shadow: 0px 0px 5px black;
margin-top: -1.5em;
padding-top: 1.5em;
background-color: #FF8800;
border-bottom: 1px solid rgba(0, 0, 0, 0.75);}
#home h1,
#home h2
{text-align: center;}
#home p
{margin-left:1em;
margin-right: 1em;}
#home form
{border-top: 1px solid rgba(0, 0, 0, 0.5);
border-bottom: 1px solid rgba(0, 0, 0, 0.5);
box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.5), 0px 1px 0px rgba(255, 255, 255, 0.5);
width: 80%;
margin: auto;
padding-bottom: 1em;
display: block;
padding-top: 1em;
text-align: center;
margin-bottom: 1em;}
#home form input[type=text]
{width: calc(49% - 20px);
margin-right: 1%;}
#home form input[type=submit]
{width: 40%;}
#home form .line
{
margin-top: 1em;
width: 100%;
}
#logo
{width: 50%;
margin: auto;
display: block;}
#home .error, #home .info, #home .success
{
margin: 1em;
width: auto;
border-radius: 5px;
}
/* Sur tablette la troisième colonne vient se placer en dessous de la première ou de la deuxième (la plus courte des deux) */
#triple-column
{
width: 90%;
margin: auto;
margin-top: 1em;
}
.column-1, .column-3, .column-2
{
width: 48%;
float: left;
margin-left:1.5%;
}
.quiz
{
width: 100%;
margin-bottom: 1em;
background-color: #FF8800;
border-radius: 5px;
border-top: 1px solid rgba(255,255,255,0.66);
border-bottom: 1px solid rgba(0,0,0,0.66);
box-shadow: 0px 0px 5px black;
}
.quiz h3
{
text-align: center;
margin: 1em;
}
.quiz p
{
margin: 2em;
text-align: justify;
}
.quiz p:last-child
{
text-align: center;
}
.quiz-image-wrapper
{
width: 95%;
background-size: cover;
border-radius: 5px;
border-top: 1px solid rgba(0,0,0,0.66);
border-bottom: 1px solid rgba(255,255,255,0.66);
box-shadow: inset 0px 5px 5px rgba(0,0,0,0.33);
background-repeat: no-repeat;
margin: auto;
}
.quiz-image-wrapper img
{
opacity: 0;
width: 100%;
}
nav#pagination
{
clear:both;
display:flex;
flex-wrap: wrap;
margin:0.5em;
}
nav#pagination div
{
flex: 0 0 50%;
text-align:center;
}
nav#pagination a
{
background-image: url('../img/background-texture.png');
}
@media screen and (max-width: 520px)
{
/* Sur mobile, les colonnes se placent les unes sous les autres */
#triple-column
{
display: block;
width: 80%;
margin: auto;
margin-top: 1em;
}
.column-1, .column-2, .column-3
{
width: 98%;
float: left;
margin-right: 1%;
}
#home form
{
text-align: center;
}
#home form input[type=text]
{
width: calc(100% - 20px);
margin-right: 0%;
}
#home form input[type=submit]
{
width: 100%;
margin-top: 1em;
display: inline-block;
}
}

View File

@ -0,0 +1,131 @@
#home
{width: 50%;
margin: auto;
border-radius: 5px;
box-shadow: 0px 0px 5px black;
margin-top: -1.5em;
padding-top: 1.5em;
background-color: #FF8800;
border-bottom: 1px solid rgba(0, 0, 0, 0.75);}
#home h1,
#home h2
{text-align: center;}
#home p
{margin-left:1em;
margin-right: 1em;}
#home form
{border-top: 1px solid rgba(0, 0, 0, 0.5);
border-bottom: 1px solid rgba(0, 0, 0, 0.5);
box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.5), 0px 1px 0px rgba(255, 255, 255, 0.5);
width: 80%;
margin: auto;
padding-bottom: 1em;
display: block;
padding-top: 1em;
text-align: center;
margin-bottom: 1em;}
#home form input[type=text]
{width: calc(66% - 20px);
margin-right: 1%;}
#home form input[type=submit]
{width: 32%;}
#home form input[type=submit]
{width: 32%;}
#home form .line
{
margin-top:0.5em;
width: 100%;
}
#logo
{width: 33%;
margin: auto;
display: block;}
#home .error, #home .info, #home .success
{
margin: 1em;
width: auto;
border-radius: 5px;
}
/* Les colonnes de l'enfer ! */
#triple-column
{
width: 95%;
margin: auto;
margin-top: 1em;
}
.column-1,
.column-2,
.column-3
{
float: left;
width: 32%;
margin-bottom: 1em;
}
.column-1, .column-2
{
margin-right: 1.33%;
}
.quiz
{
width: 100%;
margin-bottom:1em;
background-color: #FF8800;
border-radius: 5px;
border-top: 1px solid rgba(255,255,255,0.66);
border-bottom: 1px solid rgba(0,0,0,0.66);
box-shadow: 0px 0px 5px black;
}
.quiz h3
{
text-align: center;
margin:1em;
}
.quiz p
{
margin: 2em;
text-align: left;
}
.quiz p:last-child
{
text-align: center;
}
.quiz-image-wrapper
{
width: 95%;
background-size: cover;
background-repeat: no-repeat;
border-radius: 5px;
border-top: 1px solid rgba(0,0,0,0.66);
border-bottom: 1px solid rgba(255,255,255,0.66);
box-shadow: inset 0px 5px 5px rgba(0,0,0,0.33);
margin: auto;
}
.quiz-image-wrapper img
{
opacity: 0;
width: 100%;
}
nav#pagination
{
clear:both;
display:flex;
flex-wrap: wrap;
margin:0.5em;
}
nav#pagination div
{
flex: 0 0 50%;
text-align:center;
}

View File

@ -0,0 +1,28 @@
#prompt, #login
{background-color: #FF8800;
width: 75%;
margin: auto;
text-align: center;
border-radius: 5px;
margin-top: -1em;
padding-top:1.5em;
border-bottom: 1px solid rgba(0,0,0,0.8);
box-shadow: 0px 0px 5px rgba(0,0,0,0.75);}
#prompt img
{width: 33%;}
#prompt p
{
background-color: #FF8800;
box-shadow: 0px 0px 5px rgba(0,0,0,0.75);
width: 102%;
margin-left:-1%;
border-radius: 5px;
padding-top: 1em;
padding-bottom: 1em;
font-size:0.8em;
letter-spacing: 0.3em;
border-top: 1px solid rgba(255,255,255,0.5);
border-bottom: 1px solid rgba(0,0,0,0.66);
}

View File

@ -0,0 +1,27 @@
#prompt, #login
{background-color: #FF8800;
width: 50%;
margin: auto;
text-align: center;
border-radius: 5px;
margin-top: -1em;
padding-top:1.5em;
border-bottom: 1px solid rgba(0,0,0,0.8);
box-shadow: 0px 0px 5px rgba(0,0,0,0.75);}
#prompt img
{width: 33%;}
#prompt p, #login h1
{
background-color: #FF8800;
box-shadow: 0px 0px 5px rgba(0,0,0,0.75);
width: 102%;
margin-left:-1%;
border-radius: 5px;
padding-top: 1em;
padding-bottom: 1em;
letter-spacing: 0.5em;
border-top: 1px solid rgba(255,255,255,0.5);
border-bottom: 1px solid rgba(0,0,0,0.66);
}

Some files were not shown because too many files have changed in this diff Show More