Compare commits
35 Commits
Front-End-
...
BenBack
| Author | SHA1 | Date | |
|---|---|---|---|
| e02b7abb0a | |||
| edfa8c93b9 | |||
| f933065275 | |||
| 060e54d27e | |||
| 05533d68c6 | |||
| a9e7e0331c | |||
| 83857ef4e2 | |||
| 0ae685753f | |||
| aa1ec2296f | |||
| f633d4f73d | |||
| 251bbce109 | |||
| 9b63c9261e | |||
| 2d38f72292 | |||
| ec78173e05 | |||
| 9005cff6ca | |||
| 7845cdc85b | |||
| 9ae8dd93de | |||
| f8c0d6d1b2 | |||
| 188e7fbc6e | |||
| 9d51f49bf7 | |||
| ae31644831 | |||
| f2397b6d31 | |||
| aaead5a31a | |||
| f918134469 | |||
| 69a5a99e15 | |||
| 8a173ed2c7 | |||
| 52b26b0cb6 | |||
| e79cc73e6d | |||
| 5af5a0ad06 | |||
| 72108d4d03 | |||
| 52747e51f9 | |||
| 6c068443dc | |||
| b15324c27b | |||
| 43eb936532 | |||
| c36b95a15f |
58
README.md
58
README.md
@@ -1 +1,57 @@
|
|||||||
# Les Recettes des Papis
|
# Les recettes des Papis
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
- Décompressez l'archive du site.
|
||||||
|
- Pointez votre domaine sur le dossier "public".
|
||||||
|
- Modifiez les fichiers de configuration dans "config".
|
||||||
|
|
||||||
|
## Informations pratiques sur le PHP.
|
||||||
|
|
||||||
|
- Ce site utilise le principe des routes. Afin de ne pas avoir x fichiers publiques identiques pour chaque page.
|
||||||
|
- Tout est géré par le fichier public/index.php.
|
||||||
|
- index.php va charger le Kernel (Noyau) de l'application.
|
||||||
|
- Le noyau fonctionne sur le principe d'élément unique. La méthode ``Kernel::getInstance()`` va permettre d'avoir l'unique instance de la classe Kernel.
|
||||||
|
- Une fois ``Kernel::getInstance()->init()`` déclenché, le noyau enregistre l'autoloading des fichiers, puis enregistre la configuration.
|
||||||
|
- La configuration sont des fichiers dans le dossier config/ qui vont renvoyer un tableau clé/valeur. Exemple
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Fichier config/test.php
|
||||||
|
<?php return [
|
||||||
|
'cle_1' => "Val1",
|
||||||
|
'cle_2' => "Val2",
|
||||||
|
// ...
|
||||||
|
] ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
- Les fichiers de configuration sont chargés via la méthode ``ConfigFactory::loadConfigFile()`` qui va vérifier si le fichier de configuration existe et le charge en conséquence.
|
||||||
|
- On enchaine ensuite sur le Router.
|
||||||
|
- Le Router est une classe utilitaire qui va permettre de faire le lien entre la route saisie ``/test/...`` de l'utilisateur et les routes enregistrés par le site.
|
||||||
|
- Le Router va d'abord commencer par récupérer la route voulue par l'utilisateur.
|
||||||
|
- Puis, le router va rechercher tous les Controllers existants.
|
||||||
|
- Un Controller est une classe qui va permettre de définir les routes et de dire, qu'est-ce qui se passe quand je prends cette route. Les routes du Controller sont définis par la methode ``Controller::defineRoutes()``
|
||||||
|
- Pour définir les routes dans la méthode précédente, vous pouvez soit créer un objet ``Route`` en remplissant tous les champs, ou bien la méthode ``Controller::Route()`` qui va préremplir certains champs.
|
||||||
|
- Exemple de Controller
|
||||||
|
```php
|
||||||
|
class TestController extends Controller {
|
||||||
|
|
||||||
|
public static function defineRoutes(): array {
|
||||||
|
return [
|
||||||
|
self::Route( routeUrl: '/test', routeName: "Test", routeAction: "test" ),
|
||||||
|
new Route( routeUrl: "/test2", routeName: "Test2", routeController: "TestController", routeAction: "test2", routeMethods = [ 'GET' ] )
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test(){
|
||||||
|
echo "Je suis déclenché lorsque j'atteinds la route /test";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test2(){
|
||||||
|
echo "Je suis déclenché lorsque j'atteinds la route /test2 !!!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Retournons au routeur, pour chercher les controllers, il va scanner les fichiers du dossier src/Domain avec un Iterator Récursif. (Pour se simplifier un peu la tâche.)
|
||||||
|
- Il va traiter le nom du chemin absolu pour obtenir le nom de la classe, puis va vérifier si cette classe est une sous-classe de Controller.
|
||||||
|
- Après avoir trouvé tous les Controllers, il va récupérer toutes les routes de chaque controller.
|
||||||
|
- Enfin, il va vérifier si la route utilisateur correspond à une des routes dans sa liste, si ça correspond, le Router va charger la méthode ``new {RouteController}()->{RouteAction}()``
|
||||||
|
- Si par exemple, le router trouve la route ``/test`` voulu par l'utilisateur, il va déclencher la méthode ``new TestController()->test()``.
|
||||||
9
config/general.php
Normal file
9
config/general.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Config
|
||||||
|
return [
|
||||||
|
'website_url' => 'http://127.0.0.1:8080/',
|
||||||
|
'website_name' => 'Les recettes de Papis',
|
||||||
|
|
||||||
|
'website_path' => APP_ROOT,
|
||||||
|
];
|
||||||
6
config/route_arguments.php
Normal file
6
config/route_arguments.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
return [
|
||||||
|
'{int}' => '([0-9]+)',
|
||||||
|
'{chars}' => '([A-Za-z]+)',
|
||||||
|
'{string}' => '([0-9A-Za-z]+)',
|
||||||
|
];
|
||||||
4
config/views.php
Normal file
4
config/views.php
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?php
|
||||||
|
return [
|
||||||
|
'base_view_skeleton' => 'base'
|
||||||
|
];
|
||||||
659
public/assets/css/style.css
Executable file
659
public/assets/css/style.css
Executable file
@@ -0,0 +1,659 @@
|
|||||||
|
@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
|
||||||
|
- Body contenue
|
||||||
|
- Content
|
||||||
|
- Page de présentation des recettes
|
||||||
|
- Page d'une recette
|
||||||
|
- Footer
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*Tout*/
|
||||||
|
html, body{
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
/*font-size: 98%;*/
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*Header et son contenu*/
|
||||||
|
#header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 3px;
|
||||||
|
padding-left: 2%;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
background-color: blanchedalmond;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logo {
|
||||||
|
background-color: aqua;
|
||||||
|
background-image: url("../images/Logo.jpg");
|
||||||
|
/*background:no-repeat;*/
|
||||||
|
height: 103px;
|
||||||
|
width: 141px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo{
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px white solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 1px 1px 1px black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo:hover{
|
||||||
|
box-shadow: 1px 1px 2px #0bbd90;
|
||||||
|
border: 1px solid #0bbd90;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
flex: 9;
|
||||||
|
text-align: initial;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-list{
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: rgb(255, 217, 160);
|
||||||
|
display: flex;
|
||||||
|
border: 1px solid rgba(0,0,0,.125);
|
||||||
|
border-radius: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-list li {
|
||||||
|
/*border: 1px solid black;*/
|
||||||
|
border-radius: 2000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-list li:hover {
|
||||||
|
background-color: #00d8a2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.nav-element {
|
||||||
|
line-height: 2.5;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 4px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-list{
|
||||||
|
li:nth-of-type(3){
|
||||||
|
position: fixed;
|
||||||
|
right: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#login {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 2000px;
|
||||||
|
background-color: #ffc478;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login:hover {
|
||||||
|
background-color: #0bbd90;
|
||||||
|
box-shadow: 1px 1px 7px black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Body et son contenu */
|
||||||
|
body {
|
||||||
|
background-color: blanchedalmond;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
background-color: #caecd6;
|
||||||
|
border: 1px solid rgba(0,0,0,.125);
|
||||||
|
border-radius: 6px;
|
||||||
|
width: 128px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-cont {
|
||||||
|
/*display:none;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.ingr-cont {
|
||||||
|
/*display:none;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-site {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
width: 100%;
|
||||||
|
height: 50%;
|
||||||
|
font-size: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.random-proposition {
|
||||||
|
gap: 5%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recommandation img {
|
||||||
|
max-width: calc( 7vh + 7vw );
|
||||||
|
max-height: calc( 7vh + 7vw );
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-acceuil {
|
||||||
|
color: #03be90;
|
||||||
|
box-shadow: 20px 6px 8px #007054;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
||||||
|
margin-top: .5%;
|
||||||
|
padding: .5% 0 .5% .5%;
|
||||||
|
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%;
|
||||||
|
max-width: calc(128px*0.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;
|
||||||
|
flex: 1;
|
||||||
|
background-clip: border-box;
|
||||||
|
padding-right: 2%;
|
||||||
|
padding-left: 2%;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* content : le cadre de base du centre de notre site
|
||||||
|
*/
|
||||||
|
.content {
|
||||||
|
background-color: #ffe4bb;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid rgba(0,0,0,.125);
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Recettes :
|
||||||
|
représente le contenue centrale de la page.
|
||||||
|
*/
|
||||||
|
.recettes {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
grid-gap: 10px;
|
||||||
|
/* 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;
|
||||||
|
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( 5vh + 5vw );
|
||||||
|
max-height: calc( 5vh + 5vw );
|
||||||
|
border: 1px solid rgb(252, 223, 57);
|
||||||
|
border-radius: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Ici commence le CSS pour la page de chaques recettes */
|
||||||
|
/*contenue*/
|
||||||
|
.recette-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*titre*/
|
||||||
|
.recette-title {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*image*/
|
||||||
|
.recette-image {
|
||||||
|
background-color: red;
|
||||||
|
max-height: calc( 20vh + 20vw );
|
||||||
|
max-width: calc( 20vh + 20vw );
|
||||||
|
border: 1px solid white;
|
||||||
|
border-radius: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-div-image {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.recette-liste-ingr-div {
|
||||||
|
margin-left: 120px;
|
||||||
|
padding-left: 100px;
|
||||||
|
width: 75%;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 200px;
|
||||||
|
box-shadow: 7px 4px 7px black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-liste-ingr-elem {
|
||||||
|
display: flex;
|
||||||
|
padding-left: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ingr-image-prev {
|
||||||
|
max-height: 50px;
|
||||||
|
max-width: 50px;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 10px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-liste-ingr-elem-nom {
|
||||||
|
height: 100%;
|
||||||
|
width: 15%;
|
||||||
|
line-height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-div-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-div-liste-info{
|
||||||
|
padding: 30px;
|
||||||
|
margin: 10px;
|
||||||
|
width: 100%;
|
||||||
|
flex:1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-list-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-button {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding : 10px;
|
||||||
|
box-shadow: 1px 1px 1px black;
|
||||||
|
background-color: blanchedalmond;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-button:hover{
|
||||||
|
background-color: rgb(243, 215, 174);
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-liste-info-elem {
|
||||||
|
text-align: left;
|
||||||
|
padding: 4px;
|
||||||
|
padding-top: 6px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
color: #535353bd;
|
||||||
|
border-right: 1px solid #535353bd;
|
||||||
|
border-left: 1px solid #535353bd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-liste-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-liste-tag {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap; /*trouvé à l'aide de ChatGPT car je n'arrivais pas à trouver*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*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: 4vmin;
|
||||||
|
border-radius: 7px;
|
||||||
|
box-shadow: 2px 2px 0px #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: .5% 2%;
|
||||||
|
border: 2px solid black;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-top: .5%;
|
||||||
|
margin-left: .5%;
|
||||||
|
background-color: #ffd9a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background-color: #0bbd90;
|
||||||
|
box-shadow: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
padding: 1%;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 3px 4px 5px #8c8c8c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-form-div-desc {
|
||||||
|
height: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-form {
|
||||||
|
height: 80%;
|
||||||
|
width: 80%;
|
||||||
|
margin: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid black;
|
||||||
|
background: #fff9f1;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 2px 2px 3px #a06100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#recette-form-div-desc {
|
||||||
|
height: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#recette-form-description {
|
||||||
|
height: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-add-form-all {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-form-add {
|
||||||
|
flex: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-form-add-ingr {
|
||||||
|
height: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-form-add-tag {
|
||||||
|
height: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recette-form-add-additional{
|
||||||
|
flex: 1;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.err {
|
||||||
|
color: red;
|
||||||
|
display: none;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.liste-ingr-elem {
|
||||||
|
display: flex;
|
||||||
|
height: 100px;
|
||||||
|
padding:5px;
|
||||||
|
background-color: #c6fdefe0;
|
||||||
|
color: #000000;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 1px 1px 1px black;
|
||||||
|
margin-top: .25%;
|
||||||
|
margin-bottom: .25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.liste-ingr-elem:hover {
|
||||||
|
border:3px solid #007054;
|
||||||
|
box-shadow: 2px 2px 10px black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.liste-ingr-elem-text {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
flex:12;
|
||||||
|
padding-left: 20px;
|
||||||
|
line-height: 100px;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.liste-ingr-elem-modif {
|
||||||
|
line-height: 100px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.liste-tag-elem {
|
||||||
|
display: flex;
|
||||||
|
height: 40px;
|
||||||
|
padding:5px;
|
||||||
|
background-color: #c6fdefe0;
|
||||||
|
color: #000000;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 1px 1px 1px black;
|
||||||
|
margin-top: .25%;
|
||||||
|
margin-bottom: .25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.liste-tag-elem:hover {
|
||||||
|
border:3px solid #007054;
|
||||||
|
box-shadow: 2px 2px 10px black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.liste-tag-elem-text {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
flex:12;
|
||||||
|
padding-left: 20px;
|
||||||
|
line-height: 40px;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.liste-tag-elem-modif {
|
||||||
|
line-height: 40px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ingr-image {
|
||||||
|
flex: 1;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Footer et son contenue*/
|
||||||
|
footer{
|
||||||
|
padding: 25px;
|
||||||
|
background: rgb(11, 189, 144);
|
||||||
|
color: white;
|
||||||
|
border-top: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-recettes {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
BIN
public/assets/images/Logo.jpg
Executable file
BIN
public/assets/images/Logo.jpg
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
110
public/assets/js/advanced-search.js
Executable file
110
public/assets/js/advanced-search.js
Executable file
@@ -0,0 +1,110 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function(){
|
||||||
|
|
||||||
|
let searchInput = "";
|
||||||
|
let tagsId = [];
|
||||||
|
let ingredientsId = [];
|
||||||
|
|
||||||
|
function buildCards( data ){
|
||||||
|
|
||||||
|
if( Array.isArray( data ) ){
|
||||||
|
data = Object.assign({}, data );
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById( "recetteList" ).innerHTML = '';
|
||||||
|
for( const [key, recette] of Object.entries( data ) ) {
|
||||||
|
let HTML_CONTENT = `<a class="recette-icone" href="${recette.url}">
|
||||||
|
<img class="recette-preview-image" src="${recette.photo}">
|
||||||
|
<div class="recette-icone-content">
|
||||||
|
<h3>${recette.titre_recette}</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Temps de préparation : ${recette.temps_de_preparation}</li>
|
||||||
|
<li>Nombre d'ingrédients : ${recette.nb_ingredients}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</a>`
|
||||||
|
document.getElementById("recetteList").innerHTML += HTML_CONTENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function advancedSearch(){
|
||||||
|
|
||||||
|
let form = new FormData();
|
||||||
|
form.append( "title", searchInput );
|
||||||
|
form.append( "tagsId", tagsId );
|
||||||
|
form.append( "ingredientsId", ingredientsId );
|
||||||
|
|
||||||
|
const XML = new XMLHttpRequest();
|
||||||
|
XML.open( "POST", '/api/recettes/list' );
|
||||||
|
XML.onreadystatechange = function ( e ) {
|
||||||
|
if( XML.status === 200 && XML.readyState === XMLHttpRequest.DONE ) {
|
||||||
|
try {
|
||||||
|
console.log( XML.response );
|
||||||
|
let response = JSON.parse(XML.response);
|
||||||
|
console.log( "Reponse parsé" );
|
||||||
|
buildCards( response.data );
|
||||||
|
} catch( e ) {
|
||||||
|
console.log( "Ce n'est pas un fichier JSON" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
XML.send( form );
|
||||||
|
}
|
||||||
|
|
||||||
|
const INGREDIENTSLIST = document.getElementById( 'ingredientsList' );
|
||||||
|
for( let item of INGREDIENTSLIST.getElementsByTagName( 'li' ) ){
|
||||||
|
item.addEventListener( 'click', function(){
|
||||||
|
|
||||||
|
let ingredientId = Number( item.getAttribute( 'data-ingredient-id' ) );
|
||||||
|
let index = ingredientsId.indexOf( ingredientId );
|
||||||
|
|
||||||
|
if( index > -1 ) {
|
||||||
|
ingredientsId.splice( index, 1 );
|
||||||
|
item.classList.add( 'tag-unselected' );
|
||||||
|
item.classList.remove( 'tag-selected' );
|
||||||
|
} else {
|
||||||
|
ingredientsId.push( ingredientId );
|
||||||
|
item.classList.add( 'tag-selected' );
|
||||||
|
item.classList.remove( 'tag-unselected' );
|
||||||
|
}
|
||||||
|
|
||||||
|
advancedSearch();
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const TAGSLIST = document.getElementById( 'tagsList' );
|
||||||
|
for( let item of TAGSLIST.getElementsByTagName( 'li' ) ){
|
||||||
|
item.addEventListener( 'click', function(){
|
||||||
|
|
||||||
|
let tagId = Number( item.getAttribute( 'data-tag-id' ) );
|
||||||
|
let index = tagsId.indexOf( tagId );
|
||||||
|
|
||||||
|
if( index > -1 ) {
|
||||||
|
tagsId.splice( index, 1 );
|
||||||
|
item.classList.add( 'tag-unselected' );
|
||||||
|
item.classList.remove( 'tag-selected' );
|
||||||
|
} else {
|
||||||
|
tagsId.push( tagId );
|
||||||
|
item.classList.add( 'tag-selected' );
|
||||||
|
item.classList.remove( 'tag-unselected' );
|
||||||
|
}
|
||||||
|
|
||||||
|
advancedSearch();
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const SEARCHBAR = document.getElementById( 'searchForm' );
|
||||||
|
searchInput = new URLSearchParams(document.location.search).get("s") || searchInput;
|
||||||
|
|
||||||
|
if( searchInput !== undefined && searchInput != "" ) {
|
||||||
|
advancedSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
SEARCHBAR.onsubmit = function( e ){
|
||||||
|
e.preventDefault();
|
||||||
|
searchInput = document.getElementById( 'searchFormField' ).value;
|
||||||
|
advancedSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
173
public/assets/js/form.js
Normal file
173
public/assets/js/form.js
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", function(){
|
||||||
|
|
||||||
|
|
||||||
|
let compl_form = document.getElementById("recette-form-complete");
|
||||||
|
console.log(compl_form);
|
||||||
|
|
||||||
|
function formulaire_ingredient_update(event) {
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
console.log(event);
|
||||||
|
|
||||||
|
let form = document.getElementById("recette-form-ingr-add");
|
||||||
|
|
||||||
|
let cible = document.getElementById("recette-form-ingr");
|
||||||
|
let nomIngredient = document.getElementById("recette-form-ingr-nom");
|
||||||
|
let image = document.getElementById("recette-form-ingr-photo");
|
||||||
|
if( nomIngredient.value === "" || image.value === "" ){
|
||||||
|
alert( "Un champ requis est manquant." );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ingr = form.nom;
|
||||||
|
|
||||||
|
let form_data = new FormData(form);
|
||||||
|
|
||||||
|
fetch("/api/ingredients/create", {
|
||||||
|
method : "POST",
|
||||||
|
body: form_data
|
||||||
|
}).then( reponse => {
|
||||||
|
if (!reponse.ok) {
|
||||||
|
let div_err = document.getElementById("recette-form-div-err-ingr");
|
||||||
|
|
||||||
|
div_err.style.display = "inherit";
|
||||||
|
div_err.innerText = "Erreur de connection au serveur";
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reponse.json().then( data => {
|
||||||
|
if (data.success) {
|
||||||
|
let new_elem = document.createElement("option");
|
||||||
|
|
||||||
|
new_elem.setAttribute("value",data.id);
|
||||||
|
new_elem.innerText = document.getElementById('recette-form-ingr-nom').value;
|
||||||
|
|
||||||
|
cible.appendChild(new_elem);
|
||||||
|
|
||||||
|
document.getElementById('recette-form-ingr-nom').value = "";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function formulaire_tag_update(event){
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
let form = document.getElementById("recette-form-tag-add");
|
||||||
|
|
||||||
|
let cible = document.getElementById("recette-form-tag");
|
||||||
|
let nomTag = document.getElementById("recette-form-tag-nom");
|
||||||
|
if( nomTag.value === "" ){
|
||||||
|
alert( "Un champ requis est manquant." );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tag = form.tag;
|
||||||
|
|
||||||
|
let form_data = new FormData(form);
|
||||||
|
|
||||||
|
fetch("/api/tags/create", {
|
||||||
|
method : "POST",
|
||||||
|
body: form_data
|
||||||
|
}).then( reponse => {
|
||||||
|
if (!reponse.ok) {
|
||||||
|
let div_err = document.getElementById("recette-form-div-err-tag");
|
||||||
|
|
||||||
|
div_err.style.display = "inherit";
|
||||||
|
div_err.innerText = "Erreur de connection au serveur";
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reponse.json().then( data => {
|
||||||
|
if (data.success) {
|
||||||
|
let new_elem = document.createElement("option");
|
||||||
|
|
||||||
|
new_elem.setAttribute("value",data.id);
|
||||||
|
new_elem.innerText = nomTag.value;
|
||||||
|
|
||||||
|
cible.appendChild(new_elem);
|
||||||
|
|
||||||
|
nomTag.value = "";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function formulaire_traitement(event){
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
console.log( event );
|
||||||
|
|
||||||
|
let nom = document.getElementById('recette-form-nom');
|
||||||
|
let temps = document.getElementsByName('recette-form-nom');
|
||||||
|
let photo = document.getElementById('recette-form-photo');
|
||||||
|
let ingredients = document.getElementById('recette-form-ingr');
|
||||||
|
let tags = document.getElementById('recette-form-tag');
|
||||||
|
let description = document.getElementById('recette-form-description');
|
||||||
|
|
||||||
|
if( nom.value === "" || temps.value == "" || ingredients.value === "" || tags.value === ""){
|
||||||
|
console.log( "Another field" );
|
||||||
|
alert( "Un champ requis est manquant." );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if( !IS_EDIT && photo.value === ""){
|
||||||
|
console.log( "Photo field" );
|
||||||
|
alert( "Un champ requis est manquant." );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( temps <= 0 ){
|
||||||
|
alert( "Le temps doit être positif ou supérieur à 0." );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let form_data = new FormData(compl_form);
|
||||||
|
form_data.append( "ingredients", form_data.getAll("ingr").join(",") );
|
||||||
|
form_data.append( "tags", form_data.getAll( "tag").join(",") );
|
||||||
|
if( RECETTE_ID !== 0 )
|
||||||
|
form_data.append( 'id', RECETTE_ID );
|
||||||
|
|
||||||
|
let endpoint = IS_EDIT ? "/api/recettes/edit" : "/api/recettes/create";
|
||||||
|
|
||||||
|
fetch( endpoint, {
|
||||||
|
method : "POST",
|
||||||
|
body: form_data
|
||||||
|
}).then( reponse => {
|
||||||
|
if (!reponse.ok) {
|
||||||
|
alert( "Erreur survenie" );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reponse.json().then( data => {
|
||||||
|
if (data.success) {
|
||||||
|
if( IS_EDIT )
|
||||||
|
alert( "Votre recette a été ajouté, vous allez être redirigé." );
|
||||||
|
else
|
||||||
|
alert( "Votre recette a été ajouté, vous allez être redirigé." );
|
||||||
|
window.location.href = data.route !== null ? data.route : window.location.origin;
|
||||||
|
} else {
|
||||||
|
alert( data.error || "" );
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let ingr_form = document.getElementById("recette-form-ingr-add");
|
||||||
|
|
||||||
|
ingr_form.addEventListener('submit', formulaire_ingredient_update);
|
||||||
|
|
||||||
|
let tag_form = document.getElementById("recette-form-tag-add");
|
||||||
|
|
||||||
|
tag_form.addEventListener('submit', formulaire_tag_update);
|
||||||
|
compl_form.addEventListener('submit', formulaire_traitement);
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
67
public/assets/js/login.js
Executable file
67
public/assets/js/login.js
Executable file
@@ -0,0 +1,67 @@
|
|||||||
|
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( "Mauvais nom d'utilisateur ou mauvais mot de passe." );
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
FORM.showError( "Mauvais nom d'utilisateur ou mauvais mot de passe." );
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
62
public/assets/js/manage-ingredients.js
Normal file
62
public/assets/js/manage-ingredients.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", function(){
|
||||||
|
|
||||||
|
|
||||||
|
let modifier_button = document.getElementsByClassName("ingr-modifier");
|
||||||
|
|
||||||
|
console.log(modifier_button);
|
||||||
|
|
||||||
|
for (const element of modifier_button) {
|
||||||
|
element.addEventListener('click', function ( e ) {
|
||||||
|
let modif = element.parentElement.previousSibling.previousSibling;
|
||||||
|
|
||||||
|
let data = e.target.getAttribute("data-id");
|
||||||
|
|
||||||
|
let form = document.createElement("form");
|
||||||
|
|
||||||
|
form.setAttribute("method", "POST");
|
||||||
|
form.setAttribute("action", "/ingredients/edit/" + data);
|
||||||
|
form.setAttribute("class","liste-ingr-elem-text")
|
||||||
|
|
||||||
|
let input = document.createElement("input");
|
||||||
|
|
||||||
|
input.setAttribute("type", "text");
|
||||||
|
input.setAttribute("class", "form-control");
|
||||||
|
input.setAttribute("name","modif_ingr");
|
||||||
|
input.setAttribute("placeholder", "modificiation");
|
||||||
|
|
||||||
|
let hidden = document.createElement("input");
|
||||||
|
|
||||||
|
hidden.setAttribute("type","hidden");
|
||||||
|
hidden.setAttribute("name","id");
|
||||||
|
hidden.setAttribute("value", data);
|
||||||
|
|
||||||
|
let button = document.createElement("button");
|
||||||
|
|
||||||
|
button.setAttribute("type", "submit");
|
||||||
|
button.setAttribute("class", "btn btn-primary");
|
||||||
|
button.innerText = "Submit Modification";
|
||||||
|
|
||||||
|
form.append(input)
|
||||||
|
form.append(hidden)
|
||||||
|
form.append(button)
|
||||||
|
|
||||||
|
modif.replaceWith(form);
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let suppr = document.getElementsByClassName("ingr-suppr");
|
||||||
|
|
||||||
|
for (const element of suppr) {
|
||||||
|
element.addEventListener('click', function ( e ) {
|
||||||
|
let confirm = window.confirm("Voulez vous supprimez ?");
|
||||||
|
|
||||||
|
let data = e.target.getAttribute("data-id");
|
||||||
|
|
||||||
|
if (confirm) {
|
||||||
|
window.location.href = "/ingredients/delete/" + data;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
62
public/assets/js/manage-tags.js
Normal file
62
public/assets/js/manage-tags.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", function(){
|
||||||
|
|
||||||
|
|
||||||
|
let modifier_button = document.getElementsByClassName("tag-modifier");
|
||||||
|
|
||||||
|
console.log(modifier_button);
|
||||||
|
|
||||||
|
for (const element of modifier_button) {
|
||||||
|
element.addEventListener('click', function ( e ) {
|
||||||
|
let modif = element.parentElement.previousSibling.previousSibling;
|
||||||
|
|
||||||
|
let data = e.target.getAttribute("data-id");
|
||||||
|
|
||||||
|
let form = document.createElement("form");
|
||||||
|
|
||||||
|
form.setAttribute("method", "POST");
|
||||||
|
form.setAttribute("action", "/tags/edit/" + data);
|
||||||
|
form.setAttribute("class","liste-tag-elem-text")
|
||||||
|
|
||||||
|
let input = document.createElement("input");
|
||||||
|
|
||||||
|
input.setAttribute("type", "text");
|
||||||
|
input.setAttribute("class", "form-control");
|
||||||
|
input.setAttribute("name","modif_tag");
|
||||||
|
input.setAttribute("placeholder", "modificiation");
|
||||||
|
|
||||||
|
let hidden = document.createElement("input");
|
||||||
|
|
||||||
|
hidden.setAttribute("type","hidden");
|
||||||
|
hidden.setAttribute("name","id");
|
||||||
|
hidden.setAttribute("value", data);
|
||||||
|
|
||||||
|
let button = document.createElement("button");
|
||||||
|
|
||||||
|
button.setAttribute("type", "submit");
|
||||||
|
button.setAttribute("class", "btn btn-primary");
|
||||||
|
button.innerText = "Submit Modification";
|
||||||
|
|
||||||
|
form.append(input)
|
||||||
|
form.append(hidden)
|
||||||
|
form.append(button)
|
||||||
|
|
||||||
|
modif.replaceWith(form);
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let suppr = document.getElementsByClassName("tag-suppr");
|
||||||
|
|
||||||
|
for (const element of suppr) {
|
||||||
|
element.addEventListener('click', function ( e ) {
|
||||||
|
let confirm = window.confirm("Voulez vous supprimez ?");
|
||||||
|
|
||||||
|
let data = e.target.getAttribute("data-id");
|
||||||
|
|
||||||
|
if (confirm) {
|
||||||
|
window.location.href = "/tags/delete/" + data;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
13
public/index.php
Executable file
13
public/index.php
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Kernel;
|
||||||
|
|
||||||
|
if( !defined( 'APP_ROOT' ) )
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Définit la racine de l'application en chemin absolue.
|
||||||
|
*/
|
||||||
|
define( 'APP_ROOT', realpath( __DIR__ . '/../') . '/' );
|
||||||
|
|
||||||
|
require_once '../src/Kernel.php';
|
||||||
|
Kernel::start();
|
||||||
0
public/uploads/.gitignore
vendored
Normal file
0
public/uploads/.gitignore
vendored
Normal file
41
src/Domain/Controller.php
Normal file
41
src/Domain/Controller.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain;
|
||||||
|
|
||||||
|
use App\Http\Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe abstraite des controllers qui vont pouvoir permettre de gérer les routes et leur retour.
|
||||||
|
*/
|
||||||
|
abstract class Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Va permettre de créer une route en préremplissant certains arguments.
|
||||||
|
* Pour les paramètres, veuillez voir le constructeur d'une Route.
|
||||||
|
*
|
||||||
|
* @param mixed ...$args
|
||||||
|
* @return Route
|
||||||
|
*
|
||||||
|
* @see Route::__construct()
|
||||||
|
*/
|
||||||
|
public static function Route( ...$args ): Route {
|
||||||
|
$defaults = [
|
||||||
|
'routeController' => static::class,
|
||||||
|
'routeMethods' => [ 'GET' ],
|
||||||
|
'pageHeadTitle' => 'Page',
|
||||||
|
];
|
||||||
|
|
||||||
|
$args = array_merge($defaults, $args);
|
||||||
|
return new Route( ...$args );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de définir les routes du controller sous le format d'une liste d'objets Route.
|
||||||
|
* Vous pouvez utiliser la method self::Route() pour préremplir des champs.
|
||||||
|
*
|
||||||
|
* @return Route[]
|
||||||
|
* @see self::Route()
|
||||||
|
*/
|
||||||
|
abstract public static function defineRoutes(): array;
|
||||||
|
|
||||||
|
}
|
||||||
39
src/Domain/Ingredients/Ingredient.php
Normal file
39
src/Domain/Ingredients/Ingredient.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Ingredients;
|
||||||
|
|
||||||
|
use App\Domain\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe qui va gérer les ingrédients.
|
||||||
|
*/
|
||||||
|
class Ingredient extends Model {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Le numéro de l'ingrédient.
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public int $num_ingredient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Le nom de l'ingrédient
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $nom_ingredient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* L'URL vers l'image de l'ingrédient.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $photo_ingredient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne le numéro de l'ingrédient.
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getID(): int
|
||||||
|
{
|
||||||
|
return $this->num_ingredient;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
70
src/Domain/Ingredients/IngredientManagementController.php
Normal file
70
src/Domain/Ingredients/IngredientManagementController.php
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Ingredients;
|
||||||
|
|
||||||
|
use App\Domain\Controller;
|
||||||
|
use App\Domain\Ingredients\IngredientRepository;
|
||||||
|
use App\Domain\Tags\TagRepository;
|
||||||
|
use App\Helpers\Authentification;
|
||||||
|
use App\Http\JSONResponse;
|
||||||
|
use App\Http\Request;
|
||||||
|
use App\Infrastructure\View;
|
||||||
|
|
||||||
|
class IngredientManagementController extends Controller {
|
||||||
|
|
||||||
|
public static function defineRoutes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
self::Route( routeUrl: '/manage/ingredients', routeName: 'manage->ingredients', routeAction: 'index', pageHeadTitle: 'Gestion des ingredients' ),
|
||||||
|
self::Route( routeUrl: '/ingredients/edit/{int}', routeName: 'ingredients->edit', routeAction: 'edit', routeMethods: [ 'POST' ] ),
|
||||||
|
self::Route( routeUrl: '/ingredients/delete/{int}', routeName: 'ingredients->delete', routeAction: 'delete' ),
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(){
|
||||||
|
|
||||||
|
if( !Authentification::isLoggedIn() )
|
||||||
|
die( "Not logged in" );
|
||||||
|
|
||||||
|
$tags = new IngredientRepository()->getAll();
|
||||||
|
return new View( 'ingredients/index', [
|
||||||
|
'ingredients' => $tags,
|
||||||
|
]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit( int $idIngredient ): never {
|
||||||
|
if( !Authentification::isLoggedIn() )
|
||||||
|
die( "Not logged in" );
|
||||||
|
|
||||||
|
$ing = new IngredientRepository()->getByID( $idIngredient );
|
||||||
|
if( !$ing )
|
||||||
|
die( "Ingredient not found" );
|
||||||
|
|
||||||
|
$name = Request::post( 'modif_ingr' );
|
||||||
|
if( !$name || $name == "" )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'Name not defined' ] );
|
||||||
|
|
||||||
|
$ing->nom_ingredient = $name;
|
||||||
|
|
||||||
|
if( !new IngredientRepository()->update( $ing ) )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'An error occured while updating ingredient' ] );
|
||||||
|
|
||||||
|
Request::redirectTo( 'manage->ingredients');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete( int $idIngredient ): never {
|
||||||
|
if( !Authentification::isLoggedIn() )
|
||||||
|
die( "Not logged in" );
|
||||||
|
|
||||||
|
$ing = new IngredientRepository()->getByID( $idIngredient );
|
||||||
|
if( !$ing )
|
||||||
|
die( "Tag not found" );
|
||||||
|
|
||||||
|
if( !new IngredientRepository()->delete( $ing ) )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'An error occured while deleting ingredient' ] );
|
||||||
|
|
||||||
|
Request::redirectTo( 'manage->ingredients' );
|
||||||
|
}
|
||||||
|
}
|
||||||
175
src/Domain/Ingredients/IngredientRepository.php
Normal file
175
src/Domain/Ingredients/IngredientRepository.php
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Ingredients;
|
||||||
|
|
||||||
|
use App\Domain\LinkableInterface;
|
||||||
|
use App\Domain\Recettes\Recette;
|
||||||
|
use App\Domain\Repository;
|
||||||
|
use App\Domain\Model;
|
||||||
|
use App\Domain\Tags\Tag;
|
||||||
|
use App\Kernel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe qui va permettre de gérer les requêtes BDD en lien avec les ingrédients.
|
||||||
|
* Les ingrédients sont liables à d'autres entités comme les Recettes.
|
||||||
|
*/
|
||||||
|
class IngredientRepository extends Repository implements LinkableInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'avoir le nom de l'entité dont dépend ce repo.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function getEntity(): string
|
||||||
|
{
|
||||||
|
return Ingredient::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La structure de notre table dans la BDD.
|
||||||
|
* Un champ link_recettes a été ajouté pour donner le nom de la table de lien entre nos recettes.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getStructure(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'table' => 'Ingredient',
|
||||||
|
'columns' => [
|
||||||
|
'num_ingredient', 'nom_ingredient', 'photo_ingredient',
|
||||||
|
],
|
||||||
|
'link_recettes' => 'Listeingredient'
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'obtenir une liste de tous les ingrédients objet Ingreident.
|
||||||
|
*
|
||||||
|
* @return Ingredient[]|null
|
||||||
|
*/
|
||||||
|
public function getAll(): ?array {
|
||||||
|
$sqlQuery = "SELECT * FROM {$this->tableName};";
|
||||||
|
$results = $this->selectGetAll($sqlQuery);
|
||||||
|
if( $results === null )
|
||||||
|
return null;
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'obtenir un ingrédient spécifique par son ID.
|
||||||
|
*
|
||||||
|
* @param int $id
|
||||||
|
*
|
||||||
|
* @return Ingredient|null
|
||||||
|
*/
|
||||||
|
public function getByID( int $id ): ?Ingredient {
|
||||||
|
$sqlQuery = "SELECT * FROM {$this->tableName} WHERE num_ingredient = {$id}";
|
||||||
|
$results = $this->selectGetAll($sqlQuery);
|
||||||
|
if( $results === null || count( $results ) > 1 )
|
||||||
|
return null;
|
||||||
|
return $results[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'obtenir, sous forme de liste, toutes les entrées qui lient des ingrédients.
|
||||||
|
*
|
||||||
|
* @param string $linkedTo La table qui permet de faire la liaison des ingrédients avec une autre entité.
|
||||||
|
* @param string $linkingField Le champ qui permet de faire la liaison des ingrédients avec une autre entité.
|
||||||
|
* @param Model $linkedEntity L'autre entité.
|
||||||
|
*
|
||||||
|
* @see LinkableInterface
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function getIdLinkedTo( string $linkedTo, string $linkingField, Model $linkedEntity ): ?array {
|
||||||
|
|
||||||
|
$linkName = 'link_' . $linkedTo;
|
||||||
|
if( !isset( $this->globalStructure[$linkName]))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
$sqlQuery = "SELECT * FROM {$this->globalStructure[$linkName]} WHERE {$linkingField} = {$linkedEntity->getId()};";
|
||||||
|
$results = $this->selectGetAll($sqlQuery, true);
|
||||||
|
if( $results === null )
|
||||||
|
return null;
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'ajouter un lien entre un ingrédient et une autre entité comme Recette.
|
||||||
|
*
|
||||||
|
* @param string $linkedTo
|
||||||
|
* @param string $linkingField
|
||||||
|
* @param Model $linkedEntity
|
||||||
|
* @param Model $ingredientEntity
|
||||||
|
*
|
||||||
|
* @see LinkableInterface
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function addLinkBetween( string $linkedTo, string $linkingField, Model $linkedEntity, Model $ingredientEntity ): bool {
|
||||||
|
|
||||||
|
$linkName = 'link_' . $linkedTo;
|
||||||
|
if( !isset( $this->globalStructure[$linkName]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$query = "INSERT INTO {$this->globalStructure[$linkName]} ({$linkingField},num_ingredient) VALUES ({$linkedEntity->getId()}, {$ingredientEntity->getID()});";
|
||||||
|
$statement = Kernel::$DB->pdo->prepare( $query );
|
||||||
|
|
||||||
|
return $statement->execute();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retire un lien dans la BDD entre un ingrédient et une autre entité.
|
||||||
|
*
|
||||||
|
* @param string $linkedTo
|
||||||
|
* @param string $linkingField
|
||||||
|
* @param Model $linkedEntity
|
||||||
|
* @param Model $ingredientEntity
|
||||||
|
*
|
||||||
|
* @see LinkableInterface
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function removeLinkBetween(string $linkedTo, string $linkingField, Model $linkedEntity, Model $ingredientEntity ): bool
|
||||||
|
{
|
||||||
|
$linkName = 'link_' . $linkedTo;
|
||||||
|
if( !isset( $this->globalStructure[$linkName]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$query = "DELETE FROM {$this->globalStructure[$linkName]} WHERE {$linkingField} = {$linkedEntity->getId()} AND num_ingredient = {$ingredientEntity->getId()};";
|
||||||
|
$statement = Kernel::$DB->pdo->prepare( $query );
|
||||||
|
return $statement->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ajouter un ingrédient dans la BDD.
|
||||||
|
*
|
||||||
|
* @param Model $ingredient
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function add( Model $ingredient ): bool {
|
||||||
|
return $this->addEntity( $ingredient );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour un ingrédient dans la BDD.
|
||||||
|
*
|
||||||
|
* @param Model $ingredient
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function update( Model $ingredient ): bool {
|
||||||
|
return $this->updateEntity( $ingredient, 'num_ingredient' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supprime un ingrédient de la BDD.
|
||||||
|
*
|
||||||
|
* @param Model $ingredient
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function delete( Model $ingredient ): bool {
|
||||||
|
return $this->deleteEntity( $ingredient, 'num_ingredient' );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
71
src/Domain/Ingredients/IngredientsAPIController.php
Normal file
71
src/Domain/Ingredients/IngredientsAPIController.php
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Ingredients;
|
||||||
|
|
||||||
|
use App\Domain\Controller;
|
||||||
|
use App\Helpers\UploadFiles;
|
||||||
|
use App\Http\JSONResponse;
|
||||||
|
use App\Http\Request;
|
||||||
|
use App\Kernel;
|
||||||
|
|
||||||
|
class IngredientsAPIController extends Controller {
|
||||||
|
|
||||||
|
public static function defineRoutes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
self::Route( routeUrl: '/api/ingredients/create', routeName: 'api->ingredients->create', routeAction: 'create', routeMethods: ['POST'] ),
|
||||||
|
self::Route( routeUrl: '/api/ingredients/edit', routeName: 'api->ingredients->edit', routeAction: 'edit', routeMethods: ['POST'] ),
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(){
|
||||||
|
|
||||||
|
$name = Request::post( 'name' );
|
||||||
|
$fileNameField = "image";
|
||||||
|
if( !$name || $name == "" )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'Name not defined' ] );
|
||||||
|
|
||||||
|
$urlOrError = UploadFiles::uploadFile( $fileNameField, UploadFiles::IMAGE_MIME_TYPES );
|
||||||
|
if( is_int( $urlOrError ) ){
|
||||||
|
JSONResponse::sendError( [ 'error' => $urlOrError ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
$ingredient = new Ingredient();
|
||||||
|
|
||||||
|
$ingredient->num_ingredient = 0;
|
||||||
|
$ingredient->nom_ingredient = $name;
|
||||||
|
$ingredient->photo_ingredient = $urlOrError;
|
||||||
|
|
||||||
|
if( !new IngredientRepository()->add( $ingredient ) )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'An error occured while adding ingredient' ] );
|
||||||
|
|
||||||
|
JSONResponse::sendSuccess( [ 'image_url' => $urlOrError, 'id' => Kernel::$DB->pdo->lastInsertId() ] );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(){
|
||||||
|
|
||||||
|
$id = Request::post( 'id' ) ?? 0;
|
||||||
|
$ingredient = new IngredientRepository()->getByID( $id );
|
||||||
|
if( !$ingredient )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'Ingredient not found' ] );
|
||||||
|
|
||||||
|
$name = Request::post( 'name' );
|
||||||
|
$fileNameField = "image";
|
||||||
|
|
||||||
|
if( $name ) {
|
||||||
|
$ingredient->nom_ingredient = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$urlOrError = UploadFiles::uploadFile( $fileNameField );
|
||||||
|
if( !is_int( $urlOrError ) ){
|
||||||
|
$ingredient->photo_ingredient = $urlOrError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !new IngredientRepository()->update( $ingredient ) )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'An error occured while updating ingredient' ] );
|
||||||
|
|
||||||
|
JSONResponse::sendSuccess( [] );
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/Domain/Ingredients/UseIngredientsInterface.php
Normal file
38
src/Domain/Ingredients/UseIngredientsInterface.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Ingredients;
|
||||||
|
|
||||||
|
use App\Domain\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface utilisée par tous les repository qui sont en lien avec les ingrédients.
|
||||||
|
*/
|
||||||
|
interface UseIngredientsInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de récupérer tous les ingrédients liés à ce Modèle.
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function getAllLinkedIngredients( Model $entity ): ?array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'ajouter un ingrédient en lien avec notre autre entité.
|
||||||
|
*
|
||||||
|
* @param Ingredient $ingredient
|
||||||
|
* @param Model $entity
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function addAnIngredient( Ingredient $ingredient, Model $entity ): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de retirer un lien entre un ingrédient et une autre entité.
|
||||||
|
*
|
||||||
|
* @param Ingredient $ingredient
|
||||||
|
* @param Model $entity
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function removeAnIngredient( Ingredient $ingredient, Model $entity ): bool;
|
||||||
|
|
||||||
|
}
|
||||||
51
src/Domain/LinkableInterface.php
Normal file
51
src/Domain/LinkableInterface.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface pour dire qu'un objet peut avoir un lien avec un autre objet par une table.
|
||||||
|
* Il s'agit d'une interface qui s'utilise sur des repositories de meta-données (Tag, Ingrédients)
|
||||||
|
*
|
||||||
|
* Les champs de toutes les méthodes se présentent de la même façon.
|
||||||
|
* $linkedTo : Champ qui va désigner le nom de la table de liens que vous avez dans getStructure().
|
||||||
|
* $linkingField : Champ dans la BDD qui va permettre de lier l'élément courant et l'étranger.
|
||||||
|
* $linkedEntity : Une instance de l'entité étrangère que l'on veut lier.
|
||||||
|
*/
|
||||||
|
interface LinkableInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de récupérer tous les liens entre notre entité implémentée et notre entité étrangère.
|
||||||
|
*
|
||||||
|
* @param string $linkedTo
|
||||||
|
* @param string $linkingField
|
||||||
|
* @param Model $linkedEntity
|
||||||
|
*
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function getIdLinkedTo( string $linkedTo, string $linkingField, Model $linkedEntity ): ?array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'ajouter un lien entre notre entité implémentée et notre entité étrangère.
|
||||||
|
*
|
||||||
|
* @param string $linkedTo
|
||||||
|
* @param string $linkingField
|
||||||
|
* @param Model $linkedEntity
|
||||||
|
* @param Model $entity
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function addLinkBetween( string $linkedTo, string $linkingField, Model $linkedEntity, Model $entity ): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de retirer un lien entre notre entité implémentée et notre entité étrangère.
|
||||||
|
*
|
||||||
|
* @param string $linkedTo
|
||||||
|
* @param string $linkingField
|
||||||
|
* @param Model $linkedEntity
|
||||||
|
* @param Model $entity
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function removeLinkBetween( string $linkedTo, string $linkingField, Model $linkedEntity, Model $entity ): bool;
|
||||||
|
|
||||||
|
}
|
||||||
16
src/Domain/Model.php
Normal file
16
src/Domain/Model.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe abstraite pour gérer l'implémentation des entités de données.
|
||||||
|
*/
|
||||||
|
abstract class Model {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter de l'ID primaire du model spécifique.
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
abstract public function getID(): int;
|
||||||
|
|
||||||
|
}
|
||||||
35
src/Domain/Pages/PagesController.php
Normal file
35
src/Domain/Pages/PagesController.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Pages;
|
||||||
|
|
||||||
|
use App\Domain\Controller;
|
||||||
|
use App\Domain\Recettes\RecetteRepository;
|
||||||
|
use App\Infrastructure\View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller pour les pages sans lien avec un contenu spécifique.
|
||||||
|
* Exemple : Page d'accueil.
|
||||||
|
*/
|
||||||
|
class PagesController extends Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Définit les routes globales d'acceuil.
|
||||||
|
* @return array|\App\Http\Route[]
|
||||||
|
*/
|
||||||
|
public static function defineRoutes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
self::Route( routeUrl: '/', routeName: 'home', routeAction: 'index', pageHeadTitle: "Home Page" ),
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route de la page d'accueil.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function index(): View {
|
||||||
|
return new View( 'home', [ 'recettes' => new RecetteRepository()->getAllRecettesBrowse(1, 3 ) ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
63
src/Domain/Recettes/Recette.php
Normal file
63
src/Domain/Recettes/Recette.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Recettes;
|
||||||
|
|
||||||
|
use App\Domain\Ingredients\Ingredient;
|
||||||
|
use App\Domain\Ingredients\IngredientRepository;
|
||||||
|
use App\Domain\Ingredients\UseIngredientsInterface;
|
||||||
|
use App\Domain\Model;
|
||||||
|
use App\Domain\Tags\Tag;
|
||||||
|
use App\Helpers\Markdown;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe qui va permettre de gérer tous les objets recette.
|
||||||
|
*/
|
||||||
|
#[\AllowDynamicProperties]
|
||||||
|
class Recette extends Model {
|
||||||
|
|
||||||
|
public int $num_recette;
|
||||||
|
public string $titre_recette;
|
||||||
|
public string $slug;
|
||||||
|
public string $description_recette;
|
||||||
|
public string $photo;
|
||||||
|
public string $publication_date;
|
||||||
|
public int $temps_de_preparation;
|
||||||
|
|
||||||
|
public function getID(): int
|
||||||
|
{
|
||||||
|
return $this->num_recette;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convertit la description de Markdown à HTML.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getHTMLDescription(): string {
|
||||||
|
return Markdown::convertToHTML( $this->description_recette );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère une liste de tous les ingrédients liés à la recette.
|
||||||
|
* @return Ingredient[]|null
|
||||||
|
*/
|
||||||
|
public function getAllLinkedIngredients(): ?array
|
||||||
|
{
|
||||||
|
return new RecetteRepository()->getAllLinkedIngredients( $this );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère une liste de tous les tags liés à la recette.
|
||||||
|
* @return Tag[]|null
|
||||||
|
*/
|
||||||
|
public function getAllLinkedTags(): ?array
|
||||||
|
{
|
||||||
|
return new RecetteRepository()->getAllLinkedTags( $this );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNumberOfIngredients(): int {
|
||||||
|
|
||||||
|
$response = $this->getAllLinkedIngredients();
|
||||||
|
return $response !== null ? count( $response ) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
196
src/Domain/Recettes/RecetteRepository.php
Normal file
196
src/Domain/Recettes/RecetteRepository.php
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Recettes;
|
||||||
|
|
||||||
|
use App\Domain\Ingredients\Ingredient;
|
||||||
|
use App\Domain\Ingredients\IngredientRepository;
|
||||||
|
use App\Domain\Ingredients\UseIngredientsInterface;
|
||||||
|
use App\Domain\Model;
|
||||||
|
use App\Domain\Repository;
|
||||||
|
use App\Domain\Tags\Tag;
|
||||||
|
use App\Domain\Tags\TagRepository;
|
||||||
|
use App\Domain\Tags\UseTagsInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe qui permet de faire le lien entre la BDD et le site pour les recettes.
|
||||||
|
* Les recettes sont en lien avec les ingrédients.
|
||||||
|
*/
|
||||||
|
class RecetteRepository extends Repository implements UseIngredientsInterface, UseTagsInterface {
|
||||||
|
|
||||||
|
public static function getEntity(): string
|
||||||
|
{
|
||||||
|
return Recette::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getStructure(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'table' => 'Recette',
|
||||||
|
'columns' => [
|
||||||
|
'num_recette', 'titre_recette', 'slug', 'description_recette', 'photo', 'publication_date', 'temps_de_preparation'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'obtenir une liste de toutes les recettes objet Recette.
|
||||||
|
*
|
||||||
|
* @return Recette[]|null
|
||||||
|
*/
|
||||||
|
public function getAll(): ?array {
|
||||||
|
$sqlQuery = "SELECT * FROM {$this->tableName};";
|
||||||
|
$results = $this->selectGetAll($sqlQuery);
|
||||||
|
if( $results === null )
|
||||||
|
return null;
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'obtenir toutes les recettes paginées.
|
||||||
|
* Mise par défaut dans l'ordre croissant du titre et avec une pagination de 15 éléments.
|
||||||
|
*
|
||||||
|
* @param int $page
|
||||||
|
* @param int $pagination
|
||||||
|
*
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function getAllRecettesBrowse( int $page = 1, int $pagination = 15 ){
|
||||||
|
|
||||||
|
if( $page <= 0 )
|
||||||
|
$page = 1;
|
||||||
|
|
||||||
|
$offset = ( $page - 1 ) * $pagination;
|
||||||
|
|
||||||
|
$sqlQuery = "SELECT * FROM {$this->tableName} ORDER BY titre_recette ASC LIMIT {$pagination} OFFSET {$offset};";
|
||||||
|
$results = $this->selectGetAll($sqlQuery);
|
||||||
|
if( $results === null )
|
||||||
|
return null;
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 advancedRecetteSearch( string $title = "", array $tagsId= [], array $ingredientsId= [] ): ?array {
|
||||||
|
|
||||||
|
$tableLinkIngredients = IngredientRepository::getStructure()['link_recettes'];
|
||||||
|
$tableLinkTags = TagRepository::getStructure()['link_recettes'];
|
||||||
|
|
||||||
|
$sqlQuery = "SELECT ({$this->tableName}.num_recette) FROM {$this->tableName} LEFT JOIN {$tableLinkIngredients} ON {$this->tableName}.num_recette = {$tableLinkIngredients}.num_recette LEFT JOIN {$tableLinkTags} ON {$this->tableName}.num_recette = {$tableLinkTags}.num_recette";
|
||||||
|
|
||||||
|
if( $title != "" || $tagsId !== [] || $ingredientsId !== [] ) {
|
||||||
|
$sqlQuery .= " WHERE ";
|
||||||
|
if ($title != "")
|
||||||
|
$sqlQuery .= " titre_recette LIKE '%{$title}%' AND";
|
||||||
|
if ($tagsId !== [])
|
||||||
|
$sqlQuery .= " num_tag IN (" . implode(",", $tagsId) . ") AND";
|
||||||
|
if ($ingredientsId !== [])
|
||||||
|
$sqlQuery .= " num_ingredient IN (" . implode(",", $ingredientsId) . ") AND";
|
||||||
|
$sqlQuery = substr($sqlQuery, 0, -3);
|
||||||
|
}
|
||||||
|
$sqlQuery .= ";";
|
||||||
|
|
||||||
|
$results = $this->selectGetAll($sqlQuery, true);
|
||||||
|
if( $results === null )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
$alreadyGettedId = [];
|
||||||
|
for( $i = 0; $i < count( $results ); $i++ ){
|
||||||
|
if( in_array( $results[$i]['num_recette'], $alreadyGettedId ) )
|
||||||
|
unset( $results[$i] );
|
||||||
|
else
|
||||||
|
$alreadyGettedId[] = $results[$i]['num_recette'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
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' );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function getAllLinkedIngredients(Model $entity): ?array
|
||||||
|
{
|
||||||
|
$ingredientRepo = new IngredientRepository();
|
||||||
|
$response = $ingredientRepo->getIdLinkedTo( 'recettes', 'num_recette', $entity );
|
||||||
|
if( $response === null )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return array_map( function($arr) use($ingredientRepo) {
|
||||||
|
return $ingredientRepo->getByID( $arr['num_ingredient'] );
|
||||||
|
}, $response );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addAnIngredient(Ingredient $ingredient, Model $entity): bool
|
||||||
|
{
|
||||||
|
$ingredientRepo = new IngredientRepository();
|
||||||
|
return $ingredientRepo->addLinkBetween( 'recettes', 'num_recette', $entity, $ingredient );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeAnIngredient(Ingredient $ingredient, Model $entity): bool
|
||||||
|
{
|
||||||
|
$ingredientRepo = new IngredientRepository();
|
||||||
|
return $ingredientRepo->removeLinkBetween( 'recettes', 'num_recette', $entity, $ingredient );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllLinkedTags(Model $entity): ?array
|
||||||
|
{
|
||||||
|
$tagRepo = new TagRepository();
|
||||||
|
$response = $tagRepo->getIdLinkedTo( 'recettes', 'num_recette', $entity );
|
||||||
|
if( $response === null )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return array_map( function($arr) use($tagRepo) {
|
||||||
|
return $tagRepo->getByID( $arr['num_tag'] );
|
||||||
|
}, $response );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addATag(Tag $tag, Model $entity): bool
|
||||||
|
{
|
||||||
|
$tagRepo = new TagRepository();
|
||||||
|
return $tagRepo->addLinkBetween( 'recettes', 'num_recette', $entity, $tag );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeATag(Tag $tag, Model $entity): bool
|
||||||
|
{
|
||||||
|
$tagRepo = new TagRepository();
|
||||||
|
return $tagRepo->removeLinkBetween( 'recettes', 'num_recette', $entity, $tag );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
304
src/Domain/Recettes/RecettesAPIController.php
Normal file
304
src/Domain/Recettes/RecettesAPIController.php
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Recettes;
|
||||||
|
|
||||||
|
use App\Domain\Controller;
|
||||||
|
use App\Domain\Ingredients\IngredientRepository;
|
||||||
|
use App\Domain\Tags\TagRepository;
|
||||||
|
use App\Helpers\UploadFiles;
|
||||||
|
use App\Http\JSONResponse;
|
||||||
|
use App\Http\Request;
|
||||||
|
use App\Http\Router;
|
||||||
|
use App\Kernel;
|
||||||
|
|
||||||
|
class RecettesAPIController extends Controller {
|
||||||
|
|
||||||
|
public static function defineRoutes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
self::Route( routeUrl: '/api/recettes/list', routeName: 'api->recettes->list', routeAction: 'list', routeMethods: ['POST'] ),
|
||||||
|
self::Route( routeUrl: '/api/recettes/create', routeName: 'api->recettes->create', routeAction: 'create', routeMethods: ['POST'] ),
|
||||||
|
self::Route( routeUrl: '/api/recettes/edit', routeName: 'api->recettes->edit', routeAction: 'edit', routeMethods: ['POST'] ),
|
||||||
|
self::Route( routeUrl: '/api/recettes/delete', routeName: 'api->recettes->delete', routeAction: 'delete', routeMethods: ['POST'] ),
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function list(){
|
||||||
|
|
||||||
|
$title = Request::post( 'title' ) ?? "";
|
||||||
|
$tagsId = explode( ",", Request::post( 'tagsId' ) ) ?? [];
|
||||||
|
$ingredientsId = explode( ",", Request::post( 'ingredientsId' ) ) ?? [];
|
||||||
|
|
||||||
|
if( $tagsId == [ "" ] )
|
||||||
|
$tagsId = [];
|
||||||
|
if( $ingredientsId == [ "" ] )
|
||||||
|
$ingredientsId = [];
|
||||||
|
|
||||||
|
$tagsId = array_map( 'intval', $tagsId );
|
||||||
|
$ingredientsId = array_map( 'intval', $ingredientsId );
|
||||||
|
|
||||||
|
|
||||||
|
$recetteRepo = new RecetteRepository();
|
||||||
|
$resp = $recetteRepo->advancedRecetteSearch( $title, $tagsId, $ingredientsId );
|
||||||
|
|
||||||
|
$objResp = [];
|
||||||
|
foreach( $resp as $r ){
|
||||||
|
$r = $recetteRepo->getByID( $r['num_recette'] );
|
||||||
|
$r->url = Router::getRouteURL( 'recettes->show', $r->slug );
|
||||||
|
$r->nb_ingredients = $r->getNumberOfIngredients();
|
||||||
|
if( !in_array( $r, $objResp ) ){
|
||||||
|
$objResp[] = $r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONResponse::sendSuccess( [ 'data' => $objResp ] );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(){
|
||||||
|
|
||||||
|
// Récupération des champs.
|
||||||
|
$name = Request::post( 'nom' ) ?? "";
|
||||||
|
$temps = Request::post( 'temps' ) ?? "";
|
||||||
|
$fileField = "image";
|
||||||
|
|
||||||
|
$tagsId = explode( ",", Request::post( 'tags' ) ) ?? [];
|
||||||
|
$ingredientsId = explode( ",", Request::post( 'ingredients' ) ) ?? [];
|
||||||
|
|
||||||
|
if( $tagsId == [ "" ] )
|
||||||
|
$tagsId = [];
|
||||||
|
if( $ingredientsId == [ "" ] )
|
||||||
|
$ingredientsId = [];
|
||||||
|
|
||||||
|
$tagsId = array_map( 'intval', $tagsId );
|
||||||
|
$ingredientsId = array_map( 'intval', $ingredientsId );
|
||||||
|
|
||||||
|
$description = Request::post( 'description' );
|
||||||
|
|
||||||
|
// Vérification.
|
||||||
|
if( $name == "" || $temps == "" || $ingredientsId == [] || $description == "" )
|
||||||
|
JSONResponse::sendError( [ 'error' => "One required fields is missing" ] );
|
||||||
|
|
||||||
|
// Upload & Vérification de l'image.
|
||||||
|
$urlOrError = UploadFiles::uploadFile( $fileField, UploadFiles::IMAGE_MIME_TYPES );
|
||||||
|
if( is_int( $urlOrError ) ){
|
||||||
|
JSONResponse::sendError( [ 'error' => $urlOrError ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérification d'une entrée qui existerait déjà.
|
||||||
|
$slug = strtolower( $name );
|
||||||
|
$slug = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $slug);
|
||||||
|
$slug = str_replace( ' ', '', $slug );
|
||||||
|
|
||||||
|
$recetteRepo = new RecetteRepository();
|
||||||
|
|
||||||
|
if( $recetteRepo->getBySlug( $slug ) ){
|
||||||
|
JSONResponse::sendError( [ 'error' => "Recette already exists" ] );
|
||||||
|
}
|
||||||
|
if( in_array( $slug, [ 'create', 'edit', 'delete' ] ) ){
|
||||||
|
JSONResponse::sendError( [ 'error' => "Recette already exists" ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Création de la recette.
|
||||||
|
$recette = new Recette();
|
||||||
|
|
||||||
|
$recette->num_recette = 0;
|
||||||
|
$recette->titre_recette = $name;
|
||||||
|
$recette->slug = $slug;
|
||||||
|
|
||||||
|
$recette->temps_de_preparation = intval( $temps <= 0 ? 1 : $temps );
|
||||||
|
$recette->photo = $urlOrError;
|
||||||
|
$recette->description_recette = $description;
|
||||||
|
$recette->publication_date = date("Y-m-d");
|
||||||
|
|
||||||
|
// Importation de la recette.
|
||||||
|
if( !$recetteRepo->add( $recette ) ){
|
||||||
|
JSONResponse::sendError( [ 'error' => "Error adding recette" ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pour actualiser l'ID.
|
||||||
|
$recette = $recetteRepo->getById( Kernel::$DB->pdo->lastInsertId() );
|
||||||
|
|
||||||
|
// Ajout du lien ingrédients recettes.
|
||||||
|
foreach( $ingredientsId as $ingredientId ){
|
||||||
|
$ingredient = new IngredientRepository()->getByID( $ingredientId );
|
||||||
|
if( $ingredient == null )
|
||||||
|
continue;
|
||||||
|
$recetteRepo->addAnIngredient( $ingredient, $recette );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajout du lien Recettes tags.
|
||||||
|
if( $tagsId != [] ){
|
||||||
|
foreach( $tagsId as $tagId ){
|
||||||
|
$tag = new TagRepository()->getByID( $tagId );
|
||||||
|
if( $tag == null )
|
||||||
|
continue;
|
||||||
|
$recetteRepo->addATag( $tag, $recette );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONResponse::sendSuccess( [ 'recette' => $recette, 'route' => Router::getRouteURL('recettes->show', $recette->slug ) ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(){
|
||||||
|
|
||||||
|
// Récupération des champs.
|
||||||
|
$id = Request::post( 'id' );
|
||||||
|
$name = Request::post( 'nom' ) ?? "";
|
||||||
|
$temps = Request::post( 'temps' ) ?? "";
|
||||||
|
$fileField = "image";
|
||||||
|
|
||||||
|
$tagsId = explode( ",", Request::post( 'tags' ) ) ?? [];
|
||||||
|
$ingredientsId = explode( ",", Request::post( 'ingredients' ) ) ?? [];
|
||||||
|
|
||||||
|
if( $tagsId == [ "" ] )
|
||||||
|
$tagsId = [];
|
||||||
|
if( $ingredientsId == [ "" ] )
|
||||||
|
$ingredientsId = [];
|
||||||
|
|
||||||
|
$tagsId = array_map( 'intval', $tagsId );
|
||||||
|
$ingredientsId = array_map( 'intval', $ingredientsId );
|
||||||
|
|
||||||
|
$description = Request::post( 'description' );
|
||||||
|
|
||||||
|
// Vérification.
|
||||||
|
if( $id == "" || $name == "" || $temps == "" || $ingredientsId == [] || $description == "" )
|
||||||
|
JSONResponse::sendError( [ 'error' => "One required fields is missing" ] );
|
||||||
|
|
||||||
|
$recetteRepo = new RecetteRepository();
|
||||||
|
$recette = $recetteRepo->getByID( $id );
|
||||||
|
if( !$recette ){
|
||||||
|
JSONResponse::sendError( [ 'error' => "Recette not found" ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload & Vérification de l'image.
|
||||||
|
$urlOrError = UploadFiles::uploadFile( $fileField, UploadFiles::IMAGE_MIME_TYPES );
|
||||||
|
if( is_int( $urlOrError ) ){
|
||||||
|
// Ingore image.
|
||||||
|
} else {
|
||||||
|
$recette->photo = $urlOrError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérification d'une entrée qui existerait déjà.
|
||||||
|
$slug = strtolower( $name );
|
||||||
|
$slug = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $slug);
|
||||||
|
$slug = str_replace( ' ', '', $slug );
|
||||||
|
|
||||||
|
$recette->titre_recette = $name;
|
||||||
|
// $recette->slug = $slug;
|
||||||
|
|
||||||
|
$recette->temps_de_preparation = intval( $temps <= 0 ? 1 : $temps );
|
||||||
|
$recette->description_recette = $description;
|
||||||
|
|
||||||
|
// Importation de la recette.
|
||||||
|
if( !$recetteRepo->update( $recette ) ){
|
||||||
|
JSONResponse::sendError( [ 'error' => "Error updating recette" ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualisation des ingrédients.
|
||||||
|
$ingrObjArray = $recette->getAllLinkedIngredients();
|
||||||
|
if( !$ingrObjArray || $ingrObjArray === [] ){ // Si il n'y a pas déjà d'autres ingrédients, on ajoute tous ceux présents ici.
|
||||||
|
|
||||||
|
foreach( $ingredientsId as $ingredientId ){
|
||||||
|
$ingredient = new IngredientRepository()->getByID( $ingredientId );
|
||||||
|
if( $ingredient == null )
|
||||||
|
continue;
|
||||||
|
$recetteRepo->addAnIngredient( $ingredient, $recette );
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$ingrIntArray = array_map( function($ingr){
|
||||||
|
return intval( $ingr->getID() );
|
||||||
|
}, $ingrObjArray );
|
||||||
|
|
||||||
|
foreach( $ingrIntArray as $ingrId ){
|
||||||
|
// Si un ingrédient n'est pas dans la nouvelle liste d'ingrédients, il faut retirer le lien.
|
||||||
|
if( !in_array( $ingrId, $ingredientsId ) ){
|
||||||
|
$ingredient = new IngredientRepository()->getByID( $ingrId );
|
||||||
|
if( $ingredient == null )
|
||||||
|
continue;
|
||||||
|
$recetteRepo->removeAnIngredient( $ingredient, $recette );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( $ingredientsId as $ingrId ){
|
||||||
|
// Si un ingrédient n'est pas dans l'ancienne liste, il faut ajouter un lien.
|
||||||
|
if( !in_array( $ingrId, $ingrIntArray ) ){
|
||||||
|
$ingredient = new IngredientRepository()->getByID( $ingrId );
|
||||||
|
if( $ingredient == null )
|
||||||
|
continue;
|
||||||
|
$recetteRepo->addAnIngredient( $ingredient, $recette );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualisation des tags.
|
||||||
|
if( $tagsId != [] ) {
|
||||||
|
$tagObjArray = $recette->getAllLinkedTags();
|
||||||
|
if (!$tagObjArray || $tagObjArray === []) { // Si il n'y a pas déjà d'autres tags, on ajoute tous ceux présents ici.
|
||||||
|
|
||||||
|
foreach ($tagsId as $tagId) {
|
||||||
|
$tag = new TagRepository()->getByID($tagId);
|
||||||
|
if ($tag == null)
|
||||||
|
continue;
|
||||||
|
$recetteRepo->addATag($tag, $recette);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$tagIntArray = array_map(function ($ingr) {
|
||||||
|
return intval( $ingr->getID() );
|
||||||
|
}, $tagObjArray);
|
||||||
|
|
||||||
|
foreach ($tagIntArray as $tagId) {
|
||||||
|
// Si un ingrédient n'est pas dans la nouvelle liste d'ingrédients, il faut retirer le lien.
|
||||||
|
if (!in_array($tagId, $tagsId)) {
|
||||||
|
$tag = new TagRepository()->getByID($tagId);
|
||||||
|
if ($tag == null)
|
||||||
|
continue;
|
||||||
|
$recetteRepo->removeATag($tag, $recette);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($tagsId as $tagId) {
|
||||||
|
// Si un ingrédient n'est pas dans l'ancienne liste, il faut ajouter un lien.
|
||||||
|
if (!in_array($tagId, $tagIntArray)) {
|
||||||
|
$tag = new TagRepository()->getByID($tagId);
|
||||||
|
if ($tag == null)
|
||||||
|
continue;
|
||||||
|
$recetteRepo->addATag($tag, $recette);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONResponse::sendSuccess( [ 'recette' => $recette, 'route' => Router::getRouteURL('recettes->show', $recette->slug ) ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(){
|
||||||
|
$id = Request::post( 'id' );
|
||||||
|
$recette = new RecetteRepository()->getByID( $id );
|
||||||
|
if( !$recette )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'Recette not found' ] );
|
||||||
|
|
||||||
|
$recetteRepo = new RecetteRepository();
|
||||||
|
|
||||||
|
// Retirer tous les liens Ingrédients/Tags.
|
||||||
|
foreach( ( $recette->getAllLinkedIngredients() ?? [] ) as $ingr ){
|
||||||
|
$recetteRepo->removeAnIngredient( $ingr, $recette );
|
||||||
|
}
|
||||||
|
foreach( ( $recette->getAllLinkedTags() ?? [] ) as $tag ){
|
||||||
|
$recetteRepo->removeATag( $tag, $recette );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !$recetteRepo->delete( $recette ) )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'An error occured while deleting recette' ] );
|
||||||
|
|
||||||
|
JSONResponse::sendSuccess();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/Domain/Recettes/RecettesController.php
Normal file
47
src/Domain/Recettes/RecettesController.php
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Recettes;
|
||||||
|
|
||||||
|
use App\Domain\Controller;
|
||||||
|
use App\Domain\Ingredients\IngredientRepository;
|
||||||
|
use App\Domain\Tags\TagRepository;
|
||||||
|
use App\Http\JSONResponse;
|
||||||
|
use App\Http\Request;
|
||||||
|
use App\Infrastructure\View;
|
||||||
|
|
||||||
|
class RecettesController extends Controller {
|
||||||
|
|
||||||
|
public static function defineRoutes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
self::Route( routeUrl: '/recettes', routeName: 'recettes->index', routeAction: 'index', pageHeadTitle: 'Liste des recettes' ),
|
||||||
|
self::Route( routeUrl: '/recettes/{string}', routeName: 'recettes->show', routeAction: 'show', pageHeadTitle: 'Recette' ),
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(): View {
|
||||||
|
|
||||||
|
$page = Request::get( 'page' );
|
||||||
|
if( $page == null )
|
||||||
|
$page = 1;
|
||||||
|
|
||||||
|
return new View( 'recettes/index', [
|
||||||
|
'tagsList' => new TagRepository()->getAll(),
|
||||||
|
'ingredientsList' => new IngredientRepository()->getAll(),
|
||||||
|
'recettesList' => new RecetteRepository()->getAllRecettesBrowse( $page ),
|
||||||
|
] );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show( string $slug ): View {
|
||||||
|
|
||||||
|
$recette = new RecetteRepository()->getBySlug( $slug );
|
||||||
|
if( !$recette )
|
||||||
|
die( "Recette not found" );
|
||||||
|
|
||||||
|
return new View( 'recettes/show', [
|
||||||
|
'recette' => $recette,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
85
src/Domain/Recettes/RecettesManagementController.php
Normal file
85
src/Domain/Recettes/RecettesManagementController.php
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Recettes;
|
||||||
|
|
||||||
|
use App\Domain\Controller;
|
||||||
|
use App\Domain\Ingredients\IngredientRepository;
|
||||||
|
use App\Domain\Tags\TagRepository;
|
||||||
|
use App\Helpers\Authentification;
|
||||||
|
use App\Http\JSONResponse;
|
||||||
|
use App\Http\Request;
|
||||||
|
use App\Infrastructure\View;
|
||||||
|
|
||||||
|
class RecettesManagementController extends Controller {
|
||||||
|
|
||||||
|
public static function defineRoutes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
self::Route( routeUrl: '/recettes/create', routeName: 'recettes->create', routeAction: 'create', pageHeadTitle: 'Créer une recette' ),
|
||||||
|
self::Route( routeUrl: '/recettes/edit/{int}', routeName: 'recettes->edit', routeAction: 'edit', pageHeadTitle: 'Modifier une recette' ),
|
||||||
|
self::Route( routeUrl: '/recettes/delete/{int}', routeName: 'recettes->delete', routeAction: 'delete' ),
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(): View {
|
||||||
|
if( !Authentification::isLoggedIn() )
|
||||||
|
die( "Not logged in" );
|
||||||
|
|
||||||
|
return new View( 'recettes/create', [
|
||||||
|
'tags' => new TagRepository()->getAll(),
|
||||||
|
'ingredients' => new IngredientRepository()->getAll(),
|
||||||
|
] );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit( int $idRecette ): View {
|
||||||
|
if( !Authentification::isLoggedIn() )
|
||||||
|
die( "Not logged in" );
|
||||||
|
|
||||||
|
$recette = new RecetteRepository()->getByID( $idRecette );
|
||||||
|
if( !$recette )
|
||||||
|
die( "Recette not found" );
|
||||||
|
|
||||||
|
$tagsID = array_map( function($tag) {
|
||||||
|
return $tag->getId();
|
||||||
|
}
|
||||||
|
, $recette->getAllLinkedTags() ?? [] );
|
||||||
|
|
||||||
|
$ingredientsID = array_map( function($tag) {
|
||||||
|
return $tag->getId();
|
||||||
|
}
|
||||||
|
, $recette->getAllLinkedIngredients() ?? [] );
|
||||||
|
|
||||||
|
return new View( 'recettes/edit', [
|
||||||
|
'recette' => $recette,
|
||||||
|
'recetteTags' => $tagsID,
|
||||||
|
'recetteIngr' => $ingredientsID,
|
||||||
|
'tags' => new TagRepository()->getAll(),
|
||||||
|
'ingredients' => new IngredientRepository()->getAll(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete( int $idRecette ): never {
|
||||||
|
if( !Authentification::isLoggedIn() )
|
||||||
|
die( "Not logged in" );
|
||||||
|
|
||||||
|
$recette = new RecetteRepository()->getByID( $idRecette );
|
||||||
|
if( !$recette )
|
||||||
|
die( "Recette not found" );
|
||||||
|
|
||||||
|
$recetteRepo = new RecetteRepository();
|
||||||
|
|
||||||
|
// Retirer tous les liens Ingrédients/Tags.
|
||||||
|
foreach( ( $recette->getAllLinkedIngredients() ?? [] ) as $ingr ){
|
||||||
|
$recetteRepo->removeAnIngredient( $ingr, $recette );
|
||||||
|
}
|
||||||
|
foreach( ( $recette->getAllLinkedTags() ?? [] ) as $tag ){
|
||||||
|
$recetteRepo->removeATag( $tag, $recette );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !$recetteRepo->delete( $recette ) )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'An error occured while deleting recette' ] );
|
||||||
|
|
||||||
|
Request::redirectTo( 'recettes->index' );
|
||||||
|
}
|
||||||
|
}
|
||||||
170
src/Domain/Repository.php
Normal file
170
src/Domain/Repository.php
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain;
|
||||||
|
|
||||||
|
use App\Domain\Recettes\Recette;
|
||||||
|
use App\Kernel;
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe abstraite avec des méthodes pour pouvoir gérer les liens entre le site et la base de données.
|
||||||
|
*/
|
||||||
|
abstract class Repository {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instancie des éléments de cette classe par rapport au repository.
|
||||||
|
* @return class-string
|
||||||
|
*/
|
||||||
|
abstract public static function getEntity(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Doit retourner une liste du type :
|
||||||
|
* ['table' => '', 'columns' => [ ...Liste des colonnes dans la BDD ] ].
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
abstract public static function getStructure(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contient le nom de la table principale du repo.
|
||||||
|
* @var string|mixed
|
||||||
|
*/
|
||||||
|
final public string $tableName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contient le nom des colonnes de la table du repo.
|
||||||
|
* @var array|mixed
|
||||||
|
*/
|
||||||
|
final public array $tableColumns;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contient les mêmes données que getStructure().
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public private(set) array $globalStructure;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructeur.
|
||||||
|
* Reprend les informations de getStructure et les met dans des attributs.
|
||||||
|
*/
|
||||||
|
public function __construct(){
|
||||||
|
$structure = static::getStructure();
|
||||||
|
|
||||||
|
$this->tableName = $structure['table'];
|
||||||
|
$this->tableColumns = $structure['columns'];
|
||||||
|
$this->globalStructure = $structure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'avoir tous les éléments correspondant à la requête passée en paramètre.
|
||||||
|
*
|
||||||
|
* @param string $sqlQuery
|
||||||
|
* @param bool $asArray Permet de savoir si on veut que le retour soit une array ou bien un tableau de Model.
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function selectGetAll( string $sqlQuery, bool $asArray = false ): ?array {
|
||||||
|
$statement = Kernel::$DB->pdo->prepare( $sqlQuery );
|
||||||
|
if( !$statement->execute() )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if( $asArray )
|
||||||
|
$results = $statement->fetchAll( PDO::FETCH_ASSOC );
|
||||||
|
else
|
||||||
|
$results = $statement->fetchAll( PDO::FETCH_CLASS, static::getEntity() );
|
||||||
|
if( empty( $results ) )
|
||||||
|
return null;
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'ajouter une entité à la base de données.
|
||||||
|
*
|
||||||
|
* @param Model $entity
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de mettre à jour les informations de l'entité dans la base de données.
|
||||||
|
*
|
||||||
|
* @param Model $entity
|
||||||
|
* @param string $identifier
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de supprimer une entrée d'entité dans la base de données.
|
||||||
|
*
|
||||||
|
* @param Model $entity
|
||||||
|
* @param string $identifier
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fonction raccourcie pour préparer les champs nécessaires pour addEntity.
|
||||||
|
*
|
||||||
|
* @param Model $entity
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
abstract public function add( Model $entity ): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fonction raccourcie pour préparer les champs nécessaires pour updateEntity
|
||||||
|
*
|
||||||
|
* @param Model $entity
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
abstract public function update( Model $entity ): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fonction raccourcie pour préparer les champs nécessaires pour deleteEntity
|
||||||
|
*
|
||||||
|
* @param Model $entity
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
abstract public function delete( Model $entity ): bool;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
33
src/Domain/Tags/Tag.php
Normal file
33
src/Domain/Tags/Tag.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Tags;
|
||||||
|
|
||||||
|
use App\Domain\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe qui va gérer les tags.
|
||||||
|
*/
|
||||||
|
class Tag extends Model {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Le numéro du tag.
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public int $num_tag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Le nom du tag
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $nom_tag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne le numéro du tag.
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getID(): int
|
||||||
|
{
|
||||||
|
return $this->num_tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
69
src/Domain/Tags/TagManagementController.php
Normal file
69
src/Domain/Tags/TagManagementController.php
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Tags;
|
||||||
|
|
||||||
|
use App\Domain\Controller;
|
||||||
|
use App\Domain\Ingredients\IngredientRepository;
|
||||||
|
use App\Domain\Tags\TagRepository;
|
||||||
|
use App\Helpers\Authentification;
|
||||||
|
use App\Http\JSONResponse;
|
||||||
|
use App\Http\Request;
|
||||||
|
use App\Infrastructure\View;
|
||||||
|
|
||||||
|
class TagManagementController extends Controller {
|
||||||
|
|
||||||
|
public static function defineRoutes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
self::Route( routeUrl: '/manage/tags', routeName: 'manage->tags', routeAction: 'index', pageHeadTitle: 'Gestion des tags' ),
|
||||||
|
self::Route( routeUrl: '/tags/edit/{int}', routeName: 'tags->edit', routeAction: 'edit', routeMethods: [ 'POST' ] ),
|
||||||
|
self::Route( routeUrl: '/tags/delete/{int}', routeName: 'tags->delete', routeAction: 'delete' ),
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(){
|
||||||
|
|
||||||
|
if( !Authentification::isLoggedIn() )
|
||||||
|
die( "Not logged in" );
|
||||||
|
|
||||||
|
$tags = new TagRepository()->getAll();
|
||||||
|
return new View( 'tags/index', [
|
||||||
|
'tags' => $tags,
|
||||||
|
]);
|
||||||
|
|
||||||
|
}
|
||||||
|
public function edit( int $idTag ): never {
|
||||||
|
if( !Authentification::isLoggedIn() )
|
||||||
|
die( "Not logged in" );
|
||||||
|
|
||||||
|
$tag = new TagRepository()->getByID( $idTag );
|
||||||
|
if( !$tag )
|
||||||
|
die( "Tag not found" );
|
||||||
|
|
||||||
|
$name = Request::post( 'modif_tag' );
|
||||||
|
if( !$name || $name == "" )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'Name not defined' ] );
|
||||||
|
|
||||||
|
$tag->nom_tag = $name;
|
||||||
|
|
||||||
|
if( !new TagRepository()->update( $tag ) )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'An error occured while updating tag' ] );
|
||||||
|
|
||||||
|
Request::redirectTo( 'manage->tags');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete( int $idTag ): never {
|
||||||
|
if( !Authentification::isLoggedIn() )
|
||||||
|
die( "Not logged in" );
|
||||||
|
|
||||||
|
$tag = new \App\Domain\Tags\TagRepository()->getByID( $idTag );
|
||||||
|
if( !$tag )
|
||||||
|
die( "Tag not found" );
|
||||||
|
|
||||||
|
if( !new TagRepository()->delete( $tag ) )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'An error occured while deleting tag' ] );
|
||||||
|
|
||||||
|
Request::redirectTo( 'manage->tags' );
|
||||||
|
}
|
||||||
|
}
|
||||||
174
src/Domain/Tags/TagRepository.php
Normal file
174
src/Domain/Tags/TagRepository.php
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Tags;
|
||||||
|
|
||||||
|
use App\Domain\LinkableInterface;
|
||||||
|
use App\Domain\Recettes\Recette;
|
||||||
|
use App\Domain\Repository;
|
||||||
|
use App\Domain\Model;
|
||||||
|
use App\Kernel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe qui va permettre de gérer les requêtes BDD en lien avec les tags.
|
||||||
|
* Les tags sont liables à d'autres entités comme les Recettes.
|
||||||
|
*/
|
||||||
|
class TagRepository extends Repository implements LinkableInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'avoir le nom de l'entité dont dépend ce repo.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function getEntity(): string
|
||||||
|
{
|
||||||
|
return Tag::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La structure de notre table dans la BDD.
|
||||||
|
* Un champ link_recettes a été ajouté pour donner le nom de la table de lien entre nos recettes.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getStructure(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'table' => 'Tag',
|
||||||
|
'columns' => [
|
||||||
|
'num_tag', 'nom_tag'
|
||||||
|
],
|
||||||
|
'link_recettes' => 'Referencetag'
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'obtenir une liste de tous les tags objet Tag.
|
||||||
|
*
|
||||||
|
* @return Tag[]|null
|
||||||
|
*/
|
||||||
|
public function getAll(): ?array {
|
||||||
|
$sqlQuery = "SELECT * FROM {$this->tableName};";
|
||||||
|
$results = $this->selectGetAll($sqlQuery);
|
||||||
|
if( $results === null )
|
||||||
|
return null;
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'obtenir un tag spécifique par son ID.
|
||||||
|
*
|
||||||
|
* @param int $id
|
||||||
|
*
|
||||||
|
* @return Tag|null
|
||||||
|
*/
|
||||||
|
public function getByID( int $id ): ?Tag {
|
||||||
|
$sqlQuery = "SELECT * FROM {$this->tableName} WHERE num_tag = {$id}";
|
||||||
|
$results = $this->selectGetAll($sqlQuery);
|
||||||
|
if( $results === null || count( $results ) > 1 )
|
||||||
|
return null;
|
||||||
|
return $results[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'obtenir, sous forme de liste, toutes les entrées qui lient des tags.
|
||||||
|
*
|
||||||
|
* @param string $linkedTo La table qui permet de faire la liaison des tags avec une autre entité.
|
||||||
|
* @param string $linkingField Le champ qui permet de faire la liaison des tags avec une autre entité.
|
||||||
|
* @param Model $linkedEntity L'autre entité.
|
||||||
|
*
|
||||||
|
* @see LinkableInterface
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function getIdLinkedTo( string $linkedTo, string $linkingField, Model $linkedEntity ): ?array {
|
||||||
|
|
||||||
|
$linkName = 'link_' . $linkedTo;
|
||||||
|
if( !isset( $this->globalStructure[$linkName]))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
$sqlQuery = "SELECT * FROM {$this->globalStructure[$linkName]} WHERE {$linkingField} = {$linkedEntity->getId()};";
|
||||||
|
$results = $this->selectGetAll($sqlQuery, true);
|
||||||
|
if( $results === null )
|
||||||
|
return null;
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'ajouter un lien entre un tag et une autre entité comme Recette.
|
||||||
|
*
|
||||||
|
* @param string $linkedTo
|
||||||
|
* @param string $linkingField
|
||||||
|
* @param Model $linkedEntity
|
||||||
|
* @param Model $tagEntity
|
||||||
|
*
|
||||||
|
* @see LinkableInterface
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function addLinkBetween( string $linkedTo, string $linkingField, Model $linkedEntity, Model $tagEntity ): bool {
|
||||||
|
|
||||||
|
$linkName = 'link_' . $linkedTo;
|
||||||
|
if( !isset( $this->globalStructure[$linkName]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$query = "INSERT INTO {$this->globalStructure[$linkName]} ({$linkingField},num_tag) VALUES ({$linkedEntity->getId()}, {$tagEntity->getID()});";
|
||||||
|
$statement = Kernel::$DB->pdo->prepare( $query );
|
||||||
|
|
||||||
|
return $statement->execute();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retire un lien dans la BDD entre un tag et une autre entité.
|
||||||
|
*
|
||||||
|
* @param string $linkedTo
|
||||||
|
* @param string $linkingField
|
||||||
|
* @param Model $linkedEntity
|
||||||
|
* @param Model $tagEntity
|
||||||
|
*
|
||||||
|
* @see LinkableInterface
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function removeLinkBetween(string $linkedTo, string $linkingField, Model $linkedEntity, Model $tagEntity ): bool
|
||||||
|
{
|
||||||
|
$linkName = 'link_' . $linkedTo;
|
||||||
|
if( !isset( $this->globalStructure[$linkName]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$query = "DELETE FROM {$this->globalStructure[$linkName]} WHERE {$linkingField} = {$linkedEntity->getId()} AND num_tag = {$tagEntity->getId()};";
|
||||||
|
$statement = Kernel::$DB->pdo->prepare( $query );
|
||||||
|
return $statement->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ajouter un tag dans la BDD.
|
||||||
|
*
|
||||||
|
* @param Model $ingredient
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function add( Model $tag ): bool {
|
||||||
|
return $this->addEntity( $tag );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour un tag dans la BDD.
|
||||||
|
*
|
||||||
|
* @param Model $tag
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function update( Model $tag ): bool {
|
||||||
|
return $this->updateEntity( $tag, 'num_tag' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supprime un tag de la BDD.
|
||||||
|
*
|
||||||
|
* @param Model $tag
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function delete( Model $tag ): bool {
|
||||||
|
return $this->deleteEntity( $tag, 'num_tag' );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
73
src/Domain/Tags/TagsAPIController.php
Normal file
73
src/Domain/Tags/TagsAPIController.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Tags;
|
||||||
|
|
||||||
|
use App\Domain\Controller;
|
||||||
|
use App\Helpers\UploadFiles;
|
||||||
|
use App\Http\JSONResponse;
|
||||||
|
use App\Http\Request;
|
||||||
|
use App\Kernel;
|
||||||
|
|
||||||
|
class TagsAPIController extends Controller {
|
||||||
|
|
||||||
|
public static function defineRoutes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
self::Route( routeUrl: '/api/tags/create', routeName: 'api->tags->create', routeAction: 'create', routeMethods: ['POST'] ),
|
||||||
|
self::Route( routeUrl: '/api/tags/edit', routeName: 'api->tags->edit', routeAction: 'edit', routeMethods: ['POST'] ),
|
||||||
|
self::Route( routeUrl: '/api/tags/delete', routeName: 'api->tags->delete', routeAction: 'delete', routeMethods: ['POST'] ),
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(){
|
||||||
|
|
||||||
|
$name = Request::post( 'name' );
|
||||||
|
if( !$name || $name == "" )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'Name not defined' ] );
|
||||||
|
|
||||||
|
$tag = new Tag();
|
||||||
|
$tag->num_tag = 0;
|
||||||
|
$tag->nom_tag = $name;
|
||||||
|
|
||||||
|
if( !new TagRepository()->add( $tag ) )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'An error occured while adding tag' ] );
|
||||||
|
|
||||||
|
JSONResponse::sendSuccess( [ 'id' => Kernel::$DB->pdo->lastInsertId() ] );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(){
|
||||||
|
|
||||||
|
$id = Request::post( 'id' ) ?? 0;
|
||||||
|
$tag = new TagRepository()->getByID( $id );
|
||||||
|
if( !$tag )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'Tag not found' ] );
|
||||||
|
|
||||||
|
$name = Request::post( 'name' );
|
||||||
|
if( !$name || $name == "" )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'Name not defined' ] );
|
||||||
|
|
||||||
|
$tag->nom_tag = $name;
|
||||||
|
|
||||||
|
if( !new TagRepository()->update( $tag ) )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'An error occured while updating tag' ] );
|
||||||
|
|
||||||
|
JSONResponse::sendSuccess();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(){
|
||||||
|
|
||||||
|
$id = Request::post( 'id' ) ?? 0;
|
||||||
|
$tag = new TagRepository()->getByID( $id );
|
||||||
|
if( !$tag )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'Tag not found' ] );
|
||||||
|
|
||||||
|
if( !new TagRepository()->delete( $tag ) )
|
||||||
|
JSONResponse::sendError( [ 'error' => 'An error occured while deleting tag' ] );
|
||||||
|
|
||||||
|
JSONResponse::sendSuccess();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/Domain/Tags/UseTagsInterface.php
Normal file
38
src/Domain/Tags/UseTagsInterface.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Tags;
|
||||||
|
|
||||||
|
use App\Domain\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface utilisée par tous les repository qui sont en lien avec les tags.
|
||||||
|
*/
|
||||||
|
interface UseTagsInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de récupérer tous les tags liés à ce Modèle.
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function getAllLinkedTags( Model $entity ): ?array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'ajouter un tag en lien avec notre autre entité.
|
||||||
|
*
|
||||||
|
* @param Tag $tag
|
||||||
|
* @param Model $entity
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function addATag( Tag $tag, Model $entity ): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de retirer un lien entre un tag et une autre entité.
|
||||||
|
*
|
||||||
|
* @param Tag $tag
|
||||||
|
* @param Model $entity
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function removeATag( Tag $tag, Model $entity ): bool;
|
||||||
|
|
||||||
|
}
|
||||||
70
src/Domain/Utilisateurs/AuthentificationController.php
Normal file
70
src/Domain/Utilisateurs/AuthentificationController.php
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Utilisateurs;
|
||||||
|
|
||||||
|
use App\Domain\Controller;
|
||||||
|
use App\Helpers\Authentification;
|
||||||
|
use App\Http\JSONResponse;
|
||||||
|
use App\Http\Request;
|
||||||
|
use App\Infrastructure\View;
|
||||||
|
|
||||||
|
class AuthentificationController extends Controller {
|
||||||
|
|
||||||
|
public static function defineRoutes(): array {
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
// 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'] ),
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
$username = Request::post( 'username' );
|
||||||
|
$password = Request::post( 'password' );
|
||||||
|
|
||||||
|
$userId = new UtilisateurRepository()->login( $username, $password );
|
||||||
|
if( !$userId ) {
|
||||||
|
return JSONResponse::sendError();
|
||||||
|
}
|
||||||
|
|
||||||
|
Authentification::loginUser( $userId );
|
||||||
|
return JSONResponse::sendSuccess( [ 'user_id' => $userId ] );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logout(): JSONResponse {
|
||||||
|
|
||||||
|
if( !Authentification::isLoggedIn() ) {
|
||||||
|
return JSONResponse::sendError( [ 'message' => 'Already disconnected' ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
Authentification::destroySession();
|
||||||
|
return JSONResponse::sendSuccess( [ 'message' => 'Logged out' ] );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
20
src/Domain/Utilisateurs/Utilisateur.php
Normal file
20
src/Domain/Utilisateurs/Utilisateur.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Utilisateurs;
|
||||||
|
|
||||||
|
use App\Domain\Model;
|
||||||
|
//use App\Helpers\Markdown;
|
||||||
|
|
||||||
|
class Utilisateur extends Model {
|
||||||
|
|
||||||
|
public int $num_user;
|
||||||
|
public string $username;
|
||||||
|
public string $userpassword;
|
||||||
|
|
||||||
|
|
||||||
|
public function getID(): int
|
||||||
|
{
|
||||||
|
return $this->num_user;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
80
src/Domain/Utilisateurs/UtilisateurRepository.php
Normal file
80
src/Domain/Utilisateurs/UtilisateurRepository.php
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Utilisateurs;
|
||||||
|
|
||||||
|
use App\Domain\Model;
|
||||||
|
use App\Domain\Repository;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe qui permet de faire le lien entre la BDD et le site pour les utilisateurs.
|
||||||
|
*/
|
||||||
|
class UtilisateurRepository extends Repository {
|
||||||
|
|
||||||
|
public static function getEntity(): string
|
||||||
|
{
|
||||||
|
return Utilisateur::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getStructure(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'table' => 'User',
|
||||||
|
'columns' => [
|
||||||
|
'num_user', 'username', 'userpassword'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'obtenir une liste de toutes les recettes objet Utilisateur.
|
||||||
|
*
|
||||||
|
* @return Utilisateur[]|null
|
||||||
|
*/
|
||||||
|
public function getAll(): ?array {
|
||||||
|
$sqlQuery = "SELECT * FROM {$this->tableName};";
|
||||||
|
$results = $this->selectGetAll($sqlQuery);
|
||||||
|
if( $results === null )
|
||||||
|
return null;
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'avoir un utilisateur par un ID.
|
||||||
|
*
|
||||||
|
* @param int $id
|
||||||
|
* @return Utilisateur|null
|
||||||
|
*/
|
||||||
|
public function getByID( int $id ): ?Utilisateur {
|
||||||
|
$sqlQuery = "SELECT * FROM {$this->tableName} WHERE num_user = {$id}";
|
||||||
|
$results = $this->selectGetAll($sqlQuery);
|
||||||
|
if( $results === null || count( $results ) > 1 )
|
||||||
|
return null;
|
||||||
|
return $results[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function login(string $name, string $pass): int|bool{
|
||||||
|
|
||||||
|
$users = new UtilisateurRepository()->getAll();
|
||||||
|
foreach($users as $user){
|
||||||
|
if((strcmp($name,$user->username)==0) and (strcmp($pass,$user->userpassword)==0))return $user->num_user;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add( Model $utilisateurs ): bool {
|
||||||
|
return $this->addEntity( $utilisateurs );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update( Model $utilisateurs ): bool {
|
||||||
|
return $this->updateEntity( $utilisateurs, 'num_user' );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete( Model $utilisateurs ): bool {
|
||||||
|
return $this->deleteEntity( $utilisateurs, 'num_user' );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
28
src/Exceptions/ConfigFailedLoadingException.php
Normal file
28
src/Exceptions/ConfigFailedLoadingException.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception qui se déclenche lorsqu'un fichier de configuration n'a pas pu être chargé.
|
||||||
|
*
|
||||||
|
* - Peut se déclencher si un fichier de configuration non voulu.
|
||||||
|
*/
|
||||||
|
class ConfigFailedLoadingException extends \Exception {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chemin vers le fichier de configuration manquant.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public private(set) string $configFilePath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructeur.
|
||||||
|
* @param string $configFilePath Le fichier de configuration manquant.
|
||||||
|
*/
|
||||||
|
public function __construct( string $configFilePath ) {
|
||||||
|
$this->configFilePath = $configFilePath;
|
||||||
|
$this->message = "Failed to load configuration file '{$configFilePath}'.";
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
15
src/Exceptions/InvalidRouteException.php
Normal file
15
src/Exceptions/InvalidRouteException.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
class InvalidRouteException extends \Exception {
|
||||||
|
|
||||||
|
private string $clientRoute;
|
||||||
|
|
||||||
|
public function __construct(string $clientRoute)
|
||||||
|
{
|
||||||
|
$this->clientRoute = $clientRoute;
|
||||||
|
$message = "{$clientRoute} doesn't exist";
|
||||||
|
parent::__construct($message, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
13
src/Exceptions/InvalidViewException.php
Normal file
13
src/Exceptions/InvalidViewException.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class InvalidViewException extends \Exception {
|
||||||
|
|
||||||
|
public function __construct( string $viewName )
|
||||||
|
{
|
||||||
|
parent::__construct( "View {$viewName} does not exist.", 500 );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
49
src/Helpers/Authentification.php
Normal file
49
src/Helpers/Authentification.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
class Authentification {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de démarrer la variable Session.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function startSession(): void {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de supprimer la session.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function destroySession(): void {
|
||||||
|
session_destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de connecter un utilisateur.
|
||||||
|
*
|
||||||
|
* @param int $userId
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function loginUser( int $userId ){
|
||||||
|
$_SESSION['user'] = $userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO : Complete when user.
|
||||||
|
public static function getCurrentUser() {
|
||||||
|
return $_SESSION['user'] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de savoir si un utilisateur est connecté ou pas.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isLoggedIn(): bool {
|
||||||
|
return self::getCurrentUser() !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
38
src/Helpers/AutoLoader.php
Normal file
38
src/Helpers/AutoLoader.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe utilitaire contenant des méthodes pour charger automatiquement les classes.
|
||||||
|
*/
|
||||||
|
class AutoLoader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Namespace principal de l'application.
|
||||||
|
*/
|
||||||
|
public const string PRIMARY_NAMESPACE = 'App\\';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'enregistrer la méthode _autoload_ en tant que méthode pour charger des classes.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function register(): void {
|
||||||
|
spl_autoload_register( [ self::class, '_autoload_' ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de charger une classe.
|
||||||
|
*
|
||||||
|
* @param string $className
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function _autoload_( string $className ): void {
|
||||||
|
if( !str_starts_with( $className, self::PRIMARY_NAMESPACE ) ) // Ne s'occupe que des classes à charger de cette application.
|
||||||
|
return;
|
||||||
|
|
||||||
|
$className = str_replace( self::PRIMARY_NAMESPACE, '', $className );
|
||||||
|
$className = str_replace( '\\', '/', $className );
|
||||||
|
|
||||||
|
require APP_ROOT . 'src/' . $className . '.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
42
src/Helpers/ConfigFactory.php
Normal file
42
src/Helpers/ConfigFactory.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
use App\Exceptions\ConfigFailedLoadingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe outillage qui permet de charger les fichiers de configuration.
|
||||||
|
*/
|
||||||
|
class ConfigFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chemin relatif à partir de APP_ROOT du dossier contenant les fichiers de configuration.
|
||||||
|
*
|
||||||
|
* @see APP_ROOT
|
||||||
|
*/
|
||||||
|
const string CONFIG_RELATIVE_FOLDER = "config/";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de charger un fichier de configuration.
|
||||||
|
*
|
||||||
|
* @param string $fileName Le nom du fichier de configuration, avec ou sans l'extension .php.
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @throws ConfigFailedLoadingException
|
||||||
|
*/
|
||||||
|
public static function loadConfigFile( string $fileName ): array {
|
||||||
|
if( !str_ends_with( $fileName, ".php" ) ) {
|
||||||
|
$fileName .= ".php";
|
||||||
|
}
|
||||||
|
|
||||||
|
$filePath = APP_ROOT . self::CONFIG_RELATIVE_FOLDER . $fileName;
|
||||||
|
|
||||||
|
if( !file_exists( $filePath ) ){
|
||||||
|
throw new ConfigFailedLoadingException( "Config file '$fileName' does not exist" );
|
||||||
|
}
|
||||||
|
|
||||||
|
$configArray = require $filePath;
|
||||||
|
return $configArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
29
src/Helpers/Markdown.php
Normal file
29
src/Helpers/Markdown.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
class Markdown {
|
||||||
|
|
||||||
|
public static function getMarkdownEntities(): array {
|
||||||
|
return [
|
||||||
|
|
||||||
|
// Gras & Italique
|
||||||
|
'/\*\*(.*?)\*\*/' => '<b>$1</b>',
|
||||||
|
'/\*(.*?)\*/' => '<i>$1</i>',
|
||||||
|
|
||||||
|
// Titres
|
||||||
|
'/^## (.*?)$/m' => '<h3>$1</h3>',
|
||||||
|
'/^# (.*?)$/m' => '<h2>$1</h2>',
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/Helpers/SanitizeTrait.php
Normal file
27
src/Helpers/SanitizeTrait.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trait qui permet de désinfecter une variable.
|
||||||
|
*/
|
||||||
|
trait SanitizeTrait {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de désinfecter une variable
|
||||||
|
*
|
||||||
|
* @param mixed $data
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function sanitize( mixed $data ): mixed {
|
||||||
|
|
||||||
|
if( is_string( $data ) ) {
|
||||||
|
return htmlspecialchars( $data, ENT_QUOTES );
|
||||||
|
} else if( is_integer( $data ) ) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/Helpers/UploadFiles.php
Normal file
60
src/Helpers/UploadFiles.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
use App\Http\Router;
|
||||||
|
use App\Kernel;
|
||||||
|
|
||||||
|
class UploadFiles {
|
||||||
|
|
||||||
|
const array IMAGE_MIME_TYPES = [ 'image/png', 'image/jpeg' ];
|
||||||
|
|
||||||
|
public static function uploadFolderPath(){
|
||||||
|
return APP_ROOT . 'public/uploads/';
|
||||||
|
}
|
||||||
|
public static function uploadFolderUri(){
|
||||||
|
return Kernel::$configs['general']['website_url'] . 'uploads/';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $fileName
|
||||||
|
* @param array $allowedMimeTypes Les mimes types autorisés pour ce fichier. Une liste vide n'a pas de restriction de mime type.
|
||||||
|
*
|
||||||
|
* @return string|int
|
||||||
|
*
|
||||||
|
* 1: Pas de fichier $fileArg
|
||||||
|
* 2: Erreur dans le fichier
|
||||||
|
* 3: Déjà existant.
|
||||||
|
* 4: Erreur dans le déplacement.
|
||||||
|
* 5: Erreur dans le mimetype.
|
||||||
|
*/
|
||||||
|
public static function uploadFile( string $fileArg, array $allowedMimeTypes = [] ): string|int {
|
||||||
|
|
||||||
|
if( !isset( $_FILES[ $fileArg ] ) )
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
$file = $_FILES[ $fileArg ];
|
||||||
|
if( $file['error'] != 0 )
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
$tempFileName = $file['tmp_name'];
|
||||||
|
$fileName = $file['name'];
|
||||||
|
|
||||||
|
if( $allowedMimeTypes !== [] ){
|
||||||
|
if( !in_array( $file['type'], $allowedMimeTypes ) )
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
$full_name = self::uploadFolderPath() . $fileName;
|
||||||
|
$full_uri = self::uploadFolderUri() . $fileName;
|
||||||
|
|
||||||
|
if( file_exists( $full_name ) )
|
||||||
|
return 3;
|
||||||
|
|
||||||
|
if( !move_uploaded_file($tempFileName, $full_name) )
|
||||||
|
return 4;
|
||||||
|
|
||||||
|
return $full_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
51
src/Http/JSONResponse.php
Normal file
51
src/Http/JSONResponse.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de renvoyer une réponse de route au format JSON.
|
||||||
|
*/
|
||||||
|
class JSONResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Les données ajoutés au fichier JSON.
|
||||||
|
* @var array|mixed
|
||||||
|
*/
|
||||||
|
public private(set) array $data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Le code HTML de la réponse.
|
||||||
|
* @var int|mixed
|
||||||
|
*/
|
||||||
|
public private(set) int $htmlCode;
|
||||||
|
|
||||||
|
public function __construct( $data = [], $code = 200 ){
|
||||||
|
|
||||||
|
$this->data = $data;
|
||||||
|
$this->htmlCode = $code;
|
||||||
|
|
||||||
|
$this->returnResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function returnResponse(): never {
|
||||||
|
|
||||||
|
header( 'Content-type: application/json' );
|
||||||
|
http_response_code( $this->htmlCode );
|
||||||
|
|
||||||
|
$this->data['_status'] = $this->htmlCode;
|
||||||
|
$json = json_encode( $this->data );
|
||||||
|
echo $json;
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function sendSuccess( $data = [] ): self {
|
||||||
|
$data['success'] = true;
|
||||||
|
return new self( $data, 200 );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function sendError( $data = [] ): self {
|
||||||
|
$data['success'] = false;
|
||||||
|
return new self( $data, 400 );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
69
src/Http/Request.php
Normal file
69
src/Http/Request.php
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http;
|
||||||
|
use App\Helpers\SanitizeTrait;
|
||||||
|
use App\Kernel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe utilitaire ayant plusieurs méthodes pour gérer la requête actuelle.
|
||||||
|
*/
|
||||||
|
class Request {
|
||||||
|
|
||||||
|
use SanitizeTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bloquer les CORS venant d'autres sites.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function setCORS(): void {
|
||||||
|
$siteUrl = Kernel::$configs['general']['website_url'];
|
||||||
|
header("Access-Control-Allow-Origin: {$siteUrl}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'obtenir une variable GET et nettoyé.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function get( string $name ): mixed {
|
||||||
|
|
||||||
|
if( !isset( $_GET[$name] ) ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::sanitize( $_GET[$name] );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'obtenir une variable POST et nettoyé.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function post( string $name ): mixed {
|
||||||
|
|
||||||
|
if( !isset( $_POST[$name] ) ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
42
src/Http/Route.php
Normal file
42
src/Http/Route.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de définir une route précise.
|
||||||
|
*/
|
||||||
|
final class Route {
|
||||||
|
|
||||||
|
|
||||||
|
public private(set) string $routeUrl;
|
||||||
|
public private(set) string $routeName;
|
||||||
|
public private(set) string $routeController;
|
||||||
|
public private(set) string $routeAction;
|
||||||
|
public private(set) array $routeMethods;
|
||||||
|
public private(set) string $pageHeadTitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $routeUrl Le chemin vers la route.
|
||||||
|
* @param string $routeName Le nom de la route.
|
||||||
|
* @param string $routeController Le controller qui va interpréter cette route.
|
||||||
|
* @param string $routeAction L'action du controller qui va interpréter cette route.
|
||||||
|
* @param array $routeMethods Une liste de methodes GET, POST que la page accepte.
|
||||||
|
* @param string $pageHeadTitle Le nom de la page qui sera affiché dans <title></title>.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
string $routeUrl,
|
||||||
|
string $routeName,
|
||||||
|
string $routeController,
|
||||||
|
string $routeAction,
|
||||||
|
array $routeMethods,
|
||||||
|
string $pageHeadTitle,
|
||||||
|
){
|
||||||
|
$this->routeUrl = $routeUrl;
|
||||||
|
$this->routeName = $routeName;
|
||||||
|
$this->routeController = $routeController;
|
||||||
|
$this->routeAction = $routeAction;
|
||||||
|
$this->routeMethods = $routeMethods;
|
||||||
|
$this->pageHeadTitle = $pageHeadTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
199
src/Http/Router.php
Normal file
199
src/Http/Router.php
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http;
|
||||||
|
|
||||||
|
use App\Domain\Controller;
|
||||||
|
use App\Exceptions\InvalidRouteException;
|
||||||
|
use App\Helpers\AutoLoader;
|
||||||
|
use App\Kernel;
|
||||||
|
use FilesystemIterator;
|
||||||
|
|
||||||
|
final class Router {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route voulue par le client.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public private(set) static string $clientRouteString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informations de la route du client.
|
||||||
|
* @var Route
|
||||||
|
*/
|
||||||
|
public private(set) static Route $clientRoute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informations sur les arguments passés à la route.
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public private(set) static array $clientRouteArguments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liste des controllers sorti du cache ou récupéré via fetchControllers.
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private static array $controllers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liste des routes tirées des controllers. Rempli par la méthode fetchRoutes.
|
||||||
|
* @var Route[]
|
||||||
|
*/
|
||||||
|
private static array $routes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fonction principale qui va router le contenu vers la bonne méthode du bon Controller.
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @throws InvalidRouteException Si la route voulue n'existe pas ou n'est pas bien formaté.
|
||||||
|
*/
|
||||||
|
public static function routeTo(): void {
|
||||||
|
|
||||||
|
self::$clientRouteString = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||||
|
|
||||||
|
self::$controllers = self::fetchControllers();
|
||||||
|
self::$routes = self::fetchRoutes();
|
||||||
|
|
||||||
|
$clientRoute = self::clientRouteExist();
|
||||||
|
if( !$clientRoute ){
|
||||||
|
throw new InvalidRouteException( self::$clientRouteString );
|
||||||
|
}
|
||||||
|
self::$clientRoute = $clientRoute;
|
||||||
|
|
||||||
|
if( !in_array( $_SERVER['REQUEST_METHOD'], self::$clientRoute->routeMethods ) ){
|
||||||
|
throw new InvalidRouteException( self::$clientRouteString );
|
||||||
|
} else {
|
||||||
|
self::executeRouteAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de récupérer tous les controllers du dossier Domain.
|
||||||
|
* @return class-string[]
|
||||||
|
*/
|
||||||
|
private static function fetchControllers(): array {
|
||||||
|
|
||||||
|
$classes = [];
|
||||||
|
|
||||||
|
$iterator = new \RecursiveIteratorIterator(
|
||||||
|
new \RecursiveDirectoryIterator( APP_ROOT . 'src/Domain', FilesystemIterator::SKIP_DOTS ),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach( $iterator as $file ){
|
||||||
|
|
||||||
|
if( $file->isFile() && $file->getExtension() === 'php' ){
|
||||||
|
|
||||||
|
$fileName = $file->getPathname();
|
||||||
|
|
||||||
|
// Transformation du chemin du fichier vers le nom complet de la classe.
|
||||||
|
$fileName = str_replace( APP_ROOT . 'src/', AutoLoader::PRIMARY_NAMESPACE, $fileName );
|
||||||
|
$fileName = str_replace( '.php', '', $fileName );
|
||||||
|
$fileName = str_replace( '/', '\\', $fileName );
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Vérifie que l'on est bien sur un Controller.
|
||||||
|
* Ignore Controller car Controller n'est pas une sous-classe de Controller.
|
||||||
|
*/
|
||||||
|
if( is_subclass_of( $fileName, Controller::class ) ){
|
||||||
|
$classes[] = $fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $classes;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupérer toutes les routes des controllers récupéré avant.
|
||||||
|
* @return Route[]
|
||||||
|
*/
|
||||||
|
private static function fetchRoutes(): array {
|
||||||
|
|
||||||
|
$routes = [];
|
||||||
|
|
||||||
|
foreach( self::$controllers as $controllerClassString ){
|
||||||
|
$routes = array_merge( $routes, $controllerClassString::defineRoutes() );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de savoir si la route que le client veut existe.
|
||||||
|
* @return Route|bool Retourne la route de l'utilisateur ou false si inexistante.
|
||||||
|
*/
|
||||||
|
private static function clientRouteExist(): Route|bool {
|
||||||
|
|
||||||
|
$clientRouteName = trim( self::$clientRouteString, '/' );
|
||||||
|
|
||||||
|
foreach( self::$routes as $route ){
|
||||||
|
|
||||||
|
$routeName = self::getRegexRoute( $route );
|
||||||
|
if( preg_match( $routeName, $clientRouteName, $matches ) ){
|
||||||
|
array_shift( $matches ); // On enlève la chaine complète.
|
||||||
|
self::$clientRouteArguments = $matches;
|
||||||
|
return $route;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de remplacer les expressions définies dans la configuration par leurs équivalents Regex.
|
||||||
|
*
|
||||||
|
* @param Route $route
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private static function getRegexRoute( Route $route ): string {
|
||||||
|
$routeUrl = trim( $route->routeUrl, '/' );
|
||||||
|
foreach ( Kernel::$configs['route_arguments'] as $key => $value ){
|
||||||
|
$routeUrl = str_replace( $key, $value, $routeUrl );
|
||||||
|
}
|
||||||
|
return "#^{$routeUrl}$#";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Va permettre d'exécuter la méthode du Controller→action() en parsant les arguments si existants.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function executeRouteAction(): void {
|
||||||
|
$controller = self::$clientRoute->routeController;
|
||||||
|
$method = self::$clientRoute->routeAction;
|
||||||
|
|
||||||
|
new $controller()->$method( ...self::$clientRouteArguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'obtenir l'adresse du site vers une route spécifique.
|
||||||
|
*
|
||||||
|
* @param string $routeName Le nom de la route.
|
||||||
|
* @param ...$args - Les arguments de la route, si existants.
|
||||||
|
*
|
||||||
|
* @return string Renvoie l'URL demandé ou bien la racine du site si la route n'existe pas.
|
||||||
|
*/
|
||||||
|
public static function getRouteURL( string $routeName, ...$args ): string {
|
||||||
|
foreach( self::$routes as $route ){
|
||||||
|
if( $routeName === $route->routeName ){
|
||||||
|
$i = 0;
|
||||||
|
$routeUrl = preg_replace_callback( '/\{([^}]+)}/', function( $match ) use ( $args, &$i ){
|
||||||
|
return $args[$i++] ?? "";
|
||||||
|
}, $route->routeUrl);
|
||||||
|
|
||||||
|
return rtrim( rtrim( Kernel::$configs['general']['website_url'], '/' ) . $routeUrl, '/' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Kernel::$configs['general']['website_url'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getAssetURL( string $assetPath ): string {
|
||||||
|
return Kernel::$configs['general']['website_url'] . 'assets/' . $assetPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
45
src/Infrastructure/Database.php
Normal file
45
src/Infrastructure/Database.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Infrastructure;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de se connecter à la base de données et de faire le pont
|
||||||
|
* entre la classe PDO et nos actions.
|
||||||
|
*/
|
||||||
|
final class Database {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contient les informations de connexion à notre BDD.
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private array $databaseInfos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instance PDO.
|
||||||
|
* @var \PDO
|
||||||
|
*/
|
||||||
|
public private(set) \PDO $pdo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructeur, lance la connexion.
|
||||||
|
* @param array $loginInfos ['name','host','port','user','pass']
|
||||||
|
*/
|
||||||
|
public function __construct( array $loginInfos ){
|
||||||
|
$this->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() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
220
src/Infrastructure/View.php
Normal file
220
src/Infrastructure/View.php
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Infrastructure;
|
||||||
|
|
||||||
|
use App\Exceptions\InvalidViewException;
|
||||||
|
use App\Http\Router;
|
||||||
|
use App\Kernel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de rendre une vue / contenu de la page.
|
||||||
|
* /!\ : Une seule vue peut-être appellé par page, hormis celle en plus pour le squelette.
|
||||||
|
*/
|
||||||
|
final class View {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Répertoire où se trouvent les vues.
|
||||||
|
*/
|
||||||
|
const string VIEW_PATH = APP_ROOT . 'views/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Le nom du fichier de vue.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public private(set) string $viewName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Les arguments passés à la vue.
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public private(set) array $viewArgs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La base de la vue. (Pour ne pas répéter header/footer à chaque fois).
|
||||||
|
* null correspond à pas de squelette.
|
||||||
|
*
|
||||||
|
* @var string|mixed|null
|
||||||
|
*/
|
||||||
|
public private(set) ?string $skeleton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilisé par les squelettes, permet d'injecter des contenus comme le contenu de son enfant.
|
||||||
|
* @var array<string,string>
|
||||||
|
*/
|
||||||
|
public private(set) array $integratedContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Garde la dernière instance de vue.
|
||||||
|
* Si il y a un squelette, ce sera l'instance du squelette.
|
||||||
|
*
|
||||||
|
* @var View
|
||||||
|
*/
|
||||||
|
private static self $lastInstance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de construire les informations de la vue.
|
||||||
|
*
|
||||||
|
* @param string $viewName Le nom du fichier de vue.
|
||||||
|
* @param array $viewArgs Les arguments de la vue.
|
||||||
|
* @param bool $autoRender Si la méthode $this->render() est automatiquement exécuté.
|
||||||
|
* @param string|null $skeleton Le squelette du fichier, null correspond à aucun squelette, default correspond au
|
||||||
|
* squelette défini dans la configuration des vues.
|
||||||
|
* @param array<string,string> $integratedContent Le contenu intégré dans le nouveau squelette.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
string $viewName,
|
||||||
|
array $viewArgs = [],
|
||||||
|
bool $autoRender = true,
|
||||||
|
?string $skeleton = 'default',
|
||||||
|
array $integratedContent = []
|
||||||
|
) {
|
||||||
|
|
||||||
|
if( !str_ends_with( $viewName, '.php' ) ) {
|
||||||
|
$viewName .= '.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->viewName = $viewName;
|
||||||
|
$this->viewArgs = $viewArgs;
|
||||||
|
|
||||||
|
$this->skeleton = $skeleton;
|
||||||
|
if( $this->skeleton === "default" ){
|
||||||
|
$this->skeleton = Kernel::$configs['views']['base_view_skeleton'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->integratedContent = $integratedContent;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!file_exists(self::VIEW_PATH . $this->viewName)) {
|
||||||
|
throw new InvalidViewException($this->viewName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($autoRender) {
|
||||||
|
$this->render();
|
||||||
|
}
|
||||||
|
} catch (InvalidViewException $e) {
|
||||||
|
die( $e->getMessage() );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de démarrer le rendu d'une vue.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function startView(): void {
|
||||||
|
ob_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de finir le rendu d'une vue.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function endView(): void {
|
||||||
|
echo ob_get_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de faire le rendu proprement de la vue.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function render(): void {
|
||||||
|
|
||||||
|
self::$lastInstance = $this;
|
||||||
|
|
||||||
|
if( $this->skeleton === null ){
|
||||||
|
|
||||||
|
// Si on a pas de squelette, on inclut la vue.
|
||||||
|
// pour accéder aux éléments de la vue, on utilise les méthodes statiques de cette classe.
|
||||||
|
$this->startView();
|
||||||
|
require self::VIEW_PATH . $this->viewName;
|
||||||
|
$this->endView();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Si on a un squelette, on charge tout le contenu de la vue enfante.
|
||||||
|
$contentName = $this->viewName;
|
||||||
|
|
||||||
|
// On démarre la vue du squelette.
|
||||||
|
$base = new View( $this->skeleton, $this->viewArgs, skeleton: null, integratedContent: [ 'content' => $contentName ] );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* VIEW TOOLS
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'injecter un contenu sauvegardé dans $this->integratedContent.
|
||||||
|
*
|
||||||
|
* @param string $integratedContentName
|
||||||
|
* @return void N'affiche rien si le contenu n'existe pas.
|
||||||
|
*/
|
||||||
|
public static function inject( string $integratedContentName ): void {
|
||||||
|
if( isset( self::$lastInstance->integratedContent[ $integratedContentName ] ) )
|
||||||
|
require self::VIEW_PATH . self::$lastInstance->integratedContent[ $integratedContentName ];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de récupérer un argument passé à la vue.
|
||||||
|
*
|
||||||
|
* @param string $argumentName
|
||||||
|
* @return mixed null si l'argument n'existe pas.
|
||||||
|
*/
|
||||||
|
public static function arg( string $argumentName ): mixed {
|
||||||
|
if( isset( self::$lastInstance->viewArgs[ $argumentName ] ) )
|
||||||
|
return self::$lastInstance->viewArgs[ $argumentName ];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'intégrer un bloc à la vue.
|
||||||
|
*
|
||||||
|
* @param string $partialName
|
||||||
|
* @return void N'affiche rien si le bloc n'existe pas.
|
||||||
|
*/
|
||||||
|
public static function partial( string $partialName ): void {
|
||||||
|
|
||||||
|
if( !str_ends_with( $partialName, '.php' ) ) {
|
||||||
|
$partialName .= '.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
if( file_exists( self::VIEW_PATH . 'partials/' . $partialName ) ) {
|
||||||
|
require self::VIEW_PATH . 'partials/' . $partialName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'obtenir le titre de la page à mettre dans <title></title>
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function getHeadTitle(): string {
|
||||||
|
$siteUrl = Kernel::$configs['general']['website_name'];
|
||||||
|
return Router::$clientRoute->pageHeadTitle . ' - ' . $siteUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'obtenir une route
|
||||||
|
*
|
||||||
|
* @param string $routeName Le nom de la route.
|
||||||
|
* @param ...$args - Les arguments de la route, si existant.
|
||||||
|
*
|
||||||
|
* @return void Affiche uniquement.
|
||||||
|
*/
|
||||||
|
public static function routeUrl( string $routeName, ...$args ): void {
|
||||||
|
echo Router::getRouteURL( $routeName, ...$args );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'obtenir l'URL vers un asset (CSS,JS,Images).
|
||||||
|
* @param string $assetPath
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function assetUrl( string $assetPath ): void {
|
||||||
|
echo Router::getAssetURL( $assetPath );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
119
src/Kernel.php
Normal file
119
src/Kernel.php
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
use App\Exceptions\ConfigFailedLoadingException;
|
||||||
|
use App\Exceptions\InvalidRouteException;
|
||||||
|
use App\Helpers\Authentification;
|
||||||
|
use App\Helpers\AutoLoader;
|
||||||
|
use App\Helpers\ConfigFactory;
|
||||||
|
use App\Http\Router;
|
||||||
|
use App\Infrastructure\Database;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe primaire du site.
|
||||||
|
* Permet de lancer l'application.
|
||||||
|
*/
|
||||||
|
final class Kernel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liste des fichiers de configuration sous le format suivant :
|
||||||
|
* [ 'nom_de_la_config' -> [ 'cle" → valeur ] ]
|
||||||
|
*
|
||||||
|
* @var array<string,array>
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
private static ?self $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Méthode qui permet de démarrer le site.
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public static function start(): self {
|
||||||
|
self::$instance = new self();
|
||||||
|
self::$instance->init();
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet d'obtenir l'instance actuelle du site.
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public static function getInstance(): self {
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructeur.
|
||||||
|
*/
|
||||||
|
public function __construct() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de préparer le démarrage du site.
|
||||||
|
* Lancé automatiquement par start().
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @see self::start()
|
||||||
|
*/
|
||||||
|
public function init(): void {
|
||||||
|
$this->buildAutoloader();
|
||||||
|
$this->loadConfig();
|
||||||
|
$this->loadDatabase();
|
||||||
|
|
||||||
|
Authentification::startSession();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Router::routeTo();
|
||||||
|
} catch ( InvalidRouteException $e ){
|
||||||
|
die( $e->getMessage() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de mettre en place l'Autoloader.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function buildAutoloader(): void {
|
||||||
|
require_once 'Helpers/AutoLoader.php';
|
||||||
|
AutoLoader::register();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de charger tous les fichiers de configuration du site.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function loadConfig(): void {
|
||||||
|
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');
|
||||||
|
|
||||||
|
} catch( ConfigFailedLoadingException $e ){
|
||||||
|
die( $e->getMessage() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de se connecter à la base de données principale.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function loadDatabase(): void {
|
||||||
|
self::$DB = new Database( self::$configs['database'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
9
views/base.php
Normal file
9
views/base.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php use App\Infrastructure\View as V; ?>
|
||||||
|
|
||||||
|
<?php V::partial( 'header' ); ?>
|
||||||
|
|
||||||
|
<div class="main-body">
|
||||||
|
<?php V::inject( 'content'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php V::partial( 'footer' ); ?>
|
||||||
23
views/home.php
Normal file
23
views/home.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php use App\Infrastructure\View as V; ?>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="title-site">
|
||||||
|
<h1 class="title">Les Recettes De Papis</h1>
|
||||||
|
<hr class="line-acceuil">
|
||||||
|
<h3>Proposition de recette :</h3>
|
||||||
|
</div>
|
||||||
|
<div class="random-proposition">
|
||||||
|
<?php foreach( V::arg( 'recettes' ) as $recette ): ?>
|
||||||
|
<a class="recette-icone" href="<?php V::routeUrl( 'recettes->show', $recette->slug ); ?>">
|
||||||
|
<img class="recette-preview-image" src="<?php echo $recette->photo; ?>">
|
||||||
|
<div class="recette-icone-content">
|
||||||
|
<h3><?php echo $recette->titre_recette; ?></h3>
|
||||||
|
<ul class="meta-recettes">
|
||||||
|
<li>Temps de préparation : <?php echo $recette->temps_de_preparation; ?> minutes</li>
|
||||||
|
<li>Nombre d'ingrédients : <?php echo $recette->getNumberOfIngredients(); ?></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
18
views/ingredients/index.php
Normal file
18
views/ingredients/index.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php use App\Domain\Tags\Tag;
|
||||||
|
use App\Infrastructure\View as V; ?>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<?php foreach (V::arg( 'ingredients') as $ing): ?>
|
||||||
|
<div class="liste-ingr-elem">
|
||||||
|
<img class="ingr-image" src="<?php echo $ing->photo_ingredient; ?>">
|
||||||
|
<div class="liste-ingr-elem-text">
|
||||||
|
<?php echo $ing->nom_ingredient; ?>
|
||||||
|
</div>
|
||||||
|
<div class="liste-ingr-elem-modif">
|
||||||
|
<a class="recette-button ingr-modifier" data-id="<?php echo $ing->getId(); ?>">Modifier</a>
|
||||||
|
<a class="recette-button ingr-suppr" data-id="<?php echo $ing->getId(); ?>">Supprimer</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<script src="<?php V::assetUrl( 'js/manage-ingredients.js' ); ?>" ></script>
|
||||||
23
views/login.php
Normal file
23
views/login.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php use App\Infrastructure\View as V; use App\Http\Router; ?>
|
||||||
|
|
||||||
|
<div class=formcont>
|
||||||
|
<form id='login-form' action='<?php echo Router::getRouteURL( 'api->auth' ); ?>' method='post'>
|
||||||
|
|
||||||
|
<div class='form-group'>
|
||||||
|
<label for='logmsg'>Se Connecter</label>
|
||||||
|
</div>
|
||||||
|
<div id="login-errors" class="form-error" style="display:none;color:red;">
|
||||||
|
</div>
|
||||||
|
<div class='form-group'>
|
||||||
|
<input type='text' class='form-control' name='username' id='username' value='' placeholder="Pseudo">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='form-group'>
|
||||||
|
<input type='text' class='form-control' name='password' id='password' value='' placeholder="Mot de Passe">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type='submit' class='btn btn-primary'>Confirmer</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<script src="<?php echo Router::getAssetURL( 'js/login.js' ); ?>" defer></script>
|
||||||
14
views/partials/footer.php
Normal file
14
views/partials/footer.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php use App\Infrastructure\View as V; ?>
|
||||||
|
|
||||||
|
<footer id="footer">
|
||||||
|
<div class="src">
|
||||||
|
<a href="https://code.romhackplaza.org/Benjamin/LesRecettesDePapis" target="_blank">Notre Gitea</a>
|
||||||
|
</div>
|
||||||
|
<div class="Contact">
|
||||||
|
Front end : Bousquet Sébastien
|
||||||
|
--- Back end : Thorel Benjamin
|
||||||
|
--- Base de données : Glaudis Jordan
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
36
views/partials/header.php
Normal file
36
views/partials/header.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php use App\Infrastructure\View as V; ?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title><?php echo V::getHeadTitle(); ?></title>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="stylesheet" href="<?php V::assetUrl( 'css/style.css' ); ?>" />
|
||||||
|
<link rel="icon" type="image/x-icon" href="<?php V::assetUrl( 'images/Logo.ico' ); ?>" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header id="header">
|
||||||
|
<div id="logo">
|
||||||
|
<a href="<?php V::routeUrl( 'home'); ?>">
|
||||||
|
<img src="<?php V::assetUrl( 'images/Logo.jpg'); ?>" class="logo">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<nav>
|
||||||
|
<ul class="nav-list">
|
||||||
|
<li><a id="google" class="nav-element" href="<?php V::routeUrl( 'home'); ?>">Acceuil</a></li>
|
||||||
|
<li><a id="unTest" class="nav-element" href="<?php V::routeUrl( 'recettes->index'); ?>">Liste des recettes</a></li>
|
||||||
|
<?php if( \App\Helpers\Authentification::isLoggedIn() ): ?>
|
||||||
|
<li><a id="login" class= "nav-element" href="<?php V::routeUrl( 'logout'); ?>">Logout</a></li>
|
||||||
|
<li><a id="addBouton" class="nav-element" href="<?php V::routeUrl( 'recettes->create'); ?>">Ajouter une Recette</a></li>
|
||||||
|
<li><a id="ingrManage" class="nav-element" href="<?php V::routeUrl( 'manage->ingredients'); ?>">Gérer les ingrédients</a></li>
|
||||||
|
<li><a id="tagManage" class="nav-element" href="<?php V::routeUrl( 'manage->tags'); ?>">Gérer les tags</a></li>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
<li><a id="login" class= "nav-element" href="<?php V::routeUrl( 'login'); ?>">Login</a></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
<form id="searchForm" method="get" action="<?php V::routeUrl( 'recettes->index'); ?>">
|
||||||
|
<input name="s" id="searchFormField" type="text" class="search-form search-form-recette" placeholder="Rechercher une recette ..." value="<?php echo \App\Http\Request::get( 's' ); ?>">
|
||||||
|
</form>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
34
views/partials/tag-sidebar.php
Normal file
34
views/partials/tag-sidebar.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php use App\Infrastructure\View as V; use App\Http\Router;?>
|
||||||
|
|
||||||
|
<div class="sidebar">
|
||||||
|
<div class="tag-cont">
|
||||||
|
<h4>Tag</h4>
|
||||||
|
<div class="tag-selected-div">
|
||||||
|
<ul>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="tag-unselected-div">
|
||||||
|
<ul id="tagsList">
|
||||||
|
<?php foreach( V::arg( 'tagsList') as $tag ): ?>
|
||||||
|
<li class="tag tag-unselected" data-tag-id="<?php echo $tag->num_tag;?>"><?php echo $tag->nom_tag; ?></li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr id="hr">
|
||||||
|
<div class="ingr-cont">
|
||||||
|
<h4>Ingrédient</h4>
|
||||||
|
<div class="tag-selected-div">
|
||||||
|
<ul>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="ing-unselected-div">
|
||||||
|
<ul id="ingredientsList">
|
||||||
|
<?php foreach( V::arg( 'ingredientsList') as $tag ): ?>
|
||||||
|
<li class="tag tag-unselected" data-ingredient-id="<?php echo $tag->num_ingredient;?>"><?php echo $tag->nom_ingredient; ?></li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="<?php echo Router::getAssetURL( 'js/advanced-search.js' ); ?>" defer></script>
|
||||||
80
views/recettes/create.php
Normal file
80
views/recettes/create.php
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<?php use App\Domain\Recettes\Recette;
|
||||||
|
use App\Infrastructure\View as V; ?>
|
||||||
|
|
||||||
|
<div class="main-body">
|
||||||
|
<div class="content">
|
||||||
|
<div class="recette-add-form-all">
|
||||||
|
<form id="recette-form-complete" class="recette-form recette-form-add" action="/api/recettes/create" method="POST">
|
||||||
|
<legend><h1>Nouvelle Recette : </h1></legend>
|
||||||
|
<div class="recette-form-group form-group">
|
||||||
|
<label for="nom">Titre : </label>
|
||||||
|
<input type="text" class="form-control" id="recette-form-nom" name="nom" placeholder="Titre de votre recette">
|
||||||
|
</div>
|
||||||
|
<div class="recette-form-group form-group">
|
||||||
|
<label for="temps">Temps (en min) : </label>
|
||||||
|
<input type="number" class="form-control" id="recette-form-temps" name="temps" placeholder="Temps de préparation">
|
||||||
|
</div>
|
||||||
|
<div class="recette-form-group form-group">
|
||||||
|
<label for="image">Photo du plat : </label>
|
||||||
|
<input type="file" class="form-control" id="recette-form-photo" name="image" placeholder="ajouter votre image" accept="image/jpeg, image/png">
|
||||||
|
</div>
|
||||||
|
<div class="recette-form-group form-group" id="recette-form-div-ingr">
|
||||||
|
<label for="ingr">Ingredients</label>
|
||||||
|
<select multiple class="form-control" id="recette-form-ingr" name="ingr" placeholder="Ingredients">
|
||||||
|
<?php foreach( V::arg( 'ingredients') as $ingr ): ?>
|
||||||
|
<option value="<?php echo $ingr->getID(); ?>"><?php echo $ingr->nom_ingredient; ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="recette-form-group form-group" id="recette-form-div-tag">
|
||||||
|
<label for="tag">Tag</label>
|
||||||
|
<select multiple class="form-control" id="recette-form-tag" name="tag" placeholder="Tag">
|
||||||
|
<?php foreach( V::arg( 'tags') as $tag ): ?>
|
||||||
|
<option value="<?php echo $tag->getID(); ?>"><?php echo $tag->nom_tag; ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="recette-form-group form-group" id="recette-form-div-desc">
|
||||||
|
<label for="description">Description</label>
|
||||||
|
<textarea type="texte" class="form-control" id="recette-form-description" name="description" placeholder="Markdown supporté pour les titres de niveau 1,2, le gras ainsi que l'italique"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type='submit' class='btn btn-primary'>Envoyer la recette</button>
|
||||||
|
</form>
|
||||||
|
<div class="recette-form-add-additional">
|
||||||
|
<form id="recette-form-ingr-add" class="recette-form recette-form-add-ingr" action="" method="POST">
|
||||||
|
<legend><h2>Nouvel Ingrédient :</h2></legend>
|
||||||
|
<div class="recette-form-group form-group">
|
||||||
|
<label for="name">Nom de l'Ingrédient</label>
|
||||||
|
<input type="texte" class="form-control" id="recette-form-ingr-nom" name="name" placeholder="Nom de l'ingrédient" value="">
|
||||||
|
</div>
|
||||||
|
<div class="recette-form-group form-group">
|
||||||
|
<label for="image">Photo de l'Ingrédient</label>
|
||||||
|
<input type="file" class="form-control" id="recette-form-ingr-photo" name="image">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="submit-ingr" type='submit' class='btn btn-primary'>Confirmer</button>
|
||||||
|
<div id="recette-form-div-err-ingr" class="err">
|
||||||
|
Erreur
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<form id="recette-form-tag-add" class="recette-form recette-form-add-tag" action="" method="POST">
|
||||||
|
<legend><h2>Nouveau Tag : </h2></legend>
|
||||||
|
<div class="recette-form-group form-group">
|
||||||
|
<label for="tag">Tags</label>
|
||||||
|
<input type="text" class="form-control" id="recette-form-tag-nom" name="name" placeholder="Nom de l'ingrédient">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type='submit' class='btn btn-primary'>Confirmer</button>
|
||||||
|
<div id="recette-form-div-err-tag" class="err">
|
||||||
|
Erreur
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
const IS_EDIT = false; const RECETTE_ID = 0;
|
||||||
|
</script>
|
||||||
|
<script src="<?php V::assetUrl( 'js/form.js' ); ?>" defer></script>
|
||||||
80
views/recettes/edit.php
Normal file
80
views/recettes/edit.php
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<?php use App\Domain\Recettes\Recette;
|
||||||
|
use App\Infrastructure\View as V; ?>
|
||||||
|
<?php /** @var Recette $R */$R = V::arg( 'recette'); ?>
|
||||||
|
<div class="main-body">
|
||||||
|
<div class="content">
|
||||||
|
<div class="recette-add-form-all">
|
||||||
|
<form id="recette-form-complete" class="recette-form recette-form-add" action="/api/recettes/edit" method="POST">
|
||||||
|
<legend><h1>Editer une Recette : </h1></legend>
|
||||||
|
<div class="recette-form-group form-group">
|
||||||
|
<label for="nom">Titre : </label>
|
||||||
|
<input type="text" class="form-control" id="recette-form-nom" name="nom" placeholder="Titre de votre recette" value="<?php echo $R->titre_recette; ?>">
|
||||||
|
</div>
|
||||||
|
<div class="recette-form-group form-group">
|
||||||
|
<label for="temps">Temps (en min) : </label>
|
||||||
|
<input type="number" class="form-control" id="recette-form-temps" name="temps" placeholder="Temps de préparation" value="<?php echo $R->temps_de_preparation; ?>">
|
||||||
|
</div>
|
||||||
|
<div class="recette-form-group form-group">
|
||||||
|
<label for="image">Photo du plat : </label>
|
||||||
|
<input type="file" class="form-control" id="recette-form-photo" name="image" placeholder="ajouter votre image" accept="image/jpeg, image/png">
|
||||||
|
</div>
|
||||||
|
<div class="recette-form-group form-group" id="recette-form-div-ingr">
|
||||||
|
<label for="ingr">Ingredients</label>
|
||||||
|
<select multiple class="form-control" id="recette-form-ingr" name="ingr" placeholder="Ingredients">
|
||||||
|
<?php foreach( V::arg( 'ingredients') as $ingr ): ?>
|
||||||
|
<option value="<?php echo $ingr->getID(); ?>" <?php echo in_array( $ingr->getId(), V::arg( 'recetteIngr') ) ? "selected" : ""; ?> ><?php echo $ingr->nom_ingredient; ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="recette-form-group form-group" id="recette-form-div-tag">
|
||||||
|
<label for="tag">Tag</label>
|
||||||
|
<select multiple class="form-control" id="recette-form-tag" name="tag" placeholder="Tag">
|
||||||
|
<?php foreach( V::arg( 'tags') as $tag ): ?>
|
||||||
|
<option value="<?php echo $tag->getID(); ?>" <?php echo in_array( $tag->getId(), V::arg( 'recetteTags' ) ) ? "selected" : ""; ?> ><?php echo $tag->nom_tag; ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="recette-form-group form-group" id="recette-form-div-desc">
|
||||||
|
<label for="description">Description</label>
|
||||||
|
<textarea type="texte" class="form-control" id="recette-form-description" name="description" placeholder="Description..."><?php echo $R->description_recette; ?></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type='submit' class='btn btn-primary'>Confirmer</button>
|
||||||
|
</form>
|
||||||
|
<div class="recette-form-add-additional">
|
||||||
|
<form id="recette-form-ingr-add" class="recette-form recette-form-add-ingr" action="" method="POST">
|
||||||
|
<legend><h2>Nouvel Ingrédient :</h2></legend>
|
||||||
|
<div class="recette-form-group form-group">
|
||||||
|
<label for="name">Nom de l'Ingrédient</label>
|
||||||
|
<input type="texte" class="form-control" id="recette-form-ingr-nom" name="name" placeholder="Nom de l'ingrédient" value="">
|
||||||
|
</div>
|
||||||
|
<div class="recette-form-group form-group">
|
||||||
|
<label for="image">Photo de l'Ingrédient</label>
|
||||||
|
<input type="file" class="form-control" id="recette-form-ingr-photo" name="image">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="submit-ingr" type='submit' class='btn btn-primary'>Confirmer</button>
|
||||||
|
<div id="recette-form-div-err-ingr" class="err">
|
||||||
|
Erreur
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<form id="recette-form-tag-add" class="recette-form recette-form-add-tag" action="" method="POST">
|
||||||
|
<legend><h2>Nouveau Tag : </h2></legend>
|
||||||
|
<div class="recette-form-group form-group">
|
||||||
|
<label for="tag">Tags</label>
|
||||||
|
<input type="text" class="form-control" id="recette-form-tag-nom" name="name" placeholder="Nom de l'ingrédient">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type='submit' class='btn btn-primary'>Confirmer</button>
|
||||||
|
<div id="recette-form-div-err-tag" class="err">
|
||||||
|
Erreur
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
const IS_EDIT = true; const RECETTE_ID = <?php echo $R->getId(); ?>;
|
||||||
|
</script>
|
||||||
|
<script src="<?php V::assetUrl( 'js/form.js' ); ?>" defer></script>
|
||||||
19
views/recettes/index.php
Normal file
19
views/recettes/index.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php use App\Infrastructure\View as V; ?>
|
||||||
|
|
||||||
|
<?php V::partial( 'tag-sidebar' ); ?>
|
||||||
|
<div class="content">
|
||||||
|
<div id="recetteList" class="recettes">
|
||||||
|
<?php if( V::arg( 'recettesList' ) != null ) foreach( V::arg( 'recettesList' ) as $recette ): ?>
|
||||||
|
<a class="recette-icone" href="<?php V::routeUrl( 'recettes->show', $recette->slug ); ?>">
|
||||||
|
<img class="recette-preview-image" src="<?php echo $recette->photo; ?>">
|
||||||
|
<div class="recette-icone-content">
|
||||||
|
<h3><?php echo $recette->titre_recette; ?></h3>
|
||||||
|
<ul class="meta-recettes">
|
||||||
|
<li>Temps de préparation : <?php echo $recette->temps_de_preparation; ?> minutes</li>
|
||||||
|
<li>Nombre d'ingrédients : <?php echo $recette->getNumberOfIngredients(); ?></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
55
views/recettes/show.php
Normal file
55
views/recettes/show.php
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?php use App\Domain\Recettes\Recette;
|
||||||
|
use App\Infrastructure\View as V; ?>
|
||||||
|
<?php /** @var Recette $R */ $R = V::arg( 'recette' ); ?>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="recette-title">
|
||||||
|
<h1><?php echo $R->titre_recette; ?></h1>
|
||||||
|
</div>
|
||||||
|
<div class="recette-content">
|
||||||
|
<div class="recette-div-info">
|
||||||
|
<div class="recette-div-image">
|
||||||
|
<img class="recette-image" src="<?php echo $R->photo; ?>">
|
||||||
|
</div>
|
||||||
|
<div class="recette-div-liste-info">
|
||||||
|
<?php if( \App\Helpers\Authentification::isLoggedIn() ): ?>
|
||||||
|
<ul class="recette-list-buttons">
|
||||||
|
<li><a class="recette-button" href="#" id="delete" data-id="<?php echo $R->num_recette; ?>">Supprimer</a></li>
|
||||||
|
<li><a class="recette-button" href="<?php V::routeUrl( 'recettes->edit', $R->getID() ); ?>">Modifier</a></li>
|
||||||
|
</ul>
|
||||||
|
<?php endif; ?>
|
||||||
|
<ul class="recette-liste-info">
|
||||||
|
<li class="recette-liste-info-elem"><?php echo $R->publication_date; ?></li>
|
||||||
|
<li class="recette-liste-info-elem"><?php echo $R->temps_de_preparation; ?> minutes</li>
|
||||||
|
<li class="recette-liste-info-elem"><?php echo $R->getNumberOfIngredients(); ?> ingrédients</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="recette-liste-tag">
|
||||||
|
<?php foreach( $R->getAllLinkedTags() as $tag ): ?>
|
||||||
|
<li class="tag tag-unselected"><?php echo $tag->nom_tag; ?></li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="recette-liste-ingr-div">
|
||||||
|
<h3>Liste des ingrédients</h3>
|
||||||
|
<ul class="recette-liste-ingr">
|
||||||
|
<?php foreach( $R->getAllLinkedIngredients() as $ingr ): ?>
|
||||||
|
<li class="recette-liste-ingr-elem"><img class="ingr-image-prev" src="<?php echo $ingr->photo_ingredient; ?>"> <div class="recette-liste-ingr-elem-nom"><?php echo $ingr->nom_ingredient; ?></div></li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class = "recette-desc">
|
||||||
|
<div id="description">
|
||||||
|
<?php echo $R->getHTMLDescription(); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.getElementById( 'delete' ).addEventListener( 'click', e => {
|
||||||
|
if( window.confirm( "Êtes-vous sur de vouloir supprimer cette recette ? ") ){
|
||||||
|
let id = document.getElementById( 'delete' ).getAttribute( 'data-id' );
|
||||||
|
window.location.href = "/recettes/delete/" + id;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
17
views/tags/index.php
Normal file
17
views/tags/index.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php use App\Domain\Tags\Tag;
|
||||||
|
use App\Infrastructure\View as V; ?>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<?php foreach( V::arg( 'tags' ) as $tag ): ?>
|
||||||
|
<div class="liste-tag-elem">
|
||||||
|
<div class="liste-tag-elem-text">
|
||||||
|
<?php echo $tag->nom_tag; ?>
|
||||||
|
</div>
|
||||||
|
<div class="liste-tag-elem-modif">
|
||||||
|
<a class="recette-button tag-modifier" data-id="<?php echo $tag->num_tag; ?>">Modifier</a>
|
||||||
|
<a class="recette-button tag-suppr" data-id="<?php echo $tag->num_tag; ?>">Supprimer</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<script src="<?php V::assetUrl( 'js/manage-tags.js' ); ?>" ></script>
|
||||||
Reference in New Issue
Block a user