From e79cc73e6d67be5c1138ee47e33ea9155c6e883f Mon Sep 17 00:00:00 2001
From: Benjamin
Date: Thu, 2 Apr 2026 16:33:32 +0200
Subject: [PATCH] Lundi
---
config/database.php | 9 +
public/assets/css/style.css | 261 ++++++++++++++++--
public/assets/js/login.js | 66 +++++
src/Domain/Ingredients/Ingredient.php | 13 +
.../Ingredients/IngredientRepository.php | 29 ++
src/Domain/Model.php | 8 +
src/Domain/Recettes/Recette.php | 22 ++
src/Domain/Recettes/RecetteRepository.php | 66 +++++
src/Domain/Repository.php | 99 +++++++
.../AuthentificationController.php | 19 +-
src/Helpers/Markdown.php | 29 ++
src/Http/Request.php | 16 ++
src/Infrastructure/Database.php | 45 +++
src/Kernel.php | 19 ++
views/home.php | 9 +-
views/login.php | 23 ++
views/partials/header.php | 10 +-
17 files changed, 720 insertions(+), 23 deletions(-)
create mode 100644 config/database.php
create mode 100644 public/assets/js/login.js
create mode 100644 src/Domain/Ingredients/Ingredient.php
create mode 100644 src/Domain/Ingredients/IngredientRepository.php
create mode 100644 src/Domain/Model.php
create mode 100644 src/Domain/Recettes/Recette.php
create mode 100644 src/Domain/Recettes/RecetteRepository.php
create mode 100644 src/Domain/Repository.php
create mode 100644 src/Helpers/Markdown.php
create mode 100644 src/Infrastructure/Database.php
create mode 100644 views/login.php
diff --git a/config/database.php b/config/database.php
new file mode 100644
index 0000000..334dc3f
--- /dev/null
+++ b/config/database.php
@@ -0,0 +1,9 @@
+ 'localhost',
+ 'port' => 3306,
+ 'user' => 'benjamin',
+ 'pass' => '011235813',
+ 'name' => 'siterecette'
+];
\ No newline at end of file
diff --git a/public/assets/css/style.css b/public/assets/css/style.css
index 9e6b0eb..898ca90 100644
--- a/public/assets/css/style.css
+++ b/public/assets/css/style.css
@@ -1,3 +1,4 @@
+@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap');
/* Sommaire :
- body et html
- Header et ses contenues
@@ -14,6 +15,7 @@ html, body{
height: 100%;
text-align: center;
/*font-size: 98%;*/
+ font-family: "Roboto", sans-serif;
}
@@ -43,6 +45,11 @@ html, body{
box-shadow: 1px 1px 1px black;
}
+.logo:hover{
+ box-shadow: 1px 1px 2px #0bbd90;
+ border: 1px solid #0bbd90;
+}
+
nav {
flex: 9;
text-align: initial;
@@ -64,6 +71,25 @@ nav {
.nav-element {
line-height: 2.5;
padding: 10px;
+ text-decoration: none;
+ color: black;
+}
+
+.nav-list{
+ li:nth-of-type(3){
+ position: fixed;
+ right: 25px;
+ }
+}
+
+#login {
+ border: 1px solid black;
+ border-radius: 6px;
+ background-color: #ffc478;
+}
+
+#login:hover {
+ background-color: #0bbd90;
}
/*Body et son contenu */
@@ -77,21 +103,93 @@ body {
}
.sidebar {
- background-color: aquamarine;
+ background-color: #caecd6;
border: 1px solid rgba(0,0,0,.125);
- border-radius: 10px;
+ border-radius: 6px;
width: 128px;
+ display: flex;
+ flex-direction: column;
+ overflow: scroll;
}
+.tag-cont {
+ /*display:none;*/
+}
+
+.ingr-cont {
+ /*display:none;*/
+}
+
+.tag-selected-div {
+ flex: 4;
+}
+
+.tag-unselected-div {
+ flex: 4;
+}
+
+.sidebar-search {
+ flex: 1;
+}
+
+.search-form-tag {
+ width: 85%;
+}
+
+.search-form-recette {
+ width: 100%;
+ height: 25px;
+}
+
+.search-form {
+ border: 1px solid black;
+ border-radius: 20px;
+ box-shadow: 0px 0px 10px rgb(131, 131, 131);
+}
+
+nav {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.tag {
+ border: 1px solid black;
+ border-radius: 10px;
+ width: 95%;
+ box-shadow: 1px 1px 1px black;
+}
+
+.tag-selected {
+ background-color: #ffa04d;
+}
+
+.tag-selected:hover {
+ background-color: white;
+}
+
+.tag-unselected {
+ background-color: white;
+
+}
+.tag-unselected:hover {
+ background-color: #ffa04d;
+}
+
+#hr {
+ border: 1px solid #0bbd90;
+ width: 93%;
+ box-shadow: 1px 3px 5px #0bbd90;
+}
.main-body {
display: flex;
flex-direction: row;
- overflow: auto;
flex: 1;
background-clip: border-box;
- padding-right: 5%;
- padding-left: 5%;
+ padding-right: 2%;
+ padding-left: 2%;
+ overflow: hidden;
}
/* content : le cadre de base du centre de notre site
@@ -103,37 +201,77 @@ body {
height: 100%;
border: 1px solid rgba(0,0,0,.125);
border-radius: 6px;
- overflow: auto;
+ overflow: scroll;
}
/*Recettes :
représente le contenue centrale de la page.
*/
.recettes {
- --UneVariable: calc( 100vw / 500 );
display: grid;
- grid-template-columns: repeat(var(--UneVariable), 1fr);
+ grid-template-columns: repeat(3, 1fr);
grid-gap: 10px;
- grid-auto-rows: minmax(300px, auto);
- grid-auto-columns: auto;
+ /* grid-auto-rows: minmax(300px, auto);
+ grid-auto-columns: auto;*/
+ padding: 10px;
+}
+
+@media screen and (max-width : 1200px) {
+ .recettes {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+@media screen and (max-width : 800px) {
+ .recettes {
+ grid-template-columns: repeat(1, 1fr);
+ }
+
+}
+
+@media screen and (max-width : 600px) {
+
+ .sidebar {
+ display: none;
+ }
+}
+
+ul {
+ list-style-type: none;
+ margin-left: 0px;
+ text-align: center;
+ padding : 0px;
}
/* Classe recette icone :
C'est la classe de chaques éléments de notre liste de recette
*/
.recette-icone{
+ display: flex;
border: 3px solid rgba(0,0,0,.125);
border-radius: 40px;
text-align: left;
padding: 15px;
vertical-align: middle;
box-shadow: 3px 5px 5px black;
- width: 500px;
+ max-width: 400px;
+ text-decoration: none;
+ color: black;
+}
+
+.recette-icone:hover{
+ border-color: #0bbd90;
+}
+
+.recette-icone-content{
+ display: flex;
+ flex-direction: column;
+ padding-left: 10px;
}
.recette-preview-image{
- max-width: calc( 10vh + 10vw );
- max-height: calc( 10vh + 10vw );
+ max-width: calc( 5vh + 5vw );
+ max-height: calc( 5vh + 5vw );
border: 1px solid rgb(252, 223, 57);
border-radius: 25px;
}
@@ -165,11 +303,102 @@ body {
justify-content: center;
}
-/*description*/
-.recette-desc {
- padding : 10px;
+.recette-div-info {
+ display: flex;
+ flex-direction: row;
}
+.recette-div-liste-info{
+ padding: 50px;
+ margin: 10px;
+}
+
+.recette-liste-info-elem {
+ text-align: left;
+}
+
+.recette-liste-info {
+ display: flex;
+ flex-direction: column;
+}
+
+.recette-liste-tag {
+ display: flex;
+ flex-direction: row;
+}
+
+
+/*description*/
+.recette-desc {
+ padding : 30px;
+}
+
+/*Login form*/
+.formcont {
+ flex: 1;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.formcont form {
+ width: 50vmin;
+ height: 40vmin;
+ border: 2px solid rgb(0, 0, 0);
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 1.5vmin;
+ background:white;
+ border-radius: 12px;
+ box-shadow: 5px 5px 30px #555;
+}
+
+.formcont form .form-group {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 1.2vmin;
+ padding-left:0px;
+}
+
+.formcont form .form-group label {
+ width: 95%;
+ text-align: center;
+ color:black;
+ font-size:5vmin;
+}
+
+.formcont form .form-group input {
+ width: 95%;
+ box-sizing: border-box;
+ font-size:3vmin;
+ padding:1vmin;
+
+}
+
+.formcont form button {
+ width: 48%;
+ height: 19%;
+ padding:0.5vmin;
+ box-sizing: border-box;
+ margin: 0 auto;
+ background: #ffa04d;
+ color: black;
+ font-size: 5vmin;
+ border-radius: 7px;
+ box-shadow: 2px 2px 0px #000000;
+}
+
+.btn:hover {
+ background-color: #0bbd90;
+}
+
+.form-control {
+ border-radius: 10px;
+ box-shadow: 3px 4px 5px #8c8c8c;
+}
/*Footer et son contenue*/
footer{
diff --git a/public/assets/js/login.js b/public/assets/js/login.js
new file mode 100644
index 0000000..e14ca46
--- /dev/null
+++ b/public/assets/js/login.js
@@ -0,0 +1,66 @@
+document.addEventListener( 'DOMContentLoaded', function(){
+ const FORM = document.getElementById( 'login-form' );
+ const FORM_ERROR = document.getElementById( 'login-errors' );
+ if( !FORM )
+ return;
+
+ /**
+ * Permet d'afficher une erreur dans le formulaire.
+ * @param message
+ */
+ FORM.showError = function( message ){
+ FORM_ERROR.style.display = 'block';
+ FORM_ERROR.innerText = message;
+ }
+
+ /**
+ * Permet de retirer l'erreur du formulaire.
+ */
+ FORM.removeError = function(){
+ FORM_ERROR.style.display = 'none';
+ FORM_ERROR.innerText = '';
+ }
+
+ /**
+ * Gère l'envoi du formulaire.
+ * @param e
+ * @returns {boolean}
+ */
+ FORM.onsubmit = function( e ){
+
+ e.preventDefault();
+ FORM.removeError();
+
+ let userField = e.target.username;
+ let passwordField = e.target.password;
+
+ if( !userField || !passwordField || userField.value === "" || passwordField.value === "" ){
+ FORM.showError( "Le nom d'utilisateur ou le mot de passe ne doivent pas être vide.");
+ return false;
+ }
+
+ let formData = new FormData( FORM );
+ let options = {
+ method: "POST",
+ contentType: "application/json",
+ body: formData,
+ }
+
+ fetch( FORM.action, options ).then( ( response ) => {
+ if( response.ok ){
+ response.json().then( ( responseJSON ) => {
+
+ if( responseJSON.success === true ){
+ window.location.href = window.location.origin; // Redirection sur la page d'accueil si succès.
+ } else {
+ FORM.showError( responseJSON.message || "Mauvais nom d'utilisateur ou mauvais mot de passe." );
+ }
+
+ })
+ }
+ })
+
+ }
+
+
+})
\ No newline at end of file
diff --git a/src/Domain/Ingredients/Ingredient.php b/src/Domain/Ingredients/Ingredient.php
new file mode 100644
index 0000000..bfab46f
--- /dev/null
+++ b/src/Domain/Ingredients/Ingredient.php
@@ -0,0 +1,13 @@
+ 'Ingredient',
+ 'columns' => [
+ 'num_ingredient', 'nom_ingredient'
+ ]
+ ];
+
+ }
+
+ public function add( Model $ingredient ): bool {
+
+ }
+}
\ No newline at end of file
diff --git a/src/Domain/Model.php b/src/Domain/Model.php
new file mode 100644
index 0000000..7c7d6c6
--- /dev/null
+++ b/src/Domain/Model.php
@@ -0,0 +1,8 @@
+description_recette );
+ }
+
+}
\ No newline at end of file
diff --git a/src/Domain/Recettes/RecetteRepository.php b/src/Domain/Recettes/RecetteRepository.php
new file mode 100644
index 0000000..53bc4a2
--- /dev/null
+++ b/src/Domain/Recettes/RecetteRepository.php
@@ -0,0 +1,66 @@
+ 'Recette',
+ 'columns' => [
+ 'num_recette', 'titre_recette', 'slug', 'description_recette', 'photo', 'publication_date', 'temps_de_preparation'
+ ]
+ ];
+
+ }
+
+ /**
+ * Permet d'avoir une recette par un ID.
+ *
+ * @param int $id
+ * @return Recette|null
+ */
+ public function getByID( int $id ): ?Recette {
+ $sqlQuery = "SELECT * FROM {$this->tableName} WHERE num_recette = {$id}";
+ $results = $this->selectGetAll($sqlQuery);
+ if( $results === null || count( $results ) > 1 )
+ return null;
+ return $results[0];
+ }
+
+ /**
+ * Permet d'avoir une recette par un slug.
+ *
+ * @param string $slug
+ * @return Recette|null
+ */
+ public function getBySlug( string $slug ): ?Recette {
+ $sqlQuery = "SELECT * FROM {$this->tableName} WHERE slug = {$slug}";
+ $results = $this->selectGetAll($sqlQuery);
+ if( $results === null || count( $results ) > 1 )
+ return null;
+ return $results[0];
+ }
+
+ public function add( Model $recette ): bool {
+ return $this->addEntity( $recette );
+ }
+
+ public function update( Model $recette ): bool {
+ return $this->updateEntity( $recette, 'num_recette' );
+ }
+
+ public function delete( Model $recette ): bool {
+ return $this->deleteEntity( $recette, 'num_recette' );
+ }
+
+}
\ No newline at end of file
diff --git a/src/Domain/Repository.php b/src/Domain/Repository.php
new file mode 100644
index 0000000..b1933c3
--- /dev/null
+++ b/src/Domain/Repository.php
@@ -0,0 +1,99 @@
+ '', 'columns' => [ ...Liste des colonnes dans la BDD ] ].
+ * @return array
+ */
+ abstract public static function getStructure(): array;
+
+ final public string $tableName;
+ final public array $tableColumns;
+
+ public function __construct(){
+ $structure = static::getStructure();
+
+ $this->tableName = $structure['table'];
+ $this->tableColumns = $structure['columns'];
+ }
+
+
+ /**
+ * Permet d'avoir tous les éléments correspondant à la requête passée en paramètre.
+ *
+ * @param string $sqlQuery
+ * @return array|null
+ */
+ public function selectGetAll( string $sqlQuery ): ?array {
+ $statement = Kernel::$DB->pdo->prepare( $sqlQuery );
+ if( !$statement->execute() )
+ return null;
+
+ $results = $statement->fetchAll( PDO::FETCH_CLASS, static::getEntity() );
+ if( empty( $results ) )
+ return null;
+ return $results;
+ }
+
+ public function addEntity( Model $entity ): bool {
+
+ $query = "INSERT INTO {$this->tableName} (";
+ $values = "(";
+ foreach( $this->tableColumns as $column ) {
+ $query .= "`{$column}`,";
+ $values .= ":{$column},";
+ }
+ $query = substr( $query, 0, -1 ) . ")";
+ $values = substr( $values, 0, -1 ) . ")";
+ $query .= " VALUES " . $values . ";";
+
+ $statement = Kernel::$DB->pdo->prepare( $query );
+ foreach( $this->tableColumns as $column ) {
+ $statement->bindValue(":{$column}", $entity->{$column} );
+ }
+
+ return $statement->execute();
+ }
+
+ public function updateEntity( Model $entity, string $identifier ): bool {
+ $query = "UPDATE {$this->tableName} SET ";
+ foreach( $this->tableColumns as $column ) {
+ $query .= "`{$column}` = :{$column},";
+ }
+ $query = substr( $query, 0, -1 ) . " WHERE {$identifier} = :{$identifier};";
+
+ $statement = Kernel::$DB->pdo->prepare( $query );
+ foreach( $this->tableColumns as $column ) {
+ $statement->bindValue(":{$column}", $entity->{$column} );
+ }
+ $statement->bindValue( ":{$identifier}", $entity->{$identifier} );
+ return $statement->execute();
+ }
+
+ public function deleteEntity( Model $entity, string $identifier ): bool {
+ $query = "DELETE FROM {$this->tableName} WHERE {$identifier} = :{$identifier};";
+ $statement = Kernel::$DB->pdo->prepare( $query );
+ $statement->bindValue( ":{$identifier}", $entity->{$identifier} );
+ return $statement->execute();
+ }
+
+ abstract public function add( Model $entity ): bool;
+ abstract public function update( Model $entity ): bool;
+ abstract public function delete( Model $entity ): bool;
+
+
+}
diff --git a/src/Domain/Utilisateurs/AuthentificationController.php b/src/Domain/Utilisateurs/AuthentificationController.php
index 04c1d30..f1e93e8 100644
--- a/src/Domain/Utilisateurs/AuthentificationController.php
+++ b/src/Domain/Utilisateurs/AuthentificationController.php
@@ -6,6 +6,7 @@ use App\Domain\Controller;
use App\Helpers\Authentification;
use App\Http\JSONResponse;
use App\Http\Request;
+use App\Infrastructure\View;
class AuthentificationController extends Controller {
@@ -15,19 +16,29 @@ class AuthentificationController extends Controller {
// Public routes.
self::Route( routeUrl: '/login', routeName: 'login', routeAction: 'loginForm', pageHeadTitle: 'Connexion' ),
+ self::Route( routeUrl: '/logout', routeName: 'logout', routeAction: 'logoutPage', pageHeadTitle: 'Déconnexion' ),
// API Routes.
self::Route( routeUrl: '/api/auth', routeName: 'api->auth', routeAction: 'auth', routeMethods: ['POST'] ),
- self::Route( routeUrl: '/api/auth/logout', routeName: 'api->auth->logout', routeAction: 'logout', routeMethods: ['POST'] ),
+ // self::Route( routeUrl: '/api/auth/logout', routeName: 'api->auth->logout', routeAction: 'logout', routeMethods: ['POST'] ),
];
}
- public function login(): View {
+ public function loginForm(): View {
return new View( 'login' );
}
+ public function logoutPage(){
+ if( !Authentification::isLoggedIn() ) {
+ Request::redirectTo( 'home' );
+ }
+
+ Authentification::destroySession();
+ Request::redirectTo( 'home' );
+ }
+
public function auth(): JSONResponse {
Request::setCORS();
@@ -39,14 +50,14 @@ class AuthentificationController extends Controller {
$userId = 1;
Authentification::loginUser( $userId );
- JSONResponse::sendSuccess( [ 'user_id' => $userId ] );
+ return JSONResponse::sendSuccess( [ 'user_id' => $userId ] );
}
public function logout(): JSONResponse {
if( !Authentification::isLoggedIn() ) {
- return JSONResponse::sendError( [ 'message' => 'Alrady disconnected' ] );
+ return JSONResponse::sendError( [ 'message' => 'Already disconnected' ] );
}
Authentification::destroySession();
diff --git a/src/Helpers/Markdown.php b/src/Helpers/Markdown.php
new file mode 100644
index 0000000..bfe4b55
--- /dev/null
+++ b/src/Helpers/Markdown.php
@@ -0,0 +1,29 @@
+ '$1',
+ '/\*(.*?)\*/' => '$1',
+
+ // Titres
+ '/^## (.*?)$/m' => '$1
',
+ '/^# (.*?)$/m' => '$1
',
+
+ ];
+ }
+
+ public static function convertToHTML( string $markdown ): string {
+ $safeMD = htmlspecialchars( $markdown, ENT_QUOTES );
+ foreach( Markdown::getMarkdownEntities() as $key => $value ) {
+ $safeMD = preg_replace( $key, $value, $safeMD );
+ }
+
+ return nl2br( $safeMD );
+ }
+}
\ No newline at end of file
diff --git a/src/Http/Request.php b/src/Http/Request.php
index 7172602..a4c49df 100644
--- a/src/Http/Request.php
+++ b/src/Http/Request.php
@@ -50,4 +50,20 @@ class Request {
return self::sanitize( $_POST[$name] );
}
+ /**
+ * Permet de rediriger un utilisateur.
+ * /!\ Arrête tout traitement sur la page.
+ *
+ * @param string $routeName
+ * @param ...$args
+ *
+ * @return never
+ */
+ public static function redirectTo( string $routeName, ...$args ): never {
+
+ $routeUrl = Router::getRouteURL( $routeName, ...$args );
+ header("Location: {$routeUrl}");
+ die();
+ }
+
}
\ No newline at end of file
diff --git a/src/Infrastructure/Database.php b/src/Infrastructure/Database.php
new file mode 100644
index 0000000..7209ad4
--- /dev/null
+++ b/src/Infrastructure/Database.php
@@ -0,0 +1,45 @@
+databaseInfos = $loginInfos;
+ $this->tryLogin();
+ }
+
+ /**
+ * Permet d'essayer un login à la base de données.
+ * @return void - never si la connexion échoue.
+ */
+ private function tryLogin(): void {
+ try {
+ $dsn = 'mysql:dbname=' . $this->databaseInfos['name'] . ';host=' . $this->databaseInfos['host'] . ';port=' . $this->databaseInfos['port'];
+ $this->pdo = new \PDO( $dsn, $this->databaseInfos['user'], $this->databaseInfos['pass'] );
+ } catch ( \PDOException $e ) {
+ die( $e->getMessage() );
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/Kernel.php b/src/Kernel.php
index 83017c6..485eafd 100644
--- a/src/Kernel.php
+++ b/src/Kernel.php
@@ -7,6 +7,7 @@ use App\Helpers\Authentification;
use App\Helpers\AutoLoader;
use App\Helpers\ConfigFactory;
use App\Http\Router;
+use App\Infrastructure\Database;
/**
* Classe primaire du site.
@@ -22,6 +23,14 @@ final class Kernel {
*/
public private(set) static array $configs = [];
+ /**
+ * Instance à la base de données.
+ * Pour utiliser la classe PDO : $DB->PDO.
+ *
+ * @var Database|null
+ */
+ public private(set) static ?Database $DB = null;
+
/**
* Instance actuelle de l'application.
* @var Kernel|null
@@ -62,6 +71,7 @@ final class Kernel {
public function init(): void {
$this->buildAutoloader();
$this->loadConfig();
+ $this->loadDatabase();
Authentification::startSession();
@@ -89,6 +99,7 @@ final class Kernel {
try {
self::$configs['general'] = ConfigFactory::loadConfigFile('general');
+ self::$configs['database'] = ConfigFactory::loadConfigFile('database');
self::$configs['route_arguments'] = ConfigFactory::loadConfigFile('route_arguments');
self::$configs['views'] = ConfigFactory::loadConfigFile('views');
@@ -97,4 +108,12 @@ final class Kernel {
}
}
+ /**
+ * Permet de se connecter à la base de données principale.
+ * @return void
+ */
+ private function loadDatabase(): void {
+ self::$DB = new Database( self::$configs['database'] );
+ }
+
}
\ No newline at end of file
diff --git a/views/home.php b/views/home.php
index 3cb93ff..c0c0f44 100644
--- a/views/home.php
+++ b/views/home.php
@@ -1,2 +1,9 @@
Coucou
-
\ No newline at end of file
+$md
"; ?>
+
+getByID( 4 );
+$rec->temps_de_preparation = 7;
+$rec->titre_recette = "Tutu";
+
+new \App\Domain\Recettes\RecetteRepository()->delete( $rec );
+?>
diff --git a/views/login.php b/views/login.php
new file mode 100644
index 0000000..7835617
--- /dev/null
+++ b/views/login.php
@@ -0,0 +1,23 @@
+
+
+
+
diff --git a/views/partials/header.php b/views/partials/header.php
index 792aadb..2848cdd 100644
--- a/views/partials/header.php
+++ b/views/partials/header.php
@@ -8,16 +8,22 @@
-