prepare($sql); $stmt->execute([ 'praticien_id' => $praticienId, 'langue' => $langue, 'nom_affiche' => $data['nom_affiche'], 'prenom_affiche' => $data['prenom_affiche'], 'ville_affichee' => $data['ville_affichee'], 'titre' => $data['titre'], 'specialite' => $data['specialite'], 'presentation' => $data['presentation'], 'disponibilite' => $data['disponibilite'], 'statut' => $data['statut'], ]); } /** * Lecture traduction par langue */ function getPraticienTraduction(PDO $pdo, int $praticienId, string $langue): ?array { $stmt = $pdo->prepare(" SELECT * FROM praticien_traduction WHERE praticien_id = ? AND langue = ? LIMIT 1 "); $stmt->execute([$praticienId, $langue]); $row = $stmt->fetch(); return $row ?: null; } /** * Translittération simple cyrillique -> latin * Suffisant pour test / affichage. */ function transliterateToLatin(string $text): string { $map = [ 'А' => 'A', 'а' => 'a', 'Б' => 'B', 'б' => 'b', 'В' => 'V', 'в' => 'v', 'Г' => 'G', 'г' => 'g', 'Д' => 'D', 'д' => 'd', 'Е' => 'E', 'е' => 'e', 'Ё' => 'Yo', 'ё' => 'yo', 'Ж' => 'Zh', 'ж' => 'zh', 'З' => 'Z', 'з' => 'z', 'И' => 'I', 'и' => 'i', 'Й' => 'Y', 'й' => 'y', 'К' => 'K', 'к' => 'k', 'Л' => 'L', 'л' => 'l', 'М' => 'M', 'м' => 'm', 'Н' => 'N', 'н' => 'n', 'О' => 'O', 'о' => 'o', 'П' => 'P', 'п' => 'p', 'Р' => 'R', 'р' => 'r', 'С' => 'S', 'с' => 's', 'Т' => 'T', 'т' => 't', 'У' => 'U', 'у' => 'u', 'Ф' => 'F', 'ф' => 'f', 'Х' => 'Kh', 'х' => 'kh', 'Ц' => 'Ts', 'ц' => 'ts', 'Ч' => 'Ch', 'ч' => 'ch', 'Ш' => 'Sh', 'ш' => 'sh', 'Щ' => 'Shch', 'щ' => 'shch', 'Ы' => 'Y', 'ы' => 'y', 'Э' => 'E', 'э' => 'e', 'Ю' => 'Yu', 'ю' => 'yu', 'Я' => 'Ya', 'я' => 'ya', 'Ь' => '', 'ь' => '', 'Ъ' => '', 'ъ' => '', ]; return strtr($text, $map); } /** * Stub traduction. * Pour l’instant : * - renvoie le texte tel quel si pas de vrai moteur branché * - tu pourras remplacer ici par DeepL/API plus tard */ function translateText(string $text, string $sourceLang, string $targetLang): string { $text = trim($text); if ($text === '') { return ''; } $sourceLang = strtoupper($sourceLang); $targetLang = strtoupper($targetLang); if ($sourceLang === $targetLang) { return $text; } if (! defined('DEEPL_API_KEY') || DEEPL_API_KEY === '') { throw new RuntimeException('Clé API DeepL manquante.'); } $endpoint = 'https://api-free.deepl.com/v2/translate'; $postFields = http_build_query([ 'text' => $text, 'source_lang' => $sourceLang, 'target_lang' => $targetLang, ]); $ch = curl_init($endpoint); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => $postFields, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 20, CURLOPT_HTTPHEADER => [ 'Authorization: DeepL-Auth-Key ' . DEEPL_API_KEY, 'Content-Type: application/x-www-form-urlencoded', ], ]); $response = curl_exec($ch); $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlErr = curl_error($ch); curl_close($ch); if ($response === false || $curlErr) { throw new RuntimeException('Erreur réseau DeepL : ' . $curlErr); } if ($httpCode < 200 || $httpCode >= 300) { throw new RuntimeException('Erreur DeepL HTTP ' . $httpCode . ' : ' . $response); } $data = json_decode($response, true); if ( ! is_array($data) || empty($data['translations']) || ! isset($data['translations'][0]['text']) ) { throw new RuntimeException('Réponse DeepL invalide.'); } return trim($data['translations'][0]['text']); } /** * Détecte si une traduction est manuelle */ function isManualTranslation(?array $row): bool { return isset($row['statut']) && $row['statut'] === 'manuel'; } /** * Synchronise les traductions à partir de la source RU dans praticien * - ru toujours sync * - fr/en mis à jour seulement si absents ou non manuels */ function syncTranslationsFromSource(PDO $pdo, array $praticien): void { $praticienId = (int) $praticien['id']; $ruData = [ 'nom_affiche' => $praticien['nom'] ?? '', 'prenom_affiche' => $praticien['prenom'] ?? '', 'ville_affichee' => $praticien['ville'] ?? '', 'titre' => $praticien['titre'] ?? '', 'specialite' => $praticien['specialite'] ?? '', 'presentation' => $praticien['presentation'] ?? '', 'disponibilite' => $praticien['disponibilite'] ?? '', 'statut' => 'auto', ]; upsertPraticienTraduction($pdo, $praticienId, 'ru', $ruData); foreach (['fr', 'en'] as $langue) { $existing = getPraticienTraduction($pdo, $praticienId, $langue); if ($existing && isManualTranslation($existing)) { continue; } $nomLatin = transliterateToLatin((string) ($praticien['nom'] ?? '')); $prenomLatin = transliterateToLatin((string) ($praticien['prenom'] ?? '')); try { $data = [ 'nom_affiche' => $nomLatin, 'prenom_affiche' => $prenomLatin, 'ville_affichee' => translateText((string) ($praticien['ville'] ?? ''), 'RU', $langue), 'titre' => translateText((string) ($praticien['titre'] ?? ''), 'RU', $langue), 'specialite' => translateText((string) ($praticien['specialite'] ?? ''), 'RU', $langue), 'presentation' => translateText((string) ($praticien['presentation'] ?? ''), 'RU', $langue), 'disponibilite' => translateText((string) ($praticien['disponibilite'] ?? ''), 'RU', $langue), 'statut' => 'auto', ]; } catch (Throwable $e) { error_log('DeepL translation error [' . $langue . '] praticien_id=' . $praticienId . ' : ' . $e->getMessage()); //die($e->getMessage()); $data = [ 'nom_affiche' => $nomLatin, 'prenom_affiche' => $prenomLatin, 'ville_affichee' => (string) ($praticien['ville'] ?? ''), 'titre' => (string) ($praticien['titre'] ?? ''), 'specialite' => (string) ($praticien['specialite'] ?? ''), 'presentation' => (string) ($praticien['presentation'] ?? ''), 'disponibilite' => (string) ($praticien['disponibilite'] ?? ''), 'statut' => 'erreur', ]; } upsertPraticienTraduction($pdo, $praticienId, $langue, $data); } } // Base praticien $stmt = $pdo->prepare("SELECT * FROM praticien WHERE id = ?"); $stmt->execute([$id]); $praticien = $stmt->fetch(); if (! $praticien) { die(htmlspecialchars($translations['praticien_introuvable'] ?? 'Introuvable.')); } // Traduction langue courante $traduction = getPraticienTraduction($pdo, (int) $id, $currentLang); // Valeurs formulaire avec fallback $formNom = $traduction['nom_affiche'] ?? $praticien['nom'] ?? ''; $formPrenom = $traduction['prenom_affiche'] ?? $praticien['prenom'] ?? ''; $formVilleAffichee = $traduction['ville_affichee'] ?? $praticien['ville'] ?? ''; $formTitre = $traduction['titre'] ?? $praticien['titre'] ?? ''; $formSpecialite = $traduction['specialite'] ?? $praticien['specialite'] ?? ''; $formPresentation = $traduction['presentation'] ?? $praticien['presentation'] ?? ''; $formDispo = $traduction['disponibilite'] ?? $praticien['disponibilite'] ?? ''; // ==== POST ==== if ($_SERVER['REQUEST_METHOD'] === 'POST') { require_post_csrf(); $titre = trim($_POST['titre'] ?? ''); $nomAffiche = trim($_POST['nom'] ?? ''); $prenomAffiche = trim($_POST['prenom'] ?? ''); $telephone = trim($_POST['telephone'] ?? ''); $specialite = trim($_POST['specialite'] ?? ''); $adresse = trim($_POST['adresse'] ?? ''); $ville = trim($_POST['ville'] ?? ''); $code_postal = trim($_POST['code_postal'] ?? ''); $presentation = trim($_POST['presentation'] ?? ''); $duree_rdv = (int) ($_POST['duree_rdv'] ?? 60); $disponibilite = trim($_POST['disponibilite'] ?? ''); $photo = $praticien['photo'] ?? null; if ($specialite === '' || $presentation === '') { die(htmlspecialchars($translations['err_champs_obligatoires'] ?? 'Merci de remplir les champs obligatoires.')); } // En FR/EN on ignore tout update des champs globaux même si quelqu’un bidouille le HTML if (! $isRu) { $telephone = $praticien['telephone'] ?? ''; $adresse = $praticien['adresse'] ?? ''; $ville = $praticien['ville'] ?? ''; $code_postal = $praticien['code_postal'] ?? ''; $duree_rdv = (int) ($praticien['duree_rdv'] ?? 60); } // Upload photo : uniquement RU if ($isRu && ! empty($_FILES['photo']['tmp_name'])) { $file = $_FILES['photo']; $maxSize = 5 * 1024 * 1024; if ($file['size'] > $maxSize) { die("Erreur : Le fichier est trop volumineux (max 5 Mo)."); } $finfo = new finfo(FILEINFO_MIME_TYPE); $mimeType = $finfo->file($file['tmp_name']); $allowedMimeTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; if (! in_array($mimeType, $allowedMimeTypes, true)) { die("Erreur : Type de fichier non autorisé (Seuls JPG, PNG, GIF et WEBP sont acceptés)."); } $nom_nettoye = strtolower(preg_replace('/[^A-Za-z0-9]/', '', transliterateToLatin($nomAffiche))); $prenom_nettoye = strtolower(preg_replace('/[^A-Za-z0-9]/', '', transliterateToLatin($prenomAffiche))); $nouveau_nom_fichier = $nom_nettoye . '_' . $prenom_nettoye . '_' . time() . '.png'; $chemin_destination = __DIR__ . '/../uploads/' . $nouveau_nom_fichier; $image_source = @imagecreatefromstring(file_get_contents($file['tmp_name'])); if (! $image_source) { die("Erreur : Le fichier image semble corrompu."); } [$largeur_orig, $hauteur_orig] = getimagesize($file['tmp_name']); $largeur_max = 800; if ($largeur_orig > $largeur_max) { $ratio = $largeur_max / $largeur_orig; $nouvelle_largeur = $largeur_max; $nouvelle_hauteur = (int) ($hauteur_orig * $ratio); } else { $nouvelle_largeur = $largeur_orig; $nouvelle_hauteur = $hauteur_orig; } $image_dest = imagecreatetruecolor($nouvelle_largeur, $nouvelle_hauteur); imagealphablending($image_dest, false); imagesavealpha($image_dest, true); imagecopyresampled( $image_dest, $image_source, 0, 0, 0, 0, $nouvelle_largeur, $nouvelle_hauteur, $largeur_orig, $hauteur_orig ); if (imagepng($image_dest, $chemin_destination, 7)) { if (! empty($praticien['photo'])) { $ancien_chemin = __DIR__ . '/../uploads/' . $praticien['photo']; if (file_exists($ancien_chemin)) { unlink($ancien_chemin); } } $photo = $nouveau_nom_fichier; } imagedestroy($image_source); imagedestroy($image_dest); } $pdo->beginTransaction(); try { if ($isRu) { $siret = preg_replace('/\D/', '', $_POST['siret'] ?? ''); if (strlen($siret) !== 14) { throw new RuntimeException($translations['siret_format_invalide'] ?? 'SIRET invalide (14 chiffres).'); } $luhnOk = (function ($num) { $sum = 0; $alt = false; for ($i = strlen($num) - 1; $i >= 0; $i--) { $n = (int) $num[$i]; if ($alt) { $n *= 2; if ($n > 9) { $n -= 9; } } $sum += $n; $alt = ! $alt; } return $sum % 10 === 0; })($siret); if (! $luhnOk) { throw new RuntimeException($translations['siret_format_invalide'] ?? 'SIRET invalide (Luhn).'); } $chk = $pdo->prepare("SELECT id FROM praticien WHERE siret = :s AND id <> :id LIMIT 1"); $chk->execute(['s' => $siret, 'id' => $id]); if ($chk->fetch()) { throw new RuntimeException($translations['err_siret_deja_utilise'] ?? 'Ce SIRET est déjà utilisé.'); } $stmt = $pdo->prepare(" UPDATE praticien SET titre = :titre, nom = :nom, prenom = :prenom, telephone = :telephone, specialite = :specialite, adresse = :adresse, ville = :ville, code_postal = :code_postal, presentation = :presentation, photo = :photo, disponibilite = :disponibilite, duree_rdv = :duree_rdv, siret = :siret, langue_source = 'ru' WHERE id = :id "); $stmt->execute([ 'titre' => $titre, 'nom' => $nomAffiche, 'prenom' => $prenomAffiche, 'telephone' => $telephone, 'specialite' => $specialite, 'adresse' => $adresse, 'ville' => $ville, 'code_postal' => $code_postal, 'presentation' => $presentation, 'photo' => $photo, 'disponibilite' => $disponibilite, 'duree_rdv' => $duree_rdv, 'siret' => $siret, 'id' => $id, ]); // Recharge la source après update $stmt = $pdo->prepare("SELECT * FROM praticien WHERE id = ?"); $stmt->execute([$id]); $praticien = $stmt->fetch(); if (! $praticien) { throw new RuntimeException('Praticien introuvable après mise à jour.'); } syncTranslationsFromSource($pdo, $praticien); } else { upsertPraticienTraduction($pdo, (int) $id, $currentLang, [ 'nom_affiche' => $nomAffiche, 'prenom_affiche' => $prenomAffiche, 'ville_affichee' => $traduction['ville_affichee'] ?? $praticien['ville'] ?? '', 'titre' => $titre, 'specialite' => $specialite, 'presentation' => $presentation, 'disponibilite' => $disponibilite, 'statut' => 'manuel', ]); } $pdo->commit(); } catch (Throwable $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); } die(htmlspecialchars($e->getMessage())); } header('Location: /public/praticien/informations.php'); exit; } $PAGE_TITLE = $translations['modifier_infos_title']; $HEAD_EXTRA = << .message-erreur { color: red; font-size: 0.9em; display: none; margin-bottom: 10px; } HTML; require_once __DIR__ . '/../includes/header.php'; ?>

>
    >
    >
    >