$ttl); }; if ($mustReauth()) { $qs = http_build_query(['page' => 'reauth', 'redir' => ($_SERVER['REQUEST_URI'] ?? 'index.php?page=maintenance')]); header('Location:?' . $qs); exit; } // Session sûre if (session_status() !== PHP_SESSION_ACTIVE) { @session_start(); } if ($pdo && method_exists($pdo, 'setAttribute')) { $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } // ===== Accès admin praticien strict ===== const ADMIN_PRACTICIEN_ID = 88888888; // <-- adapte à ton ID $me = (int)($_SESSION['reference_id'] ?? 0); if ($me !== ADMIN_PRACTICIEN_ID) { http_response_code(403); echo '
' . htmlspecialchars($translations['admin_access_denied'] ?? 'Доступ запрещён.', ENT_QUOTES, 'UTF-8') . '
'; exit; } // =====MAILER PHP ===== require_once __DIR__ . '/../../includes/mailer.php'; use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception; // CSRF robuste if (empty($_SESSION['csrf_token']) || !is_string($_SESSION['csrf_token']) || strlen($_SESSION['csrf_token']) < 32) { try { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } catch (Throwable $e) { $_SESSION['csrf_token'] = bin2hex(openssl_random_pseudo_bytes(32)); } } $CSRF = $_SESSION['csrf_token']; // ==================== Paramètres ==================== $DATE_FMT_TABLE = $translations['date_format_table'] ?? 'd.m.Y'; $DATE_FMT_MAIL = $translations['date_format_mail'] ?? 'd.m.Y'; $RENEW_URL = 'https://uslugi.fr/public/praticien/resiliation.php'; // <- adapte si besoin // ==================== Helpers ==================== function e($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } function latestPaymentFor(PDO $pdo, int $praticienId): ?array { $sql = " SELECT p.id, p.statut, p.methode, p.date_paiement FROM paiement p WHERE p.praticien_id = :pid ORDER BY p.date_paiement DESC, p.id DESC LIMIT 1 "; $st = $pdo->prepare($sql); $st->execute([':pid'=>$praticienId]); $row = $st->fetch(PDO::FETCH_ASSOC); return $row ?: null; } function computeAboState(?string $dateExpiration, ?string $lastPayStatus, int $warningDays = 10): string { if (!$dateExpiration) return 'expired'; try { $today = new DateTimeImmutable('today'); $exp = new DateTimeImmutable($dateExpiration); } catch (Throwable $e) { return 'expired'; } if ($exp < $today) return 'expired'; $diff = $today->diff($exp); $daysLeft = (int)$diff->days; if ($daysLeft <= $warningDays) return 'expiring'; return 'active'; } function buildMailHtml(array $t, string $headlineKey, string $leadKey, ?string $dateFinFmt, string $renewUrl, string $btnLabel): string { $headline = htmlspecialchars($t[$headlineKey] ?? '', ENT_QUOTES, 'UTF-8'); $leadRaw = $t[$leadKey] ?? ''; if ($dateFinFmt) $leadRaw = str_replace('{DATE}', $dateFinFmt, $leadRaw); $lead = nl2br(htmlspecialchars($leadRaw, ENT_QUOTES, 'UTF-8')); $ctaLabel = htmlspecialchars($btnLabel, ENT_QUOTES, 'UTF-8'); $help = nl2br(htmlspecialchars($t['mail_footer_help'] ?? '', ENT_QUOTES, 'UTF-8')); $labelAbo = htmlspecialchars($t['mail_label_abo'] ?? 'Абонемент', ENT_QUOTES, 'UTF-8'); $labelDate = htmlspecialchars($t['mail_label_date_fin'] ?? 'Дата окончания', ENT_QUOTES, 'UTF-8'); $labelState = htmlspecialchars($t['mail_label_statut'] ?? 'Статус', ENT_QUOTES, 'UTF-8'); $valueAbo = htmlspecialchars($t['mail_value_abo'] ?? '', ENT_QUOTES, 'UTF-8'); $valueDate = htmlspecialchars($dateFinFmt ?? '', ENT_QUOTES, 'UTF-8'); $valueState = htmlspecialchars($t['mail_value_statut'] ?? '', ENT_QUOTES, 'UTF-8'); $brand = 'USLUGI.FR'; $brandBas = 'УСЛУГИ.ФР'; $merciLine = htmlspecialchars($t['merci_simple'] ?? 'Спасибо', ENT_QUOTES, 'UTF-8'); $renewUrl = htmlspecialchars($renewUrl, ENT_QUOTES, 'UTF-8'); $rowsHtml = ''; if ($valueAbo !== '') { $rowsHtml .= ''.$labelAbo.''.$valueAbo.''; } if ($valueDate !== '') { $rowsHtml .= ''.$labelDate.''.$valueDate.''; } if ($valueState !== '') { $rowsHtml .= ''.$labelState.''.$valueState.''; } $cardHtml = ($rowsHtml !== '') ? ''.$rowsHtml.'
' : ''; return ' '.$headline.'
'.$cardHtml.'
'.$brand.'
'.$headline.'
'.$lead.'
'.$ctaLabel.'
'.$help.'
© '.date('Y').' '.$brandBas.' — '.$merciLine.'
'; } function sendReminderEmailPhpMailer(string $toEmail, string $toName, string $subject, string $html, string $text = ''): bool { global $MAILCFG; if (!filter_var($toEmail, FILTER_VALIDATE_EMAIL)) return false; if (empty($MAILCFG['smtp']['host'])) return false; $mail = new \PHPMailer\PHPMailer\PHPMailer(true); try { $mail->isSMTP(); $mail->Host = $MAILCFG['smtp']['host']; $mail->Port = (int)($MAILCFG['smtp']['port'] ?? 587); $mail->SMTPAuth = (bool)($MAILCFG['smtp']['auth'] ?? true); $sec = $MAILCFG['smtp']['secure'] ?? 'tls'; if ($sec === 'ssl') $mail->SMTPSecure = \PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_SMTPS; else $mail->SMTPSecure = \PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS; $mail->Username = $MAILCFG['smtp']['username'] ?? ''; $mail->Password = $MAILCFG['smtp']['password'] ?? ''; $mail->Timeout = 10; $mail->SMTPKeepAlive = false; $mail->SMTPAutoTLS = true; $mail->SMTPDebug = 0; $mail->CharSet = 'UTF-8'; $mail->Encoding = 'base64'; $mail->XMailer = ''; if (!empty($MAILCFG['bounce'])) $mail->Sender = $MAILCFG['bounce']; $from = $MAILCFG['from'] ?? ['contact@uslugi.fr', 'USLUGI.FR']; $rpto = $MAILCFG['reply_to'] ?? ['contact@uslugi.fr', 'USLUGI.FR']; $mail->setFrom($from[0], $from[1] ?? ''); if (!empty($rpto[0])) $mail->addReplyTo($rpto[0], $rpto[1] ?? ''); $mail->addAddress($toEmail, $toName ?: ''); if (!empty($MAILCFG['suppress_auto'])) { $mail->addCustomHeader('Auto-Submitted', 'auto-generated'); $mail->addCustomHeader('X-Auto-Response-Suppress', 'All'); } $mail->isHTML(true); $mail->Subject = $subject; $mail->Body = $html; if (trim($text) === '') { $text = trim(strip_tags(str_replace(['
', '
', '
'], "\n", $html))); } $mail->AltBody = $text; return $mail->send(); } catch (\Throwable $e) { error_log('PHPMailer reminder send exception: ' . $e->getMessage()); if (!empty($mail->ErrorInfo)) error_log('PHPMailer reminder ErrorInfo: ' . $mail->ErrorInfo); return false; } } // ==================== Traitement POST: envoi mail ==================== $flash = null; if (($_POST['do'] ?? '') === 'send_mail') { try { if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf'] ?? '')) { throw new Exception('CSRF'); } $pid = (int)($_POST['id'] ?? 0); if ($pid <= 0) throw new Exception('ID'); $st = $pdo->prepare(" SELECT pr.id, pr.nom, pr.prenom, pr.email, pr.date_expiration, ab.nom AS abo_nom FROM praticien pr LEFT JOIN abonnement ab ON ab.id = pr.abonnement_id WHERE pr.id = ? LIMIT 1 "); $st->execute([$pid]); $pr = $st->fetch(PDO::FETCH_ASSOC); if (!$pr) throw new Exception('Not found'); $last = latestPaymentFor($pdo, $pid); $state = computeAboState($pr['date_expiration'] ?? null, $last['statut'] ?? null); if ($state === 'active') { $flash = ['type'=>'warning','msg'=> e($translations['flash_pas_necessaire'] ?? 'Pas nécessaire.')]; } else { if ($state === 'expired') { $subjectKey='mail_subject_expired'; $headlineKey='mail_headline_expired'; $leadKey='mail_lead_expired'; } else { $subjectKey='mail_subject_expiring'; $headlineKey='mail_headline_expiring'; $leadKey='mail_lead_expiring'; } $subject = $translations[$subjectKey] ?? ($translations['mail_subject_default'] ?? 'Abonnement'); $btn = $translations['mail_btn_renew'] ?? 'Renouveler'; $dateFinFmt = ''; if (!empty($pr['date_expiration'])) { try { $dateFinFmt = (new DateTimeImmutable($pr['date_expiration']))->format($DATE_FMT_MAIL); } catch(Throwable $e){} } $html = buildMailHtml($translations, $headlineKey, $leadKey, $dateFinFmt ?: null, $RENEW_URL, $btn); $destName = trim(($pr['prenom'] ?? '').' '.($pr['nom'] ?? '')); if ($destName === '') $destName = ($translations['mail_default_name'] ?? 'Collègue'); $toEmail = (string)$pr['email']; $toName = $destName; if (sendReminderEmailPhpMailer($toEmail, $toName, $subject, $html)) { $msg = ($translations['flash_envoi_ok'] ?? 'Envoyé: {EMAIL}'); $msg = str_replace('{EMAIL}', e($pr['email']), $msg); $flash = ['type'=>'success','msg'=>$msg]; } else { $flash = ['type'=>'danger','msg'=> e($translations['flash_envoi_echec'] ?? 'Échec envoi.')]; } } } catch(Throwable $e) { $flash = ['type'=>'danger','msg'=> e($translations['flash_erreur'] ?? 'Erreur')]; } } // ==================== Récupération liste praticiens ==================== $sql = " SELECT pr.id, pr.titre, pr.nom, pr.prenom, pr.email, pr.photo, pr.date_expiration, ab.nom AS abo_nom, ( SELECT p.statut FROM paiement p WHERE p.praticien_id = pr.id ORDER BY p.date_paiement DESC, p.id DESC LIMIT 1 ) AS pay_statut FROM praticien pr LEFT JOIN abonnement ab ON ab.id = pr.abonnement_id ORDER BY pr.nom ASC, pr.prenom ASC "; $rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC); // ==================== Vue ==================== ?> <?= e($translations['titre_page_gerer_praticiens'] ?? 'Gestion praticiens') ?>

$translations['statut_active'] ?? 'Active', 'expiring' => $translations['statut_expiring'] ?? 'Bientôt', 'expired' => $translations['statut_expired'] ?? 'Expirée', ][$state] ?? ($translations['statut_unknown'] ?? '—'); $badgePay = (function($st,$t){ if ($st === 'payé') return ''.e($t['pay_ok'] ?? 'Payé').''; if ($st === 'en_attente') return ''.e($t['pay_wait']?? 'En attente').''; if ($st === 'échoué') return ''.e($t['pay_ko'] ?? 'Échoué').''; return ''.e((string)$st).''; })($r['pay_statut'] ?? '', $translations); $dateFinFmt = ''; if (!empty($r['date_expiration'])) { try { $dateFinFmt = (new DateTimeImmutable($r['date_expiration']))->format($DATE_FMT_TABLE); } catch(Throwable $e){} } $hay = mb_strtolower($name.' '.($r['email']??'').' '.($r['abo_nom']??''), 'UTF-8'); ?>