Merge branch 'main' into yass

This commit is contained in:
Yass 2026-02-23 19:46:39 +01:00 committed by GitHub
commit 750b5715bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 2573 additions and 1504 deletions

9
.env Normal file
View file

@ -0,0 +1,9 @@
# config BDD
DB_CONNECTION=mysql
DB_HOSTNAME=boulayoune.com
DB_DATABASE=projet_folliow
DB_USERNAME=
DB_PASSWORD=
IMG_PATH= uploads/projects/

View file

@ -8,13 +8,13 @@
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Yasse\\Projetphp\\": "src/" "Guill\\Guillaume\\": "src/"
} }
}, },
"authors": [ "authors": [
{ {
"name": "Yasder5", "name": "GuillaumeH-Cci",
"email": "102179445+Yasder5@users.noreply.github.com" "email": "guillaume.hess@ccicampus.fr"
} }
] ]
} }

12
composer.lock generated
View file

@ -227,16 +227,16 @@
}, },
{ {
"name": "smarty/smarty", "name": "smarty/smarty",
"version": "v5.7.0", "version": "v5.8.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/smarty-php/smarty.git", "url": "https://github.com/smarty-php/smarty.git",
"reference": "73da7e90f302175a570662fcb0ba41f57b7a92ab" "reference": "78d259d3b971c59a0cd719c270cc5cbb740c36a7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/smarty-php/smarty/zipball/73da7e90f302175a570662fcb0ba41f57b7a92ab", "url": "https://api.github.com/repos/smarty-php/smarty/zipball/78d259d3b971c59a0cd719c270cc5cbb740c36a7",
"reference": "73da7e90f302175a570662fcb0ba41f57b7a92ab", "reference": "78d259d3b971c59a0cd719c270cc5cbb740c36a7",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -291,7 +291,7 @@
"support": { "support": {
"forum": "https://github.com/smarty-php/smarty/discussions", "forum": "https://github.com/smarty-php/smarty/discussions",
"issues": "https://github.com/smarty-php/smarty/issues", "issues": "https://github.com/smarty-php/smarty/issues",
"source": "https://github.com/smarty-php/smarty/tree/v5.7.0" "source": "https://github.com/smarty-php/smarty/tree/v5.8.0"
}, },
"funding": [ "funding": [
{ {
@ -299,7 +299,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2025-11-19T21:36:38+00:00" "time": "2026-02-15T14:27:15+00:00"
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",

View file

@ -0,0 +1,17 @@
<?php
require("mother_controller.php");
/**
* Le controller de la page d'aide utilisateur
* @author Laura
*/
class PageCtrl extends MotherCtrl{
public function help(){
$this->_display("help");
}
}

View file

@ -88,87 +88,170 @@
$this->_arrData['arrProject'] = $arrProject; $this->_arrData['arrProject'] = $arrProject;
$this->_arrData['arrUser'] = $arrUser; $this->_arrData['arrUser'] = $arrUser;
$this->_display("search"); $this->_display("search");
} }
/** /**
* Fonction d'affichage de la page projet * Fonction d'affichage de la page projet
* @author Christel adapter par Guillaume
*/ */
public function project (){ public function addedit_project() {
if (!isset($_SESSION['user'])){ // Pas d'utilisateur connecté
header("Location:index.php?ctrl=error&action=error_403");
exit;
}
$objProjectModel = new ProjectModel;
$arrProject = $objProjectModel->findAll(4);
$arrProjectToDisplay = array();
foreach($arrProject as $arrDetProject){
$objProject = new Project; $objProject = new Project;
$objProject->hydrate($arrDetProject); $objProjectModel = new ProjectModel;
$arrProjectToDisplay[] = $objProject; $objCategoryModel = new CategoryModel;
// dans la cas de modif
if (isset($_GET['id'])){
$arrProject = $objProjectModel->findOne($_GET['id']);
$objProject->hydrate($arrProject); // BDD
} }
$objImageModel = new ImageModel; // Tester le formulaire
$arrImage = $objImageModel->findAllImage(4); $arrError = [];
$arrImageToDisplay = array(); if (count($_POST) > 0) {
foreach($arrImage as $arrDetImage){
$objImage = new Image; $objProject->hydrate($_POST); // Formulaire
$objImage->hydrate($arrDetImage); if ($objProject->getTitle() == ""){
$arrImageToDisplay[] = $objImage; $arrError['title'] = "Le titre est obligatoire";
} }
//Variable data
$_SESSION['title'] = $_POST['titleProject']??"";
$_SESSION['description'] = $_POST['descProject']??"";
$_SESSION['content'] = $_POST['textProject']??"";
$_SESSION['thumbnail'] = $_FILES['imageThumbnail']['name']??"";
$_SESSION['status'] = 'en_attente';
$_SESSION['user_id'] = $_SESSION['user']['user_id'];
if ($objProject->getDescription() == ""){
$arrError['description'] = "La description est obligatoire";
}
$objProject = new Project(); if ($objProject->getContent() == ""){
$arrError['content'] = "Le contenu est obligatoire";
}
/** // Vérification de l'image
* Créer par Besnik le GOAT et l'autre GOAT de Guillaume $arrTypeAllowed = array('image/jpeg', 'image/png', 'image/webp');
* if ($_FILES['thumbnail']['error'] != 4){
* @return bool pour savoir si le fichier existe, if (!in_array($_FILES['thumbnail']['type'], $arrTypeAllowed)){
* puis déplace vers le fichier uploads avec les images projet des utilisateurs $arrError['thumbnail'] = "Le type de fichier n'est pas autorisé";
* Communication avec la BDD }else{
*/ // Vérification des codes d'erreur
if (($_SESSION['thumbnail'] != null)){ switch ($_FILES['thumbnail']['error']){
$strDest = ""; case 0 :
if ((count($_FILES) > 0) && ($_FILES['imageProject']['error'] != 4)){ // Renommage de l'image
$strDest = 'uploads/projects/'.$_FILES['imageProject']['name']; $strImageName = uniqid().".webp";
move_uploaded_file($_FILES['imageProject']['tmp_name'], $strDest);
// Récupère le nom de l'image avant changement
$strOldImg = $objProject->getThumbnail();
// Mise à jour du nom de l'image dans l'objet
$objProject->setThumbnail($strImageName);
break;
case 1 :
case 2 :
$arrError['thumbnail'] = "Le fichier est trop volumineux";
break;
case 3 :
$arrError['thumbnail'] = "Le fichier a été partiellement téléchargé";
break;
case 6 :
$arrError['thumbnail'] = "Le répertoire temporaire est manquant";
break;
default :
$arrError['thumbnail'] = "Erreur sur l'image";
break;
} }
} }
/** En cas d'appuis sur le bouton d'envoie ou celui de remettre a plus tard }else{
* 1. Changement de status // Est-ce que le fichier existe ?
* 2. Hydratation avec les informations récupéré de l'utilisateur if (is_null($objProject->getThumbnail())){
* 3. Envoie des données à la BDD $arrError['thumbnail'] = "L'image est obligatoire";
*/ }
if (isset($_POST['sendMessage'])) {
$_SESSION['status'] = 'publié';
$objProject->hydrate($_SESSION);
$objProject->setThumbnail($strDest);
var_dump($strDest);
var_dump($objProject);
$objProjectModel->insert($objProject);
} else if (isset($_POST['toContinue'])) {
$objProject->hydrate($_SESSION);
$objProject->setThumbnail($strDest);
$objProjectModel->insert($objProject);
} }
var_dump($_SESSION); // SI pas d'erreur : on traite l'image depuis la bdd
var_dump($objProject); if (count($arrError) == 0){
$this->_arrData['arrProjectToDiplay'] = $arrProjectToDisplay;
$this->_arrData['arrImageToDiplay'] = $arrImageToDisplay;
$this->_display("project"); $boolImageOk = true;
// Redimensionnement de l'image
if (isset($strImageName)){
$strDest = $_ENV['IMG_PATH'].$strImageName;
$strSource = $_FILES['thumbnail']['tmp_name'];
list($intWidth, $intHeight) = getimagesize($strSource);
$intDestWidth = 200; $intDestHeight = 250;
$fltDestRatio = $intDestWidth / $intDestHeight;
$fltSourceRatio = $intWidth / $intHeight;
if ($fltSourceRatio > $fltDestRatio) {
$intCropHeight = $intHeight;
$intCropWidth = (int)round($intHeight * $fltDestRatio);
$intCropX = (int)(($intWidth - $intCropWidth) / 2);
$intCropY = 0;
} else {
$intCropWidth = $intWidth;
$intCropHeight = (int)round($intWidth / $fltDestRatio);
$intCropX = 0;
$intCropY = (int)(($intHeight - $intCropHeight) / 2);
} }
// Condition en fonction de l'extension de l'image
$objDest = imagecreatetruecolor($intDestWidth, $intDestHeight);
switch ($_FILES['thumbnail']['type']) {
case 'image/jpeg' :
$objSource = imagecreatefromjpeg($strSource);
break;
case 'image/png' :
$objSource = imagecreatefrompng($strSource);
break;
case 'image/webp' :
$objSource = imagecreatefromwebp($strSource);
break;
}
imagecopyresampled($objDest, $objSource, 0, 0, $intCropX, $intCropY, $intDestWidth, $intDestHeight, $intCropWidth, $intCropHeight);
// Sauvegarde du fichier
$boolImageOk = imagewebp($objDest, $strDest);
imagedestroy($objDest);
imagedestroy($objSource);
}
// SI image ok, on balance tout dans la bdd
if ($boolImageOk){
if (!isset($_GET['id'])){
$objProject->setUser_id($_SESSION['user']['user_id']);
$boolOk = $objProjectModel->insert($objProject);
} else {
$boolOk = $objProjectModel->updateProject($objProject);
}
if ($boolOk){
// Suppression de l'ancienne image
if(isset($strOldImg) && !empty($strOldImg) && isset($strImageName)){
$strOldFile = $_ENV['IMG_PATH'].$strOldImg;
if (file_exists($strOldFile)) unlink($strOldFile);
}
$_SESSION['success'] = (!isset($_GET['id'])) ? "Le projet a bien été créé" : "Le projet a bien été modifié";
header("Location:index.php");
exit;
} else {
$arrError[] = "Erreur lors de l'enregistrement en base de données";
}
} else {
$arrError['thumbnail'] = "Erreur dans le traitement de l'image";
}
}
}
// Données pour la vue
$this->_arrData['arrCategory'] = $objCategoryModel->findAllCategory();
$this->_arrData['objProject'] = $objProject;
$this->_arrData['arrError'] = $arrError;
$this->_display('addedit_project');
}
public function display() { public function display() {
$intId = $_GET['id'] ?? null; $intId = $_GET['id'] ?? null;
@ -193,17 +276,12 @@
} }
} }
public function sendEmail(){ public function shareProject(){
if (count($_POST) > 0) { if (count($_POST) > 0)
{
$projectId = (int)($_POST['project_id'] ?? 0); $projectId = (int)($_POST['project_id'] ?? 0);
$toEmail = trim($_POST['to_email'] ?? ''); $toEmail = trim($_POST['to_email'] ?? '');
if ($projectId <= 0 || !filter_var($toEmail, FILTER_VALIDATE_EMAIL)) {
header("Location: index.php?ctrl=project&action=display&id=".$projectId."&mail=fail");
exit;
}
$objProjectModel = new ProjectModel(); $objProjectModel = new ProjectModel();
$arrProject = $objProjectModel->findOne($projectId); $arrProject = $objProjectModel->findOne($projectId);
@ -215,47 +293,46 @@
$objProject = new Project(); $objProject = new Project();
$objProject->hydrate($arrProject); $objProject->hydrate($arrProject);
$objMail = new PHPMailer();
$objMail = new PHPMailer(); // Nouvel objet Mail
$objMail->IsSMTP(); $objMail->IsSMTP();
$objMail->Mailer = "smtp"; $objMail->Mailer = "smtp";
$objMail->CharSet = PHPMailer::CHARSET_UTF8; $objMail->CharSet = PHPMailer::CHARSET_UTF8;
$objMail->SMTPDebug = 0; $objMail->SMTPDebug = 0;
$objMail->SMTPAuth = true;
$objMail->SMTPAuth = TRUE;
$objMail->SMTPSecure = "tls"; $objMail->SMTPSecure = "tls";
$objMail->Port = 587; $objMail->Port = 587;
$objMail->Host = "smtp.gmail.com"; $objMail->Host = 'smtp-relay.brevo.com';
$objMail->Username = "projet.folliow@gmail.com"; $objMail->Username = 'a2a67e001@smtp-brevo.com';
$objMail->Password = "dqnw mqbu cwvg enbp"; $objMail->Password = 'xsmtpsib-f2af87e12d3db6f1b99802a92c1acda32d45fc32a8446eeed7e49ec91c4ec7ef-AX8Y7YkRWYSmKHwS';
// Désactive la vérification du certificat SSL
// Cela permet d'éviter les erreurs liées au certificat, mais réduit la sécurité de la connexion.
$objMail->SMTPOptions = [
'ssl' => [
'verify_peer' => false,
],
];
$objMail->IsHTML(true); $objMail->IsHTML(true);
$objMail->setFrom('projet.folliow@hotmail.com', 'Folliow');
$objMail->setFrom('projet.folliow@gmail.com', 'Projet Folliow');
// Destinataire
$objMail->addAddress($toEmail); $objMail->addAddress($toEmail);
// Mail
$objMail->Subject = "Projet : " . $objProject->getTitle(); $objMail->Subject = "Projet : " . $objProject->getTitle();
$url = "http://localhost/projet_php/public/index.php?ctrl=project&action=display&id=" . $projectId; $url = "https://php.boulayoune.com/index.php?ctrl=project&action=display&id=" . $projectId;
$objMail->Body = $this->_arrData['projectTitle'] = $objProject->getTitle();
"<h3>" . $objProject->getTitle() . "</h3>" . $this->_arrData['projectDescription'] = $objProject->getDescription();
"<p>" . $objProject->getDescription() . "</p>" . $this->_arrData['projectUrl'] = $url;
"<p><a href='" . $url . "'>Voir le projet</a></p>";
$objMail->Body = $this->_display("mail_message", false);
// Envoi + redirection
if ($objMail->Send()) { if ($objMail->Send()) {
header("Location: index.php?ctrl=project&action=display&id=".$projectId."&mail=ok"); header("Location: index.php?ctrl=project&action=display&id=".$projectId."&mail=ok");
} else { } else {
// Pour debug si besoin: echo $objMail->ErrorInfo; exit;
header("Location: index.php?ctrl=project&action=display&id=".$projectId."&mail=fail"); header("Location: index.php?ctrl=project&action=display&id=".$projectId."&mail=fail");
} }
exit; exit;
@ -306,4 +383,22 @@
header("Location: index.php"); header("Location: index.php");
exit; exit;
} }
/**
* Page mentions légales
*/
public function mentions(){
// Afficher
$this->_display("mentions");
}
/**
* Page à propos
*/
public function about(){
// Afficher
$this->_display("about");
}
} }

View file

@ -309,4 +309,5 @@ class UserCtrl extends MotherCtrl {
$this->_display("useredit"); $this->_display("useredit");
} }
} }

View file

@ -2,11 +2,11 @@
require_once("mother_entity.php"); require_once("mother_entity.php");
/** /**
* Classe d'un objet Projet * Classe d'un objet Projet
* @author Laura * @author Laura
*/ */
class Image extends Entity{ class Image extends Entity{
private int $_id; private int $_id;

View file

@ -7,15 +7,15 @@ require_once("mother_entity.php");
class Project extends Entity{ class Project extends Entity{
private int $_id; private ?int $_id = null;
private string $_title; private string $_title = "";
private string $_description; private string $_description = "";
private string $_thumbnail; private ?string $_thumbnail = null;
private string $_content; private string $_content = "";
private string $_creation_date; private string $_creation_date;
private string $_status; private string $_status = "en_attente";
private int $_user; private int $_user;
private int $_category; private int $_category = 0;
private string $_creatorname; private string $_creatorname;
private string $_user_image; private string $_user_image;
@ -33,7 +33,7 @@ class Project extends Entity{
* Récuperation de l'id du Projet * Récuperation de l'id du Projet
* @return int l'id du projet * @return int l'id du projet
*/ */
public function getId():int{ public function getId():?int{
return $this->_id; return $this->_id;
} }
@ -110,7 +110,6 @@ class Project extends Entity{
$this->_content = $content; $this->_content = $content;
} }
/** /**
* Récupération de la date de création * Récupération de la date de création
* @param string lang de formatage de la date (par défaut = "fr_FR") * @param string lang de formatage de la date (par défaut = "fr_FR")
@ -173,7 +172,7 @@ class Project extends Entity{
* Récupération de la catégorie * Récupération de la catégorie
* @return int id de la catégorie * @return int id de la catégorie
*/ */
public function getCategory(){ public function getCategory():?int{
return $this->_category; return $this->_category;
} }

View file

@ -1,7 +1,7 @@
<?php <?php
session_start(); session_start();
require("./vendor/autoload.php"); require(__DIR__ . "/vendor/autoload.php");
//environnement //environnement
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__); $dotenv = Dotenv\Dotenv::createImmutable(__DIR__);

View file

@ -90,8 +90,8 @@
*/ */
public function insert(object $objProject):bool{ public function insert(object $objProject):bool{
$strRq = "INSERT INTO project (project_title, project_description, project_thumbnail, project_content, project_status, project_creation_date) $strRq = "INSERT INTO project (project_title, project_description, project_thumbnail, project_content, project_status, project_creation_date, project_user_id, project_category)
VALUES (:title, :description, :thumbnail, :content, :status, DATE(NOW()))"; VALUES (:title, :description, :thumbnail, :content, :status, DATE(NOW()), :project_user_id, :project_category)";
$rqPrep = $this->_db->prepare($strRq); $rqPrep = $this->_db->prepare($strRq);
@ -100,6 +100,8 @@
$rqPrep->bindValue(":thumbnail", $objProject->getThumbnail(), PDO::PARAM_STR); $rqPrep->bindValue(":thumbnail", $objProject->getThumbnail(), PDO::PARAM_STR);
$rqPrep->bindValue(":content", $objProject->getContent(), PDO::PARAM_STR); $rqPrep->bindValue(":content", $objProject->getContent(), PDO::PARAM_STR);
$rqPrep->bindValue(":status", $objProject->getStatus(), PDO::PARAM_STR); $rqPrep->bindValue(":status", $objProject->getStatus(), PDO::PARAM_STR);
$rqPrep->bindValue(":project_user_id", $objProject->getUser_id(), PDO::PARAM_STR);
$rqPrep->bindValue(":project_category", $objProject->getCategory(), PDO::PARAM_STR);
return $rqPrep->execute(); return $rqPrep->execute();
} }
@ -126,6 +128,12 @@
return $rqPrep->fetch(); return $rqPrep->fetch();
} }
/**
* Fonction de changement de status (accepter) d'un projet en BDD
* @author Guillaume
* @param int $id l'id du projet
* @return bool Est-ce que la requête s'est bien passée
*/
public function accept(int $id){ public function accept(int $id){
//SQL pour changer le status en accept //SQL pour changer le status en accept
@ -137,6 +145,12 @@
return $this->_db->query($strRq); return $this->_db->query($strRq);
} }
/**
* Fonction de changement de status (refusé) d'un projet en BDD
* @author Guillaume
* @param int $id l'id du projet
* @return bool Est-ce que la requête s'est bien passée
*/
public function refuse(int $id){ public function refuse(int $id){
$strRq = "UPDATE project $strRq = "UPDATE project
@ -146,6 +160,12 @@
return $this->_db->query($strRq); return $this->_db->query($strRq);
} }
/**
* Fonction de suppression d'un projet en BDD
* @author Guillaume
* @param int $id l'id du projet
* @return bool Est-ce que la requête s'est bien passée
*/
public function delete(int $id){ public function delete(int $id){
$strRq = "DELETE FROM project $strRq = "DELETE FROM project
@ -156,13 +176,17 @@
/** /**
* Fonction de mise à jour d'un projet en BDD * Fonction de mise à jour d'un projet en BDD
* @author Guillaume
* @param object $objProject L'objet utilisateur * @param object $objProject L'objet utilisateur
* @return bool Est-ce que la requête s'est bien passée * @return bool Est-ce que la requête s'est bien passée
*/ */
public function updateProject(object $objProject):bool{ public function updateProject(object $objProject):bool{
$strRq = "UPDATE project $strRq = "UPDATE project
SET project_title = :title, project_description = :description, project_content = :content SET project_title = :title,
project_description = :description,
project_content = :content,
project_thumbnail = :thumbnail
WHERE project_id = :id"; WHERE project_id = :id";
$rqPrep = $this->_db->prepare($strRq); $rqPrep = $this->_db->prepare($strRq);
@ -170,9 +194,40 @@
$rqPrep->bindValue(":title", $objProject->getTitle(), PDO::PARAM_STR); $rqPrep->bindValue(":title", $objProject->getTitle(), PDO::PARAM_STR);
$rqPrep->bindValue(":description", $objProject->getDescription(), PDO::PARAM_STR); $rqPrep->bindValue(":description", $objProject->getDescription(), PDO::PARAM_STR);
$rqPrep->bindValue(":content", $objProject->getContent(), PDO::PARAM_STR); $rqPrep->bindValue(":content", $objProject->getContent(), PDO::PARAM_STR);
$rqPrep->bindValue(":thumbnail", $objProject->getThumbnail(), PDO::PARAM_STR);
$rqPrep->bindValue(":id", $objProject->getId(), PDO::PARAM_INT);
return $rqPrep->execute();
}
public function getImagesByProjectId(int $projectId): array {
$strRq = "SELECT image_id, image_name, image_alt
FROM image
WHERE image_project = :id AND image_status = 1";
$rqPrep = $this->_db->prepare($strRq);
$rqPrep->bindValue(":id", $projectId, PDO::PARAM_INT);
$rqPrep->execute();
return $rqPrep->fetchAll(PDO::FETCH_ASSOC);
}
public function addImageInProject(string $fileName, int $projectId, string $alt = "Image projet"): bool {
$strRq = "INSERT INTO image (
image_name,
image_alt,
image_status,
image_project
)
VALUES (:name, :alt, :status, :project)";
$rqPrep = $this->_db->prepare($strRq);
$rqPrep->bindValue(":name", $fileName, PDO::PARAM_STR);
$rqPrep->bindValue(":alt", $alt, PDO::PARAM_STR);
$rqPrep->bindValue(":status", "en_attente", PDO::PARAM_STR); // Valeur string brute
$rqPrep->bindValue(":project", $projectId, PDO::PARAM_INT);
// Executer la requête
return $rqPrep->execute(); return $rqPrep->execute();
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 708 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

View file

@ -6,13 +6,13 @@ $vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir); $baseDir = dirname($vendorDir);
return array( return array(
'Yasse\\Projetphp\\' => array($baseDir . '/src'),
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
'Smarty\\' => array($vendorDir . '/smarty/smarty/src'), 'Smarty\\' => array($vendorDir . '/smarty/smarty/src'),
'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src/PhpOption'), 'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src/PhpOption'),
'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'), 'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'),
'Guill\\Guillaume\\' => array($baseDir . '/src'),
'GrahamCampbell\\ResultType\\' => array($vendorDir . '/graham-campbell/result-type/src'), 'GrahamCampbell\\ResultType\\' => array($vendorDir . '/graham-campbell/result-type/src'),
'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'), 'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'),
); );

View file

@ -14,10 +14,6 @@ class ComposerStaticInit68f8d029d347b4c0c8cdbe33eeb96101
); );
public static $prefixLengthsPsr4 = array ( public static $prefixLengthsPsr4 = array (
'Y' =>
array (
'Yasse\\Projetphp\\' => 16,
),
'S' => 'S' =>
array ( array (
'Symfony\\Polyfill\\Php80\\' => 23, 'Symfony\\Polyfill\\Php80\\' => 23,
@ -32,6 +28,7 @@ class ComposerStaticInit68f8d029d347b4c0c8cdbe33eeb96101
), ),
'G' => 'G' =>
array ( array (
'Guill\\Guillaume\\' => 16,
'GrahamCampbell\\ResultType\\' => 26, 'GrahamCampbell\\ResultType\\' => 26,
), ),
'D' => 'D' =>
@ -41,10 +38,6 @@ class ComposerStaticInit68f8d029d347b4c0c8cdbe33eeb96101
); );
public static $prefixDirsPsr4 = array ( public static $prefixDirsPsr4 = array (
'Yasse\\Projetphp\\' =>
array (
0 => __DIR__ . '/../..' . '/src',
),
'Symfony\\Polyfill\\Php80\\' => 'Symfony\\Polyfill\\Php80\\' =>
array ( array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php80', 0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
@ -69,6 +62,10 @@ class ComposerStaticInit68f8d029d347b4c0c8cdbe33eeb96101
array ( array (
0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src', 0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src',
), ),
'Guill\\Guillaume\\' =>
array (
0 => __DIR__ . '/../..' . '/src',
),
'GrahamCampbell\\ResultType\\' => 'GrahamCampbell\\ResultType\\' =>
array ( array (
0 => __DIR__ . '/..' . '/graham-campbell/result-type/src', 0 => __DIR__ . '/..' . '/graham-campbell/result-type/src',

View file

@ -230,17 +230,17 @@
}, },
{ {
"name": "smarty/smarty", "name": "smarty/smarty",
"version": "v5.7.0", "version": "v5.8.0",
"version_normalized": "5.7.0.0", "version_normalized": "5.8.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/smarty-php/smarty.git", "url": "https://github.com/smarty-php/smarty.git",
"reference": "73da7e90f302175a570662fcb0ba41f57b7a92ab" "reference": "78d259d3b971c59a0cd719c270cc5cbb740c36a7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/smarty-php/smarty/zipball/73da7e90f302175a570662fcb0ba41f57b7a92ab", "url": "https://api.github.com/repos/smarty-php/smarty/zipball/78d259d3b971c59a0cd719c270cc5cbb740c36a7",
"reference": "73da7e90f302175a570662fcb0ba41f57b7a92ab", "reference": "78d259d3b971c59a0cd719c270cc5cbb740c36a7",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -251,7 +251,7 @@
"phpunit/phpunit": "^8.5 || ^7.5", "phpunit/phpunit": "^8.5 || ^7.5",
"smarty/smarty-lexer": "^4.0.2" "smarty/smarty-lexer": "^4.0.2"
}, },
"time": "2025-11-19T21:36:38+00:00", "time": "2026-02-15T14:27:15+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
@ -297,7 +297,7 @@
"support": { "support": {
"forum": "https://github.com/smarty-php/smarty/discussions", "forum": "https://github.com/smarty-php/smarty/discussions",
"issues": "https://github.com/smarty-php/smarty/issues", "issues": "https://github.com/smarty-php/smarty/issues",
"source": "https://github.com/smarty-php/smarty/tree/v5.7.0" "source": "https://github.com/smarty-php/smarty/tree/v5.8.0"
}, },
"funding": [ "funding": [
{ {

View file

@ -3,7 +3,7 @@
'name' => 'yasse/projetphp', 'name' => 'yasse/projetphp',
'pretty_version' => 'dev-main', 'pretty_version' => 'dev-main',
'version' => 'dev-main', 'version' => 'dev-main',
'reference' => 'ea3892886993812ca42db7f03050df59d2c2163c', 'reference' => '18301190fcc0a817bf05ee42b03b5e9388fd9429',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@ -38,9 +38,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'smarty/smarty' => array( 'smarty/smarty' => array(
'pretty_version' => 'v5.7.0', 'pretty_version' => 'v5.8.0',
'version' => '5.7.0.0', 'version' => '5.8.0.0',
'reference' => '73da7e90f302175a570662fcb0ba41f57b7a92ab', 'reference' => '78d259d3b971c59a0cd719c270cc5cbb740c36a7',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../smarty/smarty', 'install_path' => __DIR__ . '/../smarty/smarty',
'aliases' => array(), 'aliases' => array(),
@ -85,7 +85,7 @@
'yasse/projetphp' => array( 'yasse/projetphp' => array(
'pretty_version' => 'dev-main', 'pretty_version' => 'dev-main',
'version' => 'dev-main', 'version' => 'dev-main',
'reference' => 'ea3892886993812ca42db7f03050df59d2c2163c', 'reference' => '18301190fcc0a817bf05ee42b03b5e9388fd9429',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),

View file

@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [5.8.0] - 2026-02-15
- Added support for Backed Enums for php versions >= 8.1 [#1171](https://github.com/smarty-php/smarty/pull/1171)
- Added support for new 'matches' operator doing regex matching [#1169](https://github.com/smarty-php/smarty/pull/1169)
- Update documentation to clarify that include inline is currently not implemented in Smarty v5 [#1152](https://github.com/smarty-php/smarty/issues/1152)
- Support for Laravel Collections style object chaining for objects return from function calls implemented as modifiers [#1151](https://github.com/smarty-php/smarty/issues/1151)
## [5.7.0] - 2025-11-19 ## [5.7.0] - 2025-11-19
- PHP 8.5 support - PHP 8.5 support

View file

@ -115,5 +115,7 @@ If you are a maintainer, you can publish the document using [mike](https://githu
mike deploy 5.x mike deploy 5.x
``` ```
Then, push the `gh-pages` branch.
## Attribution ## Attribution
This guide is based on the **contributing.md**. [Make your own](https://contributing.md/)! This guide is based on the **contributing.md**. [Make your own](https://contributing.md/)!

View file

@ -50,6 +50,120 @@ separated from surrounding elements by spaces. Note that items listed in
| is \[not\] odd by | | $a is not odd by $b | \[not\] an odd grouping | ($a / $b) % 2 != 0 | | is \[not\] odd by | | $a is not odd by $b | \[not\] an odd grouping | ($a / $b) % 2 != 0 |
| is in | | $a is in $b | exists in array | in_array($a, $b) | | is in | | $a is in $b | exists in array | in_array($a, $b) |
| is \[not\] in | | $a is not in $b | does not exist in array | !in_array($a, $b) | | is \[not\] in | | $a is not in $b | does not exist in array | !in_array($a, $b) |
| matches | | $a matches $b | regex pattern match | preg_match($b, $a) |
## Regex Matching Operator
The `matches` operator allows you to test if a string matches a regular expression pattern.
### Basic Usage
```smarty
{if "hello" matches "/^[a-z]+$/"}
String matches the pattern!
{/if}
{if $email matches "/^[^@]+@[^@]+\.[^@]+$/"}
Valid email format
{else}
Invalid email format
{/if}
```
### Using Variables
```smarty
{$pattern = '/^[a-zA-Z0-9]{8,}$/'}
{if $password matches $pattern}
Password meets requirements
{else}
Password must be at least 8 alphanumeric characters
{/if}
```
### Pattern Modifiers
The `matches` operator supports all standard PHP regex modifiers:
```smarty
{* Case insensitive matching *}
{if "HELLO" matches "/hello/i"}
Matches (case insensitive)
{/if}
{* Multiline mode *}
{if "line1\nline2" matches "/line2$/m"}
Matches in multiline mode
{/if}
{* Dot matches newlines *}
{if "hello\nworld" matches "/hello.world/s"}
Matches with dotall modifier
{/if}
```
### Complex Conditions
The `matches` operator can be combined with other operators:
```smarty
{if $username matches "/^[a-z]+$/" && $username|length > 3}
Valid username
{else}
Username must be lowercase letters and at least 4 characters
{/if}
{if $input matches "/^[0-9]+$/" || $input matches "/^[a-z]+$/"}
Input is either numeric or lowercase letters
{else}
Invalid input format
{/if}
```
### Practical Examples
**Email Validation:**
```smarty
{if $email matches "/^[^@\s]+@[^@\s]+\.[^@\s]+$/"}
Valid email address
{else}
Please enter a valid email address
{/if}
```
**Password Strength:**
```smarty
{if $password matches "/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/"}
Strong password
{else}
Password must contain uppercase, lowercase, numbers and be at least 8 characters
{/if}
```
**URL Validation:**
```smarty
{if $url matches "/^https?:\/\/(www\.)?[a-z0-9\-]+(\.[a-z]{2,})+/i"}
Valid URL format
{else}
Please enter a valid URL
{/if}
```
**Numeric Validation:**
```smarty
{if $input matches "/^[0-9]+$/"}
Valid numeric input
{else}
Please enter numbers only
{/if}
```
### Notes
- The `matches` operator uses PHP's `preg_match()` function internally
- Pattern delimiters must be valid regex delimiters (typically `/`)
- Invalid patterns will cause PHP warnings but won't break template execution
- For complex regex patterns, consider using variables for better readability
## Ternary ## Ternary
You can use the `?:` (or ternary) operator to test one expression and present the value You can use the `?:` (or ternary) operator to test one expression and present the value

View file

@ -48,10 +48,12 @@ available within the included template.
## Option Flags ## Option Flags
| Name | Description | | Name | Description |
|---------|--------------------------------------------------------------------------------------| |-----------|--------------------------------------------------------------------------------------|
| nocache | Disables caching of this subtemplate | | nocache | Disables caching of this subtemplate |
| caching | Enable caching of this subtemplate | | caching | Enable caching of this subtemplate |
| inline | If set, merge the compile-code of the subtemplate into the compiled calling template | | inline \* | If set, merge the compile-code of the subtemplate into the compiled calling template |
\* The `inline` option flag is currently not implemented in Smarty v5. Using it will not trigger an error, however.
## Examples ## Examples
```smarty ```smarty

View file

@ -124,3 +124,88 @@ this will output:
name: Zaphod Beeblebrox<br /> name: Zaphod Beeblebrox<br />
email: zaphod@slartibartfast.example.com<br /> email: zaphod@slartibartfast.example.com<br />
``` ```
## Backed Enums (PHP 8.1+)
Smarty supports accessing properties of [backed enums](https://www.php.net/manual/en/language.enumerations.backed.php) introduced in PHP 8.1.
### Accessing Enum Properties
You can access the `name` and `value` properties of backed enum cases:
```smarty
{* Access enum case properties *}
<option id="{MyEnum::Foo->name}">{MyEnum::Foo->value}</option>
```
### Complete Example
```php
<?php
use Smarty\Smarty;
// Define a backed enum
enum Status: string {
case Active = 'active';
case Inactive = 'inactive';
case Pending = 'pending';
}
$smarty = new Smarty();
$smarty->assign('currentStatus', Status::Active);
$smarty->display('template.tpl');
```
`template.tpl`:
```smarty
{* Display enum properties *}
Current status: {$currentStatus->name} (value: {$currentStatus->value})
{* Use in HTML attributes *}
<select name="status">
<option value="{Status::Active->value}" {if $currentStatus->name === 'Active'}selected{/if}>Active</option>
<option value="{Status::Inactive->value}" {if $currentStatus->name === 'Inactive'}selected{/if}>Inactive</option>
<option value="{Status::Pending->value}" {if $currentStatus->name === 'Pending'}selected{/if}>Pending</option>
</select>
```
This would output:
```html
Current status: Active (value: active)
<select name="status">
<option value="active" selected>Active</option>
<option value="inactive">Inactive</option>
<option value="pending">Pending</option>
</select>
```
### Integer-backed Enums
Integer-backed enums work the same way:
```php
<?php
enum Priority: int {
case Low = 1;
case Medium = 2;
case High = 3;
}
$smarty->assign('priority', Priority::High);
```
```smarty
{* Access integer enum properties *}
Priority level: {$priority->value} ({$priority->name})
```
Output:
```html
Priority level: 3 (High)
```
> **Note**: Backed enum support requires PHP 8.1 or higher. The enum must be registered or available in the current namespace.

View file

@ -22,6 +22,8 @@ Some of Smarty's features:
- [Template Inheritance](api/inheritance.md) for - [Template Inheritance](api/inheritance.md) for
easy management of template content. easy management of template content.
- [Plugin](api/extending/introduction.md) architecture - [Plugin](api/extending/introduction.md) architecture
- Regex pattern matching with the [`matches`](designers/language-basic-syntax/language-syntax-operators.md#regex-matching-operator) operator
- Support for PHP 8.1+ [backed enums](designers/language-variables/language-assigned-variables.md#backed-enums-php-81)
## Separation of presentation from application code ## Separation of presentation from application code
- This means templates can certainly contain logic under the condition - This means templates can certainly contain logic under the condition

View file

@ -8,6 +8,25 @@ It allows you to write **templates**, using **variables**, **modifiers**, **func
<p> <p>
The number of pixels is: {math equation="x * y" x=$height y=$width}. The number of pixels is: {math equation="x * y" x=$height y=$width}.
</p> </p>
<p>
{if $email matches "/^[^@]+@[^@]+\.[^@]+$/"}
Valid email address
{else}
Please enter a valid email
{/if}
</p>
```
```html
<h1>Hello world</h1>
<p>
The number of pixels is: 307200.
</p>
<p>
Valid email address
</p>
``` ```
When this template is rendered, with the value "Hello world" for the variable $title, 640 for $width, When this template is rendered, with the value "Hello world" for the variable $title, 640 for $width,

View file

@ -127,8 +127,6 @@ class File extends Base
&& (!function_exists('ini_get') || strlen(ini_get('opcache.restrict_api'))) < 1 && (!function_exists('ini_get') || strlen(ini_get('opcache.restrict_api'))) < 1
) { ) {
opcache_invalidate($_template->getCached()->filepath, true); opcache_invalidate($_template->getCached()->filepath, true);
} elseif (function_exists('apc_compile_file')) {
apc_compile_file($_template->getCached()->filepath);
} }
$cached = $_template->getCached(); $cached = $_template->getCached();
$cached->timestamp = $cached->exists = is_file($cached->filepath); $cached->timestamp = $cached->exists = is_file($cached->filepath);
@ -223,10 +221,8 @@ class File extends Base
$_filepath = (string)$_file; $_filepath = (string)$_file;
// directory ? // directory ?
if ($_file->isDir()) { if ($_file->isDir()) {
if (!$_cache->isDot()) {
// delete folder if empty // delete folder if empty
@rmdir($_file->getPathname()); @rmdir($_file->getPathname());
}
} else { } else {
// delete only php files // delete only php files
if (substr($_filepath, -4) !== '.php') { if (substr($_filepath, -4) !== '.php') {
@ -279,8 +275,6 @@ class File extends Base
&& (!function_exists('ini_get') || strlen(ini_get("opcache.restrict_api")) < 1) && (!function_exists('ini_get') || strlen(ini_get("opcache.restrict_api")) < 1)
) { ) {
opcache_invalidate($_filepath, true); opcache_invalidate($_filepath, true);
} elseif (function_exists('apc_delete_file')) {
apc_delete_file($_filepath);
} }
} }
} }

View file

@ -160,6 +160,7 @@ class TemplateLexer
'LOGOP' => '"<", "==" ... logical operator', 'LOGOP' => '"<", "==" ... logical operator',
'TLOGOP' => '"lt", "eq" ... logical operator; "is div by" ... if condition', 'TLOGOP' => '"lt", "eq" ... logical operator; "is div by" ... if condition',
'SCOND' => '"is even" ... if condition', 'SCOND' => '"is even" ... if condition',
'MATCHES' => '"matches" regex operator',
); );
/** /**
@ -567,7 +568,7 @@ class TemplateLexer
public function yylex3() public function yylex3()
{ {
if (!isset($this->yy_global_pattern3)) { if (!isset($this->yy_global_pattern3)) {
$this->yy_global_pattern3 = $this->replace("/\G(\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal)|\G([\"])|\G('[^'\\\\]*(?:\\\\.[^'\\\\]*)*')|\G([$][0-9]*[a-zA-Z_]\\w*)|\G([$])|\G(\\s+is\\s+(not\\s+)?in\\s+)|\G(\\s+as\\s+)|\G(\\s+to\\s+)|\G(\\s+step\\s+)|\G(\\s+instanceof\\s+)|\G(\\s*([!=][=]{1,2}|[<][=>]?|[>][=]?|[&|]{2})\\s*)|\G(\\s+(eq|ne|neq|gt|ge|gte|lt|le|lte|mod|and|or|xor)\\s+)|\G(\\s+is\\s+(not\\s+)?(odd|even|div)\\s+by\\s+)|\G(\\s+is\\s+(not\\s+)?(odd|even))|\G([!]\\s*|not\\s+)|\G([(](int(eger)?|bool(ean)?|float|double|real|string|binary|array|object)[)]\\s*)|\G(\\s*[(]\\s*)|\G(\\s*[)])|\G(\\[\\s*)|\G(\\s*\\])|\G(\\s*[-][>]\\s*)|\G(\\s*[=][>]\\s*)|\G(\\s*[=]\\s*)|\G(([+]|[-]){2})|\G(\\s*([+]|[-])\\s*)|\G(\\s*([*]{1,2}|[%\/^&]|[<>]{2})\\s*)|\G([@])|\G(array\\s*[(]\\s*)|\G([#])|\G(\\s+[0-9]*[a-zA-Z_][a-zA-Z0-9_\-:]*\\s*[=]\\s*)|\G(([0-9]*[a-zA-Z_]\\w*)?(\\\\[0-9]*[a-zA-Z_]\\w*)+)|\G([0-9]*[a-zA-Z_]\\w*)|\G(\\d+)|\G([`])|\G([|][@]?)|\G([.])|\G(\\s*[,]\\s*)|\G(\\s*[;]\\s*)|\G([:]{2})|\G(\\s*[:]\\s*)|\G(\\s*[?]\\s*)|\G(0[xX][0-9a-fA-F]+)|\G(\\s+)|\G([\S\s])/isS"); $this->yy_global_pattern3 = $this->replace("/\G(\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal)|\G([\"])|\G('[^'\\\\]*(?:\\\\.[^'\\\\]*)*')|\G([$][0-9]*[a-zA-Z_]\\w*)|\G([$])|\G(\\s+is\\s+(not\\s+)?in\\s+)|\G(\\s+matches\\s+)|\G(\\s+as\\s+)|\G(\\s+to\\s+)|\G(\\s+step\\s+)|\G(\\s+instanceof\\s+)|\G(\\s*([!=][=]{1,2}|[<][=>]?|[>][=]?|[&|]{2})\\s*)|\G(\\s+(eq|ne|neq|gt|ge|gte|lt|le|lte|mod|and|or|xor)\\s+)|\G(\\s+is\\s+(not\\s+)?(odd|even|div)\\s+by\\s+)|\G(\\s+is\\s+(not\\s+)?(odd|even))|\G([!]\\s*|not\\s+)|\G([(](int(eger)?|bool(ean)?|float|double|real|string|binary|array|object)[)]\\s*)|\G(\\s*[(]\\s*)|\G(\\s*[)])|\G(\\[\\s*)|\G(\\s*\\])|\G(\\s*[-][>]\\s*)|\G(\\s*[=][>]\\s*)|\G(\\s*[=]\\s*)|\G(([+]|[-]){2})|\G(\\s*([+]|[-])\\s*)|\G(\\s*([*]{1,2}|[%\/^&]|[<>]{2})\\s*)|\G([@])|\G(array\\s*[(]\\s*)|\G([#])|\G(\\s+[0-9]*[a-zA-Z_][a-zA-Z0-9_\-:]*\\s*[=]\\s*)|\G(([0-9]*[a-zA-Z_]\\w*)?(\\\\[0-9]*[a-zA-Z_]\\w*)+)|\G([0-9]*[a-zA-Z_]\\w*)|\G(\\d+)|\G([`])|\G([|][@]?)|\G([.])|\G(\\s*[,]\\s*)|\G(\\s*[;]\\s*)|\G([:]{2})|\G(\\s*[:]\\s*)|\G(\\s*[?]\\s*)|\G(0[xX][0-9a-fA-F]+)|\G(\\s+)|\G([\S\s])/isS");
} }
if (!isset($this->dataLength)) { if (!isset($this->dataLength)) {
$this->dataLength = strlen($this->data); $this->dataLength = strlen($this->data);
@ -662,119 +663,124 @@ class TemplateLexer
public function yy_r3_10() public function yy_r3_10()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_AS; $this->token = \Smarty\Parser\TemplateParser::TP_MATCHES;
} }
public function yy_r3_11() public function yy_r3_11()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_TO; $this->token = \Smarty\Parser\TemplateParser::TP_AS;
} }
public function yy_r3_12() public function yy_r3_12()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_STEP; $this->token = \Smarty\Parser\TemplateParser::TP_TO;
} }
public function yy_r3_13() public function yy_r3_13()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_INSTANCEOF; $this->token = \Smarty\Parser\TemplateParser::TP_STEP;
} }
public function yy_r3_14() public function yy_r3_14()
{
$this->token = \Smarty\Parser\TemplateParser::TP_INSTANCEOF;
}
public function yy_r3_15()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_LOGOP; $this->token = \Smarty\Parser\TemplateParser::TP_LOGOP;
} }
public function yy_r3_16() public function yy_r3_17()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_SLOGOP; $this->token = \Smarty\Parser\TemplateParser::TP_SLOGOP;
} }
public function yy_r3_18() public function yy_r3_19()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_TLOGOP; $this->token = \Smarty\Parser\TemplateParser::TP_TLOGOP;
} }
public function yy_r3_21() public function yy_r3_22()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_SINGLECOND; $this->token = \Smarty\Parser\TemplateParser::TP_SINGLECOND;
} }
public function yy_r3_24() public function yy_r3_25()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_NOT; $this->token = \Smarty\Parser\TemplateParser::TP_NOT;
} }
public function yy_r3_25() public function yy_r3_26()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_TYPECAST; $this->token = \Smarty\Parser\TemplateParser::TP_TYPECAST;
} }
public function yy_r3_29() public function yy_r3_30()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_OPENP; $this->token = \Smarty\Parser\TemplateParser::TP_OPENP;
} }
public function yy_r3_30() public function yy_r3_31()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_CLOSEP; $this->token = \Smarty\Parser\TemplateParser::TP_CLOSEP;
} }
public function yy_r3_31() public function yy_r3_32()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_OPENB; $this->token = \Smarty\Parser\TemplateParser::TP_OPENB;
} }
public function yy_r3_32() public function yy_r3_33()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_CLOSEB; $this->token = \Smarty\Parser\TemplateParser::TP_CLOSEB;
} }
public function yy_r3_33() public function yy_r3_34()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_PTR; $this->token = \Smarty\Parser\TemplateParser::TP_PTR;
} }
public function yy_r3_34() public function yy_r3_35()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_APTR; $this->token = \Smarty\Parser\TemplateParser::TP_APTR;
} }
public function yy_r3_35() public function yy_r3_36()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_EQUAL; $this->token = \Smarty\Parser\TemplateParser::TP_EQUAL;
} }
public function yy_r3_36() public function yy_r3_37()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_INCDEC; $this->token = \Smarty\Parser\TemplateParser::TP_INCDEC;
} }
public function yy_r3_38() public function yy_r3_39()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_UNIMATH; $this->token = \Smarty\Parser\TemplateParser::TP_UNIMATH;
} }
public function yy_r3_40() public function yy_r3_41()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_MATH; $this->token = \Smarty\Parser\TemplateParser::TP_MATH;
} }
public function yy_r3_42() public function yy_r3_43()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_AT; $this->token = \Smarty\Parser\TemplateParser::TP_AT;
} }
public function yy_r3_43() public function yy_r3_44()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_ARRAYOPEN; $this->token = \Smarty\Parser\TemplateParser::TP_ARRAYOPEN;
} }
public function yy_r3_44() public function yy_r3_45()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_HATCH; $this->token = \Smarty\Parser\TemplateParser::TP_HATCH;
} }
public function yy_r3_45() public function yy_r3_46()
{ {
// resolve conflicts with shorttag and right_delimiter starting with '=' // resolve conflicts with shorttag and right_delimiter starting with '='
@ -786,73 +792,73 @@ class TemplateLexer
$this->token = \Smarty\Parser\TemplateParser::TP_ATTR; $this->token = \Smarty\Parser\TemplateParser::TP_ATTR;
} }
} }
public function yy_r3_46() public function yy_r3_47()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_NAMESPACE; $this->token = \Smarty\Parser\TemplateParser::TP_NAMESPACE;
} }
public function yy_r3_49() public function yy_r3_50()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_ID; $this->token = \Smarty\Parser\TemplateParser::TP_ID;
} }
public function yy_r3_50() public function yy_r3_51()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_INTEGER; $this->token = \Smarty\Parser\TemplateParser::TP_INTEGER;
} }
public function yy_r3_51() public function yy_r3_52()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_BACKTICK; $this->token = \Smarty\Parser\TemplateParser::TP_BACKTICK;
$this->yypopstate(); $this->yypopstate();
} }
public function yy_r3_52() public function yy_r3_53()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_VERT; $this->token = \Smarty\Parser\TemplateParser::TP_VERT;
} }
public function yy_r3_53() public function yy_r3_54()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_DOT; $this->token = \Smarty\Parser\TemplateParser::TP_DOT;
} }
public function yy_r3_54() public function yy_r3_55()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_COMMA; $this->token = \Smarty\Parser\TemplateParser::TP_COMMA;
} }
public function yy_r3_55() public function yy_r3_56()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_SEMICOLON; $this->token = \Smarty\Parser\TemplateParser::TP_SEMICOLON;
} }
public function yy_r3_56() public function yy_r3_57()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_DOUBLECOLON; $this->token = \Smarty\Parser\TemplateParser::TP_DOUBLECOLON;
} }
public function yy_r3_57() public function yy_r3_58()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_COLON; $this->token = \Smarty\Parser\TemplateParser::TP_COLON;
} }
public function yy_r3_58() public function yy_r3_59()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_QMARK; $this->token = \Smarty\Parser\TemplateParser::TP_QMARK;
} }
public function yy_r3_59() public function yy_r3_60()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_HEX; $this->token = \Smarty\Parser\TemplateParser::TP_HEX;
} }
public function yy_r3_60() public function yy_r3_61()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_SPACE; $this->token = \Smarty\Parser\TemplateParser::TP_SPACE;
} }
public function yy_r3_61() public function yy_r3_62()
{ {
$this->token = \Smarty\Parser\TemplateParser::TP_TEXT; $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;

View file

@ -160,6 +160,7 @@ class TemplateLexer
'LOGOP' => '"<", "==" ... logical operator', 'LOGOP' => '"<", "==" ... logical operator',
'TLOGOP' => '"lt", "eq" ... logical operator; "is div by" ... if condition', 'TLOGOP' => '"lt", "eq" ... logical operator; "is div by" ... if condition',
'SCOND' => '"is even" ... if condition', 'SCOND' => '"is even" ... if condition',
'MATCHES' => '"matches" regex operator',
); );
/** /**
@ -325,6 +326,7 @@ class TemplateLexer
tlop = ~\s+is\s+(not\s+)?(odd|even|div)\s+by\s+~ tlop = ~\s+is\s+(not\s+)?(odd|even|div)\s+by\s+~
scond = ~\s+is\s+(not\s+)?(odd|even)~ scond = ~\s+is\s+(not\s+)?(odd|even)~
isin = ~\s+is\s+(not\s+)?in\s+~ isin = ~\s+is\s+(not\s+)?in\s+~
matches = ~\s+matches\s+~
as = ~\s+as\s+~ as = ~\s+as\s+~
to = ~\s+to\s+~ to = ~\s+to\s+~
step = ~\s+step\s+~ step = ~\s+step\s+~
@ -469,6 +471,9 @@ class TemplateLexer
isin { isin {
$this->token = \Smarty\Parser\TemplateParser::TP_ISIN; $this->token = \Smarty\Parser\TemplateParser::TP_ISIN;
} }
matches {
$this->token = \Smarty\Parser\TemplateParser::TP_MATCHES;
}
as { as {
$this->token = \Smarty\Parser\TemplateParser::TP_AS; $this->token = \Smarty\Parser\TemplateParser::TP_AS;
} }

File diff suppressed because it is too large Load diff

View file

@ -681,6 +681,11 @@ expr(res) ::= expr(e1) isin(c) value(v). {
res = c . e1.',(array)'.v.')'; res = c . e1.',(array)'.v.')';
} }
// regex matching
expr(res) ::= expr(e1) matchop(c) value(e2). {
res = c . e2 . ',' . e1 . ') ';
}
// null coalescing // null coalescing
nullcoalescing(res) ::= expr(v) QMARK QMARK expr(e2). { nullcoalescing(res) ::= expr(v) QMARK QMARK expr(e2). {
res = v.' ?? '.e2; res = v.' ?? '.e2;
@ -1062,6 +1067,16 @@ object(res) ::= varindexed(vi) objectchain(oc). {
} }
} }
// optional objectchain - empty
optobjectchain(res) ::= . {
res = '';
}
// optional objectchain - present
optobjectchain(res) ::= objectchain(oc). {
res = oc;
}
// single element // single element
objectchain(res) ::= objectelement(oe). { objectchain(res) ::= objectelement(oe). {
res = oe; res = oe;
@ -1111,7 +1126,7 @@ objectelement(res)::= PTR method(f). {
// //
// function // function
// //
function(res) ::= ns1(f) OPENP variablelist(v) CLOSEP. { function(res) ::= ns1(f) OPENP variablelist(v) CLOSEP optobjectchain(oc). {
if (f == 'isset') { if (f == 'isset') {
res = '(true'; res = '(true';
@ -1125,15 +1140,15 @@ function(res) ::= ns1(f) OPENP variablelist(v) CLOSEP. {
res .= ' && (' . $value . ' !== null)'; res .= ' && (' . $value . ' !== null)';
} }
} }
res .= ')'; res .= ')' . oc;
} elseif (f == 'empty') { } elseif (f == 'empty') {
if (count(v) != 1) { if (count(v) != 1) {
throw new CompilerException("Invalid number of arguments for empty. empty expects at exactly one parameter."); throw new CompilerException("Invalid number of arguments for empty. empty expects at exactly one parameter.");
} }
if (is_array(v[0])) { if (is_array(v[0])) {
res .= '( !' . v[0][0] . ' || empty(' . v[0][1] . '))'; res = '( !' . v[0][0] . ' || empty(' . v[0][1] . '))' . oc;
} else { } else {
res = 'false == ' . v[0]; res = 'false == ' . v[0] . oc;
} }
} else { } else {
$p = array(); $p = array();
@ -1144,7 +1159,7 @@ function(res) ::= ns1(f) OPENP variablelist(v) CLOSEP. {
$p[] = $value; $p[] = $value;
} }
} }
res = $this->compiler->compileModifierInExpression(f, $p); res = $this->compiler->compileModifierInExpression(f, $p) . oc;
} }
} }
@ -1244,6 +1259,11 @@ static_class_access(res) ::= ID(v). {
res = array(v, ''); res = array(v, '');
} }
// static class constant with object chain
static_class_access(res) ::= ID(v) objectchain(oc). {
res = array(v, oc);
}
// static class variables // static class variables
static_class_access(res) ::= DOLLARID(v) arrayindex(a). { static_class_access(res) ::= DOLLARID(v) arrayindex(a). {
res = array(v, a, 'property'); res = array(v, a, 'property');
@ -1303,6 +1323,10 @@ scond(res) ::= SINGLECOND(o). {
res = $scond[$op]; res = $scond[$op];
} }
matchop(res) ::= MATCHES(o). {
res = 'preg_match(';
}
// //
// ARRAY element assignment // ARRAY element assignment
// //

View file

@ -54,7 +54,7 @@ class Smarty extends \Smarty\TemplateBase {
/** /**
* smarty version * smarty version
*/ */
const SMARTY_VERSION = '5.7.0'; const SMARTY_VERSION = '5.8.0';
/** /**
* define caching modes * define caching modes
@ -1344,10 +1344,8 @@ class Smarty extends \Smarty\TemplateBase {
} }
$_filepath = (string)$_file; $_filepath = (string)$_file;
if ($_file->isDir()) { if ($_file->isDir()) {
if (!$_compile->isDot()) {
// delete folder if empty // delete folder if empty
@rmdir($_file->getPathname()); @rmdir($_file->getPathname());
}
} else { } else {
// delete only php files // delete only php files
if (substr($_filepath, -4) !== '.php') { if (substr($_filepath, -4) !== '.php') {
@ -1385,8 +1383,6 @@ class Smarty extends \Smarty\TemplateBase {
&& (!function_exists('ini_get') || strlen(ini_get('opcache.restrict_api')) < 1) && (!function_exists('ini_get') || strlen(ini_get('opcache.restrict_api')) < 1)
) { ) {
opcache_invalidate($_filepath, true); opcache_invalidate($_filepath, true);
} elseif (function_exists('apc_delete_file')) {
apc_delete_file($_filepath);
} }
} }
} }

View file

@ -251,8 +251,6 @@ class Compiled extends GeneratedPhpFile {
&& (!function_exists('ini_get') || strlen(ini_get("opcache.restrict_api")) < 1) && (!function_exists('ini_get') || strlen(ini_get("opcache.restrict_api")) < 1)
) { ) {
opcache_invalidate($this->filepath, true); opcache_invalidate($this->filepath, true);
} elseif (function_exists('apc_compile_file')) {
apc_compile_file($this->filepath);
} }
} }
if (defined('HHVM_VERSION')) { if (defined('HHVM_VERSION')) {

View file

@ -13,7 +13,7 @@
<li><a href="#">Recruter</a> <li><a href="#">Recruter</a>
<li><a href="#">Partenariat</a> <li><a href="#">Partenariat</a>
<li><a href="#">Blog</a> <li><a href="#">Blog</a>
<li><a href="#">Aide et support</a> <li><a href="index.php?ctrl=page&action=help">Aide et support</a>
</ul> </ul>
</div> </div>
<div class="col-3"> <div class="col-3">

View file

@ -35,7 +35,7 @@
</li> </li>
{if isset($smarty.session.user)} {if isset($smarty.session.user)}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="?ctrl=project&action=project">Ajouter un projet</a> <a class="nav-link" href="?ctrl=project&action=addedit_project">Ajouter un projet</a>
</li> </li>
{if $smarty.session.user.authorisation_name == "Administrateur"} {if $smarty.session.user.authorisation_name == "Administrateur"}
<li class="nav-item"> <li class="nav-item">

62
views/addedit_project.tpl Normal file
View file

@ -0,0 +1,62 @@
{extends file="views/layout.tpl"}
{block name="content"}
<section class="container mt-4">
{if isset($smarty.session.user)}
<form class="m-2" method="POST" enctype="multipart/form-data" onsubmit="return confirm('Êtes-vous certain de vouloir poster votre projet ?')">
<div class="mb-3">
<label class="form-label">Titre</label>
<input type="text" name="title" class="form-control {if (isset($arrError['title'])) } is-invalid {/if} " value="{if $objProject}{$objProject->getTitle()}{/if}" required>
</div>
<div class="mb-3">
<label class="form-label">Description (courte)</label>
<textarea name="description" class="form-control {if (isset($arrError['descProject'])) } is-invalid {/if} " rows="2">{if $objProject}{$objProject->getDescription()}{/if}</textarea>
</div>
<div class="mb-3">
<label class="form-label">Texte complet du Portfolio</label>
<textarea name="content" class="form-control {if (isset($arrError['content'])) } is-invalid {/if}" rows="5">{if $objProject}{$objProject->getContent()}{/if}</textarea>
</div>
<div class="col-6 mb-3">
<fieldset class="border p-2 rounded">
<legend class="form-label h6">Catégorie</legend>
<select class="form-select" id="category" name="category">
<option value="0" {if $objProject->getCategory() == 0}selected{/if}>Toutes les catégories</option>
{foreach $arrCategory as $arrDetCategory}
<option value="{$arrDetCategory['category_id']}"
{if $objProject->getCategory() == $arrDetCategory['category_id']}selected{/if}>
{$arrDetCategory['category_name']}
</option>
{/foreach}
</select>
</fieldset>
</div>
<div class="mb-3">
<label class="h5">Miniature de votre projet</label>
<input name="thumbnail" class="form-control" type="file">
{if $objProject && $objProject->getId()}
<label class="h5">Miniature actuelle :</label>
<img src="uploads/projects/{$objProject->getThumbnail()}" alt="Miniature">
{/if}
</div>
<div class="mb-3">
<label class="h5">Images de votre projet</label>
<input name="imageProject" class="form-control" type="file" multiple>
</div>
<div class="mt-4">
<button type="submit" class="btn btn-primary btn-md" name="sendProject">Envoyer</button>
</div>
</form>
{else}
<div class="alert alert-danger mt-5">
<p class="mb-0">Vous devez vous connecter pour accéder à cette fonctionnalité</p>
</div>
{/if}
</section>
{/block}

246
views/help.tpl Normal file
View file

@ -0,0 +1,246 @@
{extends file="views/layout.tpl"}
{block name="content"}
<h1 class="display-2 text-center">Bienvenue sur l'aide & support de Folliow !</h1>
<br>
<p class="display-6 text-center">Ici, vous trouverez de l'aide pour naviguer sur notre site.</p>
<br>
<section class="container">
{if isset($smarty.session.user)}
{if $smarty.session.user.user_status == 3}
<div class="accordion" id="accordionExample">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
Comment créer un nouveau projet ?
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show" data-bs-parent="#accordionExample">
<div class="accordion-body">
<p>Un haut, dans votre barre de navigation, vous avez un lien pour ajouter un nouveau projet.
Il faudra remplir votre projet avec un titre, une description, des images (jusqu'à 20 par projet) et une image de présentation qui apparaitra en illustration dans la liste des projets.<br>
Plus votre projet est rempli, plus il sera intéressant pour les autres profils, alors n'hésitez pas à bien le remplir !</p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
Comment modifier mon projet ?
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<p>Une fois votre projet créé, vous avez la possibilité de modifier son contenu. Si vous êtes bien connecté à votre compte, vous pouvez aller sur votre profil et dans la liste de vos projets,
il vous suffira de cliquer sur "éditer" pour voir toutes les informations apparaitres et changer directement ce que vous souhaitez modifier.
N'oubliez pas de valider vos modifications !</p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
Comment modifier mes informations personnelles ?
</button>
</h2>
<div id="collapseThree" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<p>Si vous êtes bien connecté à votre compte, vous pouvez cliquer sur votre photo de profil en haut à droite de votre écran pour accéder à votre profil.
Vous pourrez ensuite modifier vos informations directement depuis votre profil !
Simple comme "bonjour" :) </p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
Nous contacter
</button>
</h2>
<div id="collapseFour" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<p>Si vous avez des questions ? Un problème avec votre compte ?
Vous souhaitez supprimer toutes vos données ou débloquer votre compte suite à une modération ? N'hésitez pas à nous écrire à <a href="mailto:projet.folliow@hotmail.com">projet.folliow@hotmail.com </a> !</p>
</div>
</div>
</div>
</div>
{elseif $smarty.session.user.user_status == 2}
<div class="accordion" id="accordionExample">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
Comment vérifier le statut d'un projet ou d'une image ?
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show" data-bs-parent="#accordionExample">
<div class="accordion-body">
<p>Bienvenue dans notre team de modérateurs ! Vous avez maintenant accès à de nouvelles fonctionnalités.
Vous verez le statut de chaque projet et image en dessous de chacun. Si rien n'est mentionné, celà signifie que le projet est déjà approuvé !</p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
Comment modérer du contenu ?
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<p>Très bonne question ! En plus du statut du projet ou de l'image, vous avez également accès à 3 boutons : approuver, refuser et supprimer.
Si le projet est valide à nos conditions d'utilisation de Folliow, vous pouvez l'approuver. Si vous pensez qu'il faut légèrement modifier ou adapter, vous pouvez le refuser et l'utilisateur pourra modifier son projet.</p>
<p>Et si le projet enfreint nos règles d'utilisation, vous pouvez supprimer le projet de l'utilisateur. Attention, cette action est définitive.
N'hésitez pas à signaler un utilisateur à l'administrateur en cas de besoin !</p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
Comment modérer une image ?
</button>
</h2>
<div id="collapseThree" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
De la même manière que vous pouvez modérer un projet ! Dans la liste des images, vous aurez la possibilité de refuser, approuver ou bien supprimer une image.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
Nous contacter
</button>
</h2>
<div id="collapseFour" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<p>Si vous avez des questions ? Une fonctionnalité que vous avez du mal à comprendre ? Des suggestions d'amélioration ? N'hésitez pas à nous écrire à <a href="mailto:projet.folliow@hotmail.com">projet.folliow@hotmail.com </a>! </p>
</div>
</div>
</div>
</div>
{elseif $smarty.session.user.user_status == 1}
<div class="accordion" id="accordionExample">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
Comment accéder à mon espace administrateur ?
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show" data-bs-parent="#accordionExample">
<div class="accordion-body">
<p>Rien de plus simple ! Si vous êtes connecté à votre compte, un lien <a href="index.php?ctrl=admin&action=admin">"admin"</a> sera visible dans votre barre de navigation.
Cliquez dessus et vous voici dans l'antre réservée à nos fabuleux administrateurs !</p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
Comment gérer un utilisateur ?
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<p>La première fonctionnalité accessible sur votre Dashbord est la maintenance utilisateur. Vous pouvez dans le menu déroulant seléctionner l'utilisateur concerné par votre action.
Un nouveau modérateurs rejoint l'équipe ? Il suffira ensuite de choisir dans le menu déroulant, son nouveau statut ! N'oubliez pas de valider la modification !</p>
<p>Oh, oh... Quelqu'un n'a pas respecté nos règles de modération et vous devez maintenant supprimer cette utilisateur... Après l'avoir séléctionné, vous pouvez appuyer sur le bouton "supprimer l'utilisateur".
Nous vous demanderons une confirmation avant de finaliser la suppression. Attention ! Cette action est définitive et l'utilisateur perdra tous ses projets !</p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
Comment modifier ou créer une catégorie de projet ?
</button>
</h2>
<div id="collapseThree" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<p>Voici la seconde partie de votre Dashbord. Si celà vous semble judicieux ou suite à des suggestions d'utilisateurs, vous pouvez modifier l'intitulé d'une catégorie.
Dans le menu déroulant, choisissez la catégorie concerncée, puis écrivez son nouveau nom dans le champ "nouveau nom de la catégorie". N'oubliez pas de valider le changement !</p>
<p>Et si vous souhaitez créer une nouvelle catégorie, écrivez son intitulé dans le champ "nom de la nouvelle catégorie" et validez la création. Votre nouvelle catégorie apparait déjà dans le menu déroulant !</p>
<p>La suppression d'une catégorie n'est pas possible, car beaucoup de projet se retrouveraient orphelins :( Mais avec la modification et la création, vous avez les outils nécessaire pour organiser le classement de tous nos beaux projets.</p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
Nous contacter
</button>
</h2>
<div id="collapseFour" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<p> Vous avez des questions ? Un problème avec votre espace administrateur ou une suggestion d'amélioration ? N'hésitez pas à nous écrire à <a href="mailto:projet.folliow@hotmail.com">projet.folliow@hotmail.com </a> !<p>
</div>
</div>
</div>
</div>
{/if}
{else}
<div class="accordion" id="accordionExample">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
Qu'est-ce que Folliow ?
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show" data-bs-parent="#accordionExample">
<div class="accordion-body">
<p>Folliow est un réseau social de portfolio. Il vous permet de rassembler et mettre en avant tous les projets que vous avez effectué !
Et si vous êtes rectruteur à la recherche de talents, vous pourrez rechercher les profils en fonction de vos besoins, partager leur contenus et les contacter si vous souhaitez collaborer avec eux.</p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
Comment créer un compte
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<p>En haut à droite de votre écran, vous avez le lien <a href="index.php?ctrl=user&action=signin">"s'inscrire"</a>. Nous demandons certaines informations obligatoires pour l'identification,
ensuite à vous de remplir les informations complémentaires pour alimenter votre profil et permettre aux autres utilisateurs de mieux vous connaitre et vous contacter.</p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
Partager un projet
</button>
</h2>
<div id="collapseThree" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<p>Lorsque vous consultez un projet, vous pouvez remplir le champ "email" avec l'email de la personne à qui vous souhaitez partager le projet.
Votre contact recevra en moins de 5 minutes, un mail de notre part avec le lien vers le projet en question ! Pratique, n'est-ce pas ?</p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
Nous contacter
</button>
</h2>
<div id="collapseFour" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<p>Si vous avez des questions, n'hésitez pas à nous écrire à <a href="mailto:projet.folliow@hotmail.com">projet.folliow@hotmail.com </a>!</p>
</div>
</div>
</div>
</div>
{/if}
</section>
<br>
<br>
{/block}

View file

@ -1,7 +1,8 @@
{include file="views/_partial/header.tpl"} {include file="views/_partial/header.tpl"}
<main class="container">
{block name="content"} {block name="content"}
{/block} {/block}
</main>
{include file="views/_partial/footer.tpl"} {include file="views/_partial/footer.tpl"}

26
views/mail_message.tpl Normal file
View file

@ -0,0 +1,26 @@
<p>Bonjour,</p>
<p>
Un projet a été partagé avec vous via la plateforme <strong>Folliow</strong>.
</p>
<h3>{$projectTitle}</h3>
<p>
{$projectDescription}
</p>
<p>
Vous pouvez consulter le projet en cliquant sur le lien ci-dessous :
</p>
<p>
<a href="{$projectUrl}">
{$projectUrl}
</a>
</p>
<p>
Cordialement,<br>
Léquipe Folliow
</p>

287
views/mentions.tpl Normal file
View file

@ -0,0 +1,287 @@
{extends file="views/layout.tpl"}
{block name="title" append}Mentions légales{/block}
{block name="h2"}Mentions légales{/block}
{block name="p"}Informations légales et politique de confidentialité{/block}
{block name="date_maj"}
<p class="text-muted small mb-0">
<i class="fas fa-calendar-alt me-2" aria-hidden="true"></i>
Dernière mise à jour : <time datetime="2026-02-20">20 février 2026</time>
</p>
{/block}
{block name="js_footer" append}
<script>
{literal}
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
target.focus();
}
});
});
{/literal}
</script>
{/block}
{block name="content"}
<section aria-label="Mentions légales">
<h2 class="visually-hidden">Mentions légales</h2>
<div class="row g-5">
<div class="col-md-12">
<!-- Table des matières -->
<nav class="table-of-contents" aria-labelledby="toc-heading">
<h3 id="toc-heading" class="h5 mb-3">
<i class="fas fa-list me-2" aria-hidden="true"></i>
Sommaire
</h3>
<ul class="list-unstyled ms-3">
<li class="mb-2"><a href="#editeur">1. Éditeur du site</a></li>
<li class="mb-2"><a href="#hebergeur">2. Hébergement</a></li>
<li class="mb-2"><a href="#propriete">3. Propriété intellectuelle</a></li>
<li class="mb-2"><a href="#rgpd">4. Protection des données personnelles (RGPD)</a></li>
<li class="mb-2"><a href="#cookies">5. Cookies</a></li>
<li class="mb-2"><a href="#responsabilite">6. Limitation de responsabilité</a></li>
<li class="mb-2"><a href="#liens">7. Liens hypertextes</a></li>
<li class="mb-2"><a href="#droit">8. Droit applicable</a></li>
</ul>
</nav>
<!-- Section 1 : Éditeur -->
<section id="editeur" class="legal-section" aria-labelledby="editeur-heading">
<h3 id="editeur-heading" class="h4">
<i class="fas fa-building me-2 text-primary" aria-hidden="true"></i>
1. Éditeur du site
</h3>
<p class="text-muted">
FOLLIOW est un <strong>projet pédagogique</strong> réalisé dans le cadre dun exercice de formation.
</p>
<dl class="row">
<dt class="col-sm-3">Nom du projet</dt>
<dd class="col-sm-9">FOLLIOW</dd>
<dt class="col-sm-3">Nature</dt>
<dd class="col-sm-9">Projet scolaire / démonstration technique</dd>
<dt class="col-sm-3">Établissement</dt>
<dd class="col-sm-9">FOLLIOW</dd>
<dt class="col-sm-3">Forme juridique</dt>
<dd class="col-sm-9">Non applicable (projet pédagogique)</dd>
<dt class="col-sm-3">Capital social</dt>
<dd class="col-sm-9">Non applicable</dd>
<dt class="col-sm-3">Adresse</dt>
<dd class="col-sm-9">4 Rue du Rhin, 68000 Colmar</dd>
<dt class="col-sm-3">Téléphone</dt>
<dd class="col-sm-9">03 68 67 20 00</dd>
<dt class="col-sm-3">Email</dt>
<dd class="col-sm-9">
<a href="mailto:projet.folliow@hotmail.com">projet.folliow@hotmail.com</a>
</dd>
<dt class="col-sm-3">Directeur de publication</dt>
<dd class="col-sm-9">Léquipe du projet FOLLIOW</dd>
<dt class="col-sm-3">SIRET</dt>
<dd class="col-sm-9">Non applicable (projet scolaire)</dd>
</dl>
</section>
<!-- Section 2 : Hébergeur -->
<section id="hebergeur" class="legal-section" aria-labelledby="hebergeur-heading">
<h3 id="hebergeur-heading" class="h4">
<i class="fas fa-server me-2 text-primary" aria-hidden="true"></i>
2. Hébergement
</h3>
<p>Le site est hébergé sur une infrastructure pédagogique mise à disposition pour le projet :</p>
<dl class="row">
<dt class="col-sm-3">Hébergeur</dt>
<dd class="col-sm-9">OVH<dd>
<dt class="col-sm-3">Domaine / accès</dt>
<dd class="col-sm-9">php.boulayoune.com</dd>
<dt class="col-sm-3">Adresse</dt>
<dd class="col-sm-9">4 Rue du Rhin, 68000 Colmar</dd>
<dt class="col-sm-3">Téléphone</dt>
<dd class="col-sm-9">03 68 67 20 00</dd>
</dl>
</section>
<!-- Section 3 : Propriété intellectuelle -->
<section id="propriete" class="legal-section" aria-labelledby="propriete-heading">
<h3 id="propriete-heading" class="h4">
<i class="fas fa-copyright me-2 text-primary" aria-hidden="true"></i>
3. Propriété intellectuelle
</h3>
<p>
Sauf mention contraire, lensemble des contenus présents sur FOLLIOW (textes, visuels, logo, éléments dinterface)
est utilisé dans le cadre du projet et reste la propriété de leurs auteurs respectifs.
</p>
<p>
Toute reproduction ou réutilisation à des fins commerciales est interdite sans autorisation préalable.
</p>
<p>
Les éventuels contenus tiers (images, icônes, bibliothèques) restent soumis à leurs licences dorigine.
</p>
</section>
<!-- Section 4 : RGPD -->
<section id="rgpd" class="legal-section" aria-labelledby="rgpd-heading">
<h3 id="rgpd-heading" class="h4">
<i class="fas fa-shield-alt me-2 text-primary" aria-hidden="true"></i>
4. Protection des données personnelles (RGPD)
</h3>
<h4 class="h5 mt-4">4.1 Responsable du traitement</h4>
<p>
Le responsable du traitement est léquipe du projet FOLLIOW (projet pédagogique).
</p>
<h4 class="h5 mt-4">4.2 Données collectées</h4>
<p>Selon lutilisation du site, les données pouvant être collectées sont :</p>
<ul>
<li>Nom / Prenom <li>
<li>Pseudo </li>
<li>Adresse email (inscription, connexion, partage de projet par email)</li>
<li>Contenus déposés par lutilisateur (projets, descriptions, images)</li>
<li>Données techniques minimales (logs de sécurité, adresse IP) à des fins de protection et de diagnostic</li>
</ul>
<h4 class="h5 mt-4">4.3 Finalités du traitement</h4>
<ul>
<li>Création et gestion de compte</li>
<li>Publication et affichage de projets</li>
<li>Partage dun projet par email à la demande de lutilisateur</li>
<li>Sécurisation du site et prévention des abus</li>
</ul>
<h4 class="h5 mt-4">4.4 Durée de conservation</h4>
<p>
Dans le cadre de ce projet pédagogique, les données sont conservées pendant la durée du projet et des évaluations,
puis supprimées ou anonymisées, sauf obligation légale contraire.
</p>
<h4 class="h5 mt-4">4.5 Vos droits</h4>
<p>Conformément au RGPD, vous disposez de droits daccès, de rectification, deffacement et dopposition.</p>
<p>
Pour exercer ces droits, contactez-nous à :
<a href="mailto:projet.folliow@hotmail.com">projet.folliow@hotmail.com</a>
</p>
<p>
Vous pouvez également introduire une réclamation auprès de la CNIL
(<a href="https://www.cnil.fr" target="_blank" rel="noopener">www.cnil.fr</a>).
</p>
</section>
<!-- Section 5 : Cookies -->
<section id="cookies" class="legal-section" aria-labelledby="cookies-heading">
<h3 id="cookies-heading" class="h4">
<i class="fas fa-cookie-bite me-2 text-primary" aria-hidden="true"></i>
5. Cookies
</h3>
<p>
FOLLIOW peut utiliser des cookies <strong>strictement nécessaires</strong> au fonctionnement du site
(ex : session de connexion). Aucun cookie publicitaire nest utilisé.
</p>
<h4 class="h5 mt-4">5.1 Quest-ce quun cookie ?</h4>
<p>
Un cookie est un petit fichier texte déposé sur votre appareil lors de la visite dun site.
Il permet notamment de conserver une session ou des préférences.
</p>
<h4 class="h5 mt-4">5.2 Types de cookies utilisés</h4>
<ul>
<li><strong>Cookies techniques</strong> : indispensables (session, sécurité)</li>
<li><strong>Cookies de préférence</strong> : éventuels (langue, affichage) si implémentés</li>
</ul>
<h4 class="h5 mt-4">5.3 Gestion des cookies</h4>
<p>
Vous pouvez configurer votre navigateur pour refuser les cookies. Certaines fonctionnalités du site
peuvent alors ne pas fonctionner correctement.
</p>
</section>
<!-- Section 6 : Limitation de responsabilité -->
<section id="responsabilite" class="legal-section" aria-labelledby="responsabilite-heading">
<h3 id="responsabilite-heading" class="h4">
<i class="fas fa-exclamation-triangle me-2 text-primary" aria-hidden="true"></i>
6. Limitation de responsabilité
</h3>
<p>
Les informations présentées sur FOLLIOW sont fournies à titre démonstratif dans le cadre dun projet pédagogique.
Léquipe sefforce de maintenir le site accessible, sans garantie dabsence derreurs ou dinterruptions.
</p>
<p>
Léquipe FOLLIOW ne pourra être tenue responsable des dommages directs ou indirects résultant de lutilisation du site,
notamment en cas dindisponibilité ou de perte de données.
</p>
</section>
<!-- Section 7 : Liens hypertextes -->
<section id="liens" class="legal-section" aria-labelledby="liens-heading">
<h3 id="liens-heading" class="h4">
<i class="fas fa-link me-2 text-primary" aria-hidden="true"></i>
7. Liens hypertextes
</h3>
<p>
Le site peut contenir des liens vers des sites tiers. FOLLIOW nexerce aucun contrôle sur ces sites et décline toute
responsabilité quant à leur contenu ou leur disponibilité.
</p>
</section>
<!-- Section 8 : Droit applicable -->
<section id="droit" class="legal-section" aria-labelledby="droit-heading">
<h3 id="droit-heading" class="h4">
<i class="fas fa-gavel me-2 text-primary" aria-hidden="true"></i>
8. Droit applicable et juridiction compétente
</h3>
<p>
Les présentes mentions légales sont régies par le droit français. En cas de litige, et à défaut daccord amiable,
les tribunaux français seront seuls compétents.
</p>
</section>
<!-- Contact -->
<section class="mt-5 p-4 bg-light rounded" aria-labelledby="contact-legal">
<h3 id="contact-legal" class="h4 mb-3">
<i class="fas fa-envelope me-2 text-primary" aria-hidden="true"></i>
Questions ou réclamations
</h3>
<p>
Pour toute question concernant ces mentions légales ou pour exercer vos droits, vous pouvez nous contacter :
</p>
<ul class="list-unstyled">
<li class="mb-2">
<i class="fas fa-envelope me-2" aria-hidden="true"></i>
Par email : <a href="mailto:projet.folliow@hotmail.com">projet.folliow@hotmail.com</a>
</li>
</ul>
</section>
</div>
</div>
</section>
{/block}

View file

@ -1,75 +0,0 @@
{extends file="views/layout.tpl"}
{block name="content"}
<body>
<section class="container mt-5 p-5 d-flex flex-column align-items-center">
<div>
<h2>Alimenter votre projet</h2>
<form method="post">
<button type="submit" class="btn btn-primary btn-lg" name="showForm">+</button>
{if isset($smarty.post.toContinue)}
<button type="submit" class="btn btn-warning btn-lg" name="showFormContinue">Reprendre</button>
{/if}
</form>
{* Affichage d'un formulaire en cas d'appuie sur le bouton "+" *}
{if isset($smarty.post.showForm) || isset($smarty.post.showFormContinue)}
{if isset($smarty.session.user)}
<form class="m-2" method="post" enctype="multipart/form-data" onsubmit="return confirm('Voulez-vous vraiment envoyer le formulaire ?')">
<div>
<label>Titre</label>
<input type="text" name="titleProject">
</div>
<div>
<label>Description</label>
<input type="text" name="descProject">
</div>
<div>
<label>Texte Portfolio</label>
<input type="text" name="textProject">
</div>
<div>
<h3>Image thumbnail</h3>
<input name="imageThumbnail" class="form-control" type="file">
</div>
<div>
<h3>Image du projet</h3>
<input name="imageProject" class="form-control" type="file">
</div>
<div class="mt-2">
<button type="submit" class="btn btn-primary btn-md" name="sendMessage">Envoyer</button>
<button type="submit" class="btn btn-warning btn-md" name="toContinue">Remettre à plus tard</button>
</div>
</form>
{else}
<div class="border rounded mt-5">
<p class="text-danger p-2">Vous devez vous connecter pour accéder à cette fonctionnalité</p>
</div>
{/if}
{/if}
</div>
<div>
<h3>Description</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
<div>
<h3>Photos behind the scene</h3>
<div class="row mb-2">
<img>
</div>
</div>
<div>
<h3>Other projects</h3>
<div class="row mb-2">
{foreach $arrProjectToDisplay as $objProject}
{include file="../app/views/partials/preview.tpl"}
{/foreach}
</div>
</div>
</div>
<section>
</body>
{/block}

View file

@ -22,7 +22,7 @@
</p> </p>
<div class="mb-4 shadow-sm"> <div class="mb-4 shadow-sm">
<img src=".{$objProject->getThumbnail()}" <img src="{$objProject->getThumbnail()}"
class="img-fluid rounded w-100"> class="img-fluid rounded w-100">
</div> </div>
@ -35,9 +35,9 @@
</div> </div>
</div> </div>
<!-- Formulaire qui envoie la demande au contrôleur (sendEmail) --> <!-- Formulaire qui envoie la demande au contrôleur (shareProject) -->
<div class="card shadow-sm p-4 mb-5"> <div class="card shadow-sm p-4 mb-5">
<form method="post" action="index.php?ctrl=project&action=sendEmail"> <form method="post" action="index.php?ctrl=project&action=shareProject">
<input type="hidden" name="project_id" <input type="hidden" name="project_id"
value="{$objProject->getId()}"> value="{$objProject->getId()}">
@ -67,12 +67,36 @@
<p class="text-muted small"> <p class="text-muted small">
Publié le {$objProject->getCreation_date()} Publié le {$objProject->getCreation_date()}
</p> </p>
<button class="btn btn-primary">Contacter le talent</button> <button class="btn btn-primary">Contacter le talent</button>
</div> </div>
{*Controle de l'utilisateur ainsi que du status du projet + Suppression disponible pour l'utilisateur possédant le projet*}
{if isset($smarty.session.user)}
{if ($smarty.session.user.user_status == 2 || $smarty.session.user.user_id == $objProject->getUser_id())}
<div class="border rounded text-center">
<a class="btn btn-sm m-1 btn-danger" href="?ctrl=project&action=delete&id={$objProject->getId()}" name="toDelete">Supprimer le projet</a>
{/if}
{if $smarty.session.user.user_id == $objProject->getUser_id()}
<a class="btn btn-sm m-1 btn-warning" href="?ctrl=project&action=addedit_project&id={$objProject->getId()}" name="addedit">Modifier le projet</a>
{/if}
{*Conditions permettant au Modérateur de modifier le status d'un projet*}
{if $smarty.session.user.user_status == 2 && $objProject->getStatus() == "en_attente"}
<a class="btn btn-sm m-1 btn-success" href="?ctrl=project&action=accept&id={$objProject->getId()}" name="toPublished">Accepter</a>
<a class="btn btn-sm m-1 btn-warning" href="?ctrl=project&action=refuse&id={$objProject->getId()}" name="toRefused">Refuser</a>
</div> </div>
{elseif $projectStatus == "refusé"}
<p class="text-danger fw-bold">Portfolio refusé</p>
{/if}
{/if}
</div>
</div> </div>
</main> </main>

View file

@ -41,7 +41,7 @@
</select> </select>
</div> </div>
<div class="col-6">
<div class="col-6"> <div class="col-6">
<fieldset> <fieldset>
<legend class="form-label">Recherche par date</legend> <legend class="form-label">Recherche par date</legend>
@ -73,27 +73,9 @@
</div> </div>
</fieldset> </fieldset>
</div> </div>
<!-- AJOUT RECHERCHE PAR CATEGORIE -->
<div class="col-6">
<fieldset>
<legend class="form-label">Recherche par catégories</legend>
<div class="col-md-6">
<label for="author" class="form-label">Catégorie</label>
<select class="form-select" id="category" name="category">
<option value="0" {if $intCategory == 0}selected{/if}>Toutes les catégories</option>
{foreach $arrCategory as $arrDetCategory}
<option value="{$arrDetCategory['category_id']}"
{if $intCategory == $arrDetCategory['category_id']}selected{/if}
>
{$arrDetCategory['category_name']}
</option>
{/foreach}
</select>
</div>
</fieldset>
</div>
<div class="col-md-6" id="date-exact">
<div class="col-12" id="date-exact">
<label for="date" class="form-label">Date</label> <label for="date" class="form-label">Date</label>
<input <input
type="date" type="date"
@ -129,7 +111,26 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- AJOUT RECHERCHE PAR CATEGORIE -->
<div class="col-6">
<fieldset>
<legend class="form-label">Recherche par catégories</legend>
<div class="col-md-6">
<label for="author" class="form-label">Catégorie</label>
<select class="form-select" id="category" name="category">
<option value="0" {if $intCategory == 0}selected{/if}>Toutes les catégories</option>
{foreach $arrCategory as $arrDetCategory}
<option value="{$arrDetCategory['category_id']}"
{if $intCategory == $arrDetCategory['category_id']}selected{/if}
>
{$arrDetCategory['category_name']}
</option>
{/foreach}
</select>
</div>
</fieldset>
</div>
<div class="col-12"> <div class="col-12">
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
<i class="fas fa-search me-2" aria-hidden="true"></i> <i class="fas fa-search me-2" aria-hidden="true"></i>