Initial commit (code), runtime ignoré, secrets exclus
This commit is contained in:
245
config/config.php
Normal file
245
config/config.php
Normal 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
103
config/database.sample.php
Normal 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
207
config/session.php
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user