Files
geo/api/src/Controllers/UserController.php
Pierre 206c76c7db feat: Livraison version 3.0.6
- Amélioration de la gestion des entités et des utilisateurs
- Mise à jour des modèles Amicale et Client avec champs supplémentaires
- Ajout du service de logging et amélioration du chargement UI
- Refactoring des formulaires utilisateur et amicale
- Intégration de file_picker et image_picker pour la gestion des fichiers
- Amélioration de la gestion des membres et de leur suppression
- Optimisation des performances de l'API
- Mise à jour de la documentation technique

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-08 20:33:54 +02:00

1018 lines
38 KiB
PHP
Executable File

<?php
declare(strict_types=1);
namespace App\Controllers;
require_once __DIR__ . '/../Services/LogService.php';
require_once __DIR__ . '/../Services/ApiService.php';
use PDO;
use PDOException;
use Database;
use AppConfig;
use Request;
use Response;
use Session;
use LogService;
use ApiService;
class UserController {
private PDO $db;
private AppConfig $appConfig;
public function __construct() {
$this->db = Database::getInstance();
$this->appConfig = AppConfig::getInstance();
}
public function getUsers(): void {
Session::requireAuth();
// Vérification des droits d'accès (rôle administrateur)
// Récupérer le rôle de l'utilisateur depuis la base de données
$userId = Session::getUserId();
$stmt = $this->db->prepare('SELECT fk_role FROM users WHERE id = ?');
$stmt->execute([$userId]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$userRole = $result ? $result['fk_role'] : null;
if ($userRole != '1' && $userRole != '2') { // Supposons que 1 et 2 sont des rôles admin
Response::json([
'status' => 'error',
'message' => 'Accès non autorisé'
], 403);
return;
}
try {
$stmt = $this->db->prepare('
SELECT
u.id,
u.encrypted_email,
u.encrypted_name,
u.first_name,
u.fk_role as role,
u.fk_entite,
u.chk_active,
u.created_at,
u.updated_at,
e.encrypted_name as entite_name
FROM users u
LEFT JOIN entites e ON u.fk_entite = e.id
ORDER BY u.created_at DESC
');
$stmt->execute();
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Déchiffrement des données sensibles pour chaque utilisateur
foreach ($users as &$user) {
$user['email'] = ApiService::decryptSearchableData($user['encrypted_email']);
$user['name'] = ApiService::decryptData($user['encrypted_name']);
if (!empty($user['entite_name'])) {
$user['entite_name'] = ApiService::decryptData($user['entite_name']);
}
// Suppression des champs chiffrés
unset($user['encrypted_email']);
unset($user['encrypted_name']);
}
Response::json([
'status' => 'success',
'users' => $users
]);
} catch (PDOException $e) {
LogService::log('Erreur lors de la récupération des utilisateurs GeoSector', [
'level' => 'error',
'error' => $e->getMessage()
]);
Response::json([
'status' => 'error',
'message' => 'Erreur serveur'
], 500);
}
}
public function getUserById(string $id): void {
Session::requireAuth();
// Vérification des droits d'accès (rôle administrateur ou utilisateur lui-même)
$currentUserId = Session::getUserId();
// Récupérer le rôle de l'utilisateur depuis la base de données
$stmt = $this->db->prepare('SELECT fk_role FROM users WHERE id = ?');
$stmt->execute([$currentUserId]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$userRole = $result ? $result['fk_role'] : null;
if ($userRole != '1' && $userRole != '2' && $currentUserId != $id) {
Response::json([
'status' => 'error',
'message' => 'Accès non autorisé'
], 403);
return;
}
try {
$stmt = $this->db->prepare('
SELECT
u.id,
u.encrypted_email,
u.encrypted_name,
u.first_name,
u.sect_name,
u.encrypted_phone,
u.encrypted_mobile,
u.fk_role as role,
u.fk_entite,
u.chk_alert_email,
u.chk_suivi,
u.date_naissance,
u.date_embauche,
u.chk_active,
u.created_at,
u.updated_at,
e.encrypted_name as entite_name,
e.adresse1,
e.adresse2,
e.cp,
e.ville,
e.fk_region
FROM users u
LEFT JOIN entites e ON u.fk_entite = e.id
WHERE u.id = ?
');
$stmt->execute([$id]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user) {
Response::json([
'status' => 'error',
'message' => 'Utilisateur non trouvé'
], 404);
return;
}
// Déchiffrement des données sensibles
$user['email'] = ApiService::decryptSearchableData($user['encrypted_email']);
$user['name'] = ApiService::decryptData($user['encrypted_name']);
$user['phone'] = ApiService::decryptData($user['encrypted_phone'] ?? '');
$user['mobile'] = ApiService::decryptData($user['encrypted_mobile'] ?? '');
if (!empty($user['entite_name'])) {
$user['entite_name'] = ApiService::decryptData($user['entite_name']);
}
// Suppression des champs chiffrés
unset($user['encrypted_email']);
unset($user['encrypted_name']);
unset($user['encrypted_phone']);
unset($user['encrypted_mobile']);
Response::json([
'status' => 'success',
'user' => $user
]);
} catch (PDOException $e) {
LogService::log('Erreur lors de la récupération de l\'utilisateur GeoSector', [
'level' => 'error',
'error' => $e->getMessage(),
'userId' => $id
]);
Response::json([
'status' => 'error',
'message' => 'Erreur serveur'
], 500);
}
}
public function createUser(): void {
Session::requireAuth();
// Vérification des droits d'accès (rôle administrateur)
$currentUserId = Session::getUserId();
// Récupérer le rôle de l'utilisateur depuis la base de données
$stmt = $this->db->prepare('SELECT fk_role FROM users WHERE id = ?');
$stmt->execute([$currentUserId]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$userRole = $result ? $result['fk_role'] : null;
if ($userRole != '1' && $userRole != '2') {
Response::json([
'status' => 'error',
'message' => 'Accès non autorisé'
], 403);
return;
}
try {
$data = Request::getJson();
$currentUserId = Session::getUserId();
// Validation des données requises
if (!isset($data['email'], $data['name'])) {
Response::json([
'status' => 'error',
'message' => 'Email et nom requis'
], 400);
return;
}
$email = trim(strtolower($data['email']));
$name = trim($data['name']);
$firstName = isset($data['first_name']) ? trim($data['first_name']) : '';
$role = isset($data['role']) ? (int)$data['role'] : 1;
$entiteId = isset($data['fk_entite']) ? (int)$data['fk_entite'] : 1;
// Récupérer les paramètres de gestion de l'entité
$entiteStmt = $this->db->prepare('
SELECT chk_mdp_manuel, chk_username_manuel, code_postal, ville
FROM entites
WHERE id = ?
');
$entiteStmt->execute([$entiteId]);
$entiteConfig = $entiteStmt->fetch(PDO::FETCH_ASSOC);
if (!$entiteConfig) {
Response::json([
'status' => 'error',
'message' => 'Entité non trouvée'
], 404);
return;
}
$chkMdpManuel = (int)$entiteConfig['chk_mdp_manuel'];
$chkUsernameManuel = (int)$entiteConfig['chk_username_manuel'];
// Vérification des longueurs d'entrée
if (strlen($email) > 75 || strlen($name) > 50) {
Response::json([
'status' => 'error',
'message' => 'Email ou nom trop long'
], 400);
return;
}
// Validation de l'email
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
Response::json([
'status' => 'error',
'message' => 'Format d\'email invalide'
], 400);
return;
}
// Chiffrement des données sensibles
$encryptedEmail = ApiService::encryptSearchableData($email);
$encryptedName = ApiService::encryptData($name);
// Vérification de l'existence de l'email
$checkStmt = $this->db->prepare('SELECT id FROM users WHERE encrypted_email = ?');
$checkStmt->execute([$encryptedEmail]);
if ($checkStmt->fetch()) {
Response::json([
'status' => 'error',
'message' => 'Cet email est déjà utilisé'
], 409);
return;
}
// Gestion du USERNAME selon chk_username_manuel
$encryptedUsername = '';
if ($chkUsernameManuel === 1) {
// Username manuel obligatoire
if (!isset($data['username']) || empty(trim($data['username']))) {
Response::json([
'status' => 'error',
'message' => 'Le nom d\'utilisateur est requis pour cette entité'
], 400);
return;
}
$username = trim(strtolower($data['username']));
// Validation du format du username
if (!preg_match('/^[a-z][a-z0-9._-]{9,29}$/', $username)) {
Response::json([
'status' => 'error',
'message' => 'Format du nom d\'utilisateur invalide (10-30 caractères, commence par une lettre, caractères autorisés: a-z, 0-9, ., -, _)'
], 400);
return;
}
$encryptedUsername = ApiService::encryptSearchableData($username);
// Vérification de l'unicité du username
$checkUsernameStmt = $this->db->prepare('SELECT id FROM users WHERE encrypted_user_name = ?');
$checkUsernameStmt->execute([$encryptedUsername]);
if ($checkUsernameStmt->fetch()) {
Response::json([
'status' => 'error',
'message' => 'Ce nom d\'utilisateur est déjà utilisé dans GeoSector'
], 409);
return;
}
} else {
// Génération automatique du username
$username = ApiService::generateUserName(
$this->db,
$name,
$entiteConfig['code_postal'] ?? '00000',
$entiteConfig['ville'] ?? 'ville',
10
);
$encryptedUsername = ApiService::encryptSearchableData($username);
}
// Gestion du MOT DE PASSE selon chk_mdp_manuel
$password = '';
$passwordHash = '';
if ($chkMdpManuel === 1) {
// Mot de passe manuel obligatoire
if (!isset($data['password']) || empty($data['password'])) {
Response::json([
'status' => 'error',
'message' => 'Le mot de passe est requis pour cette entité'
], 400);
return;
}
$password = $data['password'];
// Validation du mot de passe (minimum 8 caractères)
if (strlen($password) < 8) {
Response::json([
'status' => 'error',
'message' => 'Le mot de passe doit contenir au moins 8 caractères'
], 400);
return;
}
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
} else {
// Génération automatique du mot de passe
$password = ApiService::generateSecurePassword();
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
}
// Préparation des champs optionnels
$phone = isset($data['phone']) ? ApiService::encryptData(trim($data['phone'])) : null;
$mobile = isset($data['mobile']) ? ApiService::encryptData(trim($data['mobile'])) : null;
$sectName = isset($data['sect_name']) ? trim($data['sect_name']) : '';
$alertEmail = isset($data['chk_alert_email']) ? (int)$data['chk_alert_email'] : 1;
$suivi = isset($data['chk_suivi']) ? (int)$data['chk_suivi'] : 0;
$dateNaissance = isset($data['date_naissance']) ? $data['date_naissance'] : null;
$dateEmbauche = isset($data['date_embauche']) ? $data['date_embauche'] : null;
// Insertion en base de données
$stmt = $this->db->prepare('
INSERT INTO users (
encrypted_email, encrypted_user_name, user_pass_hash, encrypted_name, first_name,
sect_name, encrypted_phone, encrypted_mobile, fk_role,
fk_entite, chk_alert_email, chk_suivi,
date_naissance, date_embauche,
created_at, fk_user_creat, chk_active
) VALUES (
?, ?, ?, ?, ?,
?, ?, ?, ?,
?, ?, ?,
?, ?,
NOW(), ?, 1
)
');
$stmt->execute([
$encryptedEmail,
$encryptedUsername,
$passwordHash,
$encryptedName,
$firstName,
$sectName,
$phone,
$mobile,
$role,
$entiteId,
$alertEmail,
$suivi,
$dateNaissance,
$dateEmbauche,
$currentUserId
]);
$userId = $this->db->lastInsertId();
// Envoi des emails séparés pour plus de sécurité
// 1er email : TOUJOURS envoyer l'identifiant (username)
$usernameEmailData = [
'email' => $email,
'username' => $username,
'name' => $name
];
ApiService::sendEmail($email, $name, 'welcome_username', $usernameEmailData);
// 2ème email : Envoyer le mot de passe (toujours, qu'il soit manuel ou généré)
// Attendre un peu entre les deux emails pour éviter qu'ils arrivent dans le mauvais ordre
sleep(1);
$passwordEmailData = [
'email' => $email,
'password' => $password,
'name' => $name
];
ApiService::sendEmail($email, $name, 'welcome_password', $passwordEmailData);
LogService::log('Utilisateur GeoSector créé', [
'level' => 'info',
'createdBy' => $currentUserId,
'newUserId' => $userId,
'email' => $email,
'username' => $username,
'usernameManual' => $chkUsernameManuel === 1 ? 'oui' : 'non',
'passwordManual' => $chkMdpManuel === 1 ? 'oui' : 'non',
'emailsSent' => '2 emails (username + password)'
]);
// Préparer la réponse avec les informations de connexion si générées automatiquement
$responseData = [
'status' => 'success',
'message' => 'Utilisateur créé avec succès',
'id' => $userId
];
// Ajouter le username dans la réponse (toujours, car nécessaire pour la connexion)
$responseData['username'] = $username;
// Ajouter le mot de passe seulement si généré automatiquement
if ($chkMdpManuel === 0) {
$responseData['password'] = $password;
}
Response::json($responseData, 201);
} catch (PDOException $e) {
LogService::log('Erreur lors de la création d\'un utilisateur GeoSector', [
'level' => 'error',
'error' => $e->getMessage(),
'code' => $e->getCode()
]);
Response::json([
'status' => 'error',
'message' => 'Erreur serveur'
], 500);
}
}
public function updateUser(string $id): void {
Session::requireAuth();
// Vérification des droits d'accès (rôle administrateur ou utilisateur lui-même)
$currentUserId = Session::getUserId();
// Récupérer le rôle de l'utilisateur depuis la base de données
$stmt = $this->db->prepare('SELECT fk_role FROM users WHERE id = ?');
$stmt->execute([$currentUserId]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$userRole = $result ? $result['fk_role'] : null;
if ($userRole != '1' && $userRole != '2' && $currentUserId != $id) {
Response::json([
'status' => 'error',
'message' => 'Accès non autorisé'
], 403);
return;
}
try {
$data = Request::getJson();
// Vérification qu'il y a des données à mettre à jour
if (empty($data)) {
Response::json([
'status' => 'error',
'message' => 'Aucune donnée à mettre à jour'
], 400);
return;
}
// Construction de la requête UPDATE dynamique
$updateFields = [];
$params = ['id' => $id];
// Traitement des champs à chiffrer
if (isset($data['email'])) {
// Vérification que l'email n'est pas déjà utilisé par un autre utilisateur
$email = trim(strtolower($data['email']));
$encryptedEmail = ApiService::encryptSearchableData($email);
$checkStmt = $this->db->prepare('SELECT id FROM users WHERE encrypted_email = ? AND id != ?');
$checkStmt->execute([$encryptedEmail, $id]);
if ($checkStmt->fetch()) {
Response::json([
'status' => 'error',
'message' => 'Cet email est déjà utilisé par un autre utilisateur'
], 409);
return;
}
$updateFields[] = "encrypted_email = :encrypted_email";
$params['encrypted_email'] = $encryptedEmail;
}
if (isset($data['name'])) {
$updateFields[] = "encrypted_name = :encrypted_name";
$params['encrypted_name'] = ApiService::encryptData(trim($data['name']));
}
if (isset($data['phone'])) {
$updateFields[] = "encrypted_phone = :encrypted_phone";
$params['encrypted_phone'] = ApiService::encryptData(trim($data['phone']));
}
if (isset($data['mobile'])) {
$updateFields[] = "encrypted_mobile = :encrypted_mobile";
$params['encrypted_mobile'] = ApiService::encryptData(trim($data['mobile']));
}
// Traitement des champs non chiffrés
$nonEncryptedFields = [
'first_name',
'sect_name',
'fk_role',
'fk_entite',
'chk_alert_email',
'chk_suivi',
'date_naissance',
'date_embauche',
'chk_active'
];
foreach ($nonEncryptedFields as $field) {
if (isset($data[$field])) {
$updateFields[] = "$field = :$field";
$params[$field] = is_string($data[$field]) ? trim($data[$field]) : $data[$field];
}
}
// Mise à jour du mot de passe si fourni
if (isset($data['password']) && !empty($data['password'])) {
if (strlen($data['password']) < 8) {
Response::json([
'status' => 'error',
'message' => 'Le mot de passe doit contenir au moins 8 caractères'
], 400);
return;
}
$updateFields[] = "user_pass_hash = :password";
$params['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
}
// Ajout des champs de mise à jour
$updateFields[] = "updated_at = NOW()";
$updateFields[] = "fk_user_modif = :modifier_id";
$params['modifier_id'] = $currentUserId;
if (!empty($updateFields)) {
$sql = 'UPDATE users SET ' . implode(', ', $updateFields) . ' WHERE id = :id';
$stmt = $this->db->prepare($sql);
$stmt->execute($params);
if ($stmt->rowCount() === 0) {
Response::json([
'status' => 'warning',
'message' => 'Aucune modification effectuée'
]);
return;
}
LogService::log('Utilisateur GeoSector mis à jour', [
'level' => 'info',
'modifiedBy' => $currentUserId,
'userId' => $id,
'fields' => array_keys($data),
]);
Response::json([
'status' => 'success',
'message' => 'Utilisateur mis à jour avec succès'
]);
} else {
Response::json([
'status' => 'warning',
'message' => 'Aucune donnée valide à mettre à jour'
]);
}
} catch (PDOException $e) {
LogService::log('Erreur lors de la mise à jour d\'un utilisateur GeoSector', [
'level' => 'error',
'error' => $e->getMessage(),
'userId' => $id
]);
Response::json([
'status' => 'error',
'message' => 'Erreur serveur'
], 500);
}
}
public function deleteUser(string $id): void {
Session::requireAuth();
$currentUserId = Session::getUserId();
// Récupérer les infos de l'utilisateur courant
$stmt = $this->db->prepare('SELECT fk_role, fk_entite FROM users WHERE id = ?');
$stmt->execute([$currentUserId]);
$currentUser = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$currentUser) {
Response::json([
'status' => 'error',
'message' => 'Utilisateur courant non trouvé'
], 403);
return;
}
$userRole = (int)$currentUser['fk_role'];
$userEntite = $currentUser['fk_entite'];
// Empêcher la suppression de son propre compte
if ($currentUserId == $id) {
Response::json([
'status' => 'error',
'message' => 'Vous ne pouvez pas supprimer votre propre compte'
], 400);
return;
}
// Récupérer l'utilisateur cible
$stmt2 = $this->db->prepare('SELECT fk_entite FROM users WHERE id = ?');
$stmt2->execute([$id]);
$userToDelete = $stmt2->fetch(PDO::FETCH_ASSOC);
if (!$userToDelete) {
Response::json([
'status' => 'error',
'message' => 'Utilisateur cible non trouvé'
], 404);
return;
}
// Contrôle des droits
if ($userRole === 1) {
Response::json([
'status' => 'error',
'message' => "Vous n'avez pas le droit de supprimer un utilisateur"
], 403);
return;
} elseif ($userRole === 2) {
if ($userEntite != $userToDelete['fk_entite']) {
Response::json([
'status' => 'error',
'message' => "Vous n'avez pas le droit de supprimer un utilisateur d'une autre amicale"
], 403);
return;
}
}
// fk_role > 2 : tout est permis (hors auto-suppression)
// ——— Gestion du transfert éventuel ———
$transferTo = isset($_GET['transfer_to']) ? trim($_GET['transfer_to']) : null;
if ($transferTo) {
try {
// Transférer TOUS les passages de l'utilisateur vers l'utilisateur désigné
$stmt3 = $this->db->prepare('
UPDATE ope_pass
SET fk_user = :new_user_id
WHERE fk_user = :delete_user_id
');
$stmt3->execute([
'new_user_id' => $transferTo,
'delete_user_id' => $id
]);
$transferredCount = $stmt3->rowCount();
LogService::log('Passages transférés avant suppression utilisateur', [
'level' => 'info',
'from_user' => $id,
'to_user' => $transferTo,
'passages_transferred' => $transferredCount
]);
} catch (PDOException $e) {
Response::json([
'status' => 'error',
'message' => 'Erreur lors du transfert des passages',
'error' => $e->getMessage()
], 500);
return;
}
}
// —— Suppression réelle de l'utilisateur ——
try {
// Supprimer les enregistrements dépendants dans ope_users
$stmtOpeUsers = $this->db->prepare('DELETE FROM ope_users WHERE fk_user = ?');
$stmtOpeUsers->execute([$id]);
// Supprimer les enregistrements dépendants dans ope_users_sectors
$stmtOpeUsersSectors = $this->db->prepare('DELETE FROM ope_users_sectors WHERE fk_user = ?');
$stmtOpeUsersSectors->execute([$id]);
$stmt = $this->db->prepare('DELETE FROM users WHERE id = ?');
$stmt->execute([$id]);
if ($stmt->rowCount() === 0) {
Response::json([
'status' => 'error',
'message' => 'Utilisateur non trouvé ou déjà supprimé'
], 404);
return;
}
LogService::log('Utilisateur GeoSector supprimé', [
'level' => 'info',
'deletedBy' => $currentUserId,
'userId' => $id,
'passage_transfer' => $transferTo ? "Tous les passages transférés vers utilisateur $transferTo" : 'Aucun transfert'
]);
Response::json([
'status' => 'success',
'message' => 'Utilisateur supprimé avec succès'
]);
} catch (PDOException $e) {
LogService::log('Erreur lors de la suppression d\'un utilisateur GeoSector', [
'level' => 'error',
'error' => $e->getMessage(),
'userId' => $id
]);
Response::json([
'status' => 'error',
'message' => 'Erreur serveur'
], 500);
}
}
public function resetPassword(string $id): void {
Session::requireAuth();
$currentUserId = Session::getUserId();
// Récupérer les infos de l'utilisateur courant
$stmt = $this->db->prepare('SELECT fk_role, fk_entite, chk_active FROM users WHERE id = ?');
$stmt->execute([$currentUserId]);
$currentUser = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$currentUser) {
Response::json([
'status' => 'error',
'message' => 'Utilisateur courant non trouvé'
], 403);
return;
}
// Vérifier que l'utilisateur courant est actif
if ($currentUser['chk_active'] != 1) {
Response::json([
'status' => 'error',
'message' => 'Votre compte n\'est pas actif'
], 403);
return;
}
$userRole = (int)$currentUser['fk_role'];
$userEntite = $currentUser['fk_entite'];
// Récupérer l'utilisateur cible
$stmt = $this->db->prepare('
SELECT id, encrypted_email, encrypted_name, fk_entite, chk_active
FROM users
WHERE id = ?
');
$stmt->execute([$id]);
$targetUser = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$targetUser) {
Response::json([
'status' => 'error',
'message' => 'Utilisateur non trouvé'
], 404);
return;
}
// Vérifier que l'utilisateur est actif
if ($targetUser['chk_active'] != 1) {
Response::json([
'status' => 'error',
'message' => 'L\'utilisateur n\'est pas actif'
], 400);
return;
}
// Contrôle des droits selon le rôle
if ($userRole === 1) {
// Role 1 : peut uniquement réinitialiser son propre mot de passe
if ($currentUserId != $id) {
Response::json([
'status' => 'error',
'message' => 'Vous ne pouvez réinitialiser que votre propre mot de passe'
], 403);
return;
}
} elseif ($userRole === 2) {
// Role 2 : peut réinitialiser les mots de passe de sa propre entité
if ($userEntite != $targetUser['fk_entite']) {
Response::json([
'status' => 'error',
'message' => 'Vous ne pouvez réinitialiser que les mots de passe des utilisateurs de votre entité'
], 403);
return;
}
}
// Role > 2 : peut tout faire
try {
// Déchiffrement des données
$email = ApiService::decryptSearchableData($targetUser['encrypted_email']);
$name = ApiService::decryptData($targetUser['encrypted_name']);
// Génération d'un nouveau mot de passe sécurisé
$newPassword = ApiService::generateSecurePassword();
$passwordHash = password_hash($newPassword, PASSWORD_DEFAULT);
// Mise à jour du mot de passe en base de données
$updateStmt = $this->db->prepare('
UPDATE users
SET user_pass_hash = :password,
updated_at = NOW(),
fk_user_modif = :modifier_id
WHERE id = :id
');
$updateStmt->execute([
'password' => $passwordHash,
'modifier_id' => $currentUserId,
'id' => $id
]);
// Envoi de l'email avec le nouveau mot de passe
ApiService::sendEmail($email, $name, 'password_reset', ['password' => $newPassword]);
LogService::log('Mot de passe réinitialisé', [
'level' => 'info',
'resetBy' => $currentUserId,
'userId' => $id,
'email' => $email
]);
Response::json([
'status' => 'success',
'message' => 'Mot de passe réinitialisé avec succès. Un email a été envoyé à l\'utilisateur.'
]);
} catch (PDOException $e) {
LogService::log('Erreur lors de la réinitialisation du mot de passe', [
'level' => 'error',
'error' => $e->getMessage(),
'userId' => $id
]);
Response::json([
'status' => 'error',
'message' => 'Erreur serveur'
], 500);
}
}
public function checkUsername(): void {
Session::requireAuth();
try {
$data = Request::getJson();
// Validation de la présence du username
if (!isset($data['username']) || empty(trim($data['username']))) {
Response::json([
'status' => 'error',
'message' => 'Username requis pour la vérification'
], 400);
return;
}
$username = trim(strtolower($data['username']));
// Validation du format du username
if (!preg_match('/^[a-z][a-z0-9._-]{9,29}$/', $username)) {
Response::json([
'status' => 'error',
'message' => 'Format invalide : 10-30 caractères, commence par une lettre, caractères autorisés: a-z, 0-9, ., -, _',
'available' => false
], 400);
return;
}
// Chiffrement du username pour la recherche
$encryptedUsername = ApiService::encryptSearchableData($username);
// Vérification de l'existence dans la base
$stmt = $this->db->prepare('
SELECT id, encrypted_name, fk_entite
FROM users
WHERE encrypted_user_name = ?
LIMIT 1
');
$stmt->execute([$encryptedUsername]);
$existingUser = $stmt->fetch(PDO::FETCH_ASSOC);
if ($existingUser) {
// Username déjà pris - générer des suggestions
$baseName = substr($username, 0, -2); // Enlever les 2 derniers caractères
$suggestions = [];
// Génération de 3 suggestions
$suggestions[] = $username . '_' . rand(10, 99);
$suggestions[] = $baseName . rand(100, 999);
// Suggestion avec l'année courante
$year = date('y');
$suggestions[] = $username . $year;
// Vérifier que les suggestions sont aussi disponibles
$availableSuggestions = [];
foreach ($suggestions as $suggestion) {
$encryptedSuggestion = ApiService::encryptSearchableData($suggestion);
$checkStmt = $this->db->prepare('SELECT id FROM users WHERE encrypted_user_name = ?');
$checkStmt->execute([$encryptedSuggestion]);
if (!$checkStmt->fetch()) {
$availableSuggestions[] = $suggestion;
}
}
// Si aucune suggestion n'est disponible, en générer d'autres
if (empty($availableSuggestions)) {
for ($i = 0; $i < 3; $i++) {
$randomSuffix = rand(1000, 9999);
$availableSuggestions[] = $baseName . $randomSuffix;
}
}
Response::json([
'status' => 'success',
'available' => false,
'message' => 'Ce nom d\'utilisateur est déjà utilisé',
'suggestions' => array_slice($availableSuggestions, 0, 3)
]);
} else {
// Username disponible
Response::json([
'status' => 'success',
'available' => true,
'message' => 'Nom d\'utilisateur disponible',
'username' => $username
]);
}
} catch (PDOException $e) {
LogService::log('Erreur lors de la vérification du username', [
'level' => 'error',
'error' => $e->getMessage()
]);
Response::json([
'status' => 'error',
'message' => 'Erreur serveur lors de la vérification'
], 500);
}
}
// Méthodes auxiliaires
private function validateUpdateData(array $data): ?string {
// Validation de l'email
if (isset($data['email'])) {
if (!filter_var(trim($data['email']), FILTER_VALIDATE_EMAIL)) {
return 'Format d\'email invalide';
}
}
// Validation du nom
if (isset($data['name']) && strlen(trim($data['name'])) < 2) {
return 'Le nom doit contenir au moins 2 caractères';
}
// Validation du téléphone
if (isset($data['phone']) && !empty($data['phone'])) {
if (!preg_match('/^[0-9+\s()-]{6,20}$/', trim($data['phone']))) {
return 'Format de téléphone invalide';
}
}
// Validation du mobile
if (isset($data['mobile']) && !empty($data['mobile'])) {
if (!preg_match('/^[0-9+\s()-]{6,20}$/', trim($data['mobile']))) {
return 'Format de mobile invalide';
}
}
return null;
}
}