Initial commit (code), runtime ignoré, secrets exclus

This commit is contained in:
nicoboy
2026-01-02 15:05:25 +01:00
commit 2fdcf50bc1
87 changed files with 11525 additions and 0 deletions

245
config/config.php Normal file
View File

@ -0,0 +1,245 @@
<?php
/**
* Configuration Globale Application
* Plateforme Mathématiques
*/
// Définir la racine de l'application
define('APP_ROOT', dirname(__DIR__));
// ================================================
// ENVIRONNEMENT
// ================================================
define('DEBUG_MODE', true); // Passer à false en production
define('ENVIRONMENT', 'development'); // development | production
// ================================================
// CHEMINS
// ================================================
define('BASE_URL', '/mathematiques');
define('ASSETS_URL', BASE_URL . '/assets');
define('UPLOADS_DIR', APP_ROOT . '/uploads');
define('LOGS_DIR', APP_ROOT . '/logs');
define('TEMP_DIR', APP_ROOT . '/temp');
define('BACKUP_DIR', APP_ROOT . '/backup');
// ================================================
// SESSIONS
// ================================================
define('SESSION_NAME', 'MATH_SESSION');
define('SESSION_LIFETIME', 3600 * 4); // 4 heures
define('SESSION_COOKIE_SECURE', false); // true en HTTPS
define('SESSION_COOKIE_HTTPONLY', true);
define('SESSION_COOKIE_SAMESITE', 'Lax');
// ================================================
// SÉCURITÉ
// ================================================
define('PASSWORD_MIN_LENGTH', 8);
define('MAX_LOGIN_ATTEMPTS', 5);
define('LOCKOUT_TIME', 900); // 15 minutes en secondes
define('CSRF_TOKEN_NAME', 'csrf_token');
define('CSRF_TOKEN_LIFETIME', 3600); // 1 heure
// ================================================
// UPLOADS
// ================================================
define('MAX_FILE_SIZE', 50 * 1024 * 1024); // 50 MB
define('ALLOWED_UPLOAD_TYPES', [
'json' => 'application/json',
'csv' => 'text/csv',
'pdf' => 'application/pdf',
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png'
]);
// ================================================
// PAGINATION
// ================================================
define('ITEMS_PER_PAGE', 20);
define('MAX_PAGINATION_LINKS', 5);
// ================================================
// LOGS
// ================================================
define('LOG_ERRORS', true);
define('LOG_QUERIES', DEBUG_MODE);
define('LOG_FILE_ERRORS', LOGS_DIR . '/errors.log');
define('LOG_FILE_ACCESS', LOGS_DIR . '/access.log');
// ================================================
// CONFIGURATION PHP
// ================================================
// Fuseau horaire
date_default_timezone_set('Europe/Paris');
// Affichage erreurs selon environnement
if (DEBUG_MODE) {
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
} else {
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
ini_set('display_errors', 0);
ini_set('display_startup_errors', 0);
}
// Logs personnalisés
ini_set('log_errors', LOG_ERRORS);
ini_set('error_log', LOG_FILE_ERRORS);
// Limites PHP
ini_set('memory_limit', '256M');
ini_set('max_execution_time', '300');
ini_set('upload_max_filesize', '50M');
ini_set('post_max_size', '50M');
// ================================================
// CONFIGURATION SESSIONS
// ================================================
ini_set('session.name', SESSION_NAME);
ini_set('session.gc_maxlifetime', SESSION_LIFETIME);
ini_set('session.cookie_lifetime', SESSION_LIFETIME);
ini_set('session.cookie_secure', SESSION_COOKIE_SECURE);
ini_set('session.cookie_httponly', SESSION_COOKIE_HTTPONLY);
ini_set('session.cookie_samesite', SESSION_COOKIE_SAMESITE);
ini_set('session.use_strict_mode', 1);
ini_set('session.use_only_cookies', 1);
// ================================================
// AUTOLOAD SIMPLE
// ================================================
spl_autoload_register(function($class) {
// Chercher dans includes/classes/
$file = APP_ROOT . '/includes/classes/' . $class . '.php';
if (file_exists($file)) {
require_once $file;
return true;
}
// Chercher dans src/models/
$file = APP_ROOT . '/src/models/' . $class . '.php';
if (file_exists($file)) {
require_once $file;
return true;
}
return false;
});
// ================================================
// FONCTIONS HELPER GLOBALES
// ================================================
/**
* Échapper HTML
*/
function h($string) {
return htmlspecialchars($string ?? '', ENT_QUOTES, 'UTF-8');
}
/**
* Redirection
*/
function redirect($url) {
$base = rtrim(BASE_URL, '/');
$url = ltrim($url, '/');
header("Location: {$base}/{$url}");
exit;
}
/**
* Générer URL
*/
function url($path = '') {
return BASE_URL . '/' . ltrim($path, '/');
}
/**
* Vérifier si utilisateur connecté
* CORRIGÉ : Utilise SessionManager pour synchronisation
*/
function isLoggedIn() {
// Charger SessionManager si pas déjà fait
if (!class_exists('SessionManager')) {
require_once __DIR__ . '/session.php';
}
return SessionManager::isLoggedIn();
}
/**
* Obtenir utilisateur courant
* CORRIGÉ : Utilise SessionManager pour synchronisation
*/
function currentUser() {
// Charger SessionManager si pas déjà fait
if (!class_exists('SessionManager')) {
require_once __DIR__ . '/session.php';
}
return SessionManager::getUser();
}
/**
* Vérifier type utilisateur
*/
function isEnseignant() {
if (!isLoggedIn()) return false;
$user = currentUser();
return ($user['id_type'] ?? 0) == 1;
}
function isEleve() {
if (!isLoggedIn()) return false;
$user = currentUser();
return in_array($user['id_type'] ?? 0, [2, 3]);
}
function isEleveClasse() {
if (!isLoggedIn()) return false;
$user = currentUser();
return ($user['id_type'] ?? 0) == 2;
}
function isEleveLibre() {
if (!isLoggedIn()) return false;
$user = currentUser();
return ($user['id_type'] ?? 0) == 3;
}
/**
* Logger un message
*/
function logMessage($message, $level = 'INFO') {
$timestamp = date('Y-m-d H:i:s');
$log_entry = "[{$timestamp}] [{$level}] {$message}\n";
error_log($log_entry, 3, LOG_FILE_ACCESS);
}
/**
* Formater une date
*/
function formatDate($date, $format = 'd/m/Y') {
if (empty($date)) return '';
$dt = new DateTime($date);
return $dt->format($format);
}
/**
* Formater date et heure
*/
function formatDateTime($datetime, $format = 'd/m/Y H:i') {
if (empty($datetime)) return '';
$dt = new DateTime($datetime);
return $dt->format($format);
}

103
config/database.sample.php Normal file
View File

@ -0,0 +1,103 @@
<?php
/**
* SAMPLE - Ne contient aucun secret.
* Copie en config/database.php (ignoré par git) OU utilise des variables d'environnement.
*/
return [
'host' => getenv('DB_HOST') ?: 'localhost',
'port' => (int)(getenv('DB_PORT') ?: 3306),
'dbname' => getenv('DB_NAME') ?: 'mathematiques_db',
// volontairement non renseignés dans le sample
'user' => getenv('DB_USER') ?: '',
'pass' => getenv('DB_PASS') ?: '',
'charset' => 'utf8mb4',
];
/**
* Classe Database - Gestion de la connexion PDO
*/
class Database {
private static $instance = null;
private $connection;
private function __construct() {
try {
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES " . DB_CHARSET
];
$this->connection = new PDO($dsn, DB_USER, DB_PASS, $options);
} catch (PDOException $e) {
error_log("Erreur de connexion à la base de données : " . $e->getMessage());
die("Erreur de connexion à la base de données. Veuillez contacter l'administrateur.");
}
}
/**
* Obtenir l'instance unique (Singleton)
*/
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Obtenir la connexion PDO
*/
public function getConnection() {
return $this->connection;
}
/**
* Exécuter une requête et récupérer UNE ligne
*/
public function fetchOne($query, $params = []) {
$stmt = $this->connection->prepare($query);
$stmt->execute($params);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* Exécuter une requête et récupérer TOUTES les lignes
*/
public function fetchAll($query, $params = []) {
$stmt = $this->connection->prepare($query);
$stmt->execute($params);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Exécuter une requête (INSERT, UPDATE, DELETE)
*/
public function query($query, $params = []) {
$stmt = $this->connection->prepare($query);
return $stmt->execute($params);
}
/**
* Insérer et retourner l'ID
*/
public function insert($query, $params = []) {
$this->query($query, $params);
return $this->connection->lastInsertId();
}
/**
* Empêcher le clonage de l'instance
*/
private function __clone() {}
/**
* Empêcher la désérialisation de l'instance
*/
public function __wakeup() {
throw new Exception("Cannot unserialize singleton");
}
}

207
config/session.php Normal file
View File

@ -0,0 +1,207 @@
<?php
/**
* Gestionnaire de sessions utilisateur
* Fichier : includes/session.php
*/
// ═══════════════════════════════════════════════════════════════
// CONFIGURATION COOKIES DE SESSION
// ═══════════════════════════════════════════════════════════════
// IMPORTANT : Doit être fait AVANT session_start() !
// Méthode recommandée : session_set_cookie_params()
// Définir le nom de session (doit être fait AVANT session_start)
session_name('PHPSESSID'); // Utiliser le nom standard pour cohérence
session_set_cookie_params([
'lifetime' => 0, // Cookie de session (expire à la fermeture navigateur)
'path' => '/mathematiques/', // Chemin - CRITIQUE pour que la session persiste
'domain' => '', // Domaine par défaut
'secure' => false, // HTTPS seulement (false car on utilise HTTP)
'httponly' => true, // Pas accessible via JavaScript (sécurité XSS)
'samesite' => 'Lax' // Protection CSRF (Lax = compatible avec redirections)
]);
// Configuration supplémentaire via ini_set (pour compatibilité)
ini_set('session.use_strict_mode', '1');
ini_set('session.gc_maxlifetime', '5400'); // 90 minutes d'inactivité max (x3)
// Démarrage de la session
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
/**
* Classe SessionManager - Gestion centralisée des sessions
*/
class SessionManager {
/**
* Durée de vie de la session en secondes (90 minutes)
*/
private const SESSION_LIFETIME = 5400; // 90 minutes (x3)
/**
* Vérifier si l'utilisateur est connecté
*/
public static function isLoggedIn() {
// Vérifier si la session contient un utilisateur
if (!isset($_SESSION['user_id'])) {
return false;
}
// Vérifier le timeout de session
if (isset($_SESSION['last_activity'])) {
$elapsed = time() - $_SESSION['last_activity'];
if ($elapsed > self::SESSION_LIFETIME) {
self::logout();
return false;
}
}
// Vérifier la cohérence du user agent (sécurité basique)
if (isset($_SESSION['user_agent'])) {
if ($_SESSION['user_agent'] !== $_SERVER['HTTP_USER_AGENT']) {
self::logout();
return false;
}
}
// Mettre à jour le timestamp d'activité
$_SESSION['last_activity'] = time();
return true;
}
/**
* Créer une session pour un utilisateur
*
* @param array $utilisateur Données de l'utilisateur depuis la BDD
*/
public static function login($utilisateur) {
// Régénérer l'ID de session pour éviter la fixation
session_regenerate_id(false); // false = garde ancien fichier pour éviter race condition
// Stocker les informations utilisateur
$_SESSION['user_id'] = $utilisateur['id_utilisateur'];
$_SESSION['login'] = $utilisateur['login'];
$_SESSION['nom'] = $utilisateur['nom'];
$_SESSION['prenom'] = $utilisateur['prenom'];
$_SESSION['id_type'] = $utilisateur['id_type'];
$_SESSION['type_libelle'] = $utilisateur['type_libelle'];
$_SESSION['id_classe'] = $utilisateur['id_classe'] ?? null;
$_SESSION['nom_classe'] = $utilisateur['nom_classe'] ?? null;
$_SESSION['niveau'] = $utilisateur['niveau'] ?? null;
// Informations de sécurité
$_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
$_SESSION['last_activity'] = time();
$_SESSION['login_time'] = time();
// Mettre à jour la date de dernière connexion
self::updateLastLogin($utilisateur['id_utilisateur']);
}
/**
* Détruire la session (déconnexion)
*/
public static function logout() {
$_SESSION = array();
if (isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time() - 3600, '/');
}
session_destroy();
}
/**
* Récupérer les informations de l'utilisateur connecté
*
* @return array|null Données utilisateur ou null si non connecté
*/
public static function getUser() {
if (!self::isLoggedIn()) {
return null;
}
return [
'id_utilisateur' => $_SESSION['user_id'],
'id' => $_SESSION['user_id'], // Alias pour compatibilité
'login' => $_SESSION['login'],
'nom' => $_SESSION['nom'],
'prenom' => $_SESSION['prenom'],
'id_type' => $_SESSION['id_type'],
'type_libelle' => $_SESSION['type_libelle'],
'id_classe' => $_SESSION['id_classe'],
'nom_classe' => $_SESSION['nom_classe'],
'niveau' => $_SESSION['niveau']
];
}
/**
* Vérifier si l'utilisateur est un élève
*/
public static function isEleve() {
return self::isLoggedIn() &&
(isset($_SESSION['id_type']) &&
($_SESSION['id_type'] == 2 || $_SESSION['id_type'] == 3));
}
/**
* Vérifier si l'utilisateur est un élève libre (soutien)
*/
public static function isEleveLibre() {
return self::isLoggedIn() &&
(isset($_SESSION['id_type']) && $_SESSION['id_type'] == 3);
}
/**
* Vérifier si l'utilisateur est un élève de classe fixe
*/
public static function isEleveClasse() {
return self::isLoggedIn() &&
(isset($_SESSION['id_type']) && $_SESSION['id_type'] == 2);
}
/**
* Vérifier si l'utilisateur est un enseignant
*/
public static function isEnseignant() {
return self::isLoggedIn() &&
(isset($_SESSION['id_type']) && $_SESSION['id_type'] == 1);
}
/**
* Mettre à jour la date de dernière connexion
*
* @param int $userId ID de l'utilisateur
*/
private static function updateLastLogin($userId) {
try {
require_once __DIR__ . '/../config/database.php';
$db = Database::getInstance();
$query = "UPDATE utilisateurs
SET date_derniere_connexion = NOW()
WHERE id_utilisateur = ?";
$db->query($query, [$userId]);
} catch (Exception $e) {
error_log("Erreur mise à jour dernière connexion : " . $e->getMessage());
}
}
/**
* Obtenir le temps restant de session en secondes
*/
public static function getRemainingTime() {
if (!self::isLoggedIn()) {
return 0;
}
$elapsed = time() - $_SESSION['last_activity'];
return max(0, self::SESSION_LIFETIME - $elapsed);
}
}