Création d’un module d’authentification
- Présentation
- 1. Création du sous-domaine "auth" et installation du prototype de départ
- 2. La base de données
- 3. Le prototype de départ
- 4. Création du modèle "Visiteur"
- 5. Création de la page de login
- 6. Validation de la connexion
- 7. Page de profil
- 8. Deconnexion
- 9. Sécurisation de la partie "utilisateur"
- 10. Création de compte
- 11. Validation de création de compte
- 12. Envoi du mail de validation
- 13. Validation du compte par mail
- 14. Modification du mail
- 15. Validation de la modification du mail
- 16. Modification du mot de passe
- 17. Ajout d'un code de vérification en JavaScript
- 18. Validation du changement de mot de passe
- 19. Suppression de compte
- 20. Validation de la suppression de compte
- 21. Ajout d'une image de profil
- 22. Modification de l'image de profil
- 23. Suppression de l'image lors de la suppression du compte
- 24. Partie administrateur – 1
- 25. Partie administrateur – 2
- 26. Sécurisation de la session avec un cookie
Présentation
Présentation.

1. Création du sous-domaine « auth » et installation du prototype de départ
On va partir d’un prototype (auth.zip) écrit en php avec une structure de type MVC.
On exécute le programme « psftp.exe » de puTTY pour copier le fichier archive (auth.zip contenant les sources) sur le VPS. Le fichier se trouve dans le répertoire « D:\Program Files\PuTTY\transfert »
Open contabo_remi # saisir la passPhrase # Remote working directory is /home/remi psftp>lcd transfert psftp>put auth.zip ./transfert/auth.zip psftp>exit
remi@vmi820488:~$ cd /var/www/projets/ remi@vmi820488:/var/www/projets$ ll remi@vmi820488:/var/www/projets$ sudo mkdir webAuth remi@vmi820488:/var/www/projets$ cd webAuth/ remi@vmi820488:/var/www/projets/webAuth$ sudo unzip ~/transfert/auth.zip -d . remi@vmi820488:/var/www/projets/webAuth$ cd gestionRole/ remi@vmi820488:/var/www/projets/webAuth/gestionRole$ sudo mv * ../ remi@vmi820488:/var/www/projets/webAuth/gestionRole$ sudo mv .htaccess ../ remi@vmi820488:/var/www/projets/webAuth/gestionRole$ cd .. remi@vmi820488:/var/www/projets/webAuth$ sudo rm -d ./gestionRole/ remi@vmi820488:/var/www/projets/webAuth$ ll total 32 drwxr-xr-x 6 root root 4096 Jan 13 17:57 ./ drwxr-xr-x 5 www-data www-data 4096 Jan 13 17:52 ../ -rw-r--r-- 1 root root 134 Apr 14 2021 .htaccess drwxr-xr-x 2 root root 4096 May 9 2021 controllers/ -rw-r--r-- 1 root root 877 May 2 2021 index.php drwxr-xr-x 2 root root 4096 May 9 2021 models/ drwxr-xr-x 5 root root 4096 May 9 2021 public/ drwxr-xr-x 3 root root 4096 May 9 2021 views/
A la fin du listing précédent, on remarque que le super utilisateur « root » et le groupe « root » sont propriétaires de tous les des dossiers et fichiers.
Cette situation pose un problème car le serveur Apache n’a aucun droit sur les fichiers qu’il doit administrer. On donne donc les droits à l’utilisateur et le groupe « www-data » (correspondant à Apache) à tous les fichiers et dossiers de notre site.
L’option « -R » étant les modifications à tous les fichiers et sous-répertoires à partir de la racine du site.
remi@vmi820488:/var/www/projets/webAuth$ sudo chown -R www-data:www-data /var/www/projets/webAuth remi@vmi820488:/var/www/projets/webAuth$ ll total 32 drwxr-xr-x 6 www-data www-data 4096 Jan 13 17:57 ./ drwxr-xr-x 5 www-data www-data 4096 Jan 13 17:52 ../ -rw-r--r-- 1 www-data www-data 134 Apr 14 2021 .htaccess drwxr-xr-x 2 www-data www-data 4096 May 9 2021 controllers/ -rw-r--r-- 1 www-data www-data 877 May 2 2021 index.php drwxr-xr-x 2 www-data www-data 4096 May 9 2021 models/ drwxr-xr-x 5 www-data www-data 4096 May 9 2021 public/ drwxr-xr-x 3 www-data www-data 4096 May 9 2021 views/
Voir le chapitre 3 l’article TP: Ajout du site Auth en tant que sous-domaine webOdesign.net et appliquer la procédure pour ajouter le sous-domaine « auth »
remi@vmi820488:/var/www/projets/webAuth$ cd /etc/apache2/sites-available remi@vmi820488:/etc/apache2/sites-available$ sudo nano auth.conf remi@vmi820488:/etc/apache2/sites-available$ cat auth.conf <VirtualHost *:80> ServerAdmin admin@webodesign.com ServerName www.auth.webodesign.net ServerAlias auth.webodesign.net DocumentRoot /var/www/projets/webAuth ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> remi@vmi820488:/etc/apache2/sites-available$ sudo a2ensite auth.conf Enabling site auth. To activate the new configuration, you need to run: systemctl reload apache2 remi@vmi820488:/etc/apache2/sites-available$ sudo systemctl reload apache2
auth
» et « www.auth
« remi@vmi820488:/etc/apache2/sites-available$ sudo certbot --apache Saving debug log to /var/log/letsencrypt/letsencrypt.log Plugins selected: Authenticator apache, Installer apache Which names would you like to activate HTTPS for? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1: alpha.webodesign.net 2: www.alpha.webodesign.net 3: auth.webodesign.net 4: www.auth.webodesign.net 5: www.webodesign.net - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Select the appropriate numbers separated by commas and/or spaces, or leave input blank to select all options shown (Enter 'c' to cancel): 3 Requesting a certificate for auth.webodesign.net Performing the following challenges: http-01 challenge for auth.webodesign.net Waiting for verification... Cleaning up challenges Created an SSL vhost at /etc/apache2/sites-available/auth-le-ssl.conf Deploying Certificate to VirtualHost /etc/apache2/sites-available/auth-le-ssl.conf Enabling available site: /etc/apache2/sites-available/auth-le-ssl.conf Redirecting vhost in /etc/apache2/sites-enabled/auth.conf to ssl vhost in /etc/apache2/sites-available/auth-le-ssl.conf - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Congratulations! You have successfully enabled https://auth.webodesign.net - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/auth.webodesign.net/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/auth.webodesign.net/privkey.pem Your certificate will expire on 2023-04-16. To obtain a new or tweaked version of this certificate in the future, simply run certbot again with the "certonly" option. To non-interactively renew *all* of your certificates, run "certbot renew" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le remi@vmi820488:/etc/apache2/sites-available$ sudo certbot --apache Saving debug log to /var/log/letsencrypt/letsencrypt.log Plugins selected: Authenticator apache, Installer apache Which names would you like to activate HTTPS for? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1: alpha.webodesign.net 2: www.alpha.webodesign.net 3: auth.webodesign.net 4: www.auth.webodesign.net 5: www.webodesign.net - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Select the appropriate numbers separated by commas and/or spaces, or leave input blank to select all options shown (Enter 'c' to cancel): 4 Requesting a certificate for www.auth.webodesign.net Performing the following challenges: http-01 challenge for www.auth.webodesign.net Waiting for verification... Cleaning up challenges Deploying Certificate to VirtualHost /etc/apache2/sites-enabled/auth-le-ssl.conf Enhancement redirect was already set. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Congratulations! You have successfully enabled https://www.auth.webodesign.net - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/www.auth.webodesign.net/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/www.auth.webodesign.net/privkey.pem Your certificate will expire on 2023-04-16. To obtain a new or tweaked version of this certificate in the future, simply run certbot again with the "certonly" option. To non-interactively renew *all* of your certificates, run "certbot renew" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le
auth-le-ssl.conf
« remi@vmi820488:/etc/apache2/sites-available$ ll total 52 drwxr-xr-x 2 root root 4096 Jan 16 11:41 ./ drwxr-xr-x 9 root root 4096 Jan 16 11:41 ../ -rw-r--r-- 1 root root 1694 May 16 2022 000-default-le-ssl.conf -rw-r--r-- 1 root root 1468 May 16 2022 000-default.conf -rw-r--r-- 1 root root 524 Jan 16 11:41 auth-le-ssl.conf -rw-r--r-- 1 root root 475 Jan 16 11:40 auth.conf remi@vmi820488:/etc/apache2/sites-available$ cat auth-le-ssl.conf <IfModule mod_ssl.c> <VirtualHost *:443> ServerAdmin admin@webodesign.com ServerName www.auth.webodesign.net ServerAlias auth.webodesign.net DocumentRoot /var/www/projets/webAuth ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined Include /etc/letsencrypt/options-ssl-apache.conf SSLCertificateFile /etc/letsencrypt/live/www.auth.webodesign.net/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/www.auth.webodesign.net/privkey.pem </VirtualHost> </IfModule> remi@vmi820488:/etc/apache2/sites-available$ sudo nano auth-le-ssl.conf remi@vmi820488:/etc/apache2/sites-available$ cat auth-le-ssl.conf <IfModule mod_ssl.c> <VirtualHost *:443> ServerAdmin admin@webodesign.com ServerName auth.webodesign.net ServerAlias auth.webodesign.net DocumentRoot /var/www/projets/webAuth ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined Include /etc/letsencrypt/options-ssl-apache.conf SSLCertificateFile /etc/letsencrypt/live/auth.webodesign.net/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/auth.webodesign.net/privkey.pem </VirtualHost> </IfModule> <IfModule mod_ssl.c> <VirtualHost *:443> ServerAdmin admin@webodesign.com ServerName www.auth.webodesign.net ServerAlias www.auth.webodesign.net DocumentRoot /var/www/projets/webAuth ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined Include /etc/letsencrypt/options-ssl-apache.conf SSLCertificateFile /etc/letsencrypt/live/www.auth.webodesign.net/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/www.auth.webodesign.net/privkey.pem </VirtualHost> </IfModule> remi@vmi820488:/etc/apache2/sites-available$ sudo service apache2 restart
Pendant la phase de développement, la connexion sera uniquement autorisée à une seule adresse ip publique (la mienne). On ajoutera une demande d’authentification basique pour compléter la sécurisation des échanges.
remi@vmi820488:/var/www/projets/webAuth$ cat .htaccess
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?page=$1
remi@vmi820488:/var/www/projets/webAuth$ sudo nano .htaccess
remi@vmi820488:/var/www/projets/webAuth$ cat .htaccess
# Texte affiche dans la fenetre d'authentification login/password
AuthName "Zone admin"
# Definition du type d'authentification proposé
AuthType Basic
# Chemin menant au fichier .htpasswd
AuthUserFile /var/www/projets/webAuth/.htpapa
# N'acceptera que des utilisateurs delares et autorises
# et qui ont leur adresse publique mentionnee ci-sessous
<RequireAll>
Require ip xx.xx.xx.xx
Require valid-user
</RequireAll>
# Réécriture d'URL :
# tranforme une adresse du type auth.webodesign.net/accueil en
# auth.webodesign.net/index.php?page=accueil
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?page=$1
remi@vmi820488:/var/www/projets/webAuth$ sudo cp .htpapa ../webAuth/
remi@vmi820488:/var/www/projets/webAuth$ sudo nano /etc/apache2/sites-available/auth-le-ssl.conf
remi@vmi820488:/var/www/projets/webAuth$ cat /etc/apache2/sites-available/auth-le-ssl.conf
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerAdmin admin@webodesign.com
ServerName auth.webodesign.net
ServerAlias auth.webodesign.net
DocumentRoot /var/www/projets/webAuth
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/auth.webodesign.net/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/auth.webodesign.net/privkey.pem
<Directory /var/www/projets/webAuth/>
Options Indexes FollowSymLinks
AllowOverride All
# Require expr %{TIME_HOUR} -gt 24 && %{TIME_HOUR} -lt 23
# Require all granted
</Directory>
</VirtualHost>
</IfModule>
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerAdmin admin@webodesign.com
ServerName www.auth.webodesign.net
ServerAlias www.auth.webodesign.net
DocumentRoot /var/www/projets/webAuth
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/www.auth.webodesign.net/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/www.auth.webodesign.net/privkey.pem
<Directory /var/www/projets/webAuth/>
Options Indexes FollowSymLinks
AllowOverride All
# Require expr %{TIME_HOUR} -gt 24 && %{TIME_HOUR} -lt 23
# Require all granted
</Directory>
</VirtualHost>
</IfModule>
remi@vmi820488:/var/www/projets/webAuth$ sudo service apache2 restart
2. La base de données
La base de donnée est extrêmement simple puisqu’elle ne contiendra qu’une seule table.
La liste des champs nécessaires est la suivante :
- login : clé primaire (on pourrait ne pas utiliser de login mais simplement un mail)
- password : le mot de passe utilisateur
- mail : l’adresse mail de l’utilisateur
- est_valide : permet de vérifier si le compte a été validé ou non par mail
- role : rôle de l’utilisateur
- clef : permet de créer un lien de validation du compte de l’utilisateur
- image : contient l’image du profil de l’utilisateur

Exécuter phpmyadmin et créer une nouvelle base de données « web_authentification ».

Créer une table « utilisateur » comportant sept colonnes.

Créer les champs de la table « utilisateur » puis cliquer sur enregistrer.

Aller dans l’onglet « Insérer » pour ajouter un premier utilisateur.
Pour générer le mot de passe crypté, on va utiliser une fonction php nommée password_hash :
- Dans la classe VisiteurController, dans la fonction « accueil » ajouter la ligne
echo password_hash("test",PASSWORD_DEFAULT);
- Sauvegarder et actualiser la page dans le navigateur. En haut se trouve inscrit le mot de passe crypté. Copier le et le coller dans le champ password de phpmyadmin
Exécuter pour ajouter le nouvel utilisateur (« bob »)

Si problème il y a, initialiser la valeur « clef » à zéro.
<?php abstract class Model{ private static $pdo; private static function setBdd(){ self::$pdo = new PDO("mysql:host=localhost;dbname=zsite;charset=utf8", "root", ""); self::$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } protected function getBdd(){ if(self::$pdo === null){ self::setBdd(); } return self::$pdo; } }
<?php require_once("Model.class.php"); class MainManager extends Model{ public function getDatas(){ $req = $this->getBdd()->prepare("SELECT * FROM matable"); $req->execute(); $datas = $req->fetchAll(PDO::FETCH_ASSOC); $req->closeCursor(); return $datas; } }
3. Le prototype de départ
La programmation de ce module d’authentification est conçu autour d’une architecture MVC, c’est à dire modèle, vue, contrôleur. Ce modèle permet de modulariser les traitements en fonction des tâches de programmation à accomplir. L’écriture et la lecture du code source en est nettement facilité.
Liste des modules :
- index.php : c’est le fichier de routage. Toutes les demandes/entrées passe par lui. Il aiguille chaque demande (passées via les url) vers la procédure de traitement adaptée.
- password : le mot de passe utilisateur
- mail : l’adresse mail de l’utilisateur
- est_valide : permet de vérifier si le compte a été validé ou non par mail
- role : rôle de l’utilisateur
- clef : permet de créer un lien de validation du compte de l’utilisateur
- image : contient l’image du profil de l’utilisateur

<?php // démarrage d'une session : enregistre le contexte (l'état) du profil de chaque // utilisateur à un moment donné session_start(); // Constante URL dont le rôle est de faire en sorte que toutes les demandes renvoyées // par les utilisateurs iront pointer vers à la racine du site : ce fichier "index.php" define("URL", str_replace("index.php","",(isset($_SERVER['HTTPS'])? "https" : "http"). "://".$_SERVER['HTTP_HOST'].$_SERVER["PHP_SELF"])); // Inclusion du fichier principal des contrôleurs qui auront la faculté de piloter // toutes les pages de contenu du site require_once("./controllers/MainController.controller.php"); $mainController = new MainController(); // Chaque demande d'url devra être captée dans cette structure switch/case afin d'être // aiguillé vers la fonction correspondante. try { if(empty($_GET['page'])){ $page = "accueil"; } else { // exemple : pour une demande type "xx.net/compte/profil", la fonction "explode" // renverra un tableau contenant : $url[0]='compte' et $url[1]='profil' $url = explode("/", filter_var($_GET['page'],FILTER_SANITIZE_URL)); $page = $url[0]; } switch($page){ case "accueil" : $mainController->accueil(); break; case "compte" : switch($url[1]){ case "profil": $mainController->accueil(); break; } break; default : throw new Exception("La page n'existe pas"); } } catch (Exception $e){ $mainController->pageErreur($e->getMessage()); }
<?php // démarrage d'une session : enregistre le contexte (l'état) du profil de chaque // utilisateur à un moment donné session_start(); // Constante URL dont le rôle est de faire en sorte que toutes les demandes renvoyées // par les utilisateurs iront pointer vers à la racine du site : ce fichier "index.php" define("URL", str_replace("index.php","",(isset($_SERVER['HTTPS'])? "https" : "http"). "://".$_SERVER['HTTP_HOST'].$_SERVER["PHP_SELF"])); // Inclusion du fichier principal des contrôleurs qui auront la faculté de piloter // toutes les pages de contenu du site require_once("./controllers/MainController.controller.php"); $mainController = new MainController(); // Chaque demande d'url devra être captée dans cette structure switch/case afin d'être // aiguillé vers la fonction correspondante. try { if(empty($_GET['page'])){ $page = "accueil"; } else { // exemple : pour une demande type "xx.net/compte/profil", la fonction "explode" // renverra un tableau contenant : $url[0]='compte' et $url[1]='profil' $url = explode("/", filter_var($_GET['page'],FILTER_SANITIZE_URL)); $page = $url[0]; } switch($page){ case "accueil" : $mainController->accueil(); break; case "compte" : switch($url[1]){ case "profil": $mainController->accueil(); break; } break; default : throw new Exception("La page n'existe pas"); } } catch (Exception $e){ $mainController->pageErreur($e->getMessage()); }
Nous allons ajouter des répertoires pou chaque type d’utilisateur venant visiter le site :
- Dans le dossier « Contrlollers », on créé les sous-dossiers « Visiteur », « Administrateur » et « Utilisateur »
- Dans le dossier « models », on créé les sous-dossiers « Visiteur », « Administrateur » et « Utilisateur »
- Dans le dossier « views », on créé les sous-dossiers « Visiteur », « Administrateur » et « Utilisateur »
Nous allons créer une page d’accueil accessible par les visiteurs (donc également utilisateurs et administrateurs). Il faut donc créer un contrôleur spécifique pour les visiteurs :
- Dans « Controllers/Visiteur/ » on créé un fichier contrôleur spécifique pour les visiteurs : « Visiteur.controller.php ».
On créé la class VisiteurController qui va hérité de la class MainController de façon à spécifier la vue d’affichage. - On passe la classe MainController en classe abstraite. Elle ne pourra plus être instanciée pour obtenir un objet. On modifiera le fichier index.php pour instancier un objet visiteurController à la place de mainController.
- On déplacera la fonction « accueil » de MainController vers VisiteurController car on veut la personnalisée spécifiquement pour les visiteurs.
- La fonction accueil présente dans le MainController sera déplacée dans VisiteurController. La vue (view) dans le tableau « $data_page » ira pointée vers une page d’accueil présente dans le dossier /views/Visiteur/accueil.php (déplacer le fichier accueil.php présent dans views).
Les modifications impacteront les fichiers MainController.controller.php, Visiteur.controller.php, index.php. Le fichiers /views/accueil.view.php sera déplacé dans le dossier /views/Visiteur/accueil.view.php.
<?php require_once("controllers/Toolbox.class.php"); abstract class MainController{ protected function genererPage($data){ extract($data); ob_start(); require_once($view); $page_content = ob_get_clean(); require_once($template); } protected function pageErreur($msg){ $data_page = [ "page_description" => "Page permettant de gérer les erreurs", "page_title" => "Page d'erreur", "msg" => $msg, "view" => "./views/erreur.view.php", "template" => "views/common/template.php" ]; $this->genererPage($data_page); } }
<?php require_once("./controllers/MainController.controller.php"); class VisiteurController extends MainController { public function accueil(){ $data_page = [ "page_description" => "Description de la page d'accueil", "page_title" => "Titre de la page d'accueil", "view" => "views/Visiteur/accueil.view.php", "template" => "views/common/template.php" ]; $this->genererPage($data_page); } public function pageErreur($msg){ parent::pageErreur($msg); } } ?>
<?php session_start(); define("URL", str_replace("index.php","",(isset($_SERVER['HTTPS'])? "https" : "http"). "://".$_SERVER['HTTP_HOST'].$_SERVER["PHP_SELF"])); require_once("./controllers/Visiteur/Visiteur.controller.php"); $visiteurController = new VisiteurController(); try { if(empty($_GET['page'])){ $page = "accueil"; } else { $url = explode("/", filter_var($_GET['page'],FILTER_SANITIZE_URL)); $page = $url[0]; } switch($page){ case "accueil" : $visiteurController->accueil(); break; case "compte" : switch($url[1]){ case "profil": $visiteurController->accueil(); break; } break; default : throw new Exception("La page n'existe pas"); } } catch (Exception $e){ $visiteurController->pageErreur($e->getMessage()); }
4. Création du modèle « Visiteur »
Le but est de tester l’accès et la consultation de la base de données menant à l’affichage des informations sur la page « visiteur ».
Le contenu du code créé dans cette étape ne sera pas conservée en l’état car les visiteurs n’ont pas à s’authentifier.
Les actions à mener :
- Comme pour les contrôleurs, un modèle principal est conservé et sera étendu à chaque nouveau modèle.
- Création du modèle « Visiteur » et récupération des utilisateurs
- Affichage du tableau récupéré sur la page d’accueil à l’aide de la fonction « print_r ».
- Affichage du tableau récupéré sur la page d’accueil dans le partie corps de la vue. Pour cela, ajouter une nouvelle variable « utilisateurs » dans le tableau $data-page afin d’afficher l’info correctement formatée en dessous du titre. Le tableau est transmis à la fonction « genererPage($data-page) » qui est présente dans « MainController » et qui va créer des variables à partir de chacune des clés du tableau. Donc dans la vue affichée (« accueil.view.php ») aura une variable $utilisateur utilisable pour afficher les résultats.
Dans un premier temps on transforme la classe MainManager en classe abstraite car on veut pouvoir l’étendre sur d’autres classes qui en hériterait (visiteur par exemple). Il est donc logique de déplacer la fonction « getUtilisateurs » au niveau de la classe VisiteurManager qui exécutera une requête spécifique sur une table (ici la table « utilisateur »)
<?php abstract class Model{ private static $pdo; private static function setBdd(){ self::$pdo = new PDO("mysql:host=localhost;dbname=nom_table;charset=utf8", "nom_user", "mot_de_passe"); self::$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } protected function getBdd(){ if(self::$pdo === null){ self::setBdd(); } return self::$pdo; } }
<?php require_once("Model.class.php"); abstract class MainManager extends Model{ }
On créé un modèle visiteur : « Visiteur.model.php »
<?php require_once("./models/MainManager.model.php"); class VisiteurManager extends MainManager{ public function getUtilisateurs(){ $req = $this->getBdd()->prepare("SELECT * FROM utilisateur"); $req->execute(); $datas = $req->fetchAll(PDO::FETCH_ASSOC); $req->closeCursor(); return $datas; } } ?>
Les données ($datas) seront récupérées au niveau du contrôleur (classe VisiteurController) :
Un objet Model (VisiteurManager) doit être instancié dans le constructeur du contôleur (VisiteurController)
<?php require_once("./controllers/MainController.controller.php"); require_once("./models/Visiteur/Visiteur.model.php"); class VisiteurController extends MainController { private $visiteurManager; public function __construct(){ $this->visiteurManager = new VisiteurManager(); } public function accueil(){ // Etape 4 : test de connexion à la BD : affichage des utilisateurs $mesUtilisateurs = $this->visiteurManager->getUtilisateurs(); print_r($mesUtilisateurs); $data_page = [ "page_description" => "Description de la page d'accueil", "page_title" => "Titre de la page d'accueil", "utilisateurs" => $mesUtilisateurs, "view" => "views/Visiteur/accueil.view.php", "template" => "views/common/template.php" ]; $this->genererPage($data_page); } public function pageErreur($msg){ parent::pageErreur($msg); } } ?>
Ligne 10 : On instancie un objet « Model » (visiteurManager).
Ligne 15 : On créé une variable $mesUtilisateurs qui sera un tableau contenant les informations de chaque utilisateur.
Ligne 21 : On défini un champ « utilisateurs » auquel on rattache les valeurs résultats. A partir de cela, la fonction « genererPage » va créer une variable $utilisateur qui prendra la valeur de $mesUtilisateurs. L’affichage des valeurs se fera dans la vue « accueil.view.php » comme montré ci-dessous.
<h1>Page d accueil</h1> <?php foreach($utilisateurs as $utilisateur) { echo $utilisateur['login']. " - " .$utilisateur['mail']; } ?>
Résultat :
Array ( [0] => Array ( [login] => bob [password] => $2y$1xxx...xxcEvYZFJwOa [mail] => bob@gmail.com [role] => administrateur [image] => [est_valide] => 1 [clef] => 0 ) )

5. Création de la page de login
Le but est créer la page de connexion ou page de login. Cette vue contiendra formulaire contenant les deux champs de saisi login et password ainsi qu’un bouton de validation.

- On pourra appeler cette page en ajoutant un item « Se connecter » dans le menu. Lorsque « Se connecter » sera déclenché, on appellera la vue à l’adresse « www.auth.webodesign.net/index.php?login »
<li class="nav-item"> <a class="nav-link" aria-current="page" href="<?= URL; ?>login">Se connecter</a> </li>
- Le lien sera détecté par le routeur « index.php » grâce au mot clé « login ». La demande sera alors transmise au contrôleur « Visiteur.controller.php » par l’appel de la fonction « login ».
switch($page){ case "accueil" : $visiteurController->accueil(); break; case "login" : $visiteurController->login(); break; ...
- Dans la fonction « login » on utilisera la structure de données $data_page pour créer une vue personnalisée pour la saisi de connexion.
public function login(){ $data_page = [ "page_description" => "Page de connexion", "page_title" => "Page de connexion", "view" => "views/Visiteur/login.view.php", "template" => "views/common/template.php" ]; $this->genererPage($data_page); }
- La vue sera codé en HTML dans la vue « login.view.php » en utilisant un formulaire. Dans le lien de retour (méthode POST), on utilisera le mot clé « validation_login » comme repère. Les valeurs des login et mot de passe seront contenus dans les variables « login » et « password ».
<h1>Page de connexion</h1> <form method="POST" action="validation_login"> <div class="mb-3"> <label for="login" class="form-label">Login</label> <input type="text" class="form-control" id='login' name="login" required> </div> <div class="mb-3"> <label for="password" class="form-label">Password</label> <input type="password" class="form-control" id="password" name="password" required> </div> <button type="submit" class="btn btn-primary">Connexion</button> </form>
- Le lien envoyé au serveur lors d’une validation du formulaire sera détecté par le routeur « index.php » grâce au mot clé « validation_login ». Pour l’instant, on affichera sommairement les nom « utilisateur » et « password » pour vérifier la bonne réception des information d’authentification (ligne 8 ci-dessous).
switch($page){ case "accueil" : $visiteurController->accueil(); break; // Affichage de la page de login case "login" : $visiteurController->login(); break; // Traitement au retour du formulaire de login case "validation_login" : echo $_POST['login'] .' - '. $_POST['password']; break;
Résultat :

6. Validation de la connexion
Le but est de traiter les informations retournées par le formulaire de login. Il faudra vérifier que l’utilisateur et le mot de passe correspondent bien avec un des utilisateurs stockés dans la base de données.
On utilisera également des fonctions php nécessairent à la sécurité (blocage des injection de code SQL par exemple). On en profitera pour créer un fichier dédié à la sécurité.
- Dans le fichier de routage « index.php », les informations seront testées (nom utilisateur et mot de passe renseignés), filtrées (utilisation d’une fonction à définir dans la classe sécurité), puis aiguillés vers la classe contrôleur des utilisateurs. Si les informations ne sont pas valide, on affichera une ligne mentionnant l’erreur de saisie commise dans le formulaire.
switch($page){ ... // Inclusion de fichier principal des contrôleurs qui auront la faculté de piloter // toutes les pages de contenu du site require_once("./controllers/Toolbox.class.php"); require_once("./controllers/Securite.class.php"); require_once("./controllers/Visiteur/Visiteur.controller.php"); require_once("./controllers/Utilisateur/Utilisateur.controller.php"); $visiteurController = new VisiteurController(); $utilisateurController = new UtilisateurController(); ... case "validation_login" : // Si les informations login et password sont renseignés, if(!empty ($_POST['login']) && !empty($_POST['password'])){ // on utilise la fonction secureHTML de la classe "Securite" pour // empeicher certaines attaques, $login = Securite::secureHTML($_POST['login']); $password = Securite::secureHTML($_POST['password']); // puis on aiguille les informations vers le contrôleur // (contrôle de correspndance dans la BD) $utilisateurController->validationLogin($login, $password); } else { // Si les informations login et/ou password ne sont pas renseignés, // on affiche un message d'erreur grace à la fonction "ajouterMessageAlerte" // présente dans la classe "Toolbox" Toolbox::ajouterMessageAlerte("Login ou mot de passe non renseignés", Toolbox::COULEUR_ROUGE); // on redirige la réponse vers l'url de la page de login header('Location: '.URL."login"); }; break;
- On créé un fichier « Securite.class.php » à la racine du dossier « controllers ». On définit une classe « Securite », dans laquelle on créé la fonction « secureHTML » qui fait appel à la fonction php « htmlentities » pour tester les informations envoyées dans le formulaire (protection contre les injections SQL).
<?php class Securite{ public static function secureHTML($chaine){ // Permet de supprimer ou convertir en ascii les caractères spéciaux return htmlentities($chaine); } } ?>
- On créé un fichier « Utilisateur.model.php » à la racine du dossier « models/Utilisateur ». On définit la classe « UtilisateurManager » (qui hérite de la classe « MainManager »), dans laquelle on créé les fonctions « isCombinaisonValide » et « isCompteActive ».
- La fonction « isCombinaisonValide » retourne « vrai » si l’utilisateur à saisi précédemment dans le formulaire d’authentification une combinaison login/password identique sauvegardée dans la base de donnée.Cette fonction va tester la correspondance des informations login / mot de passe dans la base de données.
- La fonction « isCompteActive » retourne « vrai » si, alors que l’utilisateur à saisi une combinaison login/password valide, le compte a de plus été activé en réponse au mail de confirmation envoyé par mail.
<?php require_once("./models/MainManager.model.php"); class UtilisateurManager extends MainManager{ // getPasswordUser : entrée -> nom de login à rechercher dans la BD // sortie -> nom crypté du password ou null // la fonction effectue une requête dans la BD sur le nom de login ($login). // La variable $data contiendra les informations de l'enregistrement correspondant // s'il est trouvé, "null" sinon. private function getPasswordUser($login){ $req = "SELECT password FROM utilisateur WHERE login = :login"; $stmt = $this->getBdd()->prepare($req); $stmt->bindValue(":login", $login, PDO::PARAM_STR); $stmt->execute(); $data = $stmt->fetch(PDO::FETCH_ASSOC); $stmt->closeCursor(); return $data['password']; } // isCombinaisonValide : entrée -> nom de login // password (non crypté) // sortie -> vrai si concordance (login/password) trouvée en BD public function isCombinaisonValide($login, $password){ // si le login existe dans la BD, $passwordBD contient le password crypté correspndant $passwordBD = $this->getPasswordUser($login); //echo $passwordBD; // password_verify renvoi "true" s'il y a correspondance entre chaine et chaine crypté return password_verify($password, $passwordBD); } // getCompteActive : entrée -> nom de login concerné // sortie -> true si le champ "est_valide" est égal à 1, zéro sinon public function isCompteActive($login){ $req = "SELECT est_valide FROM utilisateur WHERE login = :login"; $stmt = $this->getBdd()->prepare($req); $stmt->bindValue(":login", $login, PDO::PARAM_STR); $stmt->execute(); $data = $stmt->fetch(PDO::FETCH_ASSOC); $stmt->closeCursor(); return ((int)$data['est_valide'] === 1) ? true : false; } } ?>
- On créé un fichier « Utilisateur.controller.php » à la racine du dossier « controllers/Utilisateur ». On définit la classe « UtilisateurController » (qui hérite de la classe « MainController »), dans laquelle on créé la fonction « validationLogin ».
Cette fonction va tester la correspondance des informations login / mot de passe dans la base de données. On renverra un message d’erreur si l’utilisateur n’apparait pas dans la BD (fonction isCombinaisonValide de la classe modèle « utilisateurManager ») ou si l’utilisateur existe pas que le compte n’a pas été encore activé (fonction isCompteActive de la classe modèle « utilisateurManager » qui vérifie si le compte a été activé lors d’une réponse à l’envoi du mail de confirmation).
Si l’utilisateur devient connecté. on sauvegardera le nom de login dans une variable de session afin d’éviter de faire à chaque fois de nouvelles vérifications inutiles. Désormais, il suffira de vérifier l’état de la variable login pour savoir si un utilisateur est connecté et connaitre son nom.
Sinon, la réponse est orienté vers la page d’affichage du profil utilisateur.
<?php require_once("./controllers/MainController.controller.php"); require_once("./models/Utilisateur/Utilisateur.model.php"); class UtilisateurController extends MainController { private $utilisateurController; private $utilisateurManager; public function __construct(){ $this->utilisateurManager = new UtilisateurManager(); } public function validationLogin($login, $password){ if($this->utilisateurManager->isCombinaisonValide($login, $password)){ if($this->utilisateurManager->isCompteActive($login)){ Toolbox::ajouterMessageAlerte("Bon retour sur le site ".$login. " !", Toolbox::COULEUR_VERTE); // Ssauvegarde du nom de login dans une variable de session $_SESSION['profil'] = [ "login" => $login ]; // Redirection vers la page de profil utilisateur header("Location: ".URL."compte/profil"); }else{ $msg = "Le compte ".$login." n'a pas été activé par mail. "; Toolbox::ajouterMessageAlerte($msg, Toolbox::COULEUR_ROUGE); header("Location: ".URL."login"); } } else { Toolbox::ajouterMessageAlerte("Combinaison Login / Mot de passe non valide", Toolbox::COULEUR_ROUGE); header("Location: ".URL."login"); }; } } ?>
7. Page de profil
Maintenant que l’utilisateur est connecté, on veut créer une page contenant son profil.
- Pour pouvoir accéder à ce profil, on ajoutera un item « Profil » dans le menu. Cet item se affiché seulement en cas de connexion établie. De même, l’item « Se connecter » créé dans l’étape précédente sera affiché uniquement en l’absence de connexion.
Pour cela on va créer en premier, dans la classe « Securite », la fonction isConnected() qui renvoie la valeur true si connecté, false sinon.
<?php class Securite{ ... public static function isConnected(){ return (!empty($_SESSION['profil'])); } } ?>
<?php if(!Securite::isConnected()) : ?> <li class="nav-item"> <a class="nav-link" aria-current="page" href="<?= URL; ?>login">Se connecter</a> </li> <?php else : ?> <li class="nav-item"> <a class="nav-link" aria-current="page" href="<?= URL; ?>compte/profil">Profil</a> </li> <?php endif; ?>
- Dans le fichier de routage « index.php », on va simplement appeler la fonction profil qui sera créer dans contrôleur des utilisateurs. Attention cependant, « profil » sera appelé à partir de « compte » (…/compte/profil). L’accès dans la hiérarchie est donc sensiblement différente.
case "compte" : switch($url[1]){ case "profil": $utilisateurController->profil(); break; } break; default : throw new Exception("La page n'existe pas");
- Dans le fichier « Utilisateur.model.php » / classe « UtilisateurManager » on créé une fonction getUserInformation ($login) qui renvoie un tableau ($datas) contenant les données du profil de l’utilisateur ($login) à partir d’une requêtre SQL.
public function getUserInformation($login){ $req = "SELECT * FROM utilisateur WHERE login = :login"; $stmt = $this->getBdd()->prepare($req); $stmt->bindValue(":login", $login, PDO::PARAM_STR); $stmt->execute(); $datas = $stmt->fetch(PDO::FETCH_ASSOC); $stmt->closeCursor(); return $datas; }
- Dans le contrôleur « Utilisateur.controller.php » / classe « UtilisateurController » on créer une fonction profil() qui va récupérer les informations d’un utilisateur (tableau $datas) auprès du modèle (fonction getUserInformation), préparer la structure de données afin que la vue (profil.view.php) puisse afficher les données de profil.
public function profil(){ $datas = $this->utilisateurManager->getUserInformation($_SESSION['profil']['login']); $_SESSION['profil']["mail"] = $datas['mail']; $_SESSION['profil']["role"] = $datas['role']; $data_page = [ "page_description" => "Page de profil", "page_title" => "Page de profil", "view" => "views/Utilisateur/profil.view.php", "template" => "views/common/template.php" ]; $this->genererPage($data_page); }
- La vue sera simple dans un premier temps et affichera seulement le nom et l’adresse mail de l’utilisateur connecté.
<h1>Profil de <b><?= $_SESSION['profil']['login'] ?></b></h1> <div id="mail"> Mail : <?= $_SESSION['profil']['mail'] ?> </div>
Résultat :

8. Deconnexion
Maintenant que l’utilisateur est connecté, on veut accéder à un lien réalisant la déconnexion.
- On ajoutera un item « Se déconnecter » dans le menu. Cet item se affiché seulement en cas de connexion établie.
<?php if(!Securite::isConnected()) : ?> <li class="nav-item"> <a class="nav-link" aria-current="page" href="<?= URL; ?>login">Se connecter</a> </li> <?php else : ?> <li class="nav-item"> <a class="nav-link" aria-current="page" href="<?= URL; ?>compte/profil">Profil</a> </li> <li class="nav-item"> <a class="nav-link" aria-current="page" href="<?= URL; ?>compte/deconnexion">Se déconnecter</a> </li> <?php endif; ?>
case "compte" : switch($url[1]){ case "profil": $utilisateurController->profil(); break; case "deconnexion": $utilisateurController->deconnexion(); break; } break; default : throw new Exception("La page n'existe pas");
public function deconnexion(){ Toolbox::ajouterMessageAlerte("La déconnexion est effectuée", Toolbox::COULEUR_VERTE); unset($_SESSION['profil']); header("Location: ".URL."accueil"); }
Résultat :

9. Sécurisation de la partie « utilisateur »
Maintenant que l’utilisateur est connecté, on veut accéder à un lien réalisant la déconnexion.
- Pour accéder aux parties de site nécessitant un utilisateur connecté, on va faire la vérification au niveau du routeur.
- Pour savoir si un utilisateur est connecté, il suffit de regarder si $SESSION[‘profil’] n’est pas vide.
- Avec le protocole HTTPS, il est difficile de cracker une session. Cependant, on doit actualiser ses connaissance en matière de sécurité.
Remarque : L’avantage d’écrire le code de vérification de la connexion au niveau du routeur est d’éviter de le dupliquer dans chacune des fonctions du contrôleur.
public static function isConnected(){ return (!empty($_SESSION['profil'])); }
- C’est au niveau de « compte » qu’il est pertinent de mettre en place le test de connexion puisque l’accès à ce niveau de devrait être possible que lorsqu’il y a une authentification préalable.
Si l’utilisateur tente d’accéder à n’importe quelle page de compte restreinte à l’authentification préalable, on affiche le message « Veuillez vous connecter ! » et on rediriger la page vers celle de « login ».
case "compte" : if(!Securite::isConnected()){ Toolbox::ajouterMessageAlerte("Veuillez vous connecter !", Toolbox::COULEUR_ROUGE); header('Location: '.URL."login"); } else { switch($url[1]){ case "profil": $utilisateurController->profil(); break; case "deconnexion": $utilisateurController->deconnexion(); break; } } break; default : throw new Exception("La page n'existe pas");
10. Création de compte
On va construire le formulaire qui va permettre au visiteur de se créer un compte.
- On ajoutera un item « Créer un compte » dans le menu. Cet item se affiché seulement en l’absence de connexion établie.
<?php if(!Securite::isConnected()) : ?> <li class="nav-item"> <a class="nav-link" aria-current="page" href="<?= URL; ?>login">Se connecter</a> </li> <li class="nav-item"> <a class="nav-link" aria-current="page" href="<?= URL; ?>creerCompte">Créer un compte</a> </li>
- On ajoute une nouvelle route dans le routeur (creerCompte) qui appellera à partir du « visiteurController » la fonction « creerCompte() ».
case "creerCompte" : $visiteurController->creerCompte(); break;
- La fonction « creerCompte » permettra simplement d’envoyer les différents éléments à la vue qui se chargera d’afficher le formulaire de création de compte.
public function creerCompte(){ $data_page = [ "page_description" => "Création d'un compte", "page_title" => "Création d'un compte", "view" => "views/Visiteur/creerCompte.view.php", "template" => "views/common/template.php" ]; $this->genererPage($data_page); }
- La vue est un formulaire bootstrap classique.
<h1>Créer un compte !</h1> <form method="POST" action="validation_creerCompte"> <div class="mb-3"> <label for="login" class="form-label">Login</label> <input type="text" class="form-control" id='login' name="login" placeholder="Entrer un nom de login"> </div> <div class="mb-3"> <label for="mail" class="form-label">mail</label> <input type="text" class="form-control" id='mail' name="mail" placeholder="Entrer une adresse email valide"> </div> <div class="mb-3"> <label for="password" class="form-label">Password</label> <input type="password" class="form-control" id="password" name="password"> </div> <div class="text-muted">Votre compte ne sera valide qu'après la vérification par mail effectuée.</div> <button type="submit" class="btn btn-primary">Créer !</button> </form>
- Avant de terminer on ajoute dans le routeur la captation de l’envoi du formulaire spécifié par la chaine « validation_creerCompte ».
case "creerCompte" : $visiteurController->creerCompte(); break; case "validation_creerCompte" : echo "test"; break;
Résultat :

11. Validation de création de compte
On va programmer la fonction de validation des informations de création de compte renvoyées par le formulaire. Ces informations vont permettre de créer un compte dans la base de données. Pour cela, on ajoutera dans le contrôleur utilisateur la fonction « validation_creerCompte » en lui passant en paramètre les informations de nom de login, mot de passe et adresse mail.
- Avant de déclencher l’envoie des informations de création de compte au contrôleur, on vérifie que les champs login, password et mail sont renseignés.
case "validation_creerCompte" : if(!empty ($_POST['login']) && !empty($_POST['password']) && !empty($_POST['mail'])){ $login = Securite::secureHTML($_POST['login']); $mail = Securite::secureHTML($_POST['mail']); $password = Securite::secureHTML($_POST['password']); $utilisateurController->validation_creerCompte($login, $password, $mail); } else { Toolbox::ajouterMessageAlerte("Les trois informations sont obligatoires !", Toolbox::COULEUR_ROUGE); header('Location: '.URL."creerCompte"); }; break;
- Dans le modèle on créé deux fonctions qui seront appelées par le contrôleur :
- La fonction « verifLoginDisponible » test si le nom de login est déjà utilisé et stocké dans la base de données.
- La fonction « bdCreerCompte » ajoute un nouveau compte dans la base de données.
public function verifLoginDisponible($login){ $utilisateur = $this->getUserInformation($login); return empty($utilisateur); } public function bdCreerCompte($login, $passwordCrypte, $mail, $clef){ $req= "INSERT INTO utilisateur (login, password, mail, est_valide, role, clef) VALUES (:login, :password, :mail, 0, :clef)"; $stmt = $this->getBdd()->prepare($req); $stmt->bindValue(":login", $login, PDO::PARAM_STR); $stmt->bindValue(":password", $passwordCrypte, PDO::PARAM_STR); $stmt->bindValue(":mail", $mail, PDO::PARAM_STR); $stmt->bindValue(":clef", $clef, PDO::PARAM_INT); $stmt->execute(); $estAjouter = ($stmt->rowCount() > 0); $stmt->closeCursor(); return $estAjouter; }
- Le contrôleur vérifie que le login est disponible. Si ce n’est pas le cas, on affiche le message « Le login est déjà utilisé » et on redirige la demande vers le formulaire de création de compte.
Sinon, passe à la création du compte. Pour cela il faudra générer un password crypté à partir du mot de passe utilisateur envoyé dans le formulaire. On va ensuite générer une clé (nombre aléatoire) qui sera utilisé dans le processus de validation par mail.
public function validation_creerCompte($login, $password, $mail){ if($this->utilisateurManager->verifLoginDisponible($login)){ $passwordCrypte = password_hash($password, PASSWORD_DEFAULT); $clef = rand(0,9999); if($this->utilisateurManager->bdCreerCompte($login, $passwordCrypte, $mail, $clef)){ //$this->sendMailValidation($login, $mail, $clef); Toolbox::ajouterMessageAlerte("Le compte a été créé, validez le mail envoyé pour valider le compte !", Toolbox::COULEUR_VERTE); header("Location: ".URL."login"); } else { Toolbox::ajouterMessageAlerte("Erreur lors de la création du compte, veuillez recommencer !", Toolbox::COULEUR_ROUGE); header("Location: ".URL."creerCompte"); } } else { Toolbox::ajouterMessageAlerte("Le login ".$login." est déjà utilisé", Toolbox::COULEUR_ROUGE); header("Location: ".URL."creerCompte"); } }
12. Envoi du mail de validation
Si l’on se réfère à l’étape précédente, lors de la validation du formulaire de création de compte, on veut envoyer un mail de validation à l’adresse indiqué par l’utilisateur.
- On va créer une fonction dans la classe « Toolbox » qui enverra un mail à un destinataire
public static function sendMail($destinataire, $sujet, $message){ $headers = "From: nom.user@gmail.com"; if(mail($destinataire, $sujet, $message, $headers)){ self::ajouterMessageAlerte("Mail envoyé", self::COULEUR_VERTE); } else { self::ajouterMessageAlerte("Mail non envoyé", self::COULEUR_ROUGE); } }
- Dans le contrôleur utilisateur (Utilisateur.Controller.php), on écrit maintenant la fonction qui va composer le contenu du message de validation.
La composition de l’adresse de vérification ($urlVerification) correspond à l’adresse du site à laquelle on ajoute le nom de routage « validation_mailCompte/ » concaténé avec le nom de login / le numéro de la clé (choisie aléatoirement).
private function sendMailValidation($login, $mail, $clef){ $urlVerification = URL."validation_mailCompte/".$login."/".$clef; $sujet = "Création du compte sur le site xxx"; $message = "Pour valider votre compte veuillez cliquer sur le lien suivant ".$urlVerification; Toolbox::sendMail($mail, $sujet, $message); }
- Toujours dans le contrôleur utilisateur, fonction « validation_creerCompte », si la sauvegarde du nouveau compte dans la base de données c’est bien passé, on lance l’envoi du mail en appelant la fonction sendMailValidation (ligne 6)
public function validation_creerCompte($login, $password, $mail){ if($this->utilisateurManager->verifLoginDisponible($login)){ $passwordCrypte = password_hash($password, PASSWORD_DEFAULT); $clef = rand(0,9999); if($this->utilisateurManager->bdCreerCompte($login, $passwordCrypte, $mail, $clef)){ $this->sendMailValidation($login, $mail, $clef); Toolbox::ajouterMessageAlerte("Le compte a été créé, validez le mail envoyé pour valider le compte !", Toolbox::COULEUR_VERTE); header("Location: ".URL."login"); ...
Après la création de compte, l’utilisateur ne peut pas encore se connecter à son compte car il n’est pas encore activé. Pour l’activer, l’utilisateur doit répondre au mail envoyé en cliquant sur le lien de confirmation de compte.
Si l’utilisateur tente de se connecter sans avoir activé son compte, on va faire en sorte de lui rappeler d’activer son compte par un message d’erreur et en lui permettant de commander le renvoi du mail de confirmation de compte.
- Pour faire cela, dans le fichier « Utilisateur.controller.php », on va modifier la fonction « validation_login ». On ajoute la ligne 19 on l’on créé d’un lien (nouvelle route : « renvoyerMailValidation/[nom_login] ») permettant à l’utilisateur de commander le renvoi du mail de validation.
public function validationLogin($login, $password){ if($this->utilisateurManager->isCombinaisonValide($login, $password)){ if($this->utilisateurManager->isCompteActive($login)){ Toolbox::ajouterMessageAlerte("Bon retour sur le site ".$login. " !", Toolbox::COULEUR_VERTE); // Ssauvegarde du nom de login dans une variable de session $_SESSION['profil'] = [ "login" => $login ]; // Redirection vers la page de profil utilisateur header("Location: ".URL."compte/profil"); }else{ $msg = "Le compte ".$login." n'a pas été activé par mail. "; // Création d'un lien permettant à l'utilisateur de commander le renvoi du mail de validation // On créé la nouvelle route "renvoyerMailValidation/" en concaténant le nom de login. $msg .= "<a href='renvoyerMailValidation/".$login."'>Renvoyer le mail de validation</a>"; Toolbox::ajouterMessageAlerte($msg, Toolbox::COULEUR_ROUGE); header("Location: ".URL."login"); } } else { Toolbox::ajouterMessageAlerte("Combinaison Login / Mot de passe non valide", Toolbox::COULEUR_ROUGE); header("Location: ".URL."login"); }; }
- Toujours dans la classe « UtilisateurController », par anticipation, on ajoute la fonction « renvoyerMailValidation » qui va, après avoir récupérer dans la base de données les informations « mail » et « clef » (à partir du « login »), renvoyer le mail de validation de compte.
public function renvoyerMailValidation($login){ // On récupère les informations de l'utilisateur dans la BD... $user = $this->utilisateurManager->getUserInformation($login); // afin de récupérer les informations "mail" et "clé" nécessaire au renvoi du mail de validation $this->sendMailValidation($login, $user['mail'], $user['clef']); header('Location: '.URL."login"); }
- Dans le routeur « index.php », on gère la nouvelle route « renvoyerMailValidation » en appelant la fonction « renvoyerMailValidation » de la classe « UtilisateurController » créé précédemment (ligne 6) :
case "creerCompte" : $visiteurController->creerCompte(); break; case "validation_creerCompte" : ... break; case "renvoyerMailValidation" : $utilisateurController->renvoyerMailValidation($url[1]); break; case "compte" :
Sélection du menu « Créer un compte » : On rempli le formulaire…

et on clic sur le bouton « Créer »

Dans le même temps on reçoit le mail de validation de compte :

Et si on tente de se connecter sans avoir activé son compte :

13. Validation du compte par mail
Lorsque l’utilisateur sélectionne le lien de validation de compte à l’intérieur du mail reçus, le routeur reçoit le message sur la route « validationMail ». Il doit alors déclencher la finalisation du compte en l’activant. Pour activer le compte, il faut affecter la valeur 1 au champ « est_valide » de la table « utilisateur » de l’utilisateur (mail) dans la base de donnée.
Pour cela, le routeur devra appeler la méthode « validation_mailCompte » de la classe « UtilisateurController » qui demandera au modèle (classe « UtilisateurManager ») de modifier la base de données (méthode « bdValidationMail »).
case "validation_mailCompte" : $utilisateurController->validation_mailCompte($url[1], $url[2]); break;
public function validation_mailCompte($login, $clef){ if($this->utilisateurManager->bdValidationMail($login, $clef)){ Toolbox::ajouterMessageAlerte("Le compte a été activé !",Toolbox::COULEUR_VERTE); header("Location: ".URL."login"); } else { Toolbox::ajouterMessageAlerte("Le compte n'a pas pu être validé !",Toolbox::COULEUR_ROUGE); header("Location: ".URL."creerCompte"); } }
public function bdValidationMail($login, $clef){ // Test sur la valeur de la clé et affectation du flag est_valide dans la foulée (si test positif) $req= 'UPDATE utilisateur set est_valide = 1 WHERE login = :login and clef = :clef'; $stmt = $this->getBdd()->prepare($req); $stmt->bindValue(":login", $login, PDO::PARAM_STR); $stmt->bindValue(":clef", $clef, PDO::PARAM_INT); $stmt->execute(); $estModifier = ($stmt->rowCount() > 0); $stmt->closeCursor(); return $estModifier; }
14. Modification du mail
On va ajouter un bouton a côté du mail qui est affiché sur la page de profil, et lors du clic sur ce bouton, on affichera a la place du mail un champ pour le modifier ainsi qu’un bouton de validation (on utilisera du code JavaScript client).
public function profil(){ $datas = $this->utilisateurManager->getUserInformation($_SESSION['profil']['login']); $_SESSION['profil']["role"] = $datas['role']; //print_r($datas); $data_page = [ "page_description" => "Page de profil", "page_title" => "Page de profil", "utilisateur" => $datas, // page_javascript -> fonctionne avec le script à la fin de views/common/template.php // 'profil.js' se trouve dans public/javascript/ "page_javascript" => ['profil.js'], "view" => "views/Utilisateur/profil.view.php", "template" => "views/common/template.php" ]; $this->genererPage($data_page); }
<h1>Profil de <?= $utilisateur['login'] ?></h1> <div id="mail"> Mail : <?= $utilisateur['mail'] ?> <button class="btn btn-primary" id="btnModifMail"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil" viewBox="0 0 16 16"> <path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/> </svg> </button> </div> <div id="modificationMail" class="d-none"> <form method="POST" action="<?= URL; ?>compte/validation_modificationMail"> <div class="row"> <label for="mail" class="col-2 col-form-label">Mail :</label> <div class="col-8"> <input type="mail" class="form-control" name="mail" value="<?= $utilisateur['mail'] ?>" /> </div> <div class="col-2"> <button class="btn btn-success" id="btnValidModifMail" type="submit"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check" viewBox="0 0 16 16"> <path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.267.267 0 0 1 .02-.022z"/> </svg> </button> </div> </div> </form> </div>
// Récupère un pointeur sur le bouton de demande de modif : id = btnModifMail let btnModifMail = document.querySelector("#btnModifMail"); // Pointeur sur le bouton de validation de modif : id = btnValidModifMail let btnValidModifMail = document.querySelector("#btnValidModifMail"); // Pointeur sur la <div> supérieure : <div id="mail"> let divMail = document.querySelector("#mail"); // Pointeur sur la <div> inférieure : <div id="modificationMail"> let divModificationMail = document.querySelector("#modificationMail"); // Si on clique sur le bounton de demande de modif : // - on masque masque la div supérieure // - on affiche la div inférieure btnModifMail.addEventListener("click", function(){ divMail.classList.add("d-none"); divModificationMail.classList.remove("d-none"); })
Résultats :

15. Validation de la modification du mail
On va maintenant gérer la soumission de la modification de l’adresse mail initié dans l’étape précédente.
La méthode est la suivante :
- Routeur : Prise en compte de la route « validation_modificationMail » :
- Attention : La route s’effectue à partie de « /compte/validation_modificationMail »),
- Appel de la méthode « validation_modificationMail » de la classe « UtilisateurController »,
- Remarque : On n’a pas besoin de transmettre à la méthode « validation_modificationMail » la valeur du nom de login ($login) car elle est déjà présente dans la variable de session.
- Contrôleur : Appel de la méthode « bdModificationMailUser » de la classe « UtilisateurManager » pour appliquer la modification de l’adresse mail dans la base de données.
- Modèle : Modification de l’adresse mail dans la base de données.
switch($url[1]){ case "profil": $utilisateurController->profil(); break; case "deconnexion": $utilisateurController->deconnexion(); break; case "validation_modificationMail": // Remarque : On n'a pas besoin de transmettre à la méthode "validation_modificationMail" // la valeur du nom de login ($login) car elle est déjà présente dans la variable de session. $utilisateurController->validation_modificationMail(Securite::secureHTML($_POST['mail'])); break; }
public function validation_modificationMail($mail){ if($this->utilisateurManager->bdModificationMailUser($_SESSION['profil']['login'], $mail)){ Toolbox::ajouterMessageAlerte("La modification a été effectuée.",Toolbox::COULEUR_VERTE); } else { Toolbox::ajouterMessageAlerte("Aucune modification effectuée !",Toolbox::COULEUR_ROUGE); } header("Location: ".URL."compte/profil"); }
public function bdModificationMailUser($login, $mail){ $req= 'UPDATE utilisateur set mail = :mail WHERE login = :login'; $stmt = $this->getBdd()->prepare($req); $stmt->bindValue(":login", $login, PDO::PARAM_STR); $stmt->bindValue(":mail", $mail, PDO::PARAM_STR); $stmt->execute(); $estModifier = ($stmt->rowCount() > 0); $stmt->closeCursor(); return $estModifier; }
Résultats :

Remarque : Si l’on demande la modification de l’adresse mail et que l’on valide la modification sans avoir apporté de modification à son encontre, le message « Aucune modification effectuée ! » s’affiche. Ceci est normal : il n’était pas nécessaire de modifier la base de données car aucune modification n’était à apporter.

16. Modification du mot de passe
On va ajouter dans la vue du profil utilisateur un bouton pour demander la modification du mot de passe.
La méthode est la suivante :
- Ajouter le bouton dans la vue du profil (profil.view.php)
- Routeur : Prise en compte de la route « compte/modificationPassword » et appel de la méthode « modificationPassword » du contrôleur (classe « UtilisateurController »).
- Contrôleur : Initier la création d’une nouvelle vue (« view/utilisateur/modificationPassword.view.php ») en initialisant le tableau « $data_page », puis appel de la méthode « genere_page ($data_page ») ».
- création de la vue « view/utilisateur/modificationPassword.view.php ».
<div> <a href="<?=URL ?>compte/modificationPassword" class="btn btn-warning">Changer le mot de passe</a> </div>
switch($url[1]){ case "profil": $utilisateurController->profil(); break; case "deconnexion": $utilisateurController->deconnexion(); break; case "validation_modificationMail": // Remarque : On n'a pas besoin de transmettre à la méthode "validation_modificationMail" // la valeur du nom de login ($login) car elle est déjà présente dans la variable de session. $utilisateurController->validation_modificationMail(Securite::secureHTML($_POST['mail'])); break; case "modificationPassword" : $utilisateurController->modificationPassword(); break; }
public function modificationPassword(){ $data_page = [ "page_description" => "Modification du mot de passe", "page_title" => "Modification du mot de passe", "view" => "views/Utilisateur/modificationPassword.view.php", "template" => "views/common/template.php" ]; $this->genererPage($data_page); }
<h1><?= $_SESSION['profil']['login'] ?> - Modification du mot de passe </h1> <form method="POST" action="<?= URL ?>compte/validation_modificationPassword"> <div class="mb-3"> <label for="ancienPassword" class="form-label">Ancien mot de passe</label> <input type="text" class="form-control" name="ancienPassword" id="ancienPassword"> </div> <div class="mb-3"> <label for="nouveauPassword" class="form-label">Nouveau mot de passe</label> <input type="password" class="form-control" name="nouveauPassword"> </div> <div class="mb-3"> <label for="confirmNouveauPassword" class="form-label">Confirmer le mot de passe</label> <input type="password" class="form-control" name="confirmNouveauPassword"> </div> <button type="submit" class="btn btn-primary" id="btnValidation">Modifier</button> </form>
Résultats :

17. Ajout d’un code de vérification en JavaScript
On va ajouter du code de vérification en JavaScript dans la vue « modificationPassword.view.php » pour contrôler que le nouveau mot de passe qui aura été saisi et sa confirmation sont bien identiques. Si se n’est pas le cas, le bouton « Valider » sera inactif (couleur grisée) et un message sera affiché pour en expliquer la cause.
La méthode est la suivante :
- Contrôleur : Pour ajouter du code JavaScript dans la vue « modificationPassword.view.php » on modifie la méthode « modificationPassword » en ajoutant le fichier JavaScript dans la structure du tableau « $data_page »
- Vue « modificationPassword.view.php » : On modifie le code html pour :
- Inactivé par défaut le bouton de validation (id : « btnValidation »),
- Le champ « Nouveau mot de passe » aura d’id : « nouveauPassword,
- Le champ « Confirmer le mot de passe » aura d’id : « confirmNouveauPassword,
- Ajouter un message qui s’affichera en cas d’erreur (inactivé par défaut). On ajoutera une balise <div> avec pour id : « erreur »
- On créer le fichier « public/Javascript/modificationPassword.js » et construire le code permettant de vérifier l’équivalence du nouveau mot de passe avec sa confirmation. En cas de différence on :
- Inactivera le bouton de validation et,
- affichera un message d’erreur expliquafif.
public function modificationPassword(){ $data_page = [ "page_description" => "Modification du mot de passe", "page_title" => "Modification du mot de passe", "page_javascript" => ['modificationPassword.js'], "view" => "views/Utilisateur/modificationPassword.view.php", "template" => "views/common/template.php" ]; $this->genererPage($data_page); }
<h1><?= $_SESSION['profil']['login'] ?> - Modification du mot de passe </h1> <form method="POST" action="<?= URL ?>compte/validation_modificationPassword"> <div class="mb-3"> <label for="ancienPassword" class="form-label">Ancien mot de passe</label> <input type="text" class="form-control" name="ancienPassword" id="ancienPassword"> </div> <div class="mb-3"> <label for="nouveauPassword" class="form-label">Nouveau mot de passe</label> <input type="password" class="form-control" name="nouveauPassword" id="nouveauPassword"> </div> <div class="mb-3"> <label for="confirmNouveauPassword" class="form-label">Confirmer le mot de passe</label> <input type="password" class="form-control" id="confirmNouveauPassword" name="confirmNouveauPassword"> </div> <div id="erreur" class="d-none alert alert-danger">Les mots de passe ne correspondent pas !</div> <button type="submit" class="btn btn-primary" id="btnValidation" disabled>Modifier</button> </form>
const nouveauPassword = document.querySelector("#nouveauPassword"); const confirmNouveauPassword = document.querySelector("#confirmNouveauPassword"); // avec l'option "change" : l'évènement se déclenche quand le contrôle perd le focus nouveauPassword.addEventListener("keyup",function(){ //console.log("Modification du nouveau mot de passe"); verificationPassword(); }) confirmNouveauPassword.addEventListener("keyup",function(){ //console.log("Confirmation modification du nouveau mot de passe"); verificationPassword(); }) function verificationPassword(){ if(nouveauPassword.value === confirmNouveauPassword.value){ document.querySelector("#btnValidation").disabled = false; document.querySelector("#erreur").classList.add("d-none"); } else { document.querySelector("#btnValidation").disabled = true; document.querySelector("#erreur").classList.remove("d-none"); } }
Résultats :

18. Validation du changement de mot de passe
On va maintenant gérer la soumission de la modification du mot de passe initiée dans l’étape 16.
La méthode est la suivante :
- Routeur : Prise en compte de la route « /compte/validation_modificationPassword » :
- Si une des chaine de caractères « ancienPassword », « nouveauPassword » et « confirmNouveauPassword » est vide, on déroute vers une erreur.
- Appel de la méthode « validation_modificationPassword » de la classe « UtilisateurController »,
- Remarque : On n’a pas besoin de transmettre à la méthode « validation_modificationPassword » la valeur du nom de login ($login) car elle est déjà présente dans la variable de session.
- Contrôleur : Appel de la méthode « bdModificationMailPassword » de la classe « UtilisateurManager » pour appliquer la modification du mot de passe dans la base de données.
Si la valeur « ancienPassword » est différente de celle du mot de passe (décrypté) actuel dans la base de données, on déroute vers une erreur. - Modèle : Modification du mot de passe dans la base de données.
case "validation_modificationPassword" : $ancienPassword = Securite::secureHTML($_POST['ancienPassword']); $nouveauPassword = Securite::secureHTML($_POST['nouveauPassword']); $confirmNouveauPassword = Securite::secureHTML($_POST['confirmNouveauPassword']); if(!empty($ancienPassword) && !empty($nouveauPassword) && !empty($confirmNouveauPassword)){ $utilisateurController->validation_modificationPassword($ancienPassword, $nouveauPassword, $confirmNouveauPassword); } else { Toolbox::ajouterMessageAlerte("Vous n'avez pas renseigné toutes les informations", Toolbox::COULEUR_ROUGE); header('Location: '.URL."compte/modificationPassword"); } break;
public function validation_modificationPassword($ancienPassword, $nouveauPassword, $confirmNouveauPassword){ if($nouveauPassword === $confirmNouveauPassword){ if($this->utilisateurManager->isCombinaisonValide($_SESSION['profil']['login'], $ancienPassword)){ $passwordCrypte = password_hash($nouveauPassword, PASSWORD_DEFAULT); if($this->utilisateurManager->bdModificationPassword($_SESSION['profil']['login'], $passwordCrypte)){ Toolbox::ajouterMessageAlerte("Le mot de passe a été modifié avec success.",Toolbox::COULEUR_VERTE); header("Location: ".URL."compte/profil"); } else { Toolbox::ajouterMessageAlerte("La modification du mot de passe a échouée !",Toolbox::COULEUR_ROUGE); header("Location: ".URL."compte/modificationPassword"); } } else { Toolbox::ajouterMessageAlerte("Le nom du mot de passe à modifier est erroné !",Toolbox::COULEUR_ROUGE); header("Location: ".URL."compte/modificationPassword"); } } else { Toolbox::ajouterMessageAlerte("Le nouveau mot de passe ne correspond pas à celui de sa confirmation !", Toolbox::COULEUR_ROUGE); header('Location: '.URL."compte/modificationPassword"); } }
public function bdModificationPassword($login, $password){ $req= 'UPDATE utilisateur set password = :password WHERE login = :login'; $stmt = $this->getBdd()->prepare($req); $stmt->bindValue(":login", $login, PDO::PARAM_STR); $stmt->bindValue(":password", $password, PDO::PARAM_STR); $stmt->execute(); $estModifier = ($stmt->rowCount() > 0); $stmt->closeCursor(); return $estModifier; }
Résultats :


19. Suppression de compte
On va maintenant gérer la suppression totale d’un compte. Le bouton « Suppression du compte » apparait dans le profil du compte de l’utilisateur authentifié. Ainsi, l’utilisateur peut uniquement supprimer son propre compte.
Par mesure de précaussion, la demande de suppression affiche un message de confirmation possèdant un second bouton qu’il faudra sélectionner pour que la suppression se fasse.
La méthode est la suivante :
- Vue : Modification de la vue de profil dans le fichier « profil.view.php » :
- Affichage du bouton « Suppression du compte » (id : « btnSupCompte ») à la droite du bouton « Changer le mot de passe ». La sélection du bouton entraine l’affichage du message inclut dans la balise <div> décrite ci-dessous.
- Création d’une balise <div> dans laquelle sont affichés le message d’alerte demandant la confirmation de suppression, ainsi que le bouton « Je souhaite supprimer mon compte » qui déclenchera le routage vers le chemin « compte/suppressionCompte » (accessible dans le fichier index.php).
- JavaScript : Modification du fichier « profil.js » pour l’ajout de la prise en compte de l’affichage du message et du bouton de confirmation de suppression incluts dans la balise <div> (suppression de l’attribut « d-none ».
<br /> <div> <a href="<?=URL ?>compte/modificationPassword" class="btn btn-warning">Changer le mot de passe</a> <button id="btnSuppressionCompte" class="btn btn-danger">Supprimer son compte</button> </div> <div id="suppressionCompte" class="d-none m-2"> <div class="alert alert-danger"> Veuillez confirmer la suppression du compte. Cela aura pour effet de supprimer le compte définitivement ! <br /> <a href="<?=URL; ?>compte/suppressionCompte" class="btn btn-danger">Je souhaite supprimer mon compte</a> </div> </div>
... btnSuppressionCompte.addEventListener("click", function() { document.querySelector("#suppressionCompte").classList.remove("d-none"); })
Résultats :

20. Validation de la suppression de compte
On va maintenant gérer la soumission de la suppression du compte initiée dans l’étape précédente.
La méthode est la suivante :
- Routeur : Prise en compte de la route « /compte/suppressionCompte » : Appel de la méthode « suppressionCompte » de la classe « UtilisateurController ».
- Contrôleur : Appel de la méthode « bdSuppressionCompte » de la classe « UtilisateurManager » pour appliquer la suppression du compte dans la base de données.
On appèle la fonction « deconnection() » afin de supprimer la variable de session de profil et rediriger l’utilisateur vers la page d’accueil. - Modèle : Suppression du compte dans la base de données.
case "suppressionCompte" : $utilisateurController->suppressionCompte(); break;
public function suppressionCompte(){ if($this->utilisateurManager->bdSuppressionCompte($_SESSION['profil']['login'])){ Toolbox::ajouterMessageAlerte("La suppression de votre compte a été effectuée.",Toolbox::COULEUR_VERTE); $this->deconnexion(); } else { Toolbox::ajouterMessageAlerte("La suppression n'a pas fonctionnée, contactez l'administrateur !", Toolbox::COULEUR_ROUGE); header('Location: '.URL."compte/profil"); } }
public function bdSuppressionCompte($login){ $req= 'DELETE FROM utilisateur WHERE login = :login'; $stmt = $this->getBdd()->prepare($req); $stmt->bindValue(":login", $login, PDO::PARAM_STR); $stmt->execute(); $estSupprimer = ($stmt->rowCount() > 0); $stmt->closeCursor(); return $estSupprimer; }
Résultats :

21. Ajout d’une image de profil
On va maintenant gérer l’ajout d’une photo de profil sur le compte des utilisateurs. Lors de la création du compte, une photo de profil de base est attribuée aux utilisateurs. Ils auront la possibilité ensuite de modifier ensuite leur photo de profil. Dans cette étape, nous allons créer l’ajout d’une photo de base lors de la création d’un compte pour un nouvel utilisateur.
La méthode est la suivante :
- Dans le dossier « public/Assets/image/ », on créé un sous-dossier « profils/ » et on copie le fichier de l’image de base « profil.png ».
- Contrôleur – méthode « validationCreerCompte » : On passe les informations (chemin + nom) de l’image de base à la méthode « bdCreerCompte »
- Modèle- méthode bdCreerCompte : On gère l’ajout de l’image dans la base de données.
- Contrôleur – méthode « profil » : Suppression du stockage de l’adresse mail de l’utilisateur car il n’y a pas de raison de conserver le mail dans une session.
- Contrôleur – vue « profil.view.php » : On ajoutera un formulaire destiné à choisir un fichier image de profil utilisateur à transférer dans le dossier « public/Assets/image/profils/[$login]/ ».
public function validation_creerCompte($login, $password, $mail){ if($this->utilisateurManager->verifLoginDisponible($login)){ $passwordCrypte = password_hash($password, PASSWORD_DEFAULT); $clef = rand(0,9999); if($this->utilisateurManager->bdCreerCompte($login, $passwordCrypte, $mail, $clef, "profils/profil.png")){ $this->sendMailValidation($login, $mail, $clef); ...
public function bdCreerCompte($login, $passwordCrypte, $mail, $clef, $image){ $req= "INSERT INTO utilisateur (login, password, mail, est_valide, role, clef, image) VALUES (:login, :password, :mail, 0, 'utilisateur', :clef, :image)"; $stmt = $this->getBdd()->prepare($req); $stmt->bindValue(":login", $login, PDO::PARAM_STR); $stmt->bindValue(":password", $passwordCrypte, PDO::PARAM_STR); $stmt->bindValue(":mail", $mail, PDO::PARAM_STR); $stmt->bindValue(":clef", $clef, PDO::PARAM_INT); $stmt->bindValue(":image", $image, PDO::PARAM_STR); $stmt->execute(); // Si l'ajout n'a pas fonctionné, alors on reverra estAjouter=rowCount=0=flase $estAjouter = ($stmt->rowCount() > 0); $stmt->closeCursor(); return $estAjouter; }
<div> <div> <img src="<?= URL; ?>public/Assets/images/<?= $utilisateur['image'] ?>" width="100px" alt="photo de profil" /> </div> <form method="POST" action="<?= URL; ?>compte/validation_ModificationImage" enctype="multipart/form-data"> <label for="image">Changer l'image de profil</label><br /> <input type="file" class="form-control-file" id="image" name="image" onchange="submit();" /> </form> </div> <br />
Résultats :

22. Modification de l’image de profil
On va mettre en oeuvre la modification l’image de profil de l’utilisateur à par du bouton « Parcourir » avec lequel l’utilisateur pourra sélectionner sa propre image de profil. La taille de l’image de profil par défaut est de 534*720 pixels.
La méthode est la suivante :
- Dans router : On ajoute la route « validation_modificationImage ». Si l’image sélectionnée n’est pas vide, on appèle la méthode « validation_modificationImage » du contrôleur.
- Contrôleur – méthode « validation_modificationImage » :
- On créé le sous-dossier portant le nom du « $login » à partir du dossier « /public/Assets/image/profils/ », puis on copie l’image sélectionnée par l’utilisateur à l’intérieur.
- Pour cela, on fera appel à la méthode « ajoutImage » de la classe « Toolbox ».
- Avant de mettre à jour la base de données avec le nom de l’image de profil sélectionné, on interroge la base de données (méthode « getImageUtilisateur » du modèle) pour savoir si l’utilisateur avait déjà choisi une image (autre que celle par défaut). Si c’est le cas, on supprime l’ancienne image du dossier /profils/[$login]/.
- Ensuite, on appèle le méthode « bdAjoutImage » de la classe modèle « UtilisateurManager » pour enregistrer le nom de l’image présente dans le dossier « /profils/[$login]/ ».
- Enfin, on redirige le lien vers le profil utilisateur : route « compte/profil ».
- Classe « Toolbox »- méthode ajoutImage : On gère la copie d’un fichier dans un dossier dont le chemin est passé en paramètre.
- Modèle – méthode « bdAjoutImage » : On met à jour le champ « image » dans la base de donnée pour l’utilisateur connecté.
- Modèle – méthode « getImageUtilisateur » : Requête sur la table « utilisateur » pour obtenir la valeur du champ « image » de l’utilisateur connecté.
case "validation_ModificationImage" : //si l'on veut visualiser le contenu du tableau "$_FILES['image']" : print_r($_FILES['image']); // Si une image a été posté alors sa taille doit être > à 0 if($_FILES['image']['size'] > 0) { $utilisateurController->validation_modificationImage($_FILES['image']); } else { Toolbox::ajouterMessageAlerte("Vous n'avez pas modifié l'image", Toolbox::COULEUR_ROUGE); header("Location: ".URL."compte/profil"); } break;
public function validation_modificationImage($file){ try{ // Chaque utilisateur va avoir un répertoire personnel (nom login) à partir du dossier "profil" $repertoire = "public/Assets/images/profils/".$_SESSION['profil']['login']."/"; //ajout de l'image récupérée précédemment (profil.view.php) dans le rep perso de l'utilisateur $nomImage = Toolbox::ajoutImage($file,$repertoire); //Supression de l'ancienne image du répertoire (nettoyage) $this->dossierSuppressionImageUtilisateur($_SESSION['profil']['login']); //Ajout de la nouvelle image dans la BD $nomImageBD = "profils/".$_SESSION['profil']['login']."/".$nomImage; if($this->utilisateurManager->bdAjoutImage($_SESSION['profil']['login'],$nomImageBD)){ Toolbox::ajouterMessageAlerte("La modification de l'image est effectuée", Toolbox::COULEUR_VERTE); } else { Toolbox::ajouterMessageAlerte("La modification de l'image n'a pas été effectuée", Toolbox::COULEUR_ROUGE); } // Utilisation du système d'alerte pour afficher un message d'erreur lié à l'upload de l'image // (exemple : image trop grosse) } catch(Exception $e){ Toolbox::ajouterMessageAlerte($e->getMessage(), Toolbox::COULEUR_ROUGE); } header("Location: ".URL."compte/profil"); } private function dossierSuppressionImageUtilisateur($login){ //Supression de l'ancienne image du répertoire (nettoyage) $ancienneImage = $this->utilisateurManager->getImageUtilisateur($login); if($ancienneImage !== "profils/profil.png"){ unlink("public/Assets/images/".$ancienneImage); } }
// La fonction copie un fichier ($file) dans un répertoire ($dir) public static function ajoutImage($file, $dir){ if(!isset($file['name']) || empty($file['name'])) throw new Exception("Vous devez indiquer une image"); // si le répertoire n'existe pas, on le créé if(!file_exists($dir)) mkdir($dir,0777); // on isole l'extension de l'image $extension = strtolower(pathinfo($file['name'],PATHINFO_EXTENSION)); // nombre aléatoire pour donner un nom à l'image copiée $random = rand(0,99999); $target_file = $dir.$random."_".$file['name']; if(!getimagesize($file["tmp_name"])) throw new Exception("Le fichier n'est pas une image"); if($extension !== "jpg" && $extension !== "jpeg" && $extension !== "png" && $extension !== "gif") throw new Exception("L'extension du fichier n'est pas reconnu"); if(file_exists($target_file)) throw new Exception("Le fichier existe déjà"); if($file['size'] > 500000) throw new Exception("Le fichier est trop gros"); // Après les tets, si l'image est éligible, elle est copiée dans le dossier adéquat if(!move_uploaded_file($file['tmp_name'], $target_file)) throw new Exception("l'ajout de l'image n'a pas fonctionné"); else return ($random."_".$file['name']); }
public function bdAjoutImage($login,$image){ $req = "UPDATE utilisateur set image = :image WHERE login = :login"; $stmt = $this->getBdd()->prepare($req); $stmt->bindValue(":login",$login,PDO::PARAM_STR); $stmt->bindValue(":image",$image,PDO::PARAM_STR); $stmt->execute(); $estModifier = ($stmt->rowCount() > 0); $stmt->closeCursor(); return $estModifier; } public function getImageUtilisateur($login){ $req = "SELECT image FROM utilisateur WHERE login = :login"; $stmt = $this->getBdd()->prepare($req); $stmt->bindValue(":login",$login,PDO::PARAM_STR); $stmt->execute(); $resultat = $stmt->fetch(PDO::FETCH_ASSOC); $stmt->closeCursor(); return $resultat['image']; }
Résultats :

23. Suppression de l’image lors de la suppression du compte
Lors de la suppression du compte, on doit également supprimer l’image éventuelle stockée dans le dossier « /profils/[$login »], puis supprimer le dossier « /[$login] » lui-même.
On effectuera ces opérations dans la méthode « suppressionCompte » dans le contrôleur.
public function suppressionCompte(){ $this->dossierSuppressionImageUtilisateur($_SESSION['profil']['login']); rmdir("public/Assets/images/profil/".$_SESSION['profil']['login']); if($this->utilisateurManager->bdSuppressionCompte($_SESSION['profil']['login'])){ Toolbox::ajouterMessageAlerte("La suppression de votre compte a été effectuée.",Toolbox::COULEUR_VERTE); $this->deconnexion(); } else { Toolbox::ajouterMessageAlerte("La suppression n'a pas fonctionnée, contactez l'administrateur !", Toolbox::COULEUR_ROUGE); header('Location: '.URL."compte/profil"); } }
24. Partie administrateur – 1
On va maintenant prendre en compte le rôle administrateur. Pour cela, dans un premier temps, on va réaliser les actions suivantes :
- Prendre en charge l’affectation du rôle et assurer sa sauvegarde dans la base de données ; d’ou des modifications à apporter dans le modèle.
Remarque : Lors de la création, le rôle attribué est celui d’utilisateur. - Sauvegarder le rôle dans une variable de session car il est important de connaitre le avant d’exécuter des commandes dont les accès sont soumis à des authorisation de droits d’accès.
- On rajoutera à cet effet les deux fonctions « estUtilisateur » et « estAdministrateur » dans la classe « Securite »
- Dans le menu, on ajoutera un lien supplémentaire pour permettre aux administrateurs d’accéder à du contenu restreint. Les administrateurs auront les droits pour modifier le rôle des utilisateurs et des autres administrateurs.
- Le lien du menu « Gérer les droits » pointera sur la route « administration/droits ».
Actions relatives à la base de données :
public function validation_creerCompte($login, $password, $mail){ if($this->utilisateurManager->verifLoginDisponible($login)){ $passwordCrypte = password_hash($password, PASSWORD_DEFAULT); $clef = rand(0,9999); if($this->utilisateurManager->bdCreerCompte($login, $passwordCrypte, $mail, $clef, "profils/profil.png", 'utilisateur')){ ... ... } public function bdCreerCompte($login, $passwordCrypte, $mail, $clef, $image, $role){ $req= "INSERT INTO utilisateur (login, password, mail, est_valide, role, clef, image) VALUES (:login, :password, :mail, 0, :role, :clef, :image)"; $stmt = $this->getBdd()->prepare($req); $stmt->bindValue(":login", $login, PDO::PARAM_STR); $stmt->bindValue(":password", $passwordCrypte, PDO::PARAM_STR); $stmt->bindValue(":mail", $mail, PDO::PARAM_STR); $stmt->bindValue(":clef", $clef, PDO::PARAM_INT); $stmt->bindValue(":image", $image, PDO::PARAM_STR); $stmt->bindValue(":role", $role, PDO::PARAM_STR); $stmt->execute(); // Si l'ajout n'a pas fonctionné, alors on reverra estAjouter=rowCount=0=flase $estAjouter = ($stmt->rowCount() > 0); $stmt->closeCursor(); return $estAjouter; }
Actions relatives à la sécurité :
class Securite{ public static function secureHTML($chaine){ // Permet de supprimer ou convertir en ascii les caractères spéciaux return htmlentities($chaine); } public static function isConnected(){ return (!empty($_SESSION['profil'])); } public static function estUtilisateur(){ return ($_SESSION['profil']['role'] === "utilisateur"); } public static function estAdministrateur(){ return ($_SESSION['profil']['role'] === "administrateur"); } }
Ajout d’un élément de menu pour l’administrateur :
<?php if(Securite::isConnected() && Securite::estAdministrateur()) : ?> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false"> Administration </a> <ul class="dropdown-menu" aria-labelledby="navbarDropdown"> <li><a class="dropdown-item" href="<?= URL; ?>administration/droits">Gérer les droits</a></li> </ul> </li> <?php endif; ?>
25. Partie administrateur – 2
On va maintenant créé la page permettant à l’administrateur de gérer les droits des utilisateurs, réservé aux administrateur.
Pour cela, on va effectuer les opération suivantes :
- Routeur :
- Prise en compte de la route « administration/droits »
- Si l’opérateur est bien connecté et administrateur, appel de la méthode « droits » de la classe « AdministrateurController »
- Contrôleur :
- Dans le dossier « controllers/Administrateur » on créé le fichier « Administrateur.controller.php »
- On créé la structure de base de la classe « AdministrateurController »
- On implémente la méthode « droits » qui a pour but de générer la vue (fichier « views/Administrateur/droits.view.php ») en initialisant le tableau « $data_page ».
- Appel de la méthode « genererPage » qui va déclencher l’affichage de la vue « droits.view.php »
- Vue « droits.view.php » :
- Dans le dossier « /views/Administrateur/ », création du fichier « droits.view.php »
- Création de la vue permettant à l’administrateur d’accéder à la liste des utilisateurs
- Il faudra donner la possibilité à l’administrateur de modifier les droits des utilisateurs si besoin est !
Donc on construit un formulaire permettant de changer les droits. On en profitera pour ajouter le rôle de super utiliasteur (avec des droits intermédiaires) en plus de utilisateur et administrateur - La validation du formulaire dirigera un lien vers la nouvelle route « administration/validation_modificationRole »
- Routeur :
- Prise en compte de la route « administration/validation_modificationRole »
- Appel de la méthode « validation_modificationRole » de la classe « AdministrateurController »
- Contrôleur :
- On implémente la méthode « validation_modificationRole » qui a pour but de sauvegarder la modification du rôle de l’utilisateur désigné. Pour cela, on appèle la méthode « bdModificationRoleUtilisateur » dans la classe « AdministrateurManager » du modèle
- Modèle :
- Dans le dossier « models/Administrateur » on créé le fichier « Administrateur.model.php »
- On créé la structure de base de la classe « AdministrateurManager »
- On implémente la méthode « bdModificationRoleUtilisateur » qui a pour but de mettre à jour le champs « role » de la table « utilisateur » (et pour l’utilisateur spécifier par $login) dans la base de données
// Inclusion de fichier principal des contrôleurs qui auront la faculté de piloter // toutes les pages de contenu du site ... require_once("./controllers/Administrateur/Administrateur.controller.php"); $administrateurController = new AdministrateurController(); ... case "administration" : if(!Securite::isConnected()){ Toolbox::ajouterMessageAlerte("Veuillez vous connecter !", Toolbox::COULEUR_ROUGE); header("Location: ".URL."login"); } else if(!Securite::estAdministrateur()){ Toolbox::ajouterMessageAlerte("Vous n'avez pas le droit d'être là !", Toolbox::COULEUR_ROUGE); header("Location: ".URL."accueil"); } else { switch($url[1]){ case "droits": $administrateurController->droits(); break; case "validation_modificationRole": $administrateurController->validation_modificationRole($_POST['login'], $_POST['role']); break; default : throw new Exception("La page n'existe pas"); } } break; ...
<?php require_once("./controllers/MainController.controller.php"); require_once("./models/Administrateur/Administrateur.model.php"); class AdministrateurController extends MainController { private $administrateurController; private $administrateurManager; public function __construct(){ $this->administrateurManager = new AdministrateurManager(); } public function droits(){ $utilisateurs = $this->administrateurManager->getUtilisateurs(); $data_page = [ "page_description" => "Gestion des droits des utilisateurs", "page_title" => "Gestion des droits des utilisateurs", "utilisateurs" => $utilisateurs, "view" => "views/Administrateur/droits.view.php", "template" => "views/common/template.php" ]; $this->genererPage($data_page); } public function validation_modificationRole($login, $role){ if($this->administrateurManager->bdModificationRoleUtilisateur($login, $role)){ Toolbox::ajouterMessageAlerte("La modification du role de l'utilisateur a été effectuée", Toolbox::COULEUR_VERTE); } else { Toolbox::ajouterMessageAlerte("La modification du role de l'utilisateur n'a pas été effectuée", Toolbox::COULEUR_ROUGE); } header("Location: ".URL."administration/droits"); } public function pageErreur($msg){ parent::pageErreur($msg); } } ?>
<h1>Page de gestion des droits des utilisateurs</h1> <table class="table"> <thead> <tr> <th>Login</th> <th>Validé</th> <th>Rôle</th> </tr> <?php foreach ($utilisateurs as $utilisateur) : ?> <tr> <td><?= $utilisateur['login'] ?></td> <td><?= (int)$utilisateur['est_valide'] === 0 ? "non validé" : "validé" ?></td> <td> <?php if($utilisateur['role'] === 'administrateur') : ?> <?= $utilisateur['role'] ?> <?php else : ?> <form method="POST" action="<?= URL ?>administration/validation_modificationRole"> <input type="hidden" name="login" value="<?= $utilisateur['login'] ?>" /> <select class="form-select" name="role" onchange="confirm('Confirmez-vous la modification ?') ? submit() : document.location.reload()"> <option value="utilisateur" <?= $utilisateur['role'] === "utilisateur" ? "selected" : "" ?> >Utilisateur</option> <option value="Sutilisateur" <?= $utilisateur['role'] === "Sutilisateur" ? "selected" : "" ?> >Super utilisateur</option> <option value="administrateur" <?= $utilisateur['role'] === "administrateur" ? "selected" : "" ?> >Administrateur</option> </select> </form> <?php endif; ?> </td> </tr> <?php endforeach; ?> </thead> </table>
Résultats :

26. Sécurisation de la session avec un cookie
On va maintenant créé la page permettant à l’administrateur de gérer les droits des utilisateurs, réservé aux administrateur.
Pour cela, on va effectuer les opération suivantes :
- Routeur :
- Prise en compte de la route « administration/droits »
- Si l’opérateur est bien connecté et administrateur, appel de la méthode « droits » de la classe « AdministrateurController »
- Contrôleur :
- Dans le dossier « controllers/Administrateur » on créé le fichier « Administrateur.controller.php »
- On créé la structure de base de la classe « AdministrateurController »
- On implémente la méthode « droits » qui a pour but de générer la vue (fichier « views/Administrateur/droits.view.php ») en initialisant le tableau « $data_page ».
- Appel de la méthode « genererPage » qui va déclencher l’affichage de la vue « droits.view.php »
- Vue « droits.view.php » :
- Dans le dossier « /views/Administrateur/ », création du fichier « droits.view.php »
- Création de la vue permettant à l’administrateur d’accéder à la liste des utilisateurs
- Il faudra donner la possibilité à l’administrateur de modifier les droits des utilisateurs si besoin est !
Donc on construit un formulaire permettant de changer les droits. On en profitera pour ajouter le rôle de super utiliasteur (avec des droits intermédiaires) en plus de utilisateur et administrateur - La validation du formulaire dirigera un lien vers la nouvelle route « administration/validation_modificationRole »
- Routeur :
- Prise en compte de la route « administration/validation_modificationRole »
- Appel de la méthode « validation_modificationRole » de la classe « AdministrateurController »
- Contrôleur :
- On implémente la méthode « validation_modificationRole » qui a pour but de sauvegarder la modification du rôle de l’utilisateur désigné. Pour cela, on appèle la méthode « bdModificationRoleUtilisateur » dans la classe « AdministrateurManager » du modèle
- Modèle :
- Dans le dossier « models/Administrateur » on créé le fichier « Administrateur.model.php »
- On créé la structure de base de la classe « AdministrateurManager »
- On implémente la méthode « bdModificationRoleUtilisateur » qui a pour but de mettre à jour le champs « role » de la table « utilisateur » (et pour l’utilisateur spécifier par $login) dans la base de données
// Inclusion de fichier principal des contrôleurs qui auront la faculté de piloter // toutes les pages de contenu du site ... require_once("./controllers/Administrateur/Administrateur.controller.php"); $administrateurController = new AdministrateurController(); ... case "administration" : if(!Securite::isConnected()){ Toolbox::ajouterMessageAlerte("Veuillez vous connecter !", Toolbox::COULEUR_ROUGE); header("Location: ".URL."login"); } else if(!Securite::estAdministrateur()){ Toolbox::ajouterMessageAlerte("Vous n'avez pas le droit d'être là !", Toolbox::COULEUR_ROUGE); header("Location: ".URL."accueil"); } else { switch($url[1]){ case "droits": $administrateurController->droits(); break; case "validation_modificationRole": $administrateurController->validation_modificationRole($_POST['login'], $_POST['role']); break; default : throw new Exception("La page n'existe pas"); } } break; ...
<?php require_once("./controllers/MainController.controller.php"); require_once("./models/Administrateur/Administrateur.model.php"); class AdministrateurController extends MainController { private $administrateurController; private $administrateurManager; public function __construct(){ $this->administrateurManager = new AdministrateurManager(); } public function droits(){ $utilisateurs = $this->administrateurManager->getUtilisateurs(); $data_page = [ "page_description" => "Gestion des droits des utilisateurs", "page_title" => "Gestion des droits des utilisateurs", "utilisateurs" => $utilisateurs, "view" => "views/Administrateur/droits.view.php", "template" => "views/common/template.php" ]; $this->genererPage($data_page); } public function validation_modificationRole($login, $role){ if($this->administrateurManager->bdModificationRoleUtilisateur($login, $role)){ Toolbox::ajouterMessageAlerte("La modification du role de l'utilisateur a été effectuée", Toolbox::COULEUR_VERTE); } else { Toolbox::ajouterMessageAlerte("La modification du role de l'utilisateur n'a pas été effectuée", Toolbox::COULEUR_ROUGE); } header("Location: ".URL."administration/droits"); } public function pageErreur($msg){ parent::pageErreur($msg); } } ?>
<h1>Page de gestion des droits des utilisateurs</h1> <table class="table"> <thead> <tr> <th>Login</th> <th>Validé</th> <th>Rôle</th> </tr> <?php foreach ($utilisateurs as $utilisateur) : ?> <tr> <td><?= $utilisateur['login'] ?></td> <td><?= (int)$utilisateur['est_valide'] === 0 ? "non validé" : "validé" ?></td> <td> <?php if($utilisateur['role'] === 'administrateur') : ?> <?= $utilisateur['role'] ?> <?php else : ?> <form method="POST" action="<?= URL ?>administration/validation_modificationRole"> <input type="hidden" name="login" value="<?= $utilisateur['login'] ?>" /> <select class="form-select" name="role" onchange="confirm('Confirmez-vous la modification ?') ? submit() : document.location.reload()"> <option value="utilisateur" <?= $utilisateur['role'] === "utilisateur" ? "selected" : "" ?> >Utilisateur</option> <option value="Sutilisateur" <?= $utilisateur['role'] === "Sutilisateur" ? "selected" : "" ?> >Super utilisateur</option> <option value="administrateur" <?= $utilisateur['role'] === "administrateur" ? "selected" : "" ?> >Administrateur</option> </select> </form> <?php endif; ?> </td> </tr> <?php endforeach; ?> </thead> </table>
Résultats :
