$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 = '
';
$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 !== '')
? ' |
'
: '';
return '
'.$headline.'
| '.$brand.' |
| '.$headline.' |
| '.$lead.' |
'.$cardHtml.'
|
'.$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') ?>