This commit is contained in:
Nirij3m
2025-06-10 00:05:27 +02:00
parent 44c3f8ab61
commit 4326621740
22 changed files with 1212 additions and 0 deletions

60
README.md Normal file
View File

@@ -0,0 +1,60 @@
# MANUEL
A l'attention d'un CIR ou d'un CSI/EST/CENT déterminé à faire du full-stack,
voici quelques étapes et explications succintes pour setup le système de comptage d'heure que j'ai élaboré.
### CHOIX SYSTEME
En accord avec les cours de CIR2 sur le backend et le serveur web, je conseille de déployer un environnement apache2 avec php et postgresql.
Une machine virtuelle/container sur un serveur type OVH ou auto-herbergé fera largement l'affaire pour ce type d'application.
### INSTALLATION
1. Créez dans postgresql une base de donnée nommée `bdehours`. Assurez-vous que l'utilisateur `postgres` a bien accès à cette bdd et définissez le mot de passe d'accès à `Isen44N`.
Le port d'accès à la base de données sera `5432` (bien vérifier si c'est celui par défaut, au besoin le changer dans les constantes PHP ou dans le fichier de configuration postgresql). Si vous souhaitez modifier ces constantes et utiliser vos propres users/mdp/noms/port,
alors il faudra aussi penser à modifier les constantes suivantes en PHP dans le fichier `/src/appli/utils.php` avec les vôtres:
```php
const DBNAME = "bdehours";
const PORT = 5432;
const USER = "postgres";
const PASS = "Isen44N";
```
2. Veillez à bien activer le module PHP et Rewrite de apache2: `sudo a2enmod phpX.X` (X.X étant la version insallée de PHP, voir /etc/apache2/mods-available) & `sudo a2enmod rewrite`, et à configurer le fichier .conf du serveur web avec à minima les directives suivantes
```
<VirtualHost *:80>
DocumentRoot path/to/folder
DirectoryIndex index.php
<Directory path/to/folder>
DirectoryIndex index.php
Options +Indexes +FollowSymLinks
AllowOverride All
Require all granted
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^(.+) - [PT,L]
RewriteRule ^assets/(.*)$ public/assets/$1 [L]
RewriteBase /
RewriteRule ^ index.php
</Directory>
</VirtualHost>
```
Avec `path/to/folder` le chemin d'accès vers la racine du site web (ex: /var/www/html/bdeweb). Vous pouvez rajouter des directives si souhaitez mais celles-ci sont absolument mandatoires pour faire fonctionner le routeur php.
3. Éxecutez le script SQL `/ressources/CTBDE.sql`au sein de la base de donnée créée à l'étape 1 afin de la remplir de tables. Le fichier contient également quelques commentaires pour expliquer ce que font chaque tables et leurs champs associés.
4. L'étape la plus fun, il faut désormais peupler manuellement (et oui j'avais la flemme de faire un script sql) la base de données. Les commentaires du fichier `/ressources/CTBDE.sql`seront d'une grande utilité pour cela.
Il faut donc créer les Speciality souhaitée puis les Users dans les tables correspondantes. Une attention particullière doit être portée sur les champs `mail`et `password` de la table Users puisqu'ils permettront à chaque membre de se connecter à son espace personnel.
Le `mail` renseigné dans la table sera utilisé pour se connecter, et le `password` __doit au préalable avoir été hash avant d'être inséré__! Il ne faut pas insérer de mot de passe 'en brut' ou 'en clair' dans la table Users, cela ne marchera pas! Je recommande d'utiliser ce site: https://onlinephp.io/password-hash et de demander à chaque membre d'envoyer leur mot de passe hashé au sysadmin (celui qui s'occupera d'installer tout ce bazard). Les paramètres par défaut du site suffiront, attention tout de même à bien sélectionner la bonne version PHP. Une fois cette étape faite, félicitation vous avez fait le plus long, la mauvaise nouvelle c'est que je peux vous garantir qu'un membre du BDE aura forcément oublié son mot de passe d'ici la fin de l'année, et que vous devrez donc recalculer le hash avec lui et le modifier manuellement dans la base de donnée.
*Addendum sur le nom de domaine de la machine:* Si vous choisissez d'attribuer un nom de domaine à la machine afin d'y accéder (ce qui est fort probable), le nom de domaine de base ne vous permettra pas d'accéder au site. Pour accéder à la page d'accueil, il faut spécifiquement rentrer dans l'URL le nom de domaine + `/accueil` (une simple histoire de routage). Si par exemple le nom de domaine du site est `examplebde.fr` alors pour accéder à la page d'accueil il faudra entrer comme URL `examplebde.fr/accueil`. Si vous entrez juste le nom de domaine de base, vous tomberez sur une page vierge non redirigée (je pense qu'une âme dévouée saura résoudre ce problème en un tour de main).
### FONCTIONNEMENT
L'utilisation du site une fois déployé est très simple, l'utilisateur se connect à son compte et accès à son "Espace Personnel". Ici il peut entrer la date à laquelle il a travaillé, la durée du temps de travail (timeslot) et enfin un descriptif du travail réalisé. Il peut aussi consulter ses derniers ajouts et leurs statuts (Validé, Refusé, En attente). Un temps de travail est véritablement calculé dans le total d'un utilisateur QUE lorsque un administraterur a validé le timeslot. Les administrateurs ont ainsi en plus accès à un onglet spécifique qui leur permet de consulter les heures en attente de validation, et de les refuser/valider. Cet onglet permet également de consulter l'historique des autres utilisateurs. Un administrateur est défini comme tel dans la table Users grâce au champ `is_admin`.
Voilà tout, le site n'est pas parfait mais fera très bien l'affaire pour son application finale. Un utilisateur déterminé pourra ajouter un système de création de compte ou de récupération d'identifiant afin de ne pas avoir à les entrer/changer manuellement dans la base de données. Il est également possible d'ajouter un fichier exemple d'insertion SQL pour faciliter la tâche aux futurs repreneurs. Enfin un utilisateur très déterminé peut s'amuser à réecrire l'API du routeur en AJAX ou avec FETCH. Have fun ;) !
Si vous avez des questions, n'hésitez pas à me contacter: nirina.macon@gmail.com. J'invite chaque personne ayant contribué à ce projet à ajouter son épitaphe ci-dessous, j'espère que la liste s'aggrandira ;).
Nirina MACON, V1, 2024-2025, Odyssey BDE

BIN
assets/img/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

42
index.php Normal file
View File

@@ -0,0 +1,42 @@
<?php
// Organize server path requested
setlocale(LC_TIME, 'fr_FR.utf8','fra');
$method = $_SERVER["REQUEST_METHOD"]; // Récupération de la méthode (GET/POST)
$uri = explode("?", $_SERVER["REQUEST_URI"])[0]; // Récupération du contexte (/...)
/*
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);*/
require_once "src/appli/cntrlLogin.php";
require_once "src/appli/cntrlApp.php";
require_once "src/appli/utils.php";
$DaoTimeslot = new DaoTimeslot(DBHOST, DBNAME, PORT, USER, PASS);
$cntrlLogin = new cntrlLogin();
$cntrlApp = new cntrlApp();
$utils = new Utils();
if($method == "GET"){
if($uri == "/login") $cntrlLogin->getLoginForm();
if($uri == "/login/result") $cntrlLogin->getLoginResult();
if($uri == "/accueil") $cntrlApp->getAccueil();
if($uri == "/espaceperso") $cntrlApp->getEspacePerso();
if($uri == "/disconnect") $utils->destructSession();
if($uri == "/historique") $cntrlApp->getHistorique();
if($uri == "/admin") $cntrlApp->getAdminPage();
if($uri == "/debug") $DaoTimeslot->getTimeslotsByIdUser(1);
}
elseif($method == "POST"){
if($uri == "/espaceperso/register/result") $cntrlApp->getInsertResult();
if($uri == "/login/result") $cntrlLogin->getLoginResult();
if($uri == "/espaceperso/delete") $cntrlApp->getDeleteResult();
if($uri == "/admin/validate") $cntrlApp->getValidateResult();
if($uri == "/admin/refuse") $cntrlApp->getRefuseResult();
if($uri == "/admin/historique") $cntrlApp->getSpecificHistoric();
}

40
ressources/CTBDE.sql Normal file
View File

@@ -0,0 +1,40 @@
DROP TABLE IF EXISTS Speciality CASCADE;
DROP TABLE IF EXISTS Users CASCADE;
DROP TABLE IF EXISTS Timeslot CASCADE;
/* Cette table se réfère à la fonction du User au sein du BDE */
CREATE TABLE Speciality(
id SERIAL NOT NULL , /* AUTO */
type VARCHAR (128) NOT NULL , /* La fonction du User au sein du BDE. ex: 'Président', 'Vice-Président', 'Trésorier', 'Pôle Communication', 'Responsable Pôle Communication', etc... Libre à vous de nommer les fonctions comme vous le souhaitez */
CONSTRAINT Speciality_PK PRIMARY KEY (id)
);
/* Cette table décrit un utlisateur, soit un membre du BDE */
CREATE TABLE Users(
id SERIAL NOT NULL , /* AUTO */
name VARCHAR (128) NOT NULL , /* Nom */
surname VARCHAR (128) NOT NULL , /* Prénom */
cycle VARCHAR (5), /* Le cycle du User. ex: 'CIR2', 'CEST1', etc... */
mail VARCHAR (128) NOT NULL UNIQUE, /* L'adresse mail du User, attention cette dernière est utilisée pour se connecter sur le site ! */
password VARCHAR (60) NOT NULL , /* Mot de passe de l'utilisateur. Attention! Il faut entrer ici le mot de passe hash et non en brut !. Se référer au README pour plus d'explication. */
is_admin BOOLEAN NOT NULL, /* Défini si l'utilisateur est adminstrateur ou non. Un administrateur possède des droits particuliers qui lui permettent de valider/refuser et visuliaser les heures des autres */
id_Speciality INT NOT NULL, /* L'ID de la Speciality du User, c'est à dire l'ID de sa fonction associée */
CONSTRAINT User_PK PRIMARY KEY (id)
,CONSTRAINT User_Speciality_FK FOREIGN KEY (id_Speciality) REFERENCES Speciality(id)
);
CREATE TABLE Timeslot(
id SERIAL NOT NULL , /* AUTO */
date DATE NOT NULL , /* Date à laquelle a été réalisée le temps de travail au format YYYY-MM-DD*/
duration DECIMAL NOT NULL, /* La durée du temps de travail en heure ! */
id_User INT NOT NULL , /* L'ID du user ayant réalisé l'heure de travail */
id_User_Validated INT NULL, /* L'ID du user ayant validé le temps de travail (un admin typiquement) */
is_validated SMALLINT NULL, /* 1 ou 0 pour indiquer si l'heure a été validée ou non par un administrateur. 0 -> refusée, 1 -> validée, pas de valeur -> en attente de validation. */
description TEXT NOT NULL, /* Une description du temps de travail, pour savoir si c'est crédible ;) */
CONSTRAINT Timeslot_PK PRIMARY KEY (id)
,CONSTRAINT Timeslot_User_FK FOREIGN KEY (id_User) REFERENCES Users(id)
,CONSTRAINT Timeslot_User_Validated_FK FOREIGN KEY (id_User) REFERENCES Users(id)
);

View File

3
ressources/css/vuser.css Normal file
View File

@@ -0,0 +1,3 @@
.controllerInput {
display: flex;
}

160
src/appli/cntrlApp.php Normal file
View File

@@ -0,0 +1,160 @@
<?php
require_once "utils.php";
require_once "src/dao/DaoTimeslot.php";
class cntrlApp {
public function getAccueil(){
require_once PATH_VIEW . "vaccueil.php";
}
public function getHistorique(){
if(!isset($utils)){
$utils = new Utils();
}
$DaoTimeslot = new DaoTimeslot(DBHOST, DBNAME, PORT, USER, PASS);
if(session_status() == PHP_SESSION_NONE){
session_start();
}
if(!isset($_SESSION["user"])){
$this->getAccueil();
return;
}
$idUser = $_SESSION["user"]->getId();
$timeslots = $DaoTimeslot->getFullTimeslotsByIdUser($idUser);
$sumHours = $DaoTimeslot->getTotalHours($idUser)["somme"];
require_once PATH_VIEW . "vtimeslots.php";
}
public function getEspacePerso(){
if(!isset($utils)){
$utils = new Utils();
}
$DaoTimeslot = new DaoTimeslot(DBHOST, DBNAME, PORT, USER, PASS);
if(session_status() == PHP_SESSION_NONE){
session_start();
}
if(!isset($_SESSION["user"])){
$this->getAccueil();
return;
}
$idUser = $_SESSION["user"]->getId();
$timeslots = $DaoTimeslot->getTimeslotsByIdUser($idUser);
require_once PATH_VIEW . "vuser.php";
}
public function getInsertResult(){
if(!isset($utils)){
$utils = new Utils();
}
$DaoTimeslot = new DaoTimeslot(DBHOST, DBNAME, PORT, USER, PASS);
$date = $_POST["date"];
$duration = $_POST["duration"];
$description = $_POST["description"];
$newDuration = $utils->convertHoursToDecimal($duration);
if(session_status() == PHP_SESSION_NONE){
session_start();
}
$idUser = $_SESSION["user"]->getId();
$DaoTimeslot->insertTimeslot($date, $newDuration, $idUser, $description);
$this->getEspacePerso();
}
public function getAdminPage(){
if(!isset($utils)){
$utils = new Utils();
}
$DaoTimeslot = new DaoTimeslot(DBHOST, DBNAME, PORT, USER, PASS);
$DaoUser = new DaoUser(DBHOST, DBNAME, PORT, USER, PASS);
if(session_status() == PHP_SESSION_NONE){
session_start();
}
if(!isset($_SESSION["user"]) || $_SESSION["user"]->getIsAdmin() == 0){
$this->getAccueil();
return;
}
$idUser = $_SESSION["user"]->getId();
$timeslotsToValidate = $DaoTimeslot->getUnvalidatedTimeslots();
$allUsers = $DaoUser->getAllUsers();
require_once PATH_VIEW . "vadmin.php";
}
public function getSpecificHistoric(){
if(!isset($utils)){
$utils = new Utils();
}
$DaoTimeslot = new DaoTimeslot(DBHOST, DBNAME, PORT, USER, PASS);
$DaoUser = new DaoUser(DBHOST, DBNAME, PORT, USER, PASS);
if(session_status() == PHP_SESSION_NONE){
session_start();
}
if(!isset($_SESSION["user"])){
$this->getAccueil();
return;
}
if(!isset($_POST["idUserToInspect"])){
$utils->echoInfo("Veuillez renseigner un utilisateur");
$this->getAdminPage();
return;
}
$idUser = $_POST["idUserToInspect"];
if($idUser == 0){
$utils->echoInfo("Veuillez renseigner un utilisateur");
$this->getAdminPage();
return;
}
$speUser = $DaoUser->getUserById($idUser);
$sumHours = $DaoTimeslot->getTotalHours($idUser)["somme"];
$timeslots = $DaoTimeslot->getFullTimeslotsByIdUser($idUser);
require_once PATH_VIEW . "vspecifictimeslot.php";
}
public function getDeleteResult(){
if(!isset($utils)){
$utils = new Utils();
}
$DaoTimeslot = new DaoTimeslot(DBHOST, DBNAME, PORT, USER, PASS);
$idDelete = $_POST["idDelete"];
$DaoTimeslot->deleteTimeslotById($idDelete);
$this->getEspacePerso();
}
public function getValidateResult(){
if(!isset($utils)){
$utils = new Utils();
}
$idValidate = $_POST["idValidate"];
if(session_status() == PHP_SESSION_NONE){
session_start();
}
$idUser = $_SESSION["user"]->getId();
$DaoTimeslot = new DaoTimeslot(DBHOST, DBNAME, PORT, USER, PASS);
$DaoTimeslot->validateTimeslot($idValidate, $idUser);
$utils->echoSuccess("Horaire validé");
$this->getAdminPage();
}
public function getRefuseResult(){
if(!isset($utils)){
$utils = new Utils();
}
$idRefuse = $_POST["idRefuse"];
if(session_status() == PHP_SESSION_NONE){
session_start();
}
$idUser = $_SESSION["user"]->getId();
$DaoTimeslot = new DaoTimeslot(DBHOST, DBNAME, PORT, USER, PASS);
$DaoTimeslot->refuseTimeslot($idRefuse, $idUser);
$utils->echoSuccess("Horaire refusé");
$this->getAdminPage();
}
};

34
src/appli/cntrlLogin.php Normal file
View File

@@ -0,0 +1,34 @@
<?php
require_once "utils.php";
require_once "src/dao/DaoUser.php";
class cntrlLogin {
public function getLoginForm(){
require_once PATH_VIEW . "vlogin.php";
}
public function getLoginResult(){
$utils = new Utils();
$mail = $_POST['mail'];
$password = $_POST['password'];
$daoUser = new DaoUser(DBHOST, DBNAME, PORT, USER, PASS);
$id = $daoUser->connectUser($mail, $password);
if ($id == NULL) {
$needle = "L'adresse email ou le mot de passe renseigné est incorrect";
$utils->echoError($needle);
require_once PATH_VIEW . "vlogin.php";
}
else {
$utils->constructSession($id);
$needle = "Vous êtes connecté";
$utils->echoSuccess($needle);
$cntrlApp = new CntrlApp();
$cntrlApp->getEspacePerso();
}
}
};

112
src/appli/utils.php Normal file
View File

@@ -0,0 +1,112 @@
<?php
const PATH_VIEW = "src/view/";
const PATH_CSS = "ressources/css/";
const DBHOST = "localhost";
const DBNAME = "bdehours";
const PORT = 5432;
const USER = "postgres";
const PASS = "Isen44N";
require_once "src/dao/DaoUser.php";
require_once "src/metier/User.php";
require_once "src/dao/DaoSpeciality.php";
class Utils {
public function hash_password(string $password) {
password_hash($password, PASSWORD_BCRYPT);
}
public function echoSuccess($needle){
echo '
<div class="errorWrapper">
<div class="alert alert-success alert-dismissible d-flex align-items-center fade show">
<i class="bi-check-circle-fill"></i>
<strong class="mx-2">Succès!</strong>'. $needle. '
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
</div>
';
}
public function echoError($needle){
echo '
<div class="errorWrapper">
<div class="alert alert-danger alert-dismissible d-flex align-items-center fade show">
<i class="bi-exclamation-octagon-fill"></i>
<strong class="mx-2">Erreur!</strong>'. $needle . '
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
</div>
';
}
public function echoWarning($needle){
echo '
<div class="errorWrapper">
<div class="alert alert-warning alert-dismissible d-flex align-items-center fade show">
<i class="bi-exclamation-triangle-fill"></i>
<strong class="mx-2">Attention!</strong>' . $needle . '
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
</div>
';
}
public function echoInfo($needle){
echo '
<div class="errorWrapper">
<div class="alert alert-info alert-dismissible d-flex align-items-center fade show">
<i class="bi-info-circle-fill"></i>
<strong class="mx-2">Info!</strong>' . $needle . '
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
</div>
';
}
public function constructSession($id){
$DaoUser = new DaoUser(DBHOST, DBNAME, PORT, USER, PASS);
$DaoSpeciality = new DaoSpeciality(DBHOST, DBNAME, PORT, USER, PASS);
$result = $DaoUser->getUserById($id);
$user = new User($result['id'], $result['name'], $result['surname'], $result['cycle'], $result['mail'], $result['is_admin']);
if(session_status() === PHP_SESSION_NONE){
session_start();
}
$_SESSION["user"] = $user;
}
public function destructSession(){
if(session_status() === PHP_SESSION_NONE){
session_start();
}
session_destroy();
require PATH_VIEW . "vaccueil.php";
}
public function convertHoursToDecimal($time){
$sep = explode(":", $time);
$hours = $sep[0];
$minutes = round($sep[1] / 60, 2);
return (float) ($hours + $minutes);
}
function convertDecimalToHours($dec)
{
// start by converting to seconds
$seconds = ($dec * 3600);
// we're given hours, so let's get those the easy way
$hours = floor($dec);
// since we've "calculated" hours, let's remove them from the seconds variable
$seconds -= $hours * 3600;
// calculate minutes left
$minutes = ceil($seconds / 60);
if ($minutes == 0){
$minutes = "00";
}
// remove those from seconds as well
$seconds -= $minutes * 60;
// return the time formatted HH\hMM
return (string) ($hours)."h".($minutes);
}
};

33
src/dao/DaoSpeciality.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
require_once "src/metier/Speciality.php";
class DaoSpeciality {
private string $host;
private string $dbname;
private string $user;
private string $pass;
private PDO $db;
public function __construct(string $host, string $dbname, int $port, string $user, string $pass) {
$this->host = $host;
$this->dbname = $dbname;
$this->user = $user;
$this->pass = $pass;
try {
$this->db = new PDO("pgsql:dbname=" . $dbname . ";host=" . $host . ";port=" . $port, $user, $pass);
} catch (PDOException $e) {
$erreurs = [];
echo $e->getMessage();
}
}
public function getSpecialityOfUser(int $idUser){
$statement = $this->db->prepare("SELECT s.id, s.type FROM speciality s JOIN users u ON s.id = u.id_speciality WHERE u.id = :idUser");
$statement->bindParam(":idUser", $idUser);
$statement->execute();
$result = $statement->fetch(PDO::FETCH_ASSOC);
$speciality = new Speciality($result["id"], $result["type"]);
return $speciality;
}
};

133
src/dao/DaoTimeslot.php Normal file
View File

@@ -0,0 +1,133 @@
<?php
require_once "src/metier/Timeslot.php";
require_once "src/metier/Speciality.php";
class DaoTimeslot {
private string $host;
private string $dbname;
private string $user;
private string $pass;
private PDO $db;
public function __construct(string $host, string $dbname, int $port, string $user, string $pass) {
$this->host = $host;
$this->dbname = $dbname;
$this->user = $user;
$this->pass = $pass;
try {
$this->db = new PDO("pgsql:dbname=" . $dbname . ";host=" . $host . ";port=" . $port, $user, $pass);
} catch (PDOException $e) {
$erreurs = [];
echo $e->getMessage();
}
}
public function insertTimeslot(string $date, float $duration, int $idUser, string $description){
$statement = $this->db->prepare("INSERT INTO timeslot (date, duration, id_user, description) VALUES (:date, :duration, :idUser, :description)");
$statement->bindParam(":date", $date);
$statement->bindParam(":duration", $duration);
$statement->bindParam(":idUser", $idUser);
$statement->bindParam(":description", $description);
$statement->execute();
}
public function getTimeslotsByIdUser(int $idUser){
$statement = $this->db->prepare("SELECT t.id, t.date, t.duration, t.id_user, t.id_user_validated, COALESCE(is_validated, 2) as is_validated, t.description, u.id as id_user, u.name, u.surname, u.cycle, u.mail, u.is_admin
FROM timeslot t
JOIN users u ON t.id_user = u.id
WHERE id_user = :idUser
ORDER BY date DESC LIMIT 10");
$statement->bindParam(":idUser", $idUser);
$statement->execute();
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
$timeslots = [];
foreach($result as $timeslot){
$user = new User($timeslot["id_user"], $timeslot["name"], $timeslot["surname"], $timeslot["cycle"], $timeslot["mail"], $timeslot["is_admin"]);
$timeslots[] = new Timeslot($timeslot["id"], new DateTime($timeslot["date"]), $timeslot["duration"], $timeslot["description"] ,$timeslot["id_user_validated"], $timeslot["is_validated"], $user);
}
return $timeslots;
}
public function deleteTimeslotById(int $id){
$statement = $this->db->prepare("DELETE FROM timeslot WHERE id = :id");
$statement->bindParam(":id", $id);
$statement->execute();
}
public function getFullTimeslotsByIdUser(int $idUser){
$statement = $this->db->prepare("SELECT t.id, t.date, t.duration, t.id_user, t.id_user_validated, COALESCE(is_validated, 2) as is_validated, t.description, u.id as id_user, u.name, u.surname, u.cycle, u.mail, u.is_admin
FROM timeslot t
JOIN users u ON t.id_user = u.id
WHERE id_user = :idUser
ORDER BY date DESC");
$statement->bindParam(":idUser", $idUser);
$statement->execute();
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
$timeslots = [];
foreach($result as $timeslot){
$user = new User($timeslot["id_user"], $timeslot["name"], $timeslot["surname"], $timeslot["cycle"], $timeslot["mail"], $timeslot["is_admin"]);
$timeslots[] = new Timeslot($timeslot["id"], new DateTime($timeslot["date"]), $timeslot["duration"], $timeslot["description"] ,$timeslot["id_user_validated"], $timeslot["is_validated"], $user);
}
return $timeslots;
}
public function getUnvalidatedTimeslots(){
$statement = $this->db->prepare("SELECT t.id, t.date, t.duration, t.id_user, t.id_user_validated, COALESCE(is_validated, 2) as is_validated, t.description, u.id as id_user, u.name, u.surname, u.cycle, u.mail, u.is_admin
FROM timeslot t JOIN users u ON t.id_user = u.id
WHERE is_validated IS NULL
ORDER BY date DESC");
$statement->execute();
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
$timeslots = [];
foreach($result as $timeslot){
$user = new User($timeslot["id_user"], $timeslot["name"], $timeslot["surname"], $timeslot["cycle"], $timeslot["mail"], $timeslot["is_admin"]);
$timeslots[] = new Timeslot($timeslot["id"], new DateTime($timeslot["date"]), $timeslot["duration"], $timeslot["description"] ,$timeslot["id_user_validated"], $timeslot["is_validated"], $user);
}
return $timeslots;
}
public function validateTimeslot(int $idTimeslot, int $idUser){
$statement = $this->db->prepare("UPDATE timeslot SET id_user_validated = :idUser, is_validated = 1 WHERE id = :idTimeslot");
$statement->bindParam(":idUser", $idUser);
$statement->bindParam(":idTimeslot", $idTimeslot);
$statement->execute();
}
public function refuseTimeslot(int $idTimeslot, int $idUser){
$statement = $this->db->prepare("UPDATE timeslot SET id_user_validated = :idUser, is_validated = 0 WHERE id = :idTimeslot");
$statement->bindParam(":idUser", $idUser);
$statement->bindParam(":idTimeslot", $idTimeslot);
$statement->execute();
}
public function getTotalHours(int $idUser){
$statement = $this->db->prepare("SELECT SUM(t.duration) as somme from timeslot t WHERE id_user = :id AND is_validated = 1 GROUP BY id_user");
$statement->bindParam(":id", $idUser);
$statement->execute();
$result = $statement->fetch(PDO::FETCH_ASSOC);
return $result;
}
public function getRecentTimeslots(){
$statement = $this->db->prepare("SELECT t.id, t.date, t.duration, t.id_user, t.id_user_validated, COALESCE(is_validated, 2) as is_validated, t.description, u.id as id_user, u.name, u.surname, u.cycle, u.mail, u.is_admin, s.type , s.id as id_speciality, uv.name, uv.surname
FROM timeslot t JOIN users u ON t.id_user = u.id
JOIN speciality s ON u.id_speciality = s.id
JOIN users uv ON uv.id = t.id_user_validated
WHERE is_validated = 1
OR is_validated = 0
ORDER BY date DESC
LIMIT 10");
$statement->execute();
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
return $result;
}
};

52
src/dao/DaoUser.php Normal file
View File

@@ -0,0 +1,52 @@
<?php
require_once "src/metier/User.php";
class DaoUser {
private string $host;
private string $dbname;
private string $user;
private string $pass;
private PDO $db;
public function __construct(string $host, string $dbname, int $port, string $user, string $pass) {
$this->host = $host;
$this->dbname = $dbname;
$this->user = $user;
$this->pass = $pass;
try {
$this->db = new PDO("pgsql:dbname=" . $dbname . ";host=" . $host . ";port=" . $port, $user, $pass);
} catch (PDOException $e) {
$erreurs = [];
echo $e->getMessage();
}
}
public function connectUser(string $mail, string $password) {
$id = NULL;
$statement = $this->db->prepare("SELECT id, password FROM users WHERE mail = :mail");
$statement->bindParam(":mail", $mail);
$statement->execute();
$user = $statement->fetch(PDO::FETCH_ASSOC);
if ($user != false) {
if (password_verify($password, $user['password'])) $id = $user['id'];
}
return $id;
}
public function getUserById($id){
$statement = $this->db->prepare("SELECT id, name, surname, cycle, mail, id_speciality, is_admin FROM users WHERE id = :id");
$statement->bindParam(":id", $id);
$statement->execute();
$user = $statement->fetch(PDO::FETCH_ASSOC);
return $user;
}
public function getAllUsers(){
$statement = $this->db->prepare("SELECT u.id, u.name, u.surname, u.cycle, u.mail, u.is_admin FROM users u ORDER BY name ASC");
$statement->execute();
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
foreach($result as $user){
$users[] = new User($user['id'], $user['name'], $user['surname'], $user['cycle'], $user['mail'], $user['is_admin']);
}
return $users;
}
};

16
src/metier/Speciality.php Normal file
View File

@@ -0,0 +1,16 @@
<?php
class Speciality {
private int $id;
private string $type;
public function __construct(int $id, string $type){
$this->id = $id;
$this->type = $type;
}
public function getId() : int{
return $this->id;
}
public function getType() : string{
return $this->type;
}
};

52
src/metier/Timeslot.php Normal file
View File

@@ -0,0 +1,52 @@
<?php
require_once "src/metier/User.php";
class Timeslot {
private int $id;
private DateTime $date;
private float $duration;
private string $description;
private ?int $idUserValidated;
private ?int $isValidated;
private User $user;
public function __construct(int $id, DateTime $date, float $duration, string $description, ?int $idUserValidated, ?int $isValidated, User $user){
$this->id = $id;
$this->date = clone $date;
$this->duration = $duration;
$this->description = $description;
if($idUserValidated == NULL){
$this->idUserValidated = NULL;
}
else $this->idUserValidated = $idUserValidated;
if($isValidated == NULL){
$this->isValidated = NULL;
}
else $this->isValidated = $isValidated;
$this->user = $user;
}
public function getId() : int{
return $this->id;
}
public function getDate() : DateTime{
return $this->date;
}
public function getDuration() : float{
return $this->duration;
}
public function getIdUserValidated() : ?int{
return $this->idUserValidated;
}
public function getIsValidated() : ?int{
return $this->isValidated;
}
public function getDescription() : string{
return $this->description;
}
public function getUser() : User{
return $this->user;
}
};

48
src/metier/User.php Normal file
View File

@@ -0,0 +1,48 @@
<?php
require_once "src/metier/Speciality.php";
require_once "src/dao/DaoSpeciality.php";
require_once "src/appli/utils.php";
class User {
private int $id;
private string $name;
private string $surname;
private string $cycle;
private string $mail;
private Speciality $speciality;
private bool $isAdmin;
public function __construct(int $id, string $name, string $surname, string $cycle, string $mail, bool $isAdmin = NULL){
$DaoSpeciality = new DaoSpeciality(DBHOST, DBNAME, PORT, USER, PASS);
$this->id = $id;
$this->name = $name;
$this->surname = $surname;
$this->cycle = $cycle;
$this->mail = $mail;
$this->isAdmin = $isAdmin;
$this->speciality = $DaoSpeciality->getSpecialityOfUser($id);
}
public function getId() : int{
return $this->id;
}
public function getName() : string{
return $this->name;
}
public function getSurname() : string{
return $this->surname;
}
public function getCycle() : string{
return $this->cycle;
}
public function getMail() : string{
return $this->mail;
}
public function getSpeciality() : Speciality{
return $this->speciality;
}
public function getIsAdmin() : bool{
return $this->isAdmin;
}
};

116
src/view/header.php Normal file
View File

@@ -0,0 +1,116 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>BDE</title>
<link rel="icon" type="image/x-icon" href="/images/favicon.ico">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter">
<script src="https://kit.fontawesome.com/5b8b37978c.js" crossorigin="anonymous"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.min.css">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/plotly.js/1.33.1/plotly.min.js" integrity="sha512-V0j9LhrK9IMNdFYZqh+IqU4cjo7wdxyHNyH+L0td4HryBuZ7Oq6QxP2/CWr6TituX31+gv5PnolvERuTbz8UNA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<!-- Navbar -->
<nav class="navbar navbar-expand-lg navbar-light bg-body-tertiary">
<!-- Container wrapper -->
<div class="container-fluid">
<!-- Toggle button -->
<button
data-mdb-collapse-init
class="navbar-toggler"
type="button"
data-mdb-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation"
>
<i class="fas fa-bars"></i>
</button>
<!-- Collapsible wrapper -->
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Navbar brand -->
<a class="navbar-brand mt-2 mt-lg-0" href="#">
<img
src="/assets/img/logo.jpg"
height="50"
alt="MDB Logo"
loading="lazy"
class="logoBDE"
/>
</a>
<!-- Left links -->
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="/accueil">Accueil</a>
</li>
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if(isset($_SESSION["user"])){ ?>
<li class="nav-item">
<a class="nav-link" href="/espaceperso">Espace perso</a>
</li>
<?php
}
if(isset($_SESSION["user"]) && $_SESSION["user"]->getIsAdmin()){
?>
<li class="nav-item">
<a class="nav-link" href="/admin">Espace admin</a>
</li>
<?php
}
?>
</ul>
<!-- Left links -->
</div>
<!-- Collapsible wrapper -->
<!-- Right elements -->
<div class="d-flex align-items-center" style="display: flex">
<!-- Icon -->
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if(isset($_SESSION["user"])){ ?>
<a class="text-reset me-3" href="/disconnect">
<i class="fa-solid fa-xmark"></i>
</a>
<?php
}
else{ ?>
<a class="text-reset me-3" href="/login">
<i class="fa-solid fa-arrow-right-to-bracket"></i>
</a>
<?php
}?>
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if(isset($_SESSION["user"])){
$user = $_SESSION["user"];?>
<a class="text-reset me-3" style="text-decoration: none">
<?= $user->getName() . " " . strtoupper($user->getSurname()) ?>
</a>
<?php
}
else { ?>
<a class="text-reset me-3" href="/login" style="text-decoration: none">
<?= "Se connecter" ?>
</a>
<?php
}
?>
<!-- Right elements -->
</div>
<!-- Container wrapper -->
</nav>
<!-- Navbar -->

11
src/view/vaccueil.php Normal file
View File

@@ -0,0 +1,11 @@
<?php require_once "header.php"; ?>
<div class="container" style="margin: 4%">
<div class="row">
<div class="col-12">
<h1>Accueil</h1>
<hr>
<p>Bienvenue sur le site de gestion des heures d'Odyssey BDE </p>
<p>Vous pouvez vous connecter à votre compte et accéder à vos service à l'aide du bouton "Se connecter" en haut à droite</p>
<p>Si vous avez une question ou en cas de problème, contacter Nirina sur discord</p>
</div>
</div>

73
src/view/vadmin.php Normal file
View File

@@ -0,0 +1,73 @@
<?php require_once "header.php"; ?>
<div style="margin: 2%;">
<h3 class="" style="">Consulter un historique</h3>
<hr>
<form method="POST" action="/admin/historique" style="display: flex; flex-direction: row;">
<select class="form-select" aria-label="Default select example" style="width: 20%;" name="idUserToInspect">
<option value="0" selected>Sélectionner un membre</option>
<?php foreach($allUsers as $user) { ?>
<option value="<?= $user->getId() ?>"><?= $user->getName() . " " . strtoupper($user->getSurname())?></option>
<?php
}?>
</select>
<button type="submit" class="btn btn-outline-secondary">Consulter</button>
</form>
<h3 class="" style="text-align: left; margin-top: 2%;">Ajouts à valider</h3>
<hr>
<?php if(!empty($timeslotsToValidate)){ ?>
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">id</th>
<th scope="col">Personne</th>
<th scope="col">Date</th>
<th scope="col">Durée</th>
<th scope="col">Description</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach($timeslotsToValidate as $timeslot){
?>
<tr>
<td scope="row"><?php echo $timeslot->getId(); ?></td>
<td>
<div class="ms-3">
<p class="fw-bold mb-1"><?=$timeslot->getUser()->getName() . " " . strtoupper($timeslot->getUser()->getSurname()). " " . $timeslot->getUser()->getCycle() ?></p>
<p class="text-muted mb-0"><?=$timeslot->getUser()->getMail()?> </p>
<p class="text-muted mb-0"><?=$timeslot->getUser()->getSpeciality()->getType()?> </p>
</div>
</td>
<td><?php echo $timeslot->getDate()->format("l j M"); ?></td>
<td><?php echo $utils->convertDecimalToHours($timeslot->getDuration()); ?></td>
<td><?php echo $timeslot->getDescription(); ?></td>
<td>
<div style="display: flex; flex-direction: row; gap: 5%;">
<form method="POST" action="/admin/validate">
<input class="d-none" name="idValidate" value="<?= $timeslot->getId() ?>">
<button type="submit" class="btn btn-success"><i class="fa-solid fa-check"></i> Valider</button>
</form>
<form method="POST" action="/admin/refuse">
<input class="d-none" name="idRefuse" value="<?= $timeslot->getId() ?>">
<button type="submit" class="btn btn-danger"><i class="fa-solid fa-xmark"></i> Refuser</button>
</form>
</div>
</td>
</tr>
<?php } ?>
</tbody>
</table>
<?php } ?>
<h3 class="" style="text-align: left; margin-top: 2%;">Dernières actions</h3>
<hr>
</div>

51
src/view/vlogin.php Normal file
View File

@@ -0,0 +1,51 @@
<?php
require_once "header.php";
?>
<div id="containerLogin">
<div class="vh-100" style="height: 100%;">
<div class="container py-5 h-100">
<div class="row d-flex justify-content-center align-items-center h-100">
<div class="col col-xl-10">
<div class="card" style="border-radius: 1rem;">
<div class="row g-0">
<div class="col-md-6 col-lg-5 d-none d-md-block" style=" background: rgb(33,131,128);
background: radial-gradient(circle, rgba(33,131,128,1) 7%, rgba(169,204,213,1) 52%, rgba(180,199,222,1) 100%); ">
</div>
<div class="col-md-6 col-lg-7 d-flex align-items-center">
<div class="card-body p-4 p-lg-5 text-black">
<form action="/login/result" method="POST">
<div class="d-flex align-items-center mb-3 pb-1">
<i class="fas fa-cubes fa-2x me-3" style="color: #ff6219;"></i>
<span class="h1 fw-bold mb-0">BDE</span>
</div>
<h5 class="fw-normal mb-3 pb-3" style="letter-spacing: 1px;">Connectez-vous à votre compte</h5>
<div class="form-outline mb-4">
<input type="email" id="form2Example17" name="mail" class="form-control form-control-lg" />
<label class="form-label" for="form2Example17">Adresse mail</label>
</div>
<div class="form-outline mb-4">
<input type="password" id="form2Example27" name="password" class="form-control form-control-lg" />
<label class="form-label" for="form2Example27">Mot de passe</label>
</div>
<div class="pt-1 mb-4">
<button class="btn btn-dark btn-lg btn-block" type="submit">Connexion</button>
</div>
<p class="mb-5 pb-lg-2" style="color: #393f81;">Problème de connexion ? Contactez Nirina sur discord</p>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,45 @@
<?php require_once PATH_VIEW . "header.php" ?>
<div style="margin: 2%;">
<h3 class="" style="text-align: left; margin-top: 2%;">Total d'heures: <?= $utils->convertDecimalToHours($sumHours); ?> </h3>
<hr>
<h3 class="" style="text-align: left; margin-top: 2%;">Historique complet de <?= $speUser["name"] . " " . strtoupper($speUser["surname"]) ?></h3>
<hr>
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">id</th>
<th scope="col">Date</th>
<th scope="col">Durée</th>
<th scope="col">Description</th>
<th scope="col">Statut</th>
</tr>
</thead>
<tbody>
<?php foreach($timeslots as $timeslot){
?>
<tr>
<th scope="row"><?php echo $timeslot->getId(); ?></th>
<td><?php echo $timeslot->getDate()->format("l j M"); ?></td>
<td><?php echo $utils->convertDecimalToHours($timeslot->getDuration()); ?></td>
<td><?php echo $timeslot->getDescription(); ?></td>
<td><?php
$isValidated = $timeslot->getIsValidated();
if($isValidated == 2){?>
<button type="button" class="btn btn-warning"><i class="fa-solid fa-clock"></i> En attente</button>
<?php }
elseif($isValidated == 0){ ?>
<button type="button" class="btn btn-danger"><i class="fa-solid fa-xmark"></i> Refusé</button>
<?php }
else { ?>
<button type="button" class="btn btn-success"><i class="fa-solid fa-check"></i> Validé</button>
<?php } ?>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>

47
src/view/vtimeslots.php Normal file
View File

@@ -0,0 +1,47 @@
<?php require_once PATH_VIEW . "header.php" ?>
<div style="margin: 2%;">
<h3 class="" style="text-align: left; margin-top: 2%;">Total d'heures: <?= $utils->convertDecimalToHours($sumHours); ?> </h3>
<hr>
<h3 class="" style="text-align: left; margin-top: 2%;">Votre historique complet</h3>
<hr>
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">id</th>
<th scope="col">Date</th>
<th scope="col">Durée</th>
<th scope="col">Description</th>
<th scope="col">Statut</th>
<th scope="col">Supprimer</th>
</tr>
</thead>
<tbody>
<?php foreach($timeslots as $timeslot){
?>
<tr>
<th scope="row"><?php echo $timeslot->getId(); ?></th>
<td><?php echo $timeslot->getDate()->format("l j M"); ?></td>
<td><?php echo $utils->convertDecimalToHours($timeslot->getDuration()); ?></td>
<td><?php echo $timeslot->getDescription(); ?></td>
<td><?php
$isValidated = $timeslot->getIsValidated();
if($isValidated == 2){?>
<button type="button" class="btn btn-warning"><i class="fa-solid fa-clock"></i> En attente</button>
<?php }
elseif($isValidated == 0){ ?>
<button type="button" class="btn btn-danger"><i class="fa-solid fa-xmark"></i> Refusé</button>
<?php }
else { ?>
<button type="button" class="btn btn-success"><i class="fa-solid fa-check"></i> Validé</button>
<?php } ?>
</td>
<td><form method="POST" action="/espaceperso/delete"><input class="d-none" name="idDelete" value="<?= $timeslot->getId() ?>"> <button type="submit" class="btn"><i class="fa-solid fa-xmark"></i></button></form></td>
</tr>
<?php } ?>
</tbody>
</table>
</div>

84
src/view/vuser.php Normal file
View File

@@ -0,0 +1,84 @@
<?php require_once "header.php"
; ?>
<link rel="stylesheet" href="/ressources/css/vuser.css">
<body>
<div class="" style="margin: 2%; text-align: center;">
<h3 class="" style="text-align: left;">Insérer un temps de travail</h5>
<hr>
<form method="POST" action="/espaceperso/register/result">
<p>Date du travail / Durée / Descriptif</p>
<div class="input-group mb-3">
<div class="col-lg-3 col-sm-6 controllerInput">
<label for="startDate"></label>
<input id="startDate" class="form-control" type="date" name="date" placeholder="Date du travai" required/>
<span id="startDateSelected"></span>
</div>
<input type="time" id="timeEnd" name="duration" class="form-control form-control-sm" required/>
<input class="form-control" type="textarea" placeholder="Descriptif du travail réalisé" name="description" required></input>
<button type="submit" class="btn btn-outline-primary" data-mdb-ripple-init data-mdb-ripple-color="dark">Enregistrer</button>
</div>
</form>
<?php
?>
<h3 class="" style="text-align: left; margin-top: 2%;">Ajouts récents</h3>
<hr>
<?php if(!empty($timeslots)){ ?>
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">id</th>
<th scope="col">Date</th>
<th scope="col">Durée</th>
<th scope="col">Description</th>
<th scope="col">Status</th>
<th scope="col">Supprimer</th>
</tr>
</thead>
<tbody>
<?php foreach($timeslots as $timeslot){
?>
<tr>
<td scope="row"><?php echo $timeslot->getId(); ?></td>
<td><?php echo $timeslot->getDate()->format("l j M"); ?></td>
<td><?php echo $utils->convertDecimalToHours($timeslot->getDuration()); ?></td>
<td><?php echo $timeslot->getDescription(); ?></td>
<td><?php
$isValidated = $timeslot->getIsValidated();
if($isValidated == 2){?>
<button type="button" class="btn btn-warning"><i class="fa-solid fa-clock"></i> En attente</button>
<?php }
elseif($isValidated == 0){ ?>
<button type="button" class="btn btn-danger"><i class="fa-solid fa-xmark"></i> Refusé</button>
<?php }
else { ?>
<button type="button" class="btn btn-success"><i class="fa-solid fa-check"></i> Validé</button>
<?php } ?>
</td>
<td>
<form method="POST" action="/espaceperso/delete">
<input class="d-none" name="idDelete" value="<?= $timeslot->getId() ?>">
<button type="submit" class="btn"><i class="fa-solid fa-xmark"></i></button>
</form>
</td>
</tr>
<?php } ?>
</tbody>
</table>
<?php } ?>
<h3 class="" style="text-align: left; margin-top: 2%;">Votre historique complet</h3>
<hr>
<a href="/historique" target="_blank" style="text-decoration: none;"><button type="button" class="btn btn-outline-secondary">Consulter l'historique</button></a>
</div>
</body>