Un site pour mettre à l’épreuve vos connaissances du Web informatique !

Projets

web.snt.nsi.xyz est un projet répertoriant 10 énigmes sur-mesure accessibles pour un élève en classe de seconde. Plus globalement, ce site va vous permettre de découvrir différents aspects de l’informatique et du Web, en passant par l’histoire de l’informatique jusqu’au fonctionnement d’un site Web. Aujourd’hui vous est présenté la V2 de ce projet, une mise à jour majeure.

La V1 de ce projet

Ce projet, dopé au HTML, CSS, JavaScript, et PHP, a été lancé par Ilyas R. qui a conçu et développé l’entièreté du site, incluant les 10 énigmes, le système de chronométrage, etc. Il a également échafaudé la direction artistique en s’aidant d’une librairie CSS (également appelé «Framework CSS»). Il s’agit de PureCSS, une librairie gratuite et open-source que vous pouvez également utiliser dans vos projets. De manière générale, Ilyas a conçu la structure principale du site. Le site web.snt.nsi.xyz est né suite aux vacances de la Toussaint.

Une V2 ?

Pour les vacances d’hiver, une V2 du site a été envisagée incluant un bon nombre de fonctionnalités. Pour ce faire, Thomas S. a rejoint le projet et l’objectif principal est maintenant que le site fonctionne autour d’une base de données. Voici le cahier des charges que nous avons rédigés :

  • Système de gestion de session :
    • Créer une session (en tant que professeur notamment)
    • Rejoindre une session (en tant qu’élève notamment)
    • Observation d’une session (pour que le professeur voit quelles énigmes ont été faites)
    • Statistiques pour les sessions (pourcentages des énigmes résolues, etc.)
    • Et bien plus…
  • Système de comptes (Créer un compte / Se connecter)
  • Système pour parcourir des sessions précédemment créées
  • Système de traductions
  • Ainsi que d’autres petites fonctionnalités…

À partir de là, l’aventure et les galères vont commencer.

Structure de la base de données

Tout d’abord, avant de nous lancer directement à l’aveugle dans le développement, nous avons conçu et organisé notre base de données, en déterminant toutes les relations à créer ainsi que les attributs. Ainsi, voici la structure finale de notre base de données relationnelle.

Nous n’aurons, à notre plus grand regret, pas le temps nécessaire de développer un système de permissions élaboré et un système de gestion des traductions, ainsi dans la V2, les relations groups, permissions, et assets ne sont pas présentes.

Des briques fondamentales

Avant toute chose, il est important de concevoir des fonctions et des bouts de pages qui seront utilisés à maintes reprises. Par exemple le panneau de navigation à gauche.
Ou encore, ce qui nous intéresse le plus ici des fonctions pour manipuler la base de données. Il a fallu créer des fonctions pour créer, lire, modifier et supprimer (CRUD) des enregistrements dans des relations (tables) de notre base de données.

Créer un enregistrement

Voici la fonction que nous avons développé pour créer un enregistrement dans notre base de données :

function addRow($database, $relation, $values) {
    $attributs = getAttributs($database, $relation, 0); // Permet de récupérer la liste des attributs de la relation, hormis la clé primaire
    $attributs_query = "(".implode(", ", array_map(function($value) {
        return $value;
    }, $attributs)).")";
    $values_query = "(".implode(", ", array_map(function($value) {
        return "\"".$value."\"";
    }, $values)).")";
    $sql = "INSERT INTO $relation $attributs_query VALUES $values_query";
    $stmp = $database->prepare($sql);
    $stmp->execute();
}

La fonction prend 3 paramètres, $database, $relation, et $values, le premier indique dans quelle base de données agir (en l’occurrence, ce sera toujours la même étant donné que nous possédons uniquement une base de données), le deuxième indique la relation (table) où agir, et le dernier les valeurs à ajouter dans l’enregistrement. La fonction utilise ainsi la requête SQL INSERT INTO $relation {attributs} VALUES $values, en déterminant automatiquement les attributs de la relation où agir, il n’est pas nécessaire de rentrer en paramètre les attributs.

Lire des enregistrements

Voici la fonction que nous avons développé pour lire un ou plusieurs enregistrements dans notre base de données :

function getRows($database, $relation, $attribut, $query, $force_multiples_rows = 0) {
    $sql = "SELECT $attribut FROM $relation WHERE $query";
    $stmp = $database->prepare($sql);
    $stmp->execute();
    return rowsCount($database, $relation, $query) > 1 || $force_multiples_rows == 1 ? $stmp->fetchAll() : $stmp->fetch(PDO::FETCH_ASSOC);
}

La fonction prend 4 paramètres, $database, $relation, $attribut, et $query. De la même manière que la fonction addRow(), nous retrouvons les mêmes paramètres $database et $relation, mais ici avec 2 autres paramètres, $attribut qui représente le ou les attributs dans le ou lesquels les données doivent être collectées, ainsi que $query qui représente la ou les conditions à appliquer pour filtrer les lignes de la table de la base de données. La fonction utilise ainsi la requête SQL SELECT $attribut FROM $relation WHERE $query. Sachant que si nous ne voulons pas mettre de condition, il suffit de renseigner « 1 » dans le paramètre $query.

Mettre à jour un enregistrement

Voici la fonction que nous avons développé pour mettre à jour un enregistrement dans notre base de données :

function updateRow($database, $relation, $attributs_values, $query){
    $setCommand = "";
    $attributs = array_keys($attributs_values);
    for ($i = 0; $i < count($attributs) - 1; $i++){
        $setCommand = $setCommand.$attributs[$i]." = \"".$attributs_values[$attributs[$i]]."\", ";
    }
    $setCommand = $setCommand.$attributs[count($attributs) - 1]." = \"".$attributs_values[$attributs[count($attributs) - 1]]."\"";
    $sql = "UPDATE $relation SET $setCommand WHERE $query";
    $stmp = $database->prepare($sql);
    $stmp->execute();
}

La fonction prend 4 paramètres, $database, $relation, $attributs_values, et $query. De la même manière que la fonction addRow() et getRows(), nous retrouvons les mêmes paramètres $database, $relation et $query, mais ici avec un autre paramètre, $attributs_values qui représente, pour simplifier au plus, un tableau de la forme array(key => value) avec l’attribut en tant que key et la nouvelle valeur en tant que value. La fonction utilise ainsi la requête SQL UPDATE $relation SET {modifications} WHERE $query.

Supprimer un enregistrement

Voici la fonction que nous avons développé pour supprimer un enregistrement dans notre base de données :

function delRow($database, $relation, $query) {
    $sql = "DELETE FROM $relation WHERE $query";
    $stmp = $database->prepare($sql);
    $stmp->execute();
}

La fonction prend 3 paramètres, $database, $relation, et $query. De la même manière que la fonction addRow() et getRows(), et updateRows() nous retrouvons les mêmes paramètres $database, $relation et $query. La fonction utilise ainsi la requête SQL DELETE FROM $relation WHERE $query.

D’autres fonctions agissant sur la base de données

On remarque finalement une structure souvent assez similaire, plus ou moins complexe. Au niveau des paramètres pour la fonction, on retrouve toujours $database, une variable qui indique donc sur quelle base de données on agit. Les autres paramètres demandés sont en rapport avec la requête SQL que l’on souhaite exécuter. Que ce soit la création, la lecture, la modification ou la suppression, on a toujours besoin d’indiquer la $relation qui est donc une variable indiquant dans quelle relation (table) on va faire cette requête. Suite à cela, les autres variables dépendent de la requête.

D’autres fonctions peuvent être pratiques, comme une fonction pour récupérer le nombre d’enregistrements pour une table ($relation) selon une condition de recherche ($query) :

function rowsCount($database, $relation, $query) {
    $sql = "SELECT * FROM $relation WHERE $query";
    $stmp = $database->prepare($sql);
    $stmp->execute();
    return $stmp->rowCount();
}

Par exemple, je veux le nombre de sessions actuellement actives :

$db = new PDO("mysql:host=localhost:3306;dbname=web_snt_nsi_xyz", "root", "root");
$nbr_sessions_actives = rowsCount($db, "sessions", "status = 1");

Il y a bien plus de fonctions que nous avons codées en avance pour pouvoir les réutiliser plus tard, comme createSession() qui va crée un session dans la base de donnée, getRowsInJson(), qui fait la même chose que getRows() mais qui renvoie le résultat sous un autre format (le format JSON). De même on retrouve une fonction qui crée un utilisateur, qui le modifie, qui le supprime, en bref on s’est fait notre boite à outils de fonctions.

/**
* Cette fonction crée un enregistrement dans la table sessions avec des informations sur la sessions :
* son code, le propriétaire de la session, la date de création, son statut, etc. 
*/

function createSession($database, $id_owner) {
    $codeSession = generateSessionCode($database);
    addRow($database, "sessions", array($codeSession, $id_owner, date('Y-m-d H:i:s', time()), 1));
    $id_session_created = getRows($database, "sessions", "*", "id_owner = $id_owner AND status = 1")["id"];
    updateLocalDB("[]", "../js/db-$id_session_created.json");
}

/**
* Il est intéressant de comparer cette fonction avec getRows() pour voir les différences qu'il y a entre les deux.
*/

function getRowsInJSON($database, $relation, $attribut, $query) {
    $sql = "SELECT $attribut FROM $relation WHERE $query";
    $stmp = $database->prepare($sql);
    $stmp->execute();
    return json_encode($stmp->fetchAll());
}

function getRows($database, $relation, $attribut, $query, $force_multiples_rows = 0) {
    $sql = "SELECT $attribut FROM $relation WHERE $query";
    $stmp = $database->prepare($sql);
    $stmp->execute();
    return rowsCount($database, $relation, $query) > 1 || $force_multiples_rows == 1 ? $stmp->fetchAll() : $stmp->fetch(PDO::FETCH_ASSOC);
}

Gestion des comptes

Ce système est pour les utilisateurs possédants un compte vérifié sur le site, c’est-à-dire ceux qui peuvent créer une session, ou ceux qui peuvent administrer le site.

Pour cela, comme dit plus haut, des fonctions ont été conçues pour créer, modifier et supprimer des utilisateurs. Ces fonctions manipulent directement la base de données, et passent donc par les fonctions plus basiques addRow(), updateRow(), getRows(), ou delRow(). Maintenant pour permettre ces actions, il est important de faire une interface graphique. Pour cela, une page est conçue pour pouvoir créer un utilisateur, pour avoir la liste des utilisateurs et pouvoir les gérer. Voici à quoi elle ressemble :

Suite à cela, il est important qu’il y ait un système de permissions pour pouvoir empêcher certains utilisateurs d’accéder à certaines pages. En effet, on veut que seul les administrateurs aient accès à cette page, et même à d’autres.

Le système de session

Lorsqu’un utilisateur n’a pas de session active, on lui propose de créer une session, à terme, il pourra configurer certains éléments de la session (notamment la durée).

Le système de session inclut un système de génération de codes, qui permettra à d’autres utilisateurs de rejoindre la session liée à ce code.

Une session va permettre à son propriétaire d’administrer les utilisateurs temporaires (les élèves) et d’observer leur avancement sur les énigmes.

De plus, pour les administrateurs, il sera possible d’avoir des statistiques combinées de toutes les sessions qui ont été créées. De même, il leur sera possible de parcourir les sessions en cours et les anciennes.

Une interface pour rejoindre une session ou se connecter

Voici l’interface de la page pour s’identifier sur le site :

On peut rejoindre une session, se connecter, créer un compte, ou accéder aux énigmes avec le menu à gauche.

Conclusion

Finalement, ce projet a été très enrichissant pour nous, il nous a vraiment permis de nous améliorer en PHP, nous avons énormément travaillé dessus, et nous sommes fiers du résultat, malgré le fait qu’on ait pas eu le temps d’intégrer tout ce qui était prévu à la base (rendez-vous à la V3 🤫).

La V2 est accessible à l’adresse : web.snt.nsi.xyz/dev/

Code source du projet

Vous pouvez retrouver le code source du projet dans notre dépôt GitHub.