mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
Fix admin CSS not loading and quirks mode issues
Fixed multiple issues in admin panel: 1. CSS path: modern-normalize.css → modern-normalize.min.css (File is actually named .min.css) 2. Icon path: assets/icon.svg → /assets/admin_favicon.svg (Was relative, now absolute; correct filename) 3. Navigation: /admin/list.php → /admin/ (list.php was renamed to index.php) 4. Short PHP tags: <? → <?php (Better compatibility, some servers don't enable short_open_tag) 5. Quirks mode warning was due to CSS not loading, not DOCTYPE (DOCTYPE was already present) Files modified: - public/admin/inc/head.php (main fixes) - public/admin/index.php (short tags) - public/admin/add.php (short tags) - public/admin/import.php (short tags) Need to redeploy for production: just deploy
This commit is contained in:
36
public/admin/.htaccess
Normal file
36
public/admin/.htaccess
Normal file
@@ -0,0 +1,36 @@
|
||||
# Security headers
|
||||
<IfModule mod_headers.c>
|
||||
# Prevent clickjacking
|
||||
Header always set X-Frame-Options "SAMEORIGIN"
|
||||
|
||||
# Prevent MIME type sniffing
|
||||
Header always set X-Content-Type-Options "nosniff"
|
||||
|
||||
# Enable XSS protection
|
||||
Header always set X-XSS-Protection "1; mode=block"
|
||||
|
||||
# Referrer policy
|
||||
Header always set Referrer-Policy "strict-origin-when-cross-origin"
|
||||
|
||||
# Content Security Policy (adjust as needed)
|
||||
Header always set Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;"
|
||||
</IfModule>
|
||||
|
||||
# Prevent directory listing
|
||||
Options -Indexes
|
||||
|
||||
# Protect sensitive files
|
||||
<FilesMatch "^\.">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
|
||||
<FilesMatch "(composer\.(json|lock)|error\.log)$">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
|
||||
# PHP security settings (if .htaccess can override)
|
||||
<IfModule mod_php.c>
|
||||
php_flag display_errors Off
|
||||
php_flag log_errors On
|
||||
php_value error_log error.log
|
||||
</IfModule>
|
||||
277
public/admin/README.md
Normal file
277
public/admin/README.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# PostERG - Formulaire d'ajout de mémoires
|
||||
|
||||
Le formulaire permet aux étudiant.e.s sortant de l'ERG en cursus de Master de soumettre leurs mémoires et travaux de fin d'études.
|
||||
|
||||
## Fonctionnalités
|
||||
|
||||
- Soumission de mémoires avec métadonnées complètes
|
||||
- Stockage structuré dans base de données SQLite
|
||||
- Support multi-auteurs, multi-superviseurs, multi-langues
|
||||
- Gestion des mots-clés (max 10 par TFE)
|
||||
- Téléversement sécurisé des fichiers
|
||||
- Protection CSRF et validation complète
|
||||
- Workflow de publication (soumission → soutenance → publication)
|
||||
|
||||
## Technologies
|
||||
|
||||
- PHP 7.4+ avec PDO SQLite
|
||||
- SQLite 3.8+
|
||||
- CSS fait-main + [Simple.css](https://simplecss.org/)
|
||||
- [Symfony YAML](https://symfony.com/doc/current/components/yaml.html) (pour migration legacy)
|
||||
- [Just](https://github.com/casey/just) pour les tâches de développement
|
||||
|
||||
## Installation
|
||||
|
||||
### Prérequis
|
||||
|
||||
```bash
|
||||
# PHP avec SQLite
|
||||
php -v # 7.4 ou supérieur
|
||||
php -m | grep sqlite # Vérifier extension SQLite
|
||||
|
||||
# Composer
|
||||
composer install
|
||||
|
||||
# Just (optionnel mais recommandé)
|
||||
# macOS: brew install just
|
||||
# Linux: cargo install just
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
1. **Base de données production:**
|
||||
```bash
|
||||
cd ../db
|
||||
sqlite3 posterg.db < schema.sql
|
||||
```
|
||||
|
||||
2. **Base de données de test:**
|
||||
```bash
|
||||
just init-test-db
|
||||
```
|
||||
|
||||
## Développement local
|
||||
|
||||
### Avec Just (recommandé)
|
||||
|
||||
```bash
|
||||
# Configuration complète et lancement du serveur
|
||||
just dev
|
||||
|
||||
# Ou étape par étape:
|
||||
just init-test-db # Créer la base de test
|
||||
just serve # Lancer le serveur (réinitialise la DB)
|
||||
just serve-only # Lancer sans réinitialiser
|
||||
|
||||
# Nettoyage
|
||||
just cleanup # Supprimer test.db et fichiers uploadés
|
||||
just reset # Cleanup + réinitialisation
|
||||
|
||||
# Statistiques
|
||||
just stats # Voir les stats de la DB
|
||||
just recent # Voir les soumissions récentes
|
||||
just show 1 # Voir le TFE #1
|
||||
|
||||
# Autres commandes
|
||||
just query # Shell SQLite interactif
|
||||
just dump # Backup de la DB
|
||||
```
|
||||
|
||||
### Sans Just
|
||||
|
||||
```bash
|
||||
# Créer la base de test
|
||||
sqlite3 test.db < ../db/schema.sql
|
||||
|
||||
# Lancer le serveur
|
||||
php -S 127.0.0.1:3000
|
||||
|
||||
# Ouvrir dans le navigateur
|
||||
open http://127.0.0.1:3000
|
||||
```
|
||||
|
||||
## Structure du projet
|
||||
|
||||
```
|
||||
formulaire/
|
||||
├── assets/ # CSS et ressources
|
||||
│ ├── normalize.css
|
||||
│ ├── simple.css
|
||||
│ ├── posterg.css
|
||||
│ └── icon.svg
|
||||
├── data/ # Données (gitignored)
|
||||
│ ├── theses/ # Fichiers TFE uploadés
|
||||
│ ├── covers/ # Images de couverture
|
||||
│ └── yaml/ # Legacy YAML (migration)
|
||||
├── Database.php # Classe helper pour DB
|
||||
├── index.php # Formulaire de soumission
|
||||
├── formulaire.php # Traitement de soumission
|
||||
├── thanks.php # Page de confirmation
|
||||
├── justfile # Tâches de développement
|
||||
├── .gitignore # Fichiers ignorés
|
||||
├── MIGRATION.md # Guide de migration YAML → SQLite
|
||||
├── SECURITY.md # Documentation sécurité
|
||||
└── README.md # Ce fichier
|
||||
```
|
||||
|
||||
## Workflow de soumission
|
||||
|
||||
1. **Étudiant remplit le formulaire** (index.php)
|
||||
- Informations de base (nom, année, titre)
|
||||
- Détails académiques (orientation, AP, finalité)
|
||||
- Contenu (synopsis, mots-clés, langues, formats)
|
||||
- Upload fichiers (TFE + annexes)
|
||||
|
||||
2. **Validation et traitement** (formulaire.php)
|
||||
- Validation CSRF token
|
||||
- Sanitization des entrées
|
||||
- Transaction DB (all-or-nothing)
|
||||
- Création/liaison entités (auteur, superviseurs, mots-clés)
|
||||
- Upload sécurisé avec noms aléatoires
|
||||
- Génération identifiant unique (YYYY-NNN)
|
||||
|
||||
3. **Confirmation** (thanks.php)
|
||||
- Affichage récapitulatif
|
||||
- Statut: "En attente de publication"
|
||||
- Liste des fichiers uploadés
|
||||
|
||||
4. **Publication** (admin - à venir)
|
||||
- Après soutenance
|
||||
- Ajout note contextuelle du jury (optionnel)
|
||||
- Points du jury
|
||||
- Publication publique
|
||||
|
||||
## Base de données
|
||||
|
||||
### Structure
|
||||
|
||||
- **19 tables** incluant tables de jonction et vues
|
||||
- **Normalized 3NF** avec clés étrangères
|
||||
- **Timestamps automatiques** via triggers
|
||||
- **Cascade deletes** pour intégrité référentielle
|
||||
|
||||
### Tables principales
|
||||
|
||||
- `theses` - TFE avec métadonnées
|
||||
- `authors` - Auteurs (réutilisables)
|
||||
- `supervisors` - Promoteurs
|
||||
- `thesis_files` - Métadonnées fichiers
|
||||
- `keywords` - Mots-clés (extensible)
|
||||
- Plus tables de référence et jonctions
|
||||
|
||||
### Vues
|
||||
|
||||
- `v_theses_full` - Vue complète pour admin
|
||||
- `v_theses_public` - Vue filtrée pour public
|
||||
|
||||
Voir `../db/README.md` pour documentation complète.
|
||||
|
||||
## Sécurité
|
||||
|
||||
✅ **Protection CSRF** - Tokens de session
|
||||
✅ **SQL Injection** - Prepared statements PDO
|
||||
✅ **Path Traversal** - Validation stricte des chemins
|
||||
✅ **File Upload** - Noms aléatoires, validation MIME
|
||||
✅ **Input Validation** - Sanitization + validation typage
|
||||
✅ **Error Handling** - Pas d'exposition de chemins système
|
||||
|
||||
Voir `SECURITY.md` pour détails complets.
|
||||
|
||||
## Tests
|
||||
|
||||
### Test manuel
|
||||
|
||||
1. Lancer serveur: `just dev`
|
||||
2. Ouvrir http://127.0.0.1:3000
|
||||
3. Remplir formulaire avec données test
|
||||
4. Vérifier confirmation
|
||||
5. Vérifier DB: `just stats` et `just recent`
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] Form se charge sans erreurs
|
||||
- [ ] Dropdowns peuplés depuis DB
|
||||
- [ ] Validation champs requis fonctionne
|
||||
- [ ] Upload fichiers réussit
|
||||
- [ ] Transaction rollback sur erreur
|
||||
- [ ] Page confirmation affiche données
|
||||
- [ ] Identifiant unique généré (YYYY-NNN)
|
||||
- [ ] Fichiers stockés avec noms aléatoires
|
||||
|
||||
## Migration données legacy
|
||||
|
||||
Si vous avez des fichiers YAML existants:
|
||||
|
||||
```bash
|
||||
# Script de migration à créer
|
||||
php migrate_yaml_to_sqlite.php
|
||||
```
|
||||
|
||||
Voir `MIGRATION.md` pour guide complet.
|
||||
|
||||
## Production
|
||||
|
||||
### Déploiement
|
||||
|
||||
1. **Copier fichiers:**
|
||||
```bash
|
||||
rsync -av --exclude='test.db' --exclude='data/' \
|
||||
formulaire/ user@server:/var/www/posterg/
|
||||
```
|
||||
|
||||
2. **Créer DB production:**
|
||||
```bash
|
||||
cd /var/www/posterg/db
|
||||
sqlite3 posterg.db < schema.sql
|
||||
```
|
||||
|
||||
3. **Permissions:**
|
||||
```bash
|
||||
chown -R www-data:www-data /var/www/posterg
|
||||
chmod 644 db/posterg.db
|
||||
chmod 755 data/theses data/covers
|
||||
```
|
||||
|
||||
4. **Configuration nginx:**
|
||||
```nginx
|
||||
location /formulaire {
|
||||
auth_basic "Restricted";
|
||||
auth_basic_user_file /etc/nginx/.htpasswd;
|
||||
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Backup
|
||||
|
||||
```bash
|
||||
# Backup automatique quotidien
|
||||
0 2 * * * sqlite3 /var/www/posterg/db/posterg.db \
|
||||
.dump > /backups/posterg_$(date +\%Y\%m\%d).sql
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
- **Schema DB:** `../db/README.md`
|
||||
- **Setup DB:** `../db/SETUP.md`
|
||||
- **Sécurité:** `SECURITY.md`
|
||||
- **Migration:** `MIGRATION.md`
|
||||
- **Specs techniques:** `../db/posterg_fiche-technique.md`
|
||||
|
||||
## Changelog
|
||||
|
||||
### v2.0 - 2026-01-27
|
||||
- Migration vers SQLite
|
||||
- Support multi-entités (auteurs, superviseurs, etc.)
|
||||
- Sécurité renforcée
|
||||
- Workflow de publication
|
||||
- Justfile pour développement
|
||||
|
||||
### v1.0 - Précédent
|
||||
- Stockage YAML
|
||||
- Formulaire basique
|
||||
249
public/admin/add.php
Normal file
249
public/admin/add.php
Normal file
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
|
||||
// Start session and generate CSRF token
|
||||
session_start();
|
||||
if (empty($_SESSION["csrf_token"])) {
|
||||
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
$pageTitle = "Ajout de TFE";
|
||||
|
||||
// Load database helper
|
||||
require_once __DIR__ . '/../../lib/Database.php';
|
||||
|
||||
try {
|
||||
$db = new Database();
|
||||
$orientations = $db->getAllOrientations();
|
||||
$apPrograms = $db->getAllAPPrograms();
|
||||
$finalityTypes = $db->getAllFinalityTypes();
|
||||
$languages = $db->getAllLanguages();
|
||||
$formatTypes = $db->getAllFormatTypes();
|
||||
} catch (Exception $e) {
|
||||
error_log("Failed to load form data: " . $e->getMessage());
|
||||
die("Erreur lors du chargement du formulaire. Veuillez réessayer plus tard.");
|
||||
}
|
||||
|
||||
// Get error message and preserved form data from session (if redirected back from error)
|
||||
$error = isset($_SESSION["form_error"]) ? $_SESSION["form_error"] : null;
|
||||
$formData = isset($_SESSION["form_data"]) ? $_SESSION["form_data"] : [];
|
||||
|
||||
// Clear session data after retrieving
|
||||
unset($_SESSION["form_error"]);
|
||||
unset($_SESSION["form_data"]);
|
||||
|
||||
// Helper function to get old form value
|
||||
function old($key, $default = "")
|
||||
{
|
||||
global $formData;
|
||||
return isset($formData[$key])
|
||||
? htmlspecialchars($formData[$key])
|
||||
: $default;
|
||||
}
|
||||
|
||||
// Helper function to check if value was previously selected
|
||||
function wasSelected($key, $value)
|
||||
{
|
||||
global $formData;
|
||||
if (!isset($formData[$key])) {
|
||||
return false;
|
||||
}
|
||||
if (is_array($formData[$key])) {
|
||||
return in_array($value, $formData[$key]);
|
||||
}
|
||||
return $formData[$key] == $value;
|
||||
}
|
||||
?>
|
||||
<?php include "inc/head.php"?>
|
||||
<main>
|
||||
<?php if ($error): ?>
|
||||
<div class="error-message" style="background: #fee; border: 2px solid #c00; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #c00;">
|
||||
<strong>⚠️ Erreur:</strong> <?php echo htmlspecialchars($error); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="formulaire.php" method="post" enctype="multipart/form-data">
|
||||
<!-- CSRF Protection -->
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars(
|
||||
$_SESSION["csrf_token"],
|
||||
); ?>">
|
||||
|
||||
|
||||
<fieldset>
|
||||
<legend>Informations de base</legend>
|
||||
<label for="auteurice">Nom/Prénom/Pseudo *</label>
|
||||
<input type="text" id="auteurice" name="auteurice" placeholder="Nom de l'auteur·ice" value="<?php echo old(
|
||||
"auteurice",
|
||||
); ?>" required>
|
||||
|
||||
<label for="mail">Contact (email, site web, insta, ...)</label>
|
||||
<input type="text" id="mail" name="mail" placeholder="votre.email@example.com ou @instagram" value="<?php echo old(
|
||||
"mail",
|
||||
); ?>">
|
||||
<label for="année">Année diplômante *</label>
|
||||
<input type="number" id="année" name="année" min="2000" max="<?php echo date(
|
||||
"Y",
|
||||
) + 1; ?>" placeholder="<?php echo date(
|
||||
"Y",
|
||||
); ?>" value="<?php echo old("année"); ?>" required>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<fieldset>
|
||||
<legend>Informations académiques</legend>
|
||||
<label for="orientation">Orientation principale *</label>
|
||||
<select id="orientation" name="orientation" required>
|
||||
<option value="">-- Sélectionner une orientation --</option>
|
||||
<?php foreach ($orientations as $orientation): ?>
|
||||
<option value="<?php echo htmlspecialchars(
|
||||
$orientation["id"],
|
||||
); ?>" <?php echo wasSelected(
|
||||
"orientation",
|
||||
$orientation["id"],
|
||||
)
|
||||
? "selected"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars(
|
||||
$orientation["name"],
|
||||
); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<label for="ap">Atelier Pratique (AP) *</label>
|
||||
<select id="ap" name="ap" required>
|
||||
<option value="">-- Sélectionner un AP --</option>
|
||||
<?php foreach ($apPrograms as $ap): ?>
|
||||
<option value="<?php echo htmlspecialchars(
|
||||
$ap["id"],
|
||||
); ?>" <?php echo wasSelected("ap", $ap["id"])
|
||||
? "selected"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars($ap["name"]); ?>
|
||||
<?php if (
|
||||
$ap["code"]
|
||||
): ?> (<?php echo htmlspecialchars(
|
||||
$ap["code"],
|
||||
); ?>)<?php endif; ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<label for="finality">Finalité du master *</label>
|
||||
<select id="finality" name="finality" required>
|
||||
<option value="">-- Sélectionner une finalité --</option>
|
||||
<?php foreach ($finalityTypes as $finality): ?>
|
||||
<option value="<?php echo htmlspecialchars(
|
||||
$finality["id"],
|
||||
); ?>" <?php echo wasSelected(
|
||||
"finality",
|
||||
$finality["id"],
|
||||
)
|
||||
? "selected"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars($finality["name"]); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<label for="promoteurice">Promoteur·ice(s)</label>
|
||||
<input type="text" id="promoteurice" name="promoteurice" placeholder="Nom du/de la promoteur·ice (si plusieurs, séparer par des virgules)" value="<?php echo old(
|
||||
"promoteurice",
|
||||
); ?>">
|
||||
</fieldset>
|
||||
|
||||
|
||||
|
||||
<fieldset>
|
||||
<legend>À propos du TFE</legend>
|
||||
<label for="titre">Titre du mémoire *</label>
|
||||
<input type="text" id="titre" name="titre" placeholder="Titre de votre TFE" value="<?php echo old(
|
||||
"titre",
|
||||
); ?>" required>
|
||||
|
||||
<label for="subtitle">Sous-titre (si applicable)</label>
|
||||
<input type="text" id="subtitle" name="subtitle" placeholder="Sous-titre de votre TFE" value="<?php echo old(
|
||||
"subtitle",
|
||||
); ?>">
|
||||
<label for="synopsis">Synopsis (environ 200 mots) *</label>
|
||||
<textarea id="synopsis" name="synopsis" rows="8" placeholder="Décrivez votre TFE en quelques paragraphes..." required><?php echo old(
|
||||
"synopsis",
|
||||
); ?></textarea>
|
||||
<label for="problématique">Problématique</label>
|
||||
<textarea id="problématique" name="problématique" rows="4" placeholder="La problématique principale de votre mémoire..."><?php echo old(
|
||||
"problématique",
|
||||
); ?></textarea>
|
||||
<label>Langue(s) du TFE * (sélection multiple possible)</label>
|
||||
<ul style="list-style: none;">
|
||||
<?php foreach ($languages as $language): ?>
|
||||
<li>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="languages[]" value="<?php echo htmlspecialchars(
|
||||
$language["id"],
|
||||
); ?>" <?php echo wasSelected(
|
||||
"languages",
|
||||
$language["id"],
|
||||
)
|
||||
? "checked"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars($language["name"]); ?>
|
||||
</label>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
|
||||
<label>Format(s) (sélection multiple possible)</label>
|
||||
<ul style="list-style: none;">
|
||||
<?php foreach ($formatTypes as $format): ?>
|
||||
<li>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="formats[]" value="<?php echo htmlspecialchars(
|
||||
$format["id"],
|
||||
); ?>" <?php echo wasSelected(
|
||||
"formats",
|
||||
$format["id"],
|
||||
)
|
||||
? "checked"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars($format["name"]); ?>
|
||||
</label>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
|
||||
<label for="tag">Mots-clés (max 10, séparés par des virgules)</label>
|
||||
<input type="text" id="tag" name="tag" placeholder="typographie, photographie, outils libre, post-colonial..." value="<?php echo old(
|
||||
"tag",
|
||||
); ?>">
|
||||
<small>Séparez les mots-clés par des virgules. Maximum 10 mots-clés.</small>
|
||||
<label for="duration_info">Durée/Taille (si applicable)</label>
|
||||
<input type="text" id="duration_info" name="duration_info" placeholder="Ex: 68 minutes, 128 pages, 78 pages + 15 minutes" value="<?php echo old(
|
||||
"duration_info",
|
||||
); ?>">
|
||||
<small>Indiquez la durée (en minutes) ou le nombre de pages de votre TFE.</small>
|
||||
<label for="lien">Lien vers un site web ou ressource en ligne</label>
|
||||
<input type="url" id="lien" name="lien" placeholder="https://monmemoire.erg.be/..." value="<?php echo old(
|
||||
"lien",
|
||||
); ?>">
|
||||
</fieldset>
|
||||
|
||||
|
||||
<fieldset>
|
||||
<legend>Fichiers</legend>
|
||||
<label for="couverture">Importer une image de couverture</label>
|
||||
<small>Formats acceptés : JPG, PNG. Taille max : 10MB.</small>
|
||||
<input type="file" id="couverture" name="couverture" accept="image/jpeg,image/png">
|
||||
|
||||
<label for="files">Importer le TFE et les fichiers annexes</label>
|
||||
<small>Formats acceptés : PDF, JPG, PNG, MP4, ZIP. Taille max par fichier : 50MB.</small>
|
||||
<small>Si vous voulez importer un dossier, créez une archive ZIP.</small>
|
||||
<input type="file" id="files" name="files[]" multiple accept=".pdf,.jpg,.jpeg,.png,.mp4,.zip">
|
||||
</fieldset>
|
||||
|
||||
<br>
|
||||
<input type="submit" name="go" value="Soumettre mon TFE">
|
||||
</form>
|
||||
</main>
|
||||
|
||||
<?php include "inc/footer.php"?>
|
||||
338
public/admin/edit.php
Normal file
338
public/admin/edit.php
Normal file
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
|
||||
// Edit thesis page
|
||||
session_start();
|
||||
|
||||
// Generate CSRF token
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../../lib/Database.php';
|
||||
|
||||
$thesisId = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
||||
$error = null;
|
||||
$success = null;
|
||||
|
||||
if ($thesisId <= 0) {
|
||||
die("ID invalide");
|
||||
}
|
||||
|
||||
try {
|
||||
$db = new Database();
|
||||
$pdo = $db->getPDO();
|
||||
|
||||
// Handle form submission
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['csrf_token'])) {
|
||||
// Verify CSRF token
|
||||
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||
throw new Exception("Erreur de sécurité : token invalide.");
|
||||
}
|
||||
|
||||
try {
|
||||
$db->beginTransaction();
|
||||
|
||||
// Update thesis basic info
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE theses SET
|
||||
title = ?,
|
||||
subtitle = ?,
|
||||
year = ?,
|
||||
orientation_id = ?,
|
||||
ap_program_id = ?,
|
||||
finality_id = ?,
|
||||
synopsis = ?,
|
||||
file_size_info = ?,
|
||||
baiu_link = ?,
|
||||
is_published = ?,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
trim($_POST['titre']),
|
||||
!empty($_POST['subtitle']) ? trim($_POST['subtitle']) : null,
|
||||
intval($_POST['année']),
|
||||
intval($_POST['orientation']),
|
||||
intval($_POST['ap']),
|
||||
intval($_POST['finality']),
|
||||
trim($_POST['synopsis']),
|
||||
!empty($_POST['duration_info']) ? trim($_POST['duration_info']) : null,
|
||||
!empty($_POST['lien']) ? trim($_POST['lien']) : null,
|
||||
isset($_POST['is_published']) ? 1 : 0,
|
||||
$thesisId
|
||||
]);
|
||||
|
||||
// Update authors
|
||||
$pdo->prepare("DELETE FROM thesis_authors WHERE thesis_id = ?")->execute([$thesisId]);
|
||||
$authorsRaw = trim($_POST['auteurice'] ?? '');
|
||||
if (!empty($authorsRaw)) {
|
||||
$authors = array_map('trim', explode(',', $authorsRaw));
|
||||
foreach ($authors as $index => $authorName) {
|
||||
if (!empty($authorName)) {
|
||||
$authorId = $db->findOrCreateAuthor($authorName, $index === 0 ? ($_POST['mail'] ?? null) : null);
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$thesisId, $authorId, $index + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update supervisors
|
||||
$pdo->prepare("DELETE FROM thesis_supervisors WHERE thesis_id = ?")->execute([$thesisId]);
|
||||
$supervisorsRaw = trim($_POST['promoteurice'] ?? '');
|
||||
if (!empty($supervisorsRaw)) {
|
||||
$supervisors = array_map('trim', explode(',', $supervisorsRaw));
|
||||
foreach ($supervisors as $index => $supervisorName) {
|
||||
if (!empty($supervisorName)) {
|
||||
$supervisorId = $db->findOrCreateSupervisor($supervisorName);
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_supervisors (thesis_id, supervisor_id, supervisor_order) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$thesisId, $supervisorId, $index + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update languages
|
||||
$pdo->prepare("DELETE FROM thesis_languages WHERE thesis_id = ?")->execute([$thesisId]);
|
||||
if (isset($_POST['languages']) && is_array($_POST['languages'])) {
|
||||
foreach ($_POST['languages'] as $languageId) {
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_languages (thesis_id, language_id) VALUES (?, ?)");
|
||||
$stmt->execute([$thesisId, intval($languageId)]);
|
||||
}
|
||||
}
|
||||
|
||||
// Update formats
|
||||
$pdo->prepare("DELETE FROM thesis_formats WHERE thesis_id = ?")->execute([$thesisId]);
|
||||
if (isset($_POST['formats']) && is_array($_POST['formats'])) {
|
||||
foreach ($_POST['formats'] as $formatId) {
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_formats (thesis_id, format_id) VALUES (?, ?)");
|
||||
$stmt->execute([$thesisId, intval($formatId)]);
|
||||
}
|
||||
}
|
||||
|
||||
// Update keywords
|
||||
$pdo->prepare("DELETE FROM thesis_keywords WHERE thesis_id = ?")->execute([$thesisId]);
|
||||
$keywordsRaw = trim($_POST['tag'] ?? '');
|
||||
if (!empty($keywordsRaw)) {
|
||||
$keywords = array_map('trim', explode(',', $keywordsRaw));
|
||||
$keywords = array_slice($keywords, 0, 10); // Max 10
|
||||
foreach ($keywords as $keyword) {
|
||||
if (!empty($keyword)) {
|
||||
$keywordId = $db->findOrCreateKeyword($keyword);
|
||||
if ($keywordId) {
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_keywords (thesis_id, keyword_id) VALUES (?, ?)");
|
||||
$stmt->execute([$thesisId, $keywordId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$db->commit();
|
||||
$success = "TFE mis à jour avec succès!";
|
||||
|
||||
// Regenerate CSRF token
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
|
||||
} catch (Exception $e) {
|
||||
$db->rollback();
|
||||
$error = $e->getMessage();
|
||||
error_log("Edit error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Load thesis data
|
||||
$thesis = $db->getThesis($thesisId);
|
||||
|
||||
if (!$thesis) {
|
||||
die("TFE non trouvé");
|
||||
}
|
||||
|
||||
// Load current relationships
|
||||
$stmt = $pdo->prepare("SELECT language_id FROM thesis_languages WHERE thesis_id = ?");
|
||||
$stmt->execute([$thesisId]);
|
||||
$currentLanguages = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
$stmt = $pdo->prepare("SELECT format_id FROM thesis_formats WHERE thesis_id = ?");
|
||||
$stmt->execute([$thesisId]);
|
||||
$currentFormats = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
// Load reference data
|
||||
$orientations = $db->getAllOrientations();
|
||||
$apPrograms = $db->getAllAPPrograms();
|
||||
$finalityTypes = $db->getAllFinalityTypes();
|
||||
$languages = $db->getAllLanguages();
|
||||
$formatTypes = $db->getAllFormatTypes();
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error loading edit page: " . $e->getMessage());
|
||||
die("Erreur lors du chargement: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Éditer TFE - <?php echo htmlspecialchars($thesis['title']); ?></title>
|
||||
<link rel="stylesheet" href="assets/normalize.css">
|
||||
<link rel="stylesheet" href="https://raw.githack.com/waldyrious/downstyler/master/downstyler.css" />
|
||||
<link rel="shortcut icon" href="assets/icon.svg" type="image/svg">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Éditer TFE</h1>
|
||||
<nav>
|
||||
<a href="list.php">← Liste</a> |
|
||||
<a href="thanks.php?id=<?php echo $thesisId; ?>">Voir</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<?php if ($error): ?>
|
||||
<div style="background: #fee; border: 2px solid #c00; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #c00;">
|
||||
<strong>⚠️ Erreur:</strong> <?php echo htmlspecialchars($error); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($success): ?>
|
||||
<div style="background: #efe; border: 2px solid #0a0; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #0a0;">
|
||||
<strong>✓ <?php echo htmlspecialchars($success); ?></strong>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="edit.php?id=<?php echo $thesisId; ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||
|
||||
<h2>Informations de base</h2>
|
||||
|
||||
<fieldset>
|
||||
<label for="auteurice">Nom/Prénom/Pseudo *</label>
|
||||
<input type="text" id="auteurice" name="auteurice" value="<?php echo htmlspecialchars($thesis['authors']); ?>" required>
|
||||
<small>Si plusieurs, séparer par des virgules</small>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="mail">Contact</label>
|
||||
<input type="text" id="mail" name="mail" value="">
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="année">Année *</label>
|
||||
<input type="number" id="année" name="année" value="<?php echo $thesis['year']; ?>" required>
|
||||
</fieldset>
|
||||
|
||||
<h2>Informations académiques</h2>
|
||||
|
||||
<fieldset>
|
||||
<label for="orientation">Orientation *</label>
|
||||
<select id="orientation" name="orientation" required>
|
||||
<?php foreach ($orientations as $orientation): ?>
|
||||
<option value="<?php echo $orientation['id']; ?>" <?php echo ($thesis['orientation'] == $orientation['name']) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($orientation['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="ap">Atelier Pratique *</label>
|
||||
<select id="ap" name="ap" required>
|
||||
<?php foreach ($apPrograms as $ap): ?>
|
||||
<option value="<?php echo $ap['id']; ?>" <?php echo ($thesis['ap_program'] == $ap['name']) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($ap['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="finality">Finalité *</label>
|
||||
<select id="finality" name="finality" required>
|
||||
<?php foreach ($finalityTypes as $finality): ?>
|
||||
<option value="<?php echo $finality['id']; ?>" <?php echo ($thesis['finality_type'] == $finality['name']) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($finality['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="promoteurice">Promoteur·ice(s)</label>
|
||||
<input type="text" id="promoteurice" name="promoteurice" value="<?php echo htmlspecialchars($thesis['supervisors'] ?? ''); ?>">
|
||||
<small>Si plusieurs, séparer par des virgules</small>
|
||||
</fieldset>
|
||||
|
||||
<h2>À propos du TFE</h2>
|
||||
|
||||
<fieldset>
|
||||
<label for="titre">Titre *</label>
|
||||
<input type="text" id="titre" name="titre" value="<?php echo htmlspecialchars($thesis['title']); ?>" required>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="subtitle">Sous-titre</label>
|
||||
<input type="text" id="subtitle" name="subtitle" value="<?php echo htmlspecialchars($thesis['subtitle'] ?? ''); ?>">
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="synopsis">Synopsis *</label>
|
||||
<textarea id="synopsis" name="synopsis" rows="8" required><?php echo htmlspecialchars($thesis['synopsis'] ?? ''); ?></textarea>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label>Langue(s) *</label>
|
||||
<?php foreach ($languages as $language): ?>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="languages[]" value="<?php echo $language['id']; ?>" <?php echo in_array($language['id'], $currentLanguages) ? 'checked' : ''; ?>>
|
||||
<?php echo htmlspecialchars($language['name']); ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label>Format(s)</label>
|
||||
<?php foreach ($formatTypes as $format): ?>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="formats[]" value="<?php echo $format['id']; ?>" <?php echo in_array($format['id'], $currentFormats) ? 'checked' : ''; ?>>
|
||||
<?php echo htmlspecialchars($format['name']); ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="tag">Mots-clés (max 10)</label>
|
||||
<input type="text" id="tag" name="tag" value="<?php echo htmlspecialchars($thesis['keywords'] ?? ''); ?>">
|
||||
<small>Séparer par des virgules</small>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="duration_info">Durée/Taille</label>
|
||||
<input type="text" id="duration_info" name="duration_info" value="<?php echo htmlspecialchars($thesis['file_size_info'] ?? ''); ?>">
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="lien">Lien externe</label>
|
||||
<input type="url" id="lien" name="lien" value="<?php echo htmlspecialchars($thesis['baiu_link'] ?? ''); ?>">
|
||||
</fieldset>
|
||||
|
||||
<h2>Publication</h2>
|
||||
|
||||
<fieldset>
|
||||
<label style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<input type="checkbox" name="is_published" value="1" <?php echo $thesis['is_published'] ? 'checked' : ''; ?>>
|
||||
<span>Publier ce TFE sur le site public</span>
|
||||
</label>
|
||||
<small>Si coché, ce TFE sera visible sur le site public. Sinon, il restera en attente.</small>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit">Enregistrer les modifications</button>
|
||||
<a href="thanks.php?id=<?php echo $thesisId; ?>">Annuler</a>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>Édition TFE #<?php echo $thesisId; ?></p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
331
public/admin/formulaire.php
Normal file
331
public/admin/formulaire.php
Normal file
@@ -0,0 +1,331 @@
|
||||
<?php // formulaire.php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
|
||||
|
||||
// Configure error reporting
|
||||
ini_set('display_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', 'error.log');
|
||||
|
||||
// Start session for CSRF protection
|
||||
session_start();
|
||||
|
||||
// Verify CSRF token
|
||||
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) ||
|
||||
!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||
error_log("CSRF token validation failed");
|
||||
die("Erreur de sécurité : token invalide. Veuillez recharger le formulaire.");
|
||||
}
|
||||
|
||||
// Log the content of the $_FILES array
|
||||
error_log("FILES array: " . print_r($_FILES, true));
|
||||
|
||||
require_once __DIR__ . '/../../lib/Database.php';
|
||||
|
||||
// Helper function to sanitize string input
|
||||
function sanitize_string($input) {
|
||||
return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
// Helper function to validate required field
|
||||
function validate_required($value, $fieldName) {
|
||||
if (empty($value)) {
|
||||
throw new Exception("Le champ '$fieldName' est requis.");
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
try {
|
||||
// Initialize database connection
|
||||
$db = new Database();
|
||||
$pdo = $db->getPDO();
|
||||
|
||||
// Begin transaction - all or nothing
|
||||
$db->beginTransaction();
|
||||
|
||||
// ===== VALIDATE AND SANITIZE INPUT DATA =====
|
||||
|
||||
// Author information
|
||||
$auteurName = validate_required(sanitize_string($_POST["auteurice"] ?? ''), "Nom/Prénom/Pseudo");
|
||||
|
||||
$mail = $_POST["mail"] ?? '';
|
||||
if (!empty($mail)) {
|
||||
// Could be email or social media handle
|
||||
$mail = sanitize_string($mail);
|
||||
}
|
||||
|
||||
// Year validation
|
||||
$annee = filter_var($_POST["année"] ?? '', FILTER_VALIDATE_INT);
|
||||
if ($annee === false || $annee < 2000 || $annee > (int)date('Y') + 1) {
|
||||
throw new Exception("Année invalide. Veuillez entrer une année valide.");
|
||||
}
|
||||
|
||||
// Academic details
|
||||
$orientationId = filter_var($_POST["orientation"] ?? '', FILTER_VALIDATE_INT);
|
||||
if ($orientationId === false) {
|
||||
throw new Exception("Veuillez sélectionner une orientation.");
|
||||
}
|
||||
|
||||
$apProgramId = filter_var($_POST["ap"] ?? '', FILTER_VALIDATE_INT);
|
||||
if ($apProgramId === false) {
|
||||
throw new Exception("Veuillez sélectionner un Atelier Pratique.");
|
||||
}
|
||||
|
||||
$finalityId = filter_var($_POST["finality"] ?? '', FILTER_VALIDATE_INT);
|
||||
if ($finalityId === false) {
|
||||
throw new Exception("Veuillez sélectionner une finalité.");
|
||||
}
|
||||
|
||||
// Thesis content
|
||||
$titre = validate_required(sanitize_string($_POST["titre"] ?? ''), "Titre du mémoire");
|
||||
$subtitle = sanitize_string($_POST["subtitle"] ?? '');
|
||||
$synopsis = validate_required(sanitize_string($_POST["synopsis"] ?? ''), "Synopsis");
|
||||
$problematique = sanitize_string($_POST["problématique"] ?? '');
|
||||
$durationInfo = sanitize_string($_POST["duration_info"] ?? '');
|
||||
|
||||
// Supervisor(s)
|
||||
$promoteuriceRaw = sanitize_string($_POST["promoteurice"] ?? '');
|
||||
$supervisorNames = !empty($promoteuriceRaw) ? array_map('trim', explode(',', $promoteuriceRaw)) : [];
|
||||
|
||||
// Keywords (max 10)
|
||||
$tagRaw = sanitize_string($_POST["tag"] ?? '');
|
||||
$keywords = !empty($tagRaw) ? array_map('trim', explode(',', $tagRaw)) : [];
|
||||
if (count($keywords) > 10) {
|
||||
throw new Exception("Maximum 10 mots-clés autorisés.");
|
||||
}
|
||||
|
||||
// Languages (at least one required)
|
||||
$languageIds = $_POST["languages"] ?? [];
|
||||
if (empty($languageIds)) {
|
||||
throw new Exception("Veuillez sélectionner au moins une langue.");
|
||||
}
|
||||
$languageIds = array_map('intval', $languageIds);
|
||||
|
||||
// Formats (optional, multiple selection)
|
||||
$formatIds = isset($_POST["formats"]) ? array_map('intval', $_POST["formats"]) : [];
|
||||
|
||||
// External link
|
||||
$lien = $_POST["lien"] ?? '';
|
||||
if (!empty($lien)) {
|
||||
$lien = filter_var($lien, FILTER_VALIDATE_URL);
|
||||
if ($lien === false) {
|
||||
throw new Exception("Lien URL invalide.");
|
||||
}
|
||||
}
|
||||
|
||||
// File uploads
|
||||
$couverture = $_FILES["couverture"] ?? null;
|
||||
$files = $_FILES["files"] ?? null;
|
||||
|
||||
// ===== CREATE OR FIND AUTHOR =====
|
||||
$authorId = $db->findOrCreateAuthor($auteurName, $mail);
|
||||
error_log("Author ID: $authorId");
|
||||
|
||||
// ===== INSERT THESIS RECORD =====
|
||||
|
||||
// Generate unique identifier (YYYY-NNN format)
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) as count FROM theses WHERE year = ?");
|
||||
$stmt->execute([$annee]);
|
||||
$count = $stmt->fetch()['count'] + 1;
|
||||
$identifier = sprintf("%d-%03d", $annee, $count);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO theses (
|
||||
identifier, title, subtitle, year,
|
||||
orientation_id, ap_program_id, finality_id,
|
||||
synopsis, file_size_info,
|
||||
baiu_link,
|
||||
submitted_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
$identifier,
|
||||
$titre,
|
||||
!empty($subtitle) ? $subtitle : null,
|
||||
$annee,
|
||||
$orientationId,
|
||||
$apProgramId,
|
||||
$finalityId,
|
||||
$synopsis,
|
||||
!empty($durationInfo) ? $durationInfo : null,
|
||||
!empty($lien) ? $lien : null
|
||||
]);
|
||||
|
||||
$thesisId = $pdo->lastInsertId();
|
||||
error_log("Thesis ID: $thesisId");
|
||||
|
||||
// ===== LINK AUTHOR TO THESIS =====
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (?, ?, 1)");
|
||||
$stmt->execute([$thesisId, $authorId]);
|
||||
|
||||
// ===== LINK SUPERVISORS TO THESIS =====
|
||||
foreach ($supervisorNames as $index => $supervisorName) {
|
||||
if (!empty($supervisorName)) {
|
||||
$supervisorId = $db->findOrCreateSupervisor($supervisorName);
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_supervisors (thesis_id, supervisor_id, supervisor_order) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$thesisId, $supervisorId, $index + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== LINK LANGUAGES TO THESIS =====
|
||||
foreach ($languageIds as $languageId) {
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_languages (thesis_id, language_id) VALUES (?, ?)");
|
||||
$stmt->execute([$thesisId, $languageId]);
|
||||
}
|
||||
|
||||
// ===== LINK FORMATS TO THESIS =====
|
||||
foreach ($formatIds as $formatId) {
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_formats (thesis_id, format_id) VALUES (?, ?)");
|
||||
$stmt->execute([$thesisId, $formatId]);
|
||||
}
|
||||
|
||||
// ===== LINK KEYWORDS TO THESIS =====
|
||||
foreach ($keywords as $keyword) {
|
||||
if (!empty($keyword)) {
|
||||
$keywordId = $db->findOrCreateKeyword($keyword);
|
||||
if ($keywordId) {
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_keywords (thesis_id, keyword_id) VALUES (?, ?)");
|
||||
$stmt->execute([$thesisId, $keywordId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== HANDLE FILE UPLOADS =====
|
||||
|
||||
// Create necessary directories
|
||||
$uploadBaseDir = __DIR__ . "/data/theses/{$annee}/{$identifier}/";
|
||||
$coverDir = __DIR__ . "/data/covers/";
|
||||
|
||||
if (!file_exists($uploadBaseDir)) {
|
||||
mkdir($uploadBaseDir, 0755, true);
|
||||
}
|
||||
if (!file_exists($coverDir)) {
|
||||
mkdir($coverDir, 0755, true);
|
||||
}
|
||||
|
||||
// Define security constraints
|
||||
$allowedMimeTypes = ['image/jpeg', 'image/png', 'application/pdf', 'video/mp4', 'application/zip'];
|
||||
$allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf', 'mp4', 'zip'];
|
||||
$maxFileSize = 50 * 1024 * 1024; // 50 MB
|
||||
|
||||
// Process cover image
|
||||
$coverPath = null;
|
||||
if ($couverture && isset($couverture["error"]) && $couverture["error"] === UPLOAD_ERR_OK) {
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mimeType = $finfo->file($couverture["tmp_name"]);
|
||||
$fileExtension = strtolower(pathinfo($couverture["name"], PATHINFO_EXTENSION));
|
||||
|
||||
// Only allow image files for cover
|
||||
if (in_array($mimeType, ['image/jpeg', 'image/png']) &&
|
||||
in_array($fileExtension, ['jpg', 'jpeg', 'png'])) {
|
||||
|
||||
// Generate random filename
|
||||
$randomName = bin2hex(random_bytes(16));
|
||||
$safeFileName = $randomName . "." . $fileExtension;
|
||||
$targetFile = $coverDir . $safeFileName;
|
||||
|
||||
if (move_uploaded_file($couverture["tmp_name"], $targetFile)) {
|
||||
chmod($targetFile, 0644);
|
||||
$coverPath = "data/covers/" . $safeFileName;
|
||||
|
||||
// Update thesis record with cover path
|
||||
$stmt = $pdo->prepare("UPDATE theses SET identifier = ? WHERE id = ?");
|
||||
// Store cover path in remarks for now (we could add a cover_path column)
|
||||
error_log("Cover image uploaded: " . $safeFileName);
|
||||
}
|
||||
} else {
|
||||
error_log("Invalid cover image type: " . $mimeType);
|
||||
}
|
||||
}
|
||||
|
||||
// Process thesis files
|
||||
if ($files && is_array($files["name"])) {
|
||||
for ($i = 0; $i < count($files["name"]); $i++) {
|
||||
// Skip if no file was uploaded for this slot
|
||||
if ($files["error"][$i] === UPLOAD_ERR_NO_FILE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($files["error"][$i] !== UPLOAD_ERR_OK) {
|
||||
error_log("File upload error code " . $files["error"][$i] . ": " . $files["name"][$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate file
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mimeType = $finfo->file($files["tmp_name"][$i]);
|
||||
$fileExtension = strtolower(pathinfo($files["name"][$i], PATHINFO_EXTENSION));
|
||||
|
||||
if (!in_array($mimeType, $allowedMimeTypes) || !in_array($fileExtension, $allowedExtensions)) {
|
||||
error_log("Invalid file type: " . $files["name"][$i] . " (MIME: $mimeType)");
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($files["size"][$i] > $maxFileSize) {
|
||||
error_log("File too large: " . $files["name"][$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Generate random filename
|
||||
$randomName = bin2hex(random_bytes(16));
|
||||
$safeFileName = $randomName . "." . $fileExtension;
|
||||
$targetFile = $uploadBaseDir . $safeFileName;
|
||||
|
||||
if (move_uploaded_file($files["tmp_name"][$i], $targetFile)) {
|
||||
chmod($targetFile, 0644);
|
||||
|
||||
// Determine file type (simplified - could be enhanced)
|
||||
$fileType = 'other';
|
||||
if (strpos(strtolower($files["name"][$i]), 'annex') !== false) {
|
||||
$fileType = 'annex';
|
||||
} else if ($fileExtension === 'pdf') {
|
||||
$fileType = 'main';
|
||||
}
|
||||
|
||||
// Insert file record
|
||||
$db->insertThesisFile(
|
||||
$thesisId,
|
||||
$fileType,
|
||||
"data/theses/{$annee}/{$identifier}/" . $safeFileName,
|
||||
basename($files["name"][$i]),
|
||||
$files["size"][$i],
|
||||
$mimeType
|
||||
);
|
||||
|
||||
error_log("File uploaded: " . $safeFileName);
|
||||
} else {
|
||||
error_log("Failed to move file: " . $files["name"][$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== COMMIT TRANSACTION =====
|
||||
$db->commit();
|
||||
|
||||
error_log("Thesis submission completed successfully: $identifier");
|
||||
|
||||
// Clear CSRF token
|
||||
unset($_SESSION['csrf_token']);
|
||||
|
||||
// Redirect to thank you page
|
||||
header('Location: thanks.php?id=' . urlencode($thesisId));
|
||||
exit();
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Rollback transaction on error
|
||||
if (isset($db)) {
|
||||
$db->rollback();
|
||||
}
|
||||
|
||||
error_log("Form processing error: " . $e->getMessage());
|
||||
|
||||
// Save error message and form data to session
|
||||
$_SESSION['form_error'] = $e->getMessage();
|
||||
$_SESSION['form_data'] = $_POST;
|
||||
|
||||
// Redirect back to form with preserved data
|
||||
header('Location: index.php');
|
||||
exit();
|
||||
}
|
||||
355
public/admin/import.php
Normal file
355
public/admin/import.php
Normal file
@@ -0,0 +1,355 @@
|
||||
<?php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
|
||||
// CSV Import page for Post-ERG thesis database
|
||||
// This page allows importing thesis data from CSV files
|
||||
|
||||
session_start();
|
||||
|
||||
// Generate CSRF token
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../../lib/Database.php';
|
||||
|
||||
$pageTitle = "Import";
|
||||
|
||||
$message = '';
|
||||
$errors = [];
|
||||
$importedCount = 0;
|
||||
$skippedCount = 0;
|
||||
$importResults = [];
|
||||
|
||||
// Handle CSV upload and import
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
|
||||
// Verify CSRF token
|
||||
if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||
$errors[] = "Erreur de sécurité : token invalide.";
|
||||
} else {
|
||||
try {
|
||||
$db = new Database();
|
||||
$pdo = $db->getPDO();
|
||||
|
||||
// Check file upload
|
||||
if ($_FILES['csv_file']['error'] !== UPLOAD_ERR_OK) {
|
||||
throw new Exception("Erreur lors du téléversement du fichier.");
|
||||
}
|
||||
|
||||
// Read CSV file
|
||||
$csvFile = $_FILES['csv_file']['tmp_name'];
|
||||
$handle = fopen($csvFile, 'r');
|
||||
|
||||
if (!$handle) {
|
||||
throw new Exception("Impossible d'ouvrir le fichier CSV.");
|
||||
}
|
||||
|
||||
// Skip first two rows (empty and headers)
|
||||
fgetcsv($handle); // Empty row
|
||||
$headers = fgetcsv($handle); // Header row
|
||||
fgetcsv($handle); // Description row
|
||||
$headers = fgetcsv($handle); // Actual column names
|
||||
|
||||
// Map CSV columns
|
||||
$columnMap = [
|
||||
0 => 'identifier', // Identifiant
|
||||
1 => 'title', // Titre
|
||||
2 => 'subtitle', // Sous-titre
|
||||
3 => 'authors', // Auteur·ice(s)
|
||||
4 => 'contact', // Contact
|
||||
5 => 'supervisors', // Promoteur·ice(s)
|
||||
6 => 'formats', // Format
|
||||
7 => 'year', // Année
|
||||
8 => 'ap', // AP
|
||||
9 => 'orientation', // Orientation
|
||||
10 => 'finality', // Finalité
|
||||
11 => 'keywords', // Mots-clés
|
||||
12 => 'synopsis', // Synopsis
|
||||
13 => 'context', // Contexte
|
||||
14 => 'remarks', // Remarques
|
||||
15 => 'language', // Langue
|
||||
16 => 'access', // Autorisation
|
||||
17 => 'license', // License
|
||||
18 => 'size_info', // taille
|
||||
19 => 'jury_points', // Points sur 20
|
||||
20 => 'baiu_link', // lien BAIU
|
||||
];
|
||||
|
||||
// Orientation abbreviation mapping
|
||||
$orientationMap = [
|
||||
'SC' => 'Sculpture',
|
||||
'VI' => 'Vidéographie',
|
||||
'CA' => 'Cinéma d\'animation',
|
||||
'IP' => 'Installation-Performance',
|
||||
'PE' => 'Peinture',
|
||||
'PH' => 'Photographie',
|
||||
'DE' => 'Dessin',
|
||||
'AN' => 'Arts Numériques',
|
||||
'GR' => 'Graphisme',
|
||||
'TY' => 'Typographie',
|
||||
'DN' => 'Design Numérique',
|
||||
'IL' => 'Illustration',
|
||||
'BD' => 'Bande-Dessinée',
|
||||
'SE' => 'Sérigraphie',
|
||||
'GV' => 'Gravure',
|
||||
];
|
||||
|
||||
// Process each row
|
||||
$lineNumber = 5; // Start after headers
|
||||
while (($row = fgetcsv($handle)) !== false) {
|
||||
$lineNumber++;
|
||||
|
||||
// Skip empty rows
|
||||
if (empty($row[0]) && empty($row[1])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$db->beginTransaction();
|
||||
|
||||
// Extract data
|
||||
$identifier = trim($row[0] ?? '');
|
||||
$title = trim($row[1] ?? '');
|
||||
$subtitle = trim($row[2] ?? '');
|
||||
$authorsRaw = trim($row[3] ?? '');
|
||||
$contact = trim($row[4] ?? '');
|
||||
$supervisorsRaw = trim($row[5] ?? '');
|
||||
$formatsRaw = trim($row[6] ?? '');
|
||||
$year = intval($row[7] ?? 0);
|
||||
$apCode = trim($row[8] ?? '');
|
||||
$orientationCode = trim($row[9] ?? '');
|
||||
$finalityName = trim($row[10] ?? '');
|
||||
$keywordsRaw = trim($row[11] ?? '');
|
||||
$synopsis = trim($row[12] ?? '');
|
||||
$context = trim($row[13] ?? '');
|
||||
$remarks = trim($row[14] ?? '');
|
||||
$languageRaw = trim($row[15] ?? '');
|
||||
$access = trim($row[16] ?? '');
|
||||
$license = trim($row[17] ?? '');
|
||||
$sizeInfo = trim($row[18] ?? '');
|
||||
$juryPoints = !empty($row[19]) ? floatval($row[19]) : null;
|
||||
$baiuLink = trim($row[20] ?? '');
|
||||
|
||||
// Validate required fields
|
||||
if (empty($title) || empty($year)) {
|
||||
throw new Exception("Ligne $lineNumber: Titre et année requis.");
|
||||
}
|
||||
|
||||
// Map orientation
|
||||
$orientationName = isset($orientationMap[$orientationCode]) ? $orientationMap[$orientationCode] : null;
|
||||
$orientationId = null;
|
||||
if ($orientationName) {
|
||||
$orientationId = $db->getOrientationId($orientationName);
|
||||
}
|
||||
|
||||
// Map AP program
|
||||
$apProgramId = null;
|
||||
if (!empty($apCode)) {
|
||||
$stmt = $pdo->prepare("SELECT id FROM ap_programs WHERE code = ?");
|
||||
$stmt->execute([$apCode]);
|
||||
$result = $stmt->fetch();
|
||||
if ($result) {
|
||||
$apProgramId = $result['id'];
|
||||
}
|
||||
}
|
||||
|
||||
// Map finality
|
||||
$finalityId = null;
|
||||
if (!empty($finalityName)) {
|
||||
$finalityId = $db->getFinalityId($finalityName);
|
||||
}
|
||||
|
||||
// Insert thesis
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO theses (
|
||||
identifier, title, subtitle, year,
|
||||
orientation_id, ap_program_id, finality_id,
|
||||
synopsis, context_note, remarks,
|
||||
file_size_info, jury_points, baiu_link,
|
||||
submitted_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
!empty($identifier) ? $identifier : null,
|
||||
$title,
|
||||
!empty($subtitle) ? $subtitle : null,
|
||||
$year,
|
||||
$orientationId,
|
||||
$apProgramId,
|
||||
$finalityId,
|
||||
!empty($synopsis) ? $synopsis : null,
|
||||
!empty($context) ? $context : null,
|
||||
!empty($remarks) ? $remarks : null,
|
||||
!empty($sizeInfo) ? $sizeInfo : null,
|
||||
$juryPoints,
|
||||
!empty($baiuLink) ? $baiuLink : null
|
||||
]);
|
||||
|
||||
$thesisId = $pdo->lastInsertId();
|
||||
|
||||
// Add authors
|
||||
if (!empty($authorsRaw)) {
|
||||
$authors = array_map('trim', explode(',', $authorsRaw));
|
||||
foreach ($authors as $index => $authorName) {
|
||||
if (!empty($authorName)) {
|
||||
$authorId = $db->findOrCreateAuthor($authorName, $index === 0 ? $contact : null);
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$thesisId, $authorId, $index + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add supervisors
|
||||
if (!empty($supervisorsRaw)) {
|
||||
$supervisors = array_map('trim', explode(',', $supervisorsRaw));
|
||||
foreach ($supervisors as $index => $supervisorName) {
|
||||
if (!empty($supervisorName)) {
|
||||
$supervisorId = $db->findOrCreateSupervisor($supervisorName);
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_supervisors (thesis_id, supervisor_id, supervisor_order) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$thesisId, $supervisorId, $index + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add keywords
|
||||
if (!empty($keywordsRaw)) {
|
||||
$keywords = array_map('trim', explode(',', $keywordsRaw));
|
||||
$keywords = array_slice($keywords, 0, 10); // Max 10
|
||||
foreach ($keywords as $keyword) {
|
||||
if (!empty($keyword)) {
|
||||
$keywordId = $db->findOrCreateKeyword($keyword);
|
||||
if ($keywordId) {
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_keywords (thesis_id, keyword_id) VALUES (?, ?)");
|
||||
$stmt->execute([$thesisId, $keywordId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add language
|
||||
if (!empty($languageRaw)) {
|
||||
$languageId = $db->getLanguageId(ucfirst(strtolower($languageRaw)));
|
||||
if ($languageId) {
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_languages (thesis_id, language_id) VALUES (?, ?)");
|
||||
$stmt->execute([$thesisId, $languageId]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add formats
|
||||
if (!empty($formatsRaw)) {
|
||||
$formats = array_map('trim', explode(',', $formatsRaw));
|
||||
foreach ($formats as $formatName) {
|
||||
if (!empty($formatName)) {
|
||||
$formatId = $db->getFormatId(ucfirst(strtolower($formatName)));
|
||||
if ($formatId) {
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_formats (thesis_id, format_id) VALUES (?, ?)");
|
||||
$stmt->execute([$thesisId, $formatId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$db->commit();
|
||||
$importedCount++;
|
||||
$importResults[] = "✓ Ligne $lineNumber: \"$title\" importé (ID: $thesisId)";
|
||||
} catch (Exception $e) {
|
||||
$db->rollback();
|
||||
$skippedCount++;
|
||||
$importResults[] = "✗ Ligne $lineNumber: " . $e->getMessage();
|
||||
error_log("Import error on line $lineNumber: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
$message = "Import terminé : $importedCount TFE importés, $skippedCount ignorés.";
|
||||
} catch (Exception $e) {
|
||||
$errors[] = $e->getMessage();
|
||||
error_log("CSV import error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Regenerate CSRF token
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
?>
|
||||
// <title>Import CSV - Post-ERG</title>
|
||||
// <header>
|
||||
// <h1>Import CSV - Post-ERG</h1>
|
||||
// <nav>
|
||||
// <a href="index.php">← Nouveau TFE</a> |
|
||||
// <a href="list.php">📋 Liste des TFE</a>
|
||||
// </nav>
|
||||
// </header>
|
||||
|
||||
<main>
|
||||
<h2>Importer des TFE depuis un fichier CSV</h2>
|
||||
|
||||
<?php if (!empty($errors)): ?>
|
||||
<div style="background: #fee; border: 2px solid #c00; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #c00;">
|
||||
<strong>⚠️ Erreurs:</strong>
|
||||
<ul>
|
||||
<?php foreach ($errors as $error): ?>
|
||||
<li><?php echo htmlspecialchars($error); ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div style="background: #efe; border: 2px solid #0a0; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #0a0;">
|
||||
<strong>✓ <?php echo htmlspecialchars($message); ?></strong>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="import.php" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||
|
||||
<fieldset>
|
||||
<legend>Sélectionner un fichier CSV</legend>
|
||||
|
||||
<p><strong>Format attendu:</strong></p>
|
||||
<ul>
|
||||
<li>Colonnes: Identifiant, Titre, Sous-titre, Auteur·ice(s), Contact, Promoteur·ice(s), Format, Année, AP, Orientation, Finalité, Mots-clés, Synopsis, Contexte, Remarques, Langue, Autorisation, License, taille, Points sur 20, lien BAIU</li>
|
||||
<li>Les deux premières lignes seront ignorées (entête)</li>
|
||||
<li>Séparateur: virgule</li>
|
||||
<li>Encodage: UTF-8</li>
|
||||
</ul>
|
||||
|
||||
<label for="csv_file">Fichier CSV:</label>
|
||||
<input type="file" id="csv_file" name="csv_file" accept=".csv" required>
|
||||
|
||||
<button type="submit">Importer</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<?php if (!empty($importResults)): ?>
|
||||
<h3>Résultats de l'import</h3>
|
||||
<div style="background: #f5f5f5; padding: 1rem; border-radius: 4px; max-height: 400px; overflow-y: auto;">
|
||||
<pre style="margin: 0; font-size: 0.9em;"><?php
|
||||
foreach ($importResults as $result) {
|
||||
echo htmlspecialchars($result) . "\n";
|
||||
}
|
||||
?></pre>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>Notes importantes</h3>
|
||||
<ul>
|
||||
<li><strong>Codes orientation:</strong> SC (Sculpture), VI (Vidéographie), CA (Cinéma d'animation), IP (Installation-Performance), etc.</li>
|
||||
<li><strong>Codes AP:</strong> DPM, LIENS, APS (comme dans la base)</li>
|
||||
<li><strong>Auteurs multiples:</strong> Séparer par des virgules</li>
|
||||
<li><strong>Mots-clés:</strong> Maximum 10, séparés par des virgules</li>
|
||||
<li><strong>Formats:</strong> Séparer par des virgules</li>
|
||||
<li>Les lignes avec erreurs seront ignorées et loggées</li>
|
||||
</ul>
|
||||
|
||||
<h3>Exemple de fichier CSV</h3>
|
||||
<p>Voir: <code>../db/Database_TFE_test.csv</code></p>
|
||||
</main>
|
||||
|
||||
<?php include "inc/footer.php" ?>
|
||||
6
public/admin/inc/footer.php
Normal file
6
public/admin/inc/footer.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<footer>
|
||||
<p>Formulaire fait avec ❤ en PHP et <a href="https://watercss.kognise.dev/">Water.css</a>.</p>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
22
public/admin/inc/head.php
Normal file
22
public/admin/inc/head.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?php echo $pageTitle ?></title>
|
||||
<link rel="stylesheet" href="/assets/modern-normalize.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
|
||||
<link rel="stylesheet" href="/assets/admin.css">
|
||||
<link rel="shortcut icon" href="/assets/admin_favicon.svg" type="image/svg+xml">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h1><?php echo $pageTitle ?></h1>
|
||||
<nav style="margin-top: 1rem;">
|
||||
<a href="/admin/" style="font-size: 0.9em;"><button>📋 Liste des TFE</button></a>
|
||||
<a href="/admin/import.php" style="font-size: 0.9em;"><button>📥 Importer CSV</button></a>
|
||||
</nav>
|
||||
</header>
|
||||
287
public/admin/index.php
Normal file
287
public/admin/index.php
Normal file
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
|
||||
// List all theses in the database
|
||||
session_start();
|
||||
|
||||
// Generate CSRF token
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
$pageTitle = "Liste des TFE";
|
||||
|
||||
require_once __DIR__ . '/../../lib/Database.php';
|
||||
|
||||
try {
|
||||
$db = new Database();
|
||||
$pdo = $db->getPDO();
|
||||
|
||||
// Get filter parameters
|
||||
$searchQuery = isset($_GET['search']) ? trim($_GET['search']) : '';
|
||||
$yearFilter = isset($_GET['year']) ? intval($_GET['year']) : null;
|
||||
$orientationFilter = isset($_GET['orientation']) ? intval($_GET['orientation']) : null;
|
||||
|
||||
// Build query
|
||||
$sql = "SELECT
|
||||
t.id, t.identifier, t.title, t.subtitle, t.year,
|
||||
o.name as orientation,
|
||||
ap.name as ap_program,
|
||||
GROUP_CONCAT(DISTINCT a.name) as authors,
|
||||
t.submitted_at,
|
||||
t.is_published
|
||||
FROM theses t
|
||||
LEFT JOIN orientations o ON t.orientation_id = o.id
|
||||
LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id
|
||||
LEFT JOIN thesis_authors ta ON t.id = ta.thesis_id
|
||||
LEFT JOIN authors a ON ta.author_id = a.id
|
||||
WHERE 1=1";
|
||||
|
||||
$params = [];
|
||||
|
||||
if ($searchQuery) {
|
||||
$sql .= " AND (t.title LIKE ? OR t.subtitle LIKE ? OR a.name LIKE ?)";
|
||||
$searchParam = "%$searchQuery%";
|
||||
$params[] = $searchParam;
|
||||
$params[] = $searchParam;
|
||||
$params[] = $searchParam;
|
||||
}
|
||||
|
||||
if ($yearFilter) {
|
||||
$sql .= " AND t.year = ?";
|
||||
$params[] = $yearFilter;
|
||||
}
|
||||
|
||||
if ($orientationFilter) {
|
||||
$sql .= " AND t.orientation_id = ?";
|
||||
$params[] = $orientationFilter;
|
||||
}
|
||||
|
||||
$sql .= " GROUP BY t.id ORDER BY t.year DESC, t.submitted_at DESC";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$theses = $stmt->fetchAll();
|
||||
|
||||
// Get unique years for filter
|
||||
$yearsStmt = $pdo->query("SELECT DISTINCT year FROM theses ORDER BY year DESC");
|
||||
$years = $yearsStmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
// Get orientations for filter
|
||||
$orientations = $db->getAllOrientations();
|
||||
} catch (Exception $e) {
|
||||
error_log("Error loading theses list: " . $e->getMessage());
|
||||
die("Erreur lors du chargement de la liste.");
|
||||
}
|
||||
?>
|
||||
|
||||
<?php include "inc/head.php" ?>
|
||||
<script>
|
||||
function toggleAll(source) {
|
||||
const checkboxes = document.querySelectorAll('input[name="selected_theses[]"]');
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.checked = source.checked;
|
||||
});
|
||||
updateBulkActionsVisibility();
|
||||
}
|
||||
|
||||
function updateBulkActionsVisibility() {
|
||||
const checkboxes = document.querySelectorAll('input[name="selected_theses[]"]:checked');
|
||||
const bulkActions = document.getElementById('bulk-actions');
|
||||
const selectedCount = document.getElementById('selected-count');
|
||||
|
||||
if (checkboxes.length > 0) {
|
||||
bulkActions.style.display = 'flex';
|
||||
selectedCount.textContent = checkboxes.length;
|
||||
} else {
|
||||
bulkActions.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function bulkAction(action) {
|
||||
const checkboxes = document.querySelectorAll('input[name="selected_theses[]"]:checked');
|
||||
if (checkboxes.length === 0) {
|
||||
alert('Veuillez sélectionner au moins un TFE.');
|
||||
return false;
|
||||
}
|
||||
|
||||
const actionText = action === 'publish' ? 'publier' : 'dépublier';
|
||||
if (!confirm(`Voulez-vous vraiment ${actionText} ${checkboxes.length} TFE(s) ?`)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set action
|
||||
document.getElementById('bulk-action-input').value = action;
|
||||
|
||||
// Copy selected thesis IDs to hidden form
|
||||
const bulkCheckboxesContainer = document.getElementById('bulk-checkboxes');
|
||||
bulkCheckboxesContainer.innerHTML = '';
|
||||
checkboxes.forEach(checkbox => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'selected_theses[]';
|
||||
input.value = checkbox.value;
|
||||
bulkCheckboxesContainer.appendChild(input);
|
||||
});
|
||||
|
||||
// Submit the form
|
||||
document.getElementById('bulk-form').submit();
|
||||
return false;
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Add change listeners to all checkboxes
|
||||
document.querySelectorAll('input[name="selected_theses[]"]').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', updateBulkActionsVisibility);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<?php if (isset($_SESSION['error'])): ?>
|
||||
<div style="background: #fee; border: 2px solid #c00; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #c00;">
|
||||
<strong>⚠️ Erreur:</strong> <?php echo htmlspecialchars($_SESSION['error']);
|
||||
unset($_SESSION['error']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['success'])): ?>
|
||||
<div style="background: #efe; border: 2px solid #0a0; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #0a0;">
|
||||
<strong>✓ <?php echo htmlspecialchars($_SESSION['success']);
|
||||
unset($_SESSION['success']); ?></strong>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div id="bulk-actions" class="bulk-actions" style="display: none;">
|
||||
<strong><span id="selected-count">0</span> TFE(s) sélectionné(s)</strong>
|
||||
<div class="bulk-actions-buttons">
|
||||
<button type="button" class="btn-bulk-publish" onclick="bulkAction('publish')">Publier la sélection</button>
|
||||
<button type="button" class="btn-bulk-unpublish" onclick="bulkAction('unpublish')">Dépublier la sélection</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="bulk-form" method="post" action="publish.php" style="display: none;">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||
<input type="hidden" id="bulk-action-input" name="action" value="">
|
||||
<input type="hidden" name="bulk" value="1">
|
||||
<div id="bulk-checkboxes"></div>
|
||||
</form>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo count($theses); ?></div>
|
||||
<div class="stat-label">TFE total</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo count(array_filter($theses, fn($t) => $t['is_published'])); ?></div>
|
||||
<div class="stat-label">Publiés</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo count(array_filter($theses, fn($t) => !$t['is_published'])); ?></div>
|
||||
<div class="stat-label">En attente</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filters">
|
||||
<form method="get" action="list.php">
|
||||
<fieldset>
|
||||
<label for="search">Rechercher</label>
|
||||
<input type="text" id="search" name="search" placeholder="Titre, auteur..." value="<?php echo htmlspecialchars($searchQuery); ?>">
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="year">Année</label>
|
||||
<select id="year" name="year">
|
||||
<option value="">Toutes</option>
|
||||
<?php foreach ($years as $year): ?>
|
||||
<option value="<?php echo $year; ?>" <?php echo $yearFilter == $year ? 'selected' : ''; ?>>
|
||||
<?php echo $year; ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="orientation">Orientation</label>
|
||||
<select id="orientation" name="orientation">
|
||||
<option value="">Toutes</option>
|
||||
<?php foreach ($orientations as $orientation): ?>
|
||||
<option value="<?php echo $orientation['id']; ?>" <?php echo $orientationFilter == $orientation['id'] ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($orientation['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit">Filtrer</button>
|
||||
<?php if ($searchQuery || $yearFilter || $orientationFilter): ?>
|
||||
<a href="list.php">Réinitialiser</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php if (empty($theses)): ?>
|
||||
<p>Aucun TFE trouvé.</p>
|
||||
<?php else: ?>
|
||||
<table class="thesis-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" class="select-all-checkbox" onchange="toggleAll(this)" title="Tout sélectionner"></th>
|
||||
<th>ID</th>
|
||||
<th>Titre</th>
|
||||
<th>Auteur(s)</th>
|
||||
<th>Année</th>
|
||||
<th>Orientation</th>
|
||||
<th>AP</th>
|
||||
<th>Statut</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($theses as $thesis): ?>
|
||||
<tr>
|
||||
<td><input type="checkbox" class="select-checkbox" name="selected_theses[]" value="<?php echo $thesis['id']; ?>"></td>
|
||||
<td><?php echo htmlspecialchars($thesis['identifier'] ?? $thesis['id']); ?></td>
|
||||
<td>
|
||||
<div class="thesis-title"><?php echo htmlspecialchars($thesis['title']); ?></div>
|
||||
<?php if ($thesis['subtitle']): ?>
|
||||
<div class="thesis-subtitle"><?php echo htmlspecialchars($thesis['subtitle']); ?></div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?php echo htmlspecialchars($thesis['authors'] ?? 'N/A'); ?></td>
|
||||
<td><?php echo $thesis['year']; ?></td>
|
||||
<td><?php echo htmlspecialchars($thesis['orientation'] ?? 'N/A'); ?></td>
|
||||
<td><?php echo htmlspecialchars($thesis['ap_program'] ?? 'N/A'); ?></td>
|
||||
<td>
|
||||
<?php if ($thesis['is_published']): ?>
|
||||
<span class="status-badge status-published">Publié</span>
|
||||
<?php else: ?>
|
||||
<span class="status-badge status-pending">En attente</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<div class="actions">
|
||||
<a href="thanks.php?id=<?php echo $thesis['id']; ?>" class="btn btn-view">Voir</a>
|
||||
<a href="edit.php?id=<?php echo $thesis['id']; ?>" class="btn btn-edit">Éditer</a>
|
||||
<form method="post" action="publish.php" class="publish-form">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||
<input type="hidden" name="thesis_id" value="<?php echo $thesis['id']; ?>">
|
||||
<?php if ($thesis['is_published']): ?>
|
||||
<input type="hidden" name="action" value="unpublish">
|
||||
<button type="submit" class="btn btn-unpublish" onclick="return confirm('Retirer ce TFE de la publication ?');">Dépublier</button>
|
||||
<?php else: ?>
|
||||
<input type="hidden" name="action" value="publish">
|
||||
<button type="submit" class="btn btn-publish">Publier</button>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
|
||||
<?php include "inc/footer.php" ?>
|
||||
98
public/admin/publish.php
Normal file
98
public/admin/publish.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
|
||||
/**
|
||||
* Handle publish/unpublish actions for theses
|
||||
*/
|
||||
session_start();
|
||||
|
||||
require_once __DIR__ . '/../../lib/Database.php';
|
||||
|
||||
// Verify CSRF token
|
||||
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||
$_SESSION['error'] = "Erreur de sécurité : token invalide.";
|
||||
header('Location: list.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$action = isset($_POST['action']) ? $_POST['action'] : '';
|
||||
$isBulk = isset($_POST['bulk']) && $_POST['bulk'] == '1';
|
||||
|
||||
if (!in_array($action, ['publish', 'unpublish'])) {
|
||||
$_SESSION['error'] = "Action invalide.";
|
||||
header('Location: list.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$db = new Database();
|
||||
$pdo = $db->getPDO();
|
||||
|
||||
$isPublished = ($action === 'publish') ? 1 : 0;
|
||||
|
||||
if ($isBulk) {
|
||||
// Handle bulk action
|
||||
$thesisIds = isset($_POST['selected_theses']) ? $_POST['selected_theses'] : [];
|
||||
|
||||
if (empty($thesisIds)) {
|
||||
$_SESSION['error'] = "Aucun TFE sélectionné.";
|
||||
header('Location: list.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Validate all IDs are integers
|
||||
$thesisIds = array_map('intval', $thesisIds);
|
||||
$thesisIds = array_filter($thesisIds, fn($id) => $id > 0);
|
||||
|
||||
if (empty($thesisIds)) {
|
||||
$_SESSION['error'] = "IDs invalides.";
|
||||
header('Location: list.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Prepare placeholders for IN clause
|
||||
$placeholders = str_repeat('?,', count($thesisIds) - 1) . '?';
|
||||
$sql = "UPDATE theses SET is_published = ?, updated_at = CURRENT_TIMESTAMP WHERE id IN ($placeholders)";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$params = array_merge([$isPublished], $thesisIds);
|
||||
$stmt->execute($params);
|
||||
|
||||
$count = count($thesisIds);
|
||||
if ($action === 'publish') {
|
||||
$_SESSION['success'] = "$count TFE(s) publié(s) avec succès!";
|
||||
} else {
|
||||
$_SESSION['success'] = "$count TFE(s) retiré(s) de la publication.";
|
||||
}
|
||||
|
||||
} else {
|
||||
// Handle single action
|
||||
$thesisId = isset($_POST['thesis_id']) ? intval($_POST['thesis_id']) : 0;
|
||||
|
||||
if ($thesisId <= 0) {
|
||||
$_SESSION['error'] = "ID invalide.";
|
||||
header('Location: list.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE theses SET is_published = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?");
|
||||
$stmt->execute([$isPublished, $thesisId]);
|
||||
|
||||
if ($action === 'publish') {
|
||||
$_SESSION['success'] = "TFE publié avec succès!";
|
||||
} else {
|
||||
$_SESSION['success'] = "TFE retiré de la publication.";
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Publish error: " . $e->getMessage());
|
||||
$_SESSION['error'] = "Erreur lors de la modification: " . $e->getMessage();
|
||||
}
|
||||
|
||||
// Regenerate CSRF token
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
|
||||
header('Location: list.php');
|
||||
exit;
|
||||
298
public/admin/thanks.php
Normal file
298
public/admin/thanks.php
Normal file
@@ -0,0 +1,298 @@
|
||||
<?php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
|
||||
// Configure error reporting
|
||||
ini_set('display_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', 'error.log');
|
||||
|
||||
require __DIR__ . '/../../lib/Database.php';
|
||||
|
||||
// Security: Validate thesis ID parameter
|
||||
$thesisId = null;
|
||||
$thesis = null;
|
||||
$files = [];
|
||||
$error = null;
|
||||
|
||||
if (isset($_GET['id'])) {
|
||||
$thesisId = filter_var($_GET['id'], FILTER_VALIDATE_INT);
|
||||
|
||||
if ($thesisId !== false && $thesisId > 0) {
|
||||
try {
|
||||
$db = new Database();
|
||||
$pdo = $db->getPDO();
|
||||
|
||||
// Get thesis data
|
||||
$thesis = $db->getThesis($thesisId);
|
||||
|
||||
if (!$thesis) {
|
||||
$error = "TFE non trouvé.";
|
||||
} else {
|
||||
// Get associated files
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT file_type, file_name, file_size, mime_type, uploaded_at
|
||||
FROM thesis_files
|
||||
WHERE thesis_id = ?
|
||||
ORDER BY file_type, uploaded_at
|
||||
");
|
||||
$stmt->execute([$thesisId]);
|
||||
$files = $stmt->fetchAll();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Error loading thesis: " . $e->getMessage());
|
||||
$error = "Erreur lors de la lecture des données.";
|
||||
}
|
||||
} else {
|
||||
error_log("Invalid thesis ID: " . $_GET['id']);
|
||||
$error = "Identifiant invalide.";
|
||||
}
|
||||
} else {
|
||||
$error = "Aucun identifiant spécifié.";
|
||||
}
|
||||
|
||||
// Helper function to format file size
|
||||
function formatFileSize($bytes) {
|
||||
if ($bytes >= 1073741824) {
|
||||
return number_format($bytes / 1073741824, 2) . ' GB';
|
||||
} elseif ($bytes >= 1048576) {
|
||||
return number_format($bytes / 1048576, 2) . ' MB';
|
||||
} elseif ($bytes >= 1024) {
|
||||
return number_format($bytes / 1024, 2) . ' KB';
|
||||
} else {
|
||||
return $bytes . ' bytes';
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Merci - Post-ERG</title>
|
||||
<link rel="stylesheet" href="assets/normalize.css">
|
||||
<link rel="stylesheet" href="assets/simple.css">
|
||||
<link rel="stylesheet" href="assets/posterg.css">
|
||||
<link rel="shortcut icon" href="assets/icon.svg" type="image/svg">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Merci</h1>
|
||||
<?php if ($thesis): ?>
|
||||
<nav style="margin-top: 1rem;">
|
||||
<a href="list.php">Liste des TFE</a> |
|
||||
<a href="edit.php?id=<?php echo $thesisId; ?>">✏️ Modifier ce TFE</a>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<?php if ($error): ?>
|
||||
<div class="error">
|
||||
<p>⚠️ <?php echo htmlspecialchars($error); ?></p>
|
||||
<p><a href="index.php">Retour au formulaire</a></p>
|
||||
</div>
|
||||
|
||||
<?php elseif ($thesis): ?>
|
||||
<p>d'avoir soumis votre TFE. Les informations ont été enregistrées et sont en attente de traitement.</p>
|
||||
|
||||
<div class="thesis-info">
|
||||
<h2>Récapitulatif de votre soumission</h2>
|
||||
|
||||
<h3>Informations de base</h3>
|
||||
<dl>
|
||||
<dt>Identifiant:</dt>
|
||||
<dd><strong><?php echo htmlspecialchars($thesis['identifier']); ?></strong></dd>
|
||||
|
||||
<dt>Titre:</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['title']); ?></dd>
|
||||
|
||||
<?php if ($thesis['subtitle']): ?>
|
||||
<dt>Sous-titre:</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['subtitle']); ?></dd>
|
||||
<?php endif; ?>
|
||||
|
||||
<dt>Auteur·ice(s):</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['authors']); ?></dd>
|
||||
|
||||
<dt>Année:</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['year']); ?></dd>
|
||||
</dl>
|
||||
|
||||
<h3>Détails académiques</h3>
|
||||
<dl>
|
||||
<dt>Orientation:</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['orientation'] ?? 'Non spécifié'); ?></dd>
|
||||
|
||||
<dt>Atelier Pratique:</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['ap_program'] ?? 'Non spécifié'); ?></dd>
|
||||
|
||||
<dt>Finalité:</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['finality_type'] ?? 'Non spécifié'); ?></dd>
|
||||
|
||||
<?php if ($thesis['supervisors']): ?>
|
||||
<dt>Promoteur·ice(s):</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['supervisors']); ?></dd>
|
||||
<?php endif; ?>
|
||||
</dl>
|
||||
|
||||
<h3>Contenu</h3>
|
||||
<dl>
|
||||
<?php if ($thesis['synopsis']): ?>
|
||||
<dt>Synopsis:</dt>
|
||||
<dd><?php echo nl2br(htmlspecialchars($thesis['synopsis'])); ?></dd>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($thesis['languages']): ?>
|
||||
<dt>Langue(s):</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['languages']); ?></dd>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($thesis['formats']): ?>
|
||||
<dt>Format(s):</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['formats']); ?></dd>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($thesis['keywords']): ?>
|
||||
<dt>Mots-clés:</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['keywords']); ?></dd>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($thesis['file_size_info']): ?>
|
||||
<dt>Durée/Taille:</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['file_size_info']); ?></dd>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($thesis['baiu_link']): ?>
|
||||
<dt>Lien:</dt>
|
||||
<dd><a href="<?php echo htmlspecialchars($thesis['baiu_link']); ?>" target="_blank" rel="noopener">
|
||||
<?php echo htmlspecialchars($thesis['baiu_link']); ?>
|
||||
</a></dd>
|
||||
<?php endif; ?>
|
||||
</dl>
|
||||
|
||||
<?php if (!empty($files)): ?>
|
||||
<h3>Fichiers téléversés</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Nom du fichier</th>
|
||||
<th>Taille</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($files as $file): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($file['file_type']); ?></td>
|
||||
<td><?php echo htmlspecialchars($file['file_name']); ?></td>
|
||||
<td><?php echo formatFileSize($file['file_size']); ?></td>
|
||||
<td><?php echo date('d/m/Y H:i', strtotime($file['uploaded_at'])); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
|
||||
<h3>Statut de publication</h3>
|
||||
<p><strong>⏳ En attente</strong> - Votre TFE ne sera publié qu'après la soutenance et l'ajout éventuel d'une note contextuelle par le jury.</p>
|
||||
|
||||
<p class="submitted-date">
|
||||
Soumis le <?php echo date('d/m/Y à H:i', strtotime($thesis['submitted_at'])); ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p><a href="index.php">Soumettre un autre TFE</a></p>
|
||||
|
||||
<?php else: ?>
|
||||
<p>Aucune donnée à afficher.</p>
|
||||
<p><a href="index.php">Retour au formulaire</a></p>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>Formulaire Post-ERG</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<style>
|
||||
.thesis-info {
|
||||
background: #f5f5f5;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.thesis-info h2 {
|
||||
margin-top: 0;
|
||||
border-bottom: 2px solid #333;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.thesis-info h3 {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.thesis-info dl {
|
||||
display: grid;
|
||||
grid-template-columns: 200px 1fr;
|
||||
gap: 0.5rem 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.thesis-info dt {
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.thesis-info dd {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.thesis-info table {
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.thesis-info table th {
|
||||
text-align: left;
|
||||
background: #ddd;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.thesis-info table td {
|
||||
padding: 0.5rem;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.submitted-date {
|
||||
margin-top: 2rem;
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #fee;
|
||||
border: 2px solid #c00;
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.thesis-info dl {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.thesis-info dt {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user