Initial commit : CMS Simplifié
This commit is contained in:
5
.env.example
Normal file
5
.env.example
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_NAME=cms_simplifie
|
||||||
|
DB_USER=root
|
||||||
|
DB_PASS=CHANGE_ME
|
||||||
|
APP_SECRET=CHANGE_ME_TOO
|
||||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Fichiers secrets qu'on NE VEUT PAS envoyer sur Gitea
|
||||||
|
.env
|
||||||
|
inc/config.local.php
|
||||||
|
# --- secrets ---
|
||||||
|
.env
|
||||||
|
inc/config.local.php
|
||||||
101
README.md
Normal file
101
README.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Mini-Projet de Développement Web – CMS Simplifié
|
||||||
|
|
||||||
|
## Présentation du projet
|
||||||
|
Ce mini-projet a été réalisé dans le cadre du module **Développement Web** (BTS CIEL – 2ᵉ année).
|
||||||
|
|
||||||
|
L’objectif était de créer un **mini CMS** (Content Management System) simplifié permettant :
|
||||||
|
- la **connexion sécurisée** d’un administrateur ;
|
||||||
|
- la **création**, **modification** et **suppression** d’articles (CRUD) ;
|
||||||
|
- l’affichage des données stockées en base **MySQL**.
|
||||||
|
|
||||||
|
Le projet est réalisé **sans framework**, uniquement avec les langages vus en cours :
|
||||||
|
**HTML, CSS, Bootstrap, PHP (PDO), et SQL**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Technologies utilisées
|
||||||
|
| Technologie | Utilisation |
|
||||||
|
|--------------|--------------|
|
||||||
|
| **HTML5** | Structure des pages |
|
||||||
|
| **CSS3 / Bootstrap 5** | Mise en forme et design responsive (thème noir & or) |
|
||||||
|
| **PHP (Sessions, PDO)** | Connexion, sécurité et interaction avec MySQL |
|
||||||
|
| **MySQL** | Base de données pour les utilisateurs et articles |
|
||||||
|
| **Docker** | Conteneurs PHP/Apache et MySQL |
|
||||||
|
| **Git / Gitea** | Gestion de version et hébergement du projet |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Structure du projet
|
||||||
|
|
||||||
|
cms-simplifie/
|
||||||
|
│
|
||||||
|
├── public/
|
||||||
|
│ ├── assets/
|
||||||
|
│ │ └── style.css → Design global (thème noir & or)
|
||||||
|
│ ├── add.php → Page d’ajout d’un article
|
||||||
|
│ ├── edit.php → Page de modification d’un article
|
||||||
|
│ ├── delete.php → Page de suppression d’un article
|
||||||
|
│ ├── admin.php → Tableau de bord (gestion des articles)
|
||||||
|
│ ├── login.php → Page de connexion administrateur
|
||||||
|
│ ├── logout.php → Déconnexion
|
||||||
|
│ ├── article.php → Lecture d’un article
|
||||||
|
│ └── index.php → Accueil (affichage des articles)
|
||||||
|
│
|
||||||
|
├── inc/
|
||||||
|
│ └── db.php → Connexion PDO sécurisée à MySQL
|
||||||
|
│
|
||||||
|
├── scripts/ → (optionnel, outils ou scripts SQL)
|
||||||
|
│
|
||||||
|
├── .env.example → Exemple de configuration (sans mot de passe)
|
||||||
|
├── .gitignore → Exclut .env et fichiers sensibles
|
||||||
|
├── stack.bibli.yaml / Dockerfile → Configuration Docker
|
||||||
|
└── README.md → Ce fichier
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fonctionnalités principales
|
||||||
|
- Connexion d’un utilisateur (admin) avec vérification du mot de passe haché.
|
||||||
|
- Gestion complète des articles (**CRUD**) : créer, lire, modifier, supprimer.
|
||||||
|
- Interface d’administration simple et claire.
|
||||||
|
- Design **noir & or** pour une ambiance moderne et élégante.
|
||||||
|
- Sécurisation via **sessions PHP** et **requêtes préparées PDO**.
|
||||||
|
- Aucune donnée sensible visible dans le code source.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sécurité
|
||||||
|
- Les identifiants MySQL sont stockés dans **`.env`**, jamais dans le code.
|
||||||
|
- Le fichier `.gitignore` empêche toute fuite de `.env`.
|
||||||
|
- Les mots de passe sont **hachés** avec `password_hash()` et vérifiés avec `password_verify()`.
|
||||||
|
- Connexion à la base via **PDO** pour éviter les injections SQL.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lancer le projet en local
|
||||||
|
|
||||||
|
### Étapes :
|
||||||
|
|
||||||
|
1️⃣ Démarrer les conteneurs Docker :
|
||||||
|
|
||||||
|
docker stack deploy -c stack.bibli.yaml cms
|
||||||
|
|
||||||
|
|
||||||
|
2️⃣ Copier le fichier .env.example pour créer ton .env personnel :
|
||||||
|
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
|
||||||
|
3️⃣ Modifier le fichier .env pour y mettre tes identifiants MySQL :
|
||||||
|
|
||||||
|
DB_HOST=mysql
|
||||||
|
DB_NAME=cms_simplifie
|
||||||
|
DB_USER=root
|
||||||
|
DB_PASS=tonmotdepasse
|
||||||
|
|
||||||
|
|
||||||
|
4️⃣ Accéder au site localement :
|
||||||
|
👉 http://localhost:8000
|
||||||
|
|
||||||
|
5️⃣ Tester la connexion à la base :
|
||||||
|
👉 http://localhost:8000/test-db.php
|
||||||
0
admin/dashboard.php
Normal file
0
admin/dashboard.php
Normal file
0
admin/login.php
Normal file
0
admin/login.php
Normal file
0
admin/logout.php
Normal file
0
admin/logout.php
Normal file
0
inc/auth.php
Normal file
0
inc/auth.php
Normal file
30
inc/db.php
Normal file
30
inc/db.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
$config = [];
|
||||||
|
if (file_exists(__DIR__ . '/config.local.php')) {
|
||||||
|
$config = require __DIR__ . '/config.local.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
$DB_HOST = $config['DB_HOST'] ?? '127.0.0.1';
|
||||||
|
$DB_NAME = $config['DB_NAME'] ?? 'cms_simplifie';
|
||||||
|
$DB_USER = $config['DB_USER'] ?? 'root';
|
||||||
|
$DB_PASS = $config['DB_PASS'] ?? '';
|
||||||
|
$DB_CHARSET = 'utf8mb4';
|
||||||
|
|
||||||
|
$options = [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
PDO::ATTR_EMULATE_PREPARES => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = new PDO(
|
||||||
|
"mysql:host=$DB_HOST;dbname=$DB_NAME;charset=$DB_CHARSET",
|
||||||
|
$DB_USER,
|
||||||
|
$DB_PASS,
|
||||||
|
$options
|
||||||
|
);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log('Erreur connexion DB : ' . $e->getMessage());
|
||||||
|
http_response_code(500);
|
||||||
|
exit('Erreur de connexion à la base de données.');
|
||||||
|
}
|
||||||
55
public/add.php
Normal file
55
public/add.php
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
if(!isset($_SESSION['user_id'])){ header('Location: login.php'); exit; }
|
||||||
|
require __DIR__ . '/../inc/db.php';
|
||||||
|
$error = null;
|
||||||
|
if($_SERVER['REQUEST_METHOD']==='POST'){
|
||||||
|
$titre = trim($_POST['titre'] ?? '');
|
||||||
|
$contenu = trim($_POST['contenu'] ?? '');
|
||||||
|
if($titre!=='' && $contenu!==''){
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO articles(titre,contenu) VALUES(?,?)");
|
||||||
|
$stmt->execute([$titre,$contenu]);
|
||||||
|
header('Location: admin.php'); exit;
|
||||||
|
} else { $error = 'Champs obligatoires.'; }
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>Ajouter un article</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="assets/style.css?v=6" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar">
|
||||||
|
<div class="container container-narrow d-flex align-items-center gap-3">
|
||||||
|
<a class="navbar-brand fw-semibold" href="admin.php">Administration</a>
|
||||||
|
<div class="ms-auto navbar-text">Connecté: <?= htmlspecialchars($_SESSION['user_login']) ?></div>
|
||||||
|
<a class="btn btn-outline-accent btn-sm" href="logout.php">Se déconnecter</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="container container-narrow py-4" style="max-width:820px">
|
||||||
|
<div class="card p-4">
|
||||||
|
<h1 class="h4 mb-3">Ajouter un article</h1>
|
||||||
|
<?php if($error): ?><div class="alert alert-danger"><?= htmlspecialchars($error) ?></div><?php endif; ?>
|
||||||
|
<form method="post" class="vstack gap-3">
|
||||||
|
<div>
|
||||||
|
<label class="form-label">Titre</label>
|
||||||
|
<input class="form-control" type="text" name="titre" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="form-label">Contenu</label>
|
||||||
|
<textarea class="form-control" rows="10" name="contenu" required></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button class="btn btn-accent">Enregistrer</button>
|
||||||
|
<a class="btn btn-outline-accent" href="admin.php">Annuler</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
54
public/admin.php
Normal file
54
public/admin.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
if(!isset($_SESSION['user_id'])) { header('Location: login.php'); exit; }
|
||||||
|
require __DIR__ . '/../inc/db.php';
|
||||||
|
$stmt = $pdo->query("SELECT * FROM articles ORDER BY date_creation DESC");
|
||||||
|
$articles = $stmt->fetchAll();
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>Administration</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="assets/style.css?v=6" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<nav class="navbar">
|
||||||
|
<div class="container container-narrow d-flex align-items-center gap-3">
|
||||||
|
<a class="navbar-brand fw-semibold" href="/">CMS Simplifié</a>
|
||||||
|
<div class="ms-auto navbar-text">Connecté: <?= htmlspecialchars($_SESSION['user_login']) ?></div>
|
||||||
|
<a class="btn btn-outline-accent btn-sm" href="logout.php">Se déconnecter</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="container container-narrow py-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h1 class="h4 m-0">Articles</h1>
|
||||||
|
<a class="btn btn-accent btn-sm" href="add.php">Ajouter</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if(!$articles): ?>
|
||||||
|
<div class="alert alert-secondary">Aucun article.</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="list-group">
|
||||||
|
<?php foreach($articles as $a): ?>
|
||||||
|
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<div class="fw-semibold item-title"><?= htmlspecialchars($a['titre']) ?></div>
|
||||||
|
<small class="text-muted"><?= htmlspecialchars($a['date_creation']) ?></small>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<a class="btn btn-outline-accent btn-sm" href="edit.php?id=<?= (int)$a['id'] ?>">Modifier</a>
|
||||||
|
<a class="btn btn-danger btn-sm" href="delete.php?id=<?= (int)$a['id'] ?>">Supprimer</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
40
public/article.php
Normal file
40
public/article.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
require __DIR__ . '/../inc/db.php';
|
||||||
|
$id=filter_input(INPUT_GET,'id',FILTER_VALIDATE_INT);
|
||||||
|
if(!$id){http_response_code(404);exit('404 - Article non trouvé');}
|
||||||
|
$stmt=$pdo->prepare("SELECT id,titre,contenu,date_creation FROM articles WHERE id=?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$article=$stmt->fetch();
|
||||||
|
if(!$article){http_response_code(404);exit('404 - Article non trouvé');}
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title><?= htmlspecialchars($article['titre']) ?></title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="assets/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar">
|
||||||
|
<div class="container container-narrow">
|
||||||
|
<a class="navbar-brand fw-semibold" href="/">CMS Simplifié</a>
|
||||||
|
<a class="btn btn-sm btn-light" href="/">Accueil</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="container container-narrow py-4">
|
||||||
|
<div class="card p-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<h1 class="h3 mb-2"><?= htmlspecialchars($article['titre']) ?></h1>
|
||||||
|
<span class="badge badge-date"><?= htmlspecialchars($article['date_creation']) ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3"><?= nl2br(htmlspecialchars($article['contenu'])) ?></div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3">
|
||||||
|
<a class="btn btn-outline-secondary btn-sm" href="/">← Retour</a>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
119
public/assets/style.css
Normal file
119
public/assets/style.css
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
:root{
|
||||||
|
--bg-dark:#121212;
|
||||||
|
--bg-card:#1e1e1e;
|
||||||
|
--text-light:#f5f5f5;
|
||||||
|
--text-muted:#b3b3b3;
|
||||||
|
--accent:#d4af37;
|
||||||
|
}
|
||||||
|
|
||||||
|
body{
|
||||||
|
background:var(--bg-dark);
|
||||||
|
color:var(--text-light);
|
||||||
|
font-family:'Inter',sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-narrow{max-width:860px}
|
||||||
|
|
||||||
|
.navbar{
|
||||||
|
background:linear-gradient(90deg,#1a1a1a,#333);
|
||||||
|
box-shadow:0 2px 15px rgba(0,0,0,.6);
|
||||||
|
}
|
||||||
|
.navbar .navbar-brand{
|
||||||
|
color:var(--accent)!important;
|
||||||
|
font-weight:600;
|
||||||
|
letter-spacing:.5px;
|
||||||
|
}
|
||||||
|
.navbar .navbar-text{
|
||||||
|
color:var(--text-light)!important;
|
||||||
|
}
|
||||||
|
.navbar a.btn-outline-accent{
|
||||||
|
border-color:var(--accent)!important;
|
||||||
|
color:var(--accent)!important;
|
||||||
|
}
|
||||||
|
.navbar a.btn-outline-accent:hover{
|
||||||
|
background:var(--accent);
|
||||||
|
color:#000!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,h2,h3,h4,h5{color:var(--text-light);margin:0 0 .6rem}
|
||||||
|
.item-title{color:var(--text-light)}
|
||||||
|
.text-muted{color:var(--text-muted)!important}
|
||||||
|
|
||||||
|
.card{
|
||||||
|
background:var(--bg-card);
|
||||||
|
border:1px solid #2d2d2d;
|
||||||
|
box-shadow:0 8px 25px rgba(0,0,0,.5);
|
||||||
|
color:var(--text-light);
|
||||||
|
border-radius:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-accent{
|
||||||
|
background:var(--accent);
|
||||||
|
color:#000;
|
||||||
|
border:0;
|
||||||
|
font-weight:600;
|
||||||
|
transition:.25s;
|
||||||
|
}
|
||||||
|
.btn-accent:hover{
|
||||||
|
background:#b8952d;
|
||||||
|
color:#000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-accent{
|
||||||
|
background:transparent;
|
||||||
|
border:1px solid var(--accent);
|
||||||
|
color:var(--accent);
|
||||||
|
font-weight:600;
|
||||||
|
transition:.25s;
|
||||||
|
}
|
||||||
|
.btn-outline-accent:hover{
|
||||||
|
background:var(--accent);
|
||||||
|
color:#000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger{
|
||||||
|
background:#b51c1c;
|
||||||
|
border:none;
|
||||||
|
color:#fff;
|
||||||
|
font-weight:600;
|
||||||
|
transition:.25s;
|
||||||
|
}
|
||||||
|
.btn-danger:hover{
|
||||||
|
background:#d62828;
|
||||||
|
color:#fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control,textarea{
|
||||||
|
background:#2a2a2a;
|
||||||
|
border:1px solid #444;
|
||||||
|
color:var(--text-light);
|
||||||
|
}
|
||||||
|
.form-control:focus,textarea:focus{
|
||||||
|
border-color:var(--accent);
|
||||||
|
box-shadow:0 0 0 .15rem rgba(212,175,55,.25);
|
||||||
|
background:#2f2f2f;
|
||||||
|
color:#fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert{
|
||||||
|
background:#2d2d2d;
|
||||||
|
color:#ccc;
|
||||||
|
border:none;
|
||||||
|
}
|
||||||
|
.list-group-item{
|
||||||
|
background:#1c1c1c;
|
||||||
|
border:1px solid #333;
|
||||||
|
}
|
||||||
|
.list-group-item .text-muted{
|
||||||
|
color:var(--text-muted)!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a{
|
||||||
|
color:var(--accent);
|
||||||
|
text-decoration:none;
|
||||||
|
font-weight:500;
|
||||||
|
}
|
||||||
|
a:hover{
|
||||||
|
text-decoration:underline;
|
||||||
|
color:#f1c94e;
|
||||||
|
}
|
||||||
46
public/delete.php
Normal file
46
public/delete.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
if(!isset($_SESSION['user_id'])){ header('Location: login.php'); exit; }
|
||||||
|
require __DIR__ . '/../inc/db.php';
|
||||||
|
$id = filter_input(INPUT_GET,'id',FILTER_VALIDATE_INT);
|
||||||
|
if(!$id){ http_response_code(404); exit('404'); }
|
||||||
|
$stmt = $pdo->prepare("SELECT id,titre FROM articles WHERE id=?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$article = $stmt->fetch();
|
||||||
|
if(!$article){ http_response_code(404); exit('404'); }
|
||||||
|
if($_SERVER['REQUEST_METHOD']==='POST'){
|
||||||
|
$del = $pdo->prepare("DELETE FROM articles WHERE id=?");
|
||||||
|
$del->execute([$id]);
|
||||||
|
header('Location: admin.php'); exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>Supprimer un article</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="assets/style.css?v=6" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar">
|
||||||
|
<div class="container container-narrow d-flex align-items-center gap-3">
|
||||||
|
<a class="navbar-brand fw-semibold" href="admin.php">Administration</a>
|
||||||
|
<div class="ms-auto navbar-text">Connecté: <?= htmlspecialchars($_SESSION['user_login']) ?></div>
|
||||||
|
<a class="btn btn-outline-accent btn-sm" href="logout.php">Se déconnecter</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="container container-narrow py-4" style="max-width:700px">
|
||||||
|
<div class="card p-4">
|
||||||
|
<h1 class="h4 mb-3">Confirmation</h1>
|
||||||
|
<div class="alert alert-warning mb-4">Supprimer « <?= htmlspecialchars($article['titre']) ?> » ?</div>
|
||||||
|
<form method="post" class="d-flex gap-2">
|
||||||
|
<button class="btn btn-accent">Confirmer</button>
|
||||||
|
<a class="btn btn-outline-accent" href="admin.php">Annuler</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
61
public/edit.php
Normal file
61
public/edit.php
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
if(!isset($_SESSION['user_id'])){ header('Location: login.php'); exit; }
|
||||||
|
require __DIR__ . '/../inc/db.php';
|
||||||
|
$id = filter_input(INPUT_GET,'id',FILTER_VALIDATE_INT);
|
||||||
|
if(!$id){ http_response_code(404); exit('404'); }
|
||||||
|
$stmt = $pdo->prepare("SELECT id,titre,contenu FROM articles WHERE id=?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$article = $stmt->fetch();
|
||||||
|
if(!$article){ http_response_code(404); exit('404'); }
|
||||||
|
$error = null;
|
||||||
|
if($_SERVER['REQUEST_METHOD']==='POST'){
|
||||||
|
$titre = trim($_POST['titre'] ?? '');
|
||||||
|
$contenu = trim($_POST['contenu'] ?? '');
|
||||||
|
if($titre!=='' && $contenu!==''){
|
||||||
|
$upd = $pdo->prepare("UPDATE articles SET titre=?,contenu=? WHERE id=?");
|
||||||
|
$upd->execute([$titre,$contenu,$id]);
|
||||||
|
header('Location: admin.php'); exit;
|
||||||
|
} else { $error = 'Champs obligatoires.'; }
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>Modifier un article</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="assets/style.css?v=6" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar">
|
||||||
|
<div class="container container-narrow d-flex align-items-center gap-3">
|
||||||
|
<a class="navbar-brand fw-semibold" href="admin.php">Administration</a>
|
||||||
|
<div class="ms-auto navbar-text">Connecté: <?= htmlspecialchars($_SESSION['user_login']) ?></div>
|
||||||
|
<a class="btn btn-outline-accent btn-sm" href="logout.php">Se déconnecter</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="container container-narrow py-4" style="max-width:820px">
|
||||||
|
<div class="card p-4">
|
||||||
|
<h1 class="h4 mb-3">Modifier l’article</h1>
|
||||||
|
<?php if($error): ?><div class="alert alert-danger"><?= htmlspecialchars($error) ?></div><?php endif; ?>
|
||||||
|
<form method="post" class="vstack gap-3">
|
||||||
|
<div>
|
||||||
|
<label class="form-label">Titre</label>
|
||||||
|
<input class="form-control" type="text" name="titre" required value="<?= htmlspecialchars($article['titre']) ?>">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="form-label">Contenu</label>
|
||||||
|
<textarea class="form-control" rows="10" name="contenu" required><?= htmlspecialchars($article['contenu']) ?></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button class="btn btn-accent">Mettre à jour</button>
|
||||||
|
<a class="btn btn-outline-accent" href="admin.php">Annuler</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
48
public/index.php
Normal file
48
public/index.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
require __DIR__ . '/../inc/db.php';
|
||||||
|
$stmt=$pdo->prepare("SELECT id,titre,SUBSTRING(contenu,1,180) AS intro,date_creation FROM articles ORDER BY date_creation DESC LIMIT 10");
|
||||||
|
$stmt->execute();
|
||||||
|
$articles=$stmt->fetchAll();
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>Accueil</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="assets/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-expand-lg">
|
||||||
|
<div class="container container-narrow">
|
||||||
|
<a class="navbar-brand fw-semibold" href="/">CMS Simplifié</a>
|
||||||
|
<div class="ms-auto">
|
||||||
|
<a class="btn btn-sm btn-light" href="login.php">Admin</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="container container-narrow py-4">
|
||||||
|
<h1 class="mb-4">Derniers articles</h1>
|
||||||
|
<?php if(!$articles): ?>
|
||||||
|
<div class="alert alert-secondary">Aucun article pour le moment.</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="row g-4">
|
||||||
|
<?php foreach($articles as $a): ?>
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card p-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<h5 class="mb-2"><?= htmlspecialchars($a['titre']) ?></h5>
|
||||||
|
<span class="badge badge-date"><?= htmlspecialchars($a['date_creation']) ?></span>
|
||||||
|
</div>
|
||||||
|
<p class="mb-3"><?= nl2br(htmlspecialchars($a['intro'])) ?>...</p>
|
||||||
|
<a class="btn btn-accent btn-sm" href="article.php?id=<?= (int)$a['id'] ?>">Lire</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
52
public/login.php
Normal file
52
public/login.php
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require __DIR__ . '/../inc/db.php';
|
||||||
|
$error=null;
|
||||||
|
if($_SERVER['REQUEST_METHOD']==='POST'){
|
||||||
|
$login=isset($_POST['login'])?trim($_POST['login']):'';
|
||||||
|
$password=$_POST['password']??'';
|
||||||
|
$stmt=$pdo->prepare("SELECT * FROM utilisateur WHERE login=?");
|
||||||
|
$stmt->execute([$login]);
|
||||||
|
$user=$stmt->fetch();
|
||||||
|
if($user && password_verify($password,$user['password'])){
|
||||||
|
$_SESSION['user_id']=$user['id'];
|
||||||
|
$_SESSION['user_login']=$user['login'];
|
||||||
|
header('Location: admin.php'); exit;
|
||||||
|
} else { $error='Identifiants incorrects.'; }
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>Connexion</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="assets/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar">
|
||||||
|
<div class="container container-narrow">
|
||||||
|
<a class="navbar-brand fw-semibold" href="/">CMS Simplifié</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="container container-narrow py-5" style="max-width:520px">
|
||||||
|
<div class="card p-4">
|
||||||
|
<h1 class="h4 mb-3">Connexion</h1>
|
||||||
|
<?php if($error): ?><div class="alert alert-danger"><?= htmlspecialchars($error) ?></div><?php endif; ?>
|
||||||
|
<form method="post" class="vstack gap-3">
|
||||||
|
<div>
|
||||||
|
<label class="form-label">Login</label>
|
||||||
|
<input class="form-control" type="text" name="login" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="form-label">Mot de passe</label>
|
||||||
|
<input class="form-control" type="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-accent">Se connecter</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
public/logout.php
Normal file
5
public/logout.php
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
session_destroy();
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
Reference in New Issue
Block a user