' . htmlspecialchars($translations['non_autorise'] ?? 'Доступ запрещён.', ENT_QUOTES, 'UTF-8') . '
'; exit; } $mustReauth=function(int $ttl=600){$t=(int)($_SESSION['admin_reauth_at']??0);return ($t===0||(time()-$t)>$ttl);}; if ($mustReauth()){ $qs=http_build_query(['page'=>'reauth','redir'=>($_SERVER['REQUEST_URI']??'index.php?page=maintenance')]); header('Location:?'.$qs); exit; } if (!isset($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } $CSRF = $_SESSION['csrf_token']; function e($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } // --------- Template HTML du mail --------- function buildAdminMailHtml(string $subject, string $bodyHtml): string { global $translations; $brand = 'USLUGI.FR'; $brandBas = $translations['mailing_praticiens_brand_bas'] ?? 'УСЛУГИ.ФР'; $ctaUrl = 'https://uslugi.fr/'; $ctaLabel = $translations['mailing_praticiens_cta_open_site'] ?? 'Открыть сайт'; $thanks = $translations['mailing_praticiens_footer_thanks'] ?? 'Спасибо'; return ' '.e($subject).'
'.$brand.'
'.$bodyHtml.'
'.e($ctaLabel).'
© '.date('Y').' '.$brandBas.' — '.e($thanks).'
'; } function sendPraticienMail(string $to, string $subject, string $html): bool { // Optionnel: définir From/Reply-To ici, sinon mail_config.php fait foi [$ok, $err] = uslugi_mail($to, $subject, $html, null, [ // 'from' => ['no-reply@uslugi.fr', 'USLUGI.FR'], // 'reply_to' => ['support@uslugi.fr', 'USLUGI.FR'], // 'headers' => ['X-USLUGI-Source' => 'admin_mailing_praticiens'], ]); if (!$ok && $err) { error_log('mailing_praticiens send error to '.$to.' : '.$err); } return $ok; } // --------- Traitement POST --------- $flash = null; if (($_POST['do'] ?? '') === 'send_mailing') { try { if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf'] ?? '')) { throw new Exception('CSRF'); } $subject = trim($_POST['subject'] ?? ''); $body = trim($_POST['body'] ?? ''); $ids = $_POST['recipients'] ?? []; if ($subject === '' || $body === '') { throw new Exception('EMPTY'); } if (!is_array($ids) || count($ids) === 0) { throw new Exception('NO_RECIPIENTS'); } $ids = array_values(array_unique(array_filter(array_map('intval', $ids), fn($v)=>$v>0))); if (!$ids) { throw new Exception('NO_RECIPIENTS'); } // Récupérer les emails $in = implode(',', array_fill(0, count($ids), '?')); $sql = "SELECT id, prenom, nom, email FROM praticien WHERE id IN ($in) AND email <> ''"; $st = $pdo->prepare($sql); $st->execute($ids); $rows = $st->fetchAll(PDO::FETCH_ASSOC); if (!$rows) { throw new Exception('NO_EMAILS'); } $sent = 0; $total = count($rows); foreach ($rows as $r) { $email = trim($r['email'] ?? ''); if ($email === '') continue; // On laisse TinyMCE produire le HTML, on l’utilise tel quel $html = buildAdminMailHtml($subject, $body); if (sendPraticienMail($email, $subject, $html)) { $sent++; } // usleep(100000); // 0.1s } $flash = [ 'type' => 'success', 'msg' => ($translations['mailing_praticiens_sent_count'] ?? 'Отправлено писем: %d / %d') ? sprintf((string)($translations['mailing_praticiens_sent_count'] ?? 'Отправлено писем: %d / %d'), $sent, $total) : ('Отправлено писем: '.$sent.' / '.$total) ]; } catch (Throwable $e) { $code = $e->getMessage(); $msg = $translations['mailing_praticiens_err_send'] ?? 'Ошибка при отправке.'; if ($code === 'EMPTY') $msg = $translations['mailing_praticiens_err_empty'] ?? 'Тема и текст письма обязательны.'; if ($code === 'NO_RECIPIENTS') $msg = $translations['mailing_praticiens_err_no_recipients'] ?? 'Выберите хотя бы одного практикующего.'; if ($code === 'CSRF') $msg = $translations['mailing_praticiens_err_csrf'] ?? 'Ошибка безопасности. Обновите страницу.'; $flash = ['type'=>'danger','msg'=>$msg]; } } // --------- Récupération des praticiens --------- $sql = " SELECT pr.id, pr.nom, pr.prenom, pr.email, pr.abonnement_id, pr.date_expiration, ab.nom AS abo_nom 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); // Préparer options de filtre par abonnement $aboOptions = []; foreach ($rows as $r) { if (!empty($r['abonnement_id'])) { $aid = (int)$r['abonnement_id']; $aboOptions[$aid] = $r['abo_nom'] ?: (($translations['mailing_praticiens_tarif_id'] ?? 'ID %d') ? sprintf((string)($translations['mailing_praticiens_tarif_id'] ?? 'ID %d'), $aid) : ('ID '.$aid)); } } ksort($aboOptions); $today = new DateTimeImmutable('today'); ?>


format('Y-m-d H:i'); $status = ($exp < $today) ? 'expired' : 'active'; } catch (Throwable $e) { $status = 'noabo'; } } $hay = mb_strtolower( trim(($r['prenom'] ?? '').' '.($r['nom'] ?? '').' '.($r['email'] ?? '').' '.($r['abo_nom'] ?? '')), 'UTF-8' ); $aboId = (int)($r['abonnement_id'] ?? 0); ?>