Ajout du dossier api avec la géolocalisation automatique des casernes de pompiers

This commit is contained in:
d6soft
2025-05-16 21:03:04 +02:00
parent 69dcff42f8
commit f4f7882963
143 changed files with 24329 additions and 1 deletions

View File

@@ -0,0 +1,632 @@
<?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;
use Exception;
class EntiteController {
private PDO $db;
private AppConfig $appConfig;
public function __construct() {
$this->db = Database::getInstance();
$this->appConfig = AppConfig::getInstance();
}
/**
* Crée une nouvelle entité (amicale) si elle n'existe pas déjà avec le code postal spécifié
*
* @param string $name Nom de l'amicale
* @param string $postalCode Code postal
* @param string $cityName Nom de la ville
* @return array|false Tableau contenant l'ID de l'entité créée ou false en cas d'erreur
* @throws Exception Si une entité existe déjà avec ce code postal
*/
public function createEntite(string $name, string $postalCode, string $cityName): array|false {
try {
// Vérification que le code postal n'existe pas déjà
$stmt = $this->db->prepare('SELECT id FROM entites WHERE code_postal = ?');
$stmt->execute([$postalCode]);
if ($stmt->fetch()) {
throw new Exception('Une amicale existe déjà sur ce code postal');
}
// Chiffrement du nom
$encryptedName = ApiService::encryptData($name);
// Insertion de la nouvelle entité
$stmt = $this->db->prepare('
INSERT INTO entites (
encrypted_name,
code_postal,
ville,
fk_type,
created_at,
chk_active
) VALUES (?, ?, ?, 1, NOW(), 1)
');
$stmt->execute([
$encryptedName,
$postalCode,
$cityName
]);
$entiteId = $this->db->lastInsertId();
if (!$entiteId) {
throw new Exception('Erreur lors de la création de l\'entité');
}
LogService::log('Création d\'une nouvelle entité GeoSector', [
'level' => 'info',
'entiteId' => $entiteId,
'name' => $name,
'postalCode' => $postalCode,
'cityName' => $cityName
]);
return [
'id' => $entiteId,
'name' => $name,
'postalCode' => $postalCode,
'cityName' => $cityName
];
} catch (Exception $e) {
LogService::log('Erreur lors de la création de l\'entité GeoSector', [
'level' => 'error',
'error' => $e->getMessage(),
'name' => $name,
'postalCode' => $postalCode,
'cityName' => $cityName
]);
throw $e;
}
}
/**
* Récupère une entité par son ID
*
* @param int $id ID de l'entité
* @return array|false Données de l'entité ou false si non trouvée
*/
public function getEntiteById(int $id): array|false {
try {
$stmt = $this->db->prepare('
SELECT id, encrypted_name, code_postal, ville, fk_type, chk_active
FROM entites
WHERE id = ? AND chk_active = 1
');
$stmt->execute([$id]);
$entite = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$entite) {
return false;
}
// Déchiffrement du nom
$entite['name'] = ApiService::decryptData($entite['encrypted_name']);
unset($entite['encrypted_name']);
return $entite;
} catch (Exception $e) {
LogService::log('Erreur lors de la récupération de l\'entité GeoSector', [
'level' => 'error',
'error' => $e->getMessage(),
'id' => $id
]);
return false;
}
}
/**
* Récupère une entité par son code postal
*
* @param string $postalCode Code postal
* @return array|false Données de l'entité ou false si non trouvée
*/
public function getEntiteByPostalCode(string $postalCode): array|false {
try {
$stmt = $this->db->prepare('
SELECT id, encrypted_name, code_postal, ville, fk_type, chk_active
FROM entites
WHERE code_postal = ? AND chk_active = 1
');
$stmt->execute([$postalCode]);
$entite = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$entite) {
return false;
}
// Déchiffrement du nom
$entite['name'] = ApiService::decryptData($entite['encrypted_name']);
unset($entite['encrypted_name']);
return $entite;
} catch (Exception $e) {
LogService::log('Erreur lors de la récupération de l\'entité GeoSector par code postal', [
'level' => 'error',
'error' => $e->getMessage(),
'postalCode' => $postalCode
]);
return false;
}
}
/**
* Vérifie si une entité existe avec le code postal spécifié, et en crée une nouvelle si nécessaire
*
* @param string $name Nom de l'amicale
* @param string $postalCode Code postal
* @return int ID de l'entité créée ou existante
* @throws Exception Si une entité existe déjà avec ce code postal
*/
public function getOrCreateEntiteByPostalCode(string $name, string $postalCode): int {
try {
// Vérification que le code postal n'existe pas déjà
$stmt = $this->db->prepare('SELECT COUNT(*) as count FROM entites WHERE code_postal = ?');
$stmt->execute([$postalCode]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result && $result['count'] > 0) {
throw new Exception('Une amicale est déjà inscrite à ce code postal');
}
// Chiffrement du nom
$encryptedName = ApiService::encryptData($name);
// Insertion de la nouvelle entité
$stmt = $this->db->prepare('
INSERT INTO entites (
encrypted_name,
code_postal,
ville,
fk_type,
created_at,
chk_active
) VALUES (?, ?, "", 1, NOW(), 1)
');
$stmt->execute([
$encryptedName,
$postalCode
]);
$entiteId = (int)$this->db->lastInsertId();
if (!$entiteId) {
throw new Exception('Erreur lors de la création de l\'entité');
}
LogService::log('Création d\'une nouvelle entité GeoSector via getOrCreateEntiteByPostalCode', [
'level' => 'info',
'entiteId' => $entiteId,
'name' => $name,
'postalCode' => $postalCode
]);
return $entiteId;
} catch (Exception $e) {
LogService::log('Erreur lors de la vérification/création de l\'entité GeoSector', [
'level' => 'error',
'error' => $e->getMessage(),
'name' => $name,
'postalCode' => $postalCode
]);
throw $e;
}
}
/**
* Récupère toutes les entités actives
*
* @return array Liste des entités
*/
public function getEntites(): void {
try {
$stmt = $this->db->prepare('
SELECT id, encrypted_name, code_postal, ville, fk_type, chk_active
FROM entites
WHERE chk_active = 1
ORDER BY code_postal ASC
');
$stmt->execute();
$entites = $stmt->fetchAll(PDO::FETCH_ASSOC);
$result = [];
foreach ($entites as $entite) {
// Déchiffrement du nom pour chaque entité
$entite['name'] = ApiService::decryptData($entite['encrypted_name']);
unset($entite['encrypted_name']);
$result[] = $entite;
}
Response::json([
'status' => 'success',
'entites' => $result
], 200);
} catch (Exception $e) {
LogService::log('Erreur lors de la récupération des entités GeoSector', [
'level' => 'error',
'error' => $e->getMessage()
]);
Response::json([
'status' => 'error',
'message' => 'Erreur lors de la récupération des entités'
], 500);
}
}
/**
* Recherche les coordonnées GPS d'une caserne de pompiers à partir d'une adresse
*
* @param string $address Adresse complète (adresse + code postal + ville)
* @param string $postalCode Code postal
* @param string $city Ville
* @return array|null Tableau contenant les coordonnées GPS [lat, lng] ou null si non trouvé
*/
private function findFireStationCoordinates(string $address, string $postalCode, string $city): ?array {
try {
// Construire l'adresse complète
$fullAddress = urlencode($address . ' ' . $postalCode . ' ' . $city);
// Mots-clés pour rechercher une caserne de pompiers
$keywords = ['pompiers', 'sapeurs-pompiers', 'sdis', 'caserne', 'centre de secours'];
foreach ($keywords as $keyword) {
// Construire l'URL de recherche
$searchUrl = "https://api-adresse.data.gouv.fr/search/?q=" . urlencode($keyword) . "&postcode=$postalCode&limit=1";
// Effectuer la requête
$response = file_get_contents($searchUrl);
if ($response) {
$data = json_decode($response, true);
// Vérifier si des résultats ont été trouvés
if (isset($data['features']) && count($data['features']) > 0) {
$feature = $data['features'][0];
// Vérifier si les coordonnées sont disponibles
if (isset($feature['geometry']['coordinates'])) {
$coordinates = $feature['geometry']['coordinates'];
// Les coordonnées sont au format [longitude, latitude]
return [
'lat' => $coordinates[1],
'lng' => $coordinates[0]
];
}
}
}
}
// Si aucune caserne n'a été trouvée, essayer avec l'adresse de la mairie
$searchUrl = "https://api-adresse.data.gouv.fr/search/?q=mairie&postcode=$postalCode&limit=1";
$response = file_get_contents($searchUrl);
if ($response) {
$data = json_decode($response, true);
if (isset($data['features']) && count($data['features']) > 0) {
$feature = $data['features'][0];
if (isset($feature['geometry']['coordinates'])) {
$coordinates = $feature['geometry']['coordinates'];
return [
'lat' => $coordinates[1],
'lng' => $coordinates[0]
];
}
}
}
// Si toujours rien, essayer avec l'adresse complète
$searchUrl = "https://api-adresse.data.gouv.fr/search/?q=$fullAddress&limit=1";
$response = file_get_contents($searchUrl);
if ($response) {
$data = json_decode($response, true);
if (isset($data['features']) && count($data['features']) > 0) {
$feature = $data['features'][0];
if (isset($feature['geometry']['coordinates'])) {
$coordinates = $feature['geometry']['coordinates'];
return [
'lat' => $coordinates[1],
'lng' => $coordinates[0]
];
}
}
}
// Aucune coordonnée trouvée
return null;
} catch (Exception $e) {
LogService::log('Erreur lors de la recherche des coordonnées GPS', [
'level' => 'error',
'error' => $e->getMessage(),
'address' => $address,
'postalCode' => $postalCode,
'city' => $city
]);
return null;
}
}
/**
* Met à jour une entité existante avec les données fournies
* Seuls les administrateurs (rôle > 2) peuvent modifier certains champs
*
* @return void
*/
public function updateEntite(): void {
try {
// Vérifier l'authentification et les droits d'accès
$userId = Session::getUserId();
if (!$userId) {
Response::json([
'status' => 'error',
'message' => 'Vous devez être connecté pour effectuer cette action'
], 401);
return;
}
// Récupérer le rôle de l'utilisateur
$stmt = $this->db->prepare('SELECT fk_role FROM users WHERE id = ?');
$stmt->execute([$userId]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user) {
Response::json([
'status' => 'error',
'message' => 'Utilisateur non trouvé'
], 404);
return;
}
$userRole = (int)$user['fk_role'];
$isAdmin = $userRole > 2;
// Récupérer les données de la requête
$data = Request::getJson();
if (!isset($data['id']) || empty($data['id'])) {
Response::json([
'status' => 'error',
'message' => 'ID de l\'entité requis'
], 400);
return;
}
$entiteId = (int)$data['id'];
// Récupérer les données actuelles de l'entité pour vérifier si l'adresse a changé
$stmt = $this->db->prepare('SELECT adresse1, adresse2, code_postal, ville FROM entites WHERE id = ?');
$stmt->execute([$entiteId]);
$currentEntite = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$currentEntite) {
Response::json([
'status' => 'error',
'message' => 'Entité non trouvée'
], 404);
return;
}
// Vérifier si l'adresse a changé
$addressChanged = false;
$newAdresse1 = $data['adresse1'] ?? $currentEntite['adresse1'];
$newAdresse2 = $data['adresse2'] ?? $currentEntite['adresse2'];
$newCodePostal = $data['code_postal'] ?? $currentEntite['code_postal'];
$newVille = $data['ville'] ?? $currentEntite['ville'];
// Vérifier si l'adresse a changé
if (
$newAdresse1 !== $currentEntite['adresse1'] ||
$newAdresse2 !== $currentEntite['adresse2'] ||
$newCodePostal !== $currentEntite['code_postal'] ||
$newVille !== $currentEntite['ville']
) {
$addressChanged = true;
}
// Si l'adresse a changé, recalculer les coordonnées GPS
if ($addressChanged) {
// Construire l'adresse complète
$fullAddress = $newAdresse1;
if (!empty($newAdresse2)) {
$fullAddress .= ' ' . $newAdresse2;
}
// Rechercher les coordonnées GPS de la caserne de pompiers
$coordinates = $this->findFireStationCoordinates($fullAddress, $newCodePostal, $newVille);
// Si des coordonnées ont été trouvées, les ajouter aux champs à mettre à jour
if ($coordinates) {
$data['gps_lat'] = $coordinates['lat'];
$data['gps_lng'] = $coordinates['lng'];
LogService::log('Coordonnées GPS mises à jour suite à un changement d\'adresse', [
'level' => 'info',
'entiteId' => $entiteId,
'lat' => $coordinates['lat'],
'lng' => $coordinates['lng']
]);
}
}
// Préparer les champs à mettre à jour
$updateFields = [];
$params = [];
// Champs modifiables par tous les utilisateurs
if (isset($data['name']) && !empty($data['name'])) {
$updateFields[] = 'encrypted_name = ?';
$params[] = ApiService::encryptData($data['name']);
}
if (isset($data['adresse1'])) {
$updateFields[] = 'adresse1 = ?';
$params[] = $data['adresse1'];
}
if (isset($data['adresse2'])) {
$updateFields[] = 'adresse2 = ?';
$params[] = $data['adresse2'];
}
if (isset($data['code_postal']) && !empty($data['code_postal'])) {
$updateFields[] = 'code_postal = ?';
$params[] = $data['code_postal'];
}
if (isset($data['ville'])) {
$updateFields[] = 'ville = ?';
$params[] = $data['ville'];
}
if (isset($data['fk_region'])) {
$updateFields[] = 'fk_region = ?';
$params[] = $data['fk_region'];
}
if (isset($data['phone'])) {
$updateFields[] = 'encrypted_phone = ?';
$params[] = ApiService::encryptData($data['phone']);
}
if (isset($data['mobile'])) {
$updateFields[] = 'encrypted_mobile = ?';
$params[] = ApiService::encryptData($data['mobile']);
}
if (isset($data['email']) && !empty($data['email'])) {
$updateFields[] = 'encrypted_email = ?';
$params[] = ApiService::encryptSearchableData($data['email']);
}
if (isset($data['chk_copie_mail_recu'])) {
$updateFields[] = 'chk_copie_mail_recu = ?';
$params[] = $data['chk_copie_mail_recu'] ? 1 : 0;
}
if (isset($data['chk_accept_sms'])) {
$updateFields[] = 'chk_accept_sms = ?';
$params[] = $data['chk_accept_sms'] ? 1 : 0;
}
// Champs modifiables uniquement par les administrateurs
if ($isAdmin) {
if (isset($data['gps_lat'])) {
$updateFields[] = 'gps_lat = ?';
$params[] = $data['gps_lat'];
}
if (isset($data['gps_lng'])) {
$updateFields[] = 'gps_lng = ?';
$params[] = $data['gps_lng'];
}
if (isset($data['stripe_id'])) {
$updateFields[] = 'encrypted_stripe_id = ?';
$params[] = ApiService::encryptData($data['stripe_id']);
}
if (isset($data['chk_demo'])) {
$updateFields[] = 'chk_demo = ?';
$params[] = $data['chk_demo'] ? 1 : 0;
}
if (isset($data['chk_active'])) {
$updateFields[] = 'chk_active = ?';
$params[] = $data['chk_active'] ? 1 : 0;
}
if (isset($data['chk_stripe'])) {
$updateFields[] = 'chk_stripe = ?';
$params[] = $data['chk_stripe'] ? 1 : 0;
}
}
// Si aucun champ à mettre à jour, retourner une erreur
if (empty($updateFields)) {
Response::json([
'status' => 'error',
'message' => 'Aucune donnée à mettre à jour'
], 400);
return;
}
// Ajouter la date de mise à jour
$updateFields[] = 'updated_at = NOW()';
// Construire la requête SQL
$sql = 'UPDATE entites SET ' . implode(', ', $updateFields) . ' WHERE id = ?';
$params[] = $entiteId;
// Exécuter la requête
$stmt = $this->db->prepare($sql);
$stmt->execute($params);
// Vérifier si la mise à jour a réussi
if ($stmt->rowCount() === 0) {
Response::json([
'status' => 'warning',
'message' => 'Aucune modification effectuée'
], 200);
return;
}
LogService::log('Mise à jour d\'une entité GeoSector', [
'level' => 'info',
'userId' => $userId,
'entiteId' => $entiteId,
'isAdmin' => $isAdmin
]);
Response::json([
'status' => 'success',
'message' => 'Entité mise à jour avec succès'
], 200);
} catch (Exception $e) {
LogService::log('Erreur lors de la mise à jour de l\'entité GeoSector', [
'level' => 'error',
'error' => $e->getMessage()
]);
Response::json([
'status' => 'error',
'message' => 'Erreur lors de la mise à jour de l\'entité'
], 500);
}
}
}

View File

@@ -0,0 +1,161 @@
<?php
declare(strict_types=1);
class LogController {
private const REQUIRED_FIELDS = [
'metadata' => [
'side',
'version',
'level',
'timestamp',
'environment',
'client' => [
'ip',
'browser' => ['name', 'version'],
'os' => ['name', 'version'],
'screenResolution',
'userAgent'
]
],
'message'
];
public function index(): void {
try {
// Récupérer la configuration de l'application
$appConfig = AppConfig::getInstance();
$appName = $appConfig->getName();
$clientType = ClientDetector::getClientType();
// Récupérer et décoder le JSON
$data = json_decode(file_get_contents('php://input'), true);
// Vérifier si le JSON est valide
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception('Invalid JSON format');
}
// Valider la structure des données
$this->validateData($data);
// Ajouter le type de client aux métadonnées
$data['metadata']['client_type'] = $clientType;
// Si c'est une app mobile, ajouter l'identifiant de l'app
if ($clientType === 'mobile') {
$data['metadata']['app_identifier'] = ClientDetector::getAppIdentifier();
}
// Définir le chemin du dossier logs à la racine du projet
$logDir = __DIR__ . '/../../logs';
// Créer le dossier logs s'il n'existe pas
if (!is_dir($logDir)) {
if (!mkdir($logDir, 0777, true)) {
throw new \Exception("Impossible de créer le dossier de logs: {$logDir}");
}
// S'assurer que les permissions sont correctes
chmod($logDir, 0777);
}
// Vérifier si le dossier est accessible en écriture
if (!is_writable($logDir)) {
throw new \Exception("Le dossier de logs n'est pas accessible en écriture: {$logDir}");
}
// Récupérer l'environnement défini dans la configuration
$environment = $appConfig->getEnvironment();
// Créer le nom du fichier basé sur l'environnement et la date
// Format: geosector-production-2025-03-28.log, geosector-recette-2025-03-28.log, geosector-development-2025-03-28.log
$filename = $logDir . '/geosector-' . $environment . '-' . date('Y-m-d') . '.log';
// Formater la ligne de log au format plat demandé
// timestamp;browser.name@browser.version;os.name@os.version;client_type;$metadata;$message
$timestamp = date('Y-m-d\TH:i:s');
$browserInfo = $data['metadata']['client']['browser']['name'] . '@' . $data['metadata']['client']['browser']['version'];
$osInfo = $data['metadata']['client']['os']['name'] . '@' . $data['metadata']['client']['os']['version'];
$clientType = $data['metadata']['client_type'];
// Extraire le niveau de log
$level = isset($data['metadata']['level']) ? (is_array($data['metadata']['level']) ? 'info' : $data['metadata']['level']) : 'info';
// Préparer les métadonnées supplémentaires (exclure celles déjà incluses dans le format et les informations client)
$additionalMetadata = [];
foreach ($data['metadata'] as $key => $value) {
// Exclure les informations client, type client, side, version, level et environment
if (!in_array($key, ['client', 'client_type', 'side', 'version', 'level', 'environment'])) {
if (is_array($value)) {
$additionalMetadata[$key] = json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} else {
$additionalMetadata[$key] = $value;
}
}
}
// Joindre les métadonnées supplémentaires avec des virgules
$metadataStr = !empty($additionalMetadata) ? implode(',', array_map(function($k, $v) {
return $k . '=' . $v;
}, array_keys($additionalMetadata), $additionalMetadata)) : '-';
// Construire la ligne de log au format demandé
$logLine = implode(';', [
$timestamp,
$browserInfo,
$osInfo,
$clientType,
$level,
$metadataStr,
$data['message']
]) . "\n";
// Écrire dans le fichier avec vérification complète
if (file_put_contents($filename, $logLine, FILE_APPEND) === false) {
throw new \Exception("Impossible d'écrire dans le fichier de logs: {$filename}");
}
// Retourner 204 No Content en cas de succès
http_response_code(204);
} catch (\Exception $e) {
Response::json([
'success' => false,
'message' => $e->getMessage()
], 400);
}
}
private function validateData($data): void {
if (!isset($data['metadata']) || !isset($data['message'])) {
throw new \Exception('Missing required root fields');
}
// Valider la structure metadata
foreach (self::REQUIRED_FIELDS['metadata'] as $key => $value) {
if (is_array($value)) {
if (!isset($data['metadata'][$key])) {
throw new \Exception("Missing metadata field: {$key}");
}
foreach ($value as $subKey => $subValue) {
if (is_array($subValue)) {
foreach ($subValue as $field) {
if (!isset($data['metadata'][$key][$subKey][$field])) {
throw new \Exception("Missing metadata field: {$key}.{$subKey}.{$field}");
}
}
} else {
if (!isset($data['metadata'][$key][$subValue])) {
throw new \Exception("Missing metadata field: {$key}.{$subValue}");
}
}
}
} else {
if (!isset($data['metadata'][$value])) {
throw new \Exception("Missing metadata field: {$value}");
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,605 @@
<?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.encrypt_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['encrypt_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['encrypt_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.encrypt_email,
u.encrypted_name,
u.first_name,
u.sect_name,
u.encrypt_phone,
u.encrypt_mobile,
u.fk_role as role,
u.fk_entite,
u.infos,
u.chk_alert_email,
u.chk_suivi,
u.date_naissance,
u.date_embauche,
u.matricule,
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['encrypt_email']);
$user['name'] = ApiService::decryptData($user['encrypted_name']);
$user['phone'] = ApiService::decryptData($user['encrypt_phone'] ?? '');
$user['mobile'] = ApiService::decryptData($user['encrypt_mobile'] ?? '');
if (!empty($user['entite_name'])) {
$user['entite_name'] = ApiService::decryptData($user['entite_name']);
}
// Suppression des champs chiffrés
unset($user['encrypt_email']);
unset($user['encrypted_name']);
unset($user['encrypt_phone']);
unset($user['encrypt_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']) ? trim($data['role']) : '1';
$entiteId = isset($data['fk_entite']) ? (int)$data['fk_entite'] : 1;
// Vérification des longueurs d'entrée
if (strlen($email) > 255 || strlen($name) > 255) {
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 encrypt_email = ?');
$checkStmt->execute([$encryptedEmail]);
if ($checkStmt->fetch()) {
Response::json([
'status' => 'error',
'message' => 'Cet email est déjà utilisé'
], 409);
return;
}
// Génération 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']) : '';
$infos = isset($data['infos']) ? trim($data['infos']) : '';
$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;
$matricule = isset($data['matricule']) ? trim($data['matricule']) : '';
// Insertion en base de données
$stmt = $this->db->prepare('
INSERT INTO users (
encrypt_email, user_pswd, encrypted_name, first_name,
sect_name, encrypt_phone, encrypt_mobile, fk_role,
fk_entite, infos, chk_alert_email, chk_suivi,
date_naissance, date_embauche, matricule,
created_at, fk_user_creat, chk_active
) VALUES (
?, ?, ?, ?,
?, ?, ?, ?,
?, ?, ?, ?,
?, ?, ?,
NOW(), ?, 1
)
');
$stmt->execute([
$encryptedEmail,
$passwordHash,
$encryptedName,
$firstName,
$sectName,
$phone,
$mobile,
$role,
$entiteId,
$infos,
$alertEmail,
$suivi,
$dateNaissance,
$dateEmbauche,
$matricule,
$currentUserId
]);
$userId = $this->db->lastInsertId();
// Envoi de l'email avec les identifiants
ApiService::sendEmail($email, $name, 'welcome', ['password' => $password]);
LogService::log('Utilisateur GeoSector créé', [
'level' => 'info',
'createdBy' => $currentUserId,
'newUserId' => $userId,
'email' => $email
]);
Response::json([
'status' => 'success',
'message' => 'Utilisateur créé avec succès',
'id' => $userId
], 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 encrypt_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[] = "encrypt_email = :encrypt_email";
$params['encrypt_email'] = $encryptedEmail;
}
if (isset($data['name'])) {
$updateFields[] = "encrypted_name = :encrypted_name";
$params['encrypted_name'] = ApiService::encryptData(trim($data['name']));
}
if (isset($data['phone'])) {
$updateFields[] = "encrypt_phone = :encrypt_phone";
$params['encrypt_phone'] = ApiService::encryptData(trim($data['phone']));
}
if (isset($data['mobile'])) {
$updateFields[] = "encrypt_mobile = :encrypt_mobile";
$params['encrypt_mobile'] = ApiService::encryptData(trim($data['mobile']));
}
// Traitement des champs non chiffrés
$nonEncryptedFields = [
'first_name',
'sect_name',
'fk_role',
'fk_entite',
'infos',
'chk_alert_email',
'chk_suivi',
'date_naissance',
'date_embauche',
'matricule',
'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_pswd = :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();
// 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;
}
$currentUserId = Session::getUserId();
// 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;
}
try {
// Désactivation de l'utilisateur plutôt que suppression
$stmt = $this->db->prepare('
UPDATE users
SET chk_active = 0,
updated_at = NOW(),
fk_user_modif = ?
WHERE id = ?
');
$stmt->execute([$currentUserId, $id]);
if ($stmt->rowCount() === 0) {
Response::json([
'status' => 'error',
'message' => 'Utilisateur non trouvé'
], 404);
return;
}
LogService::log('Utilisateur GeoSector désactivé', [
'level' => 'info',
'deactivatedBy' => $currentUserId,
'userId' => $id
]);
Response::json([
'status' => 'success',
'message' => 'Utilisateur désactivé avec succès'
]);
} catch (PDOException $e) {
LogService::log('Erreur lors de la désactivation d\'un utilisateur GeoSector', [
'level' => 'error',
'error' => $e->getMessage(),
'userId' => $id
]);
Response::json([
'status' => 'error',
'message' => 'Erreur serveur'
], 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;
}
}

View File

@@ -0,0 +1,99 @@
<?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;
use Exception;
class VilleController {
private PDO $db;
private AppConfig $appConfig;
public function __construct() {
$this->db = Database::getInstance();
$this->appConfig = AppConfig::getInstance();
}
/**
* Recherche les villes dont le code postal commence par les chiffres saisis
*
* @return void
*/
public function searchVillesByPostalCode(): void {
try {
// Récupérer le paramètre code_postal de la requête
$postalCode = Request::getValue('code_postal');
if (empty($postalCode) || strlen($postalCode) < 3) {
Response::json([
'status' => 'error',
'message' => 'Le code postal doit contenir au moins 3 chiffres'
], 400);
return;
}
// Valider que le code postal ne contient que des chiffres
if (!ctype_digit($postalCode)) {
Response::json([
'status' => 'error',
'message' => 'Le code postal doit contenir uniquement des chiffres'
], 400);
return;
}
// Rechercher les villes dont le code postal commence par les chiffres saisis
$stmt = $this->db->prepare('
SELECT id, fk_departement, libelle, code_postal
FROM x_villes
WHERE code_postal LIKE ?
ORDER BY libelle ASC
LIMIT 20
');
$stmt->execute([$postalCode . '%']);
$villes = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Renommer les champs pour une meilleure lisibilité côté client
$result = [];
foreach ($villes as $ville) {
$result[] = [
'id' => $ville['id'],
'departement_id' => $ville['fk_departement'],
'nom' => $ville['libelle'],
'code_postal' => $ville['code_postal']
];
}
Response::json([
'status' => 'success',
'success' => true,
'data' => $result
], 200);
} catch (Exception $e) {
LogService::log('Erreur lors de la recherche de villes par code postal', [
'level' => 'error',
'error' => $e->getMessage(),
'postalCode' => $postalCode ?? 'non défini'
]);
Response::json([
'status' => 'error',
'success' => false,
'message' => 'Erreur lors de la recherche de villes'
], 500);
}
}
}