From 7b7cc9c02a6f3f35e7a985e1e4eba122cc248d0a Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 11 Apr 2026 19:16:03 +0000 Subject: [PATCH] =?UTF-8?q?Wysy=C5=82ka=20ponowna=20raport=C3=B3w?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin.php | 99 +++++++++++++ app/diagnostic_functions.php | 263 ++++++++++++++++++++++++++++++++--- diagnostic.php | 13 +- mail/MailService.php | 4 + 4 files changed, 361 insertions(+), 18 deletions(-) diff --git a/admin.php b/admin.php index e98cf45..c1a472e 100644 --- a/admin.php +++ b/admin.php @@ -22,6 +22,51 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { header('Location: /admin.php'); exit; } + if ($action === 'save-notification-email') { + if (!diagnostic_admin_is_authenticated()) { + diagnostic_flash_set('warning', 'Zaloguj się jako administrator, aby zmienić adres powiadomień.'); + header('Location: /admin.php'); + exit; + } + + $notificationEmail = trim((string)($_POST['notification_email'] ?? '')); + if (!filter_var($notificationEmail, FILTER_VALIDATE_EMAIL)) { + diagnostic_flash_set('danger', 'Podaj poprawny adres e-mail administratora do powiadomień o nowych prośbach o kontakt.'); + header('Location: /admin.php'); + exit; + } + + diagnostic_admin_setting_set('admin_notification_email', $notificationEmail); + diagnostic_flash_set('success', 'Adres e-mail administratora do powiadomień został zapisany.'); + header('Location: /admin.php'); + exit; + } + if ($action === 'resend-report') { + if (!diagnostic_admin_is_authenticated()) { + diagnostic_flash_set('warning', 'Zaloguj się jako administrator, aby ponownie wysłać raport.'); + header('Location: /admin.php'); + exit; + } + + $attemptId = (int)($_POST['attempt_id'] ?? 0); + if ($attemptId <= 0) { + diagnostic_flash_set('danger', 'Nie udało się ustalić, dla której próby należy ponownie wysłać raport.'); + header('Location: /admin.php'); + exit; + } + + $result = diagnostic_admin_resend_report($attemptId); + if (!empty($result['success'])) { + $attempt = diagnostic_get_attempt($attemptId); + $recipient = $attempt['email'] ?? ''; + diagnostic_flash_set('success', 'Raport został wysłany ponownie do klienta' . ($recipient !== '' ? ' (' . $recipient . ')' : '') . '.'); + } else { + diagnostic_flash_set('danger', 'Nie udało się ponownie wysłać raportu: ' . (string)($result['error'] ?? 'Nieznany błąd.')); + } + + header('Location: /admin.php?attempt=' . $attemptId); + exit; + } } if (diagnostic_admin_is_authenticated() && ($_GET['export'] ?? '') === 'csv') { @@ -45,6 +90,7 @@ if (!$selectedAttempt && !empty($attempts)) { } $questionMap = diagnostic_question_map(); $credentials = diagnostic_admin_credentials(); +$notificationConfig = diagnostic_admin_is_authenticated() ? diagnostic_admin_notification_config() : ['configured_email' => '', 'fallback_email' => '', 'effective_email' => '', 'source' => 'none']; ?> @@ -137,6 +183,41 @@ $credentials = diagnostic_admin_credentials();
%Średni wynik
+
+
+
+
Powiadomienia o konsultacji
+

Adres administratora dla nowych numerów telefonu

+

Gdy użytkownik poda numer telefonu po ukończeniu diagnozy, system wyśle powiadomienie e-mail właśnie na ten adres.

+
+ +
+ + +
+
+ +
+
+
+
+
+ Aktywna konfiguracja + + Powiadomienia trafiają na: + Źródło: ustawienie zapisane w panelu administracyjnym. + + Powiadomienia tymczasowo trafią na: + To fallback testowy z MAIL_TO. Ustaw adres w panelu, aby zarządzać nim bez edycji środowiska. + + Brak ustawionego adresu do powiadomień. + Do czasu zapisania adresu w panelu e-mail o nowej prośbie o kontakt nie zostanie wysłany. + +
+
+
+
+
@@ -214,6 +295,24 @@ $credentials = diagnostic_admin_credentials();
Telefon kontaktowy
Prośba o kontakt
Zakończono
+
+
+ Ponowna wysyłka raportu + + Wyślij ponownie raport na adres klienta: +
+ + + +
+ + Ponowna wysyłka będzie dostępna po ukończeniu diagnozy i wygenerowaniu wyniku. + + + Ostatni błąd wysyłki: + +
+
diff --git a/app/diagnostic_functions.php b/app/diagnostic_functions.php index 6dab6aa..de6b7a9 100644 --- a/app/diagnostic_functions.php +++ b/app/diagnostic_functions.php @@ -32,6 +32,14 @@ function ensure_diagnostic_schema(): void if (!diagnostic_schema_column_exists('diagnostic_attempts', 'consultation_requested_at')) { db()->exec("ALTER TABLE diagnostic_attempts ADD COLUMN consultation_requested_at DATETIME NULL AFTER contact_phone"); } + + db()->exec( + "CREATE TABLE IF NOT EXISTS diagnostic_settings ( + setting_key VARCHAR(100) NOT NULL PRIMARY KEY, + setting_value VARCHAR(255) NULL, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ); } function diagnostic_schema_column_exists(string $tableName, string $columnName): bool @@ -321,17 +329,91 @@ function diagnostic_finalize_attempt(int $attemptId): ?array $attempt = diagnostic_get_attempt($attemptId); $emailResult = diagnostic_send_report_email($attempt); - - $emailStmt = db()->prepare('UPDATE diagnostic_attempts SET email_report_status = :status, email_report_error = :error WHERE id = :id'); - $emailStmt->execute([ - ':status' => $emailResult['success'] ? 'sent' : 'failed', - ':error' => $emailResult['success'] ? null : ($emailResult['error'] ?? 'Nieznany błąd wysyłki e-mail'), - ':id' => $attemptId, - ]); + diagnostic_store_report_delivery($attemptId, $emailResult); return diagnostic_get_attempt($attemptId); } +function diagnostic_store_report_delivery(int $attemptId, array $emailResult): void +{ + $emailStmt = db()->prepare( + 'UPDATE diagnostic_attempts + SET email_report_status = :status, email_report_error = :error, updated_at = NOW() + WHERE id = :id' + ); + $emailStmt->execute([ + ':status' => !empty($emailResult['success']) ? 'sent' : 'failed', + ':error' => !empty($emailResult['success']) ? null : ($emailResult['error'] ?? 'Nieznany błąd wysyłki e-mail'), + ':id' => $attemptId, + ]); +} + +function diagnostic_attempt_can_send_report(array $attempt): bool +{ + return ($attempt['status'] ?? '') === 'completed' + && !empty($attempt['email']) + && filter_var((string)$attempt['email'], FILTER_VALIDATE_EMAIL) + && !empty($attempt['result']); +} + +function diagnostic_admin_resend_report(int $attemptId): array +{ + $attempt = diagnostic_get_attempt($attemptId); + if (!$attempt) { + return ['success' => false, 'error' => 'Nie znaleziono wskazanej próby.']; + } + + if (!diagnostic_attempt_can_send_report($attempt)) { + return ['success' => false, 'error' => 'Raport można wysłać ponownie tylko dla ukończonej próby z poprawnym adresem e-mail i wynikiem.']; + } + + $emailResult = diagnostic_send_report_email($attempt); + diagnostic_store_report_delivery($attemptId, $emailResult); + + return $emailResult; +} + +function diagnostic_email_escape(?string $value): string +{ + return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); +} + +function diagnostic_email_document(string $subject, string $preheader, string $contentHtml): string +{ + $safeSubject = diagnostic_email_escape($subject); + $safePreheader = diagnostic_email_escape($preheader); + + return '' + . '' . $safeSubject . '' + . '' + . '
' . $safePreheader . '
' + . '' + . '
' + . '' + . '' + . '' + . '' + . '
' + . '
Przegląd procesów
' + . '
' . $safeSubject . '
' + . '
' . $contentHtml . '
Ta wiadomość została wygenerowana automatycznie przez narzędzie diagnozy dojrzałości procesowej.
' + . '
' + . ''; +} + +function diagnostic_email_data_table(array $rows): string +{ + $html = ''; + foreach ($rows as $label => $value) { + $html .= '' + . '' + . '' + . ''; + } + return $html . '
' . diagnostic_email_escape((string)$label) . '' . diagnostic_email_escape((string)$value) . '
'; +} + + function diagnostic_build_report_html(array $attempt): string { $result = $attempt['result'] ?? []; @@ -339,24 +421,51 @@ function diagnostic_build_report_html(array $attempt): string $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; $resultUrl = $scheme . '://' . $host . '/diagnostic.php?view=result'; - $weakest = ''; + $priorityCards = ''; foreach (($result['weakest_areas'] ?? []) as $area) { - $weakest .= '
  • ' . htmlspecialchars($area['section_name']) . ': ' . (int)$area['percentage'] . '% dojrzałości — ten obszar najprawdopodobniej generuje dziś największe tarcia, opóźnienia lub ukryte ryzyka.
  • '; + $priorityCards .= '
    ' + . '
    Priorytet
    ' + . '
    ' . diagnostic_email_escape($area['section_name'] ?? '') . '
    ' + . '
    ' . (int)($area['percentage'] ?? 0) . '% dojrzałości — ten obszar najczęściej wymaga najszybszego uporządkowania.
    ' + . '
    '; + } + if ($priorityCards === '') { + $priorityCards = '

    Brak danych o priorytetowych obszarach do pokazania w tym raporcie.

    '; } $recommendations = ''; foreach (($result['recommendations'] ?? []) as $item) { - $recommendations .= '
  • ' . htmlspecialchars($item) . '
  • '; + $recommendations .= '
  • ' . diagnostic_email_escape((string)$item) . '
  • '; + } + if ($recommendations === '') { + $recommendations = '
  • Brak dodatkowych rekomendacji.
  • '; } - return '
    ' - . '

    Raport z diagnozy dojrzałości procesowej

    ' - . '

    Dziękujemy za wypełnienie diagnozy. Obecny poziom dojrzałości operacyjnej Twojej firmy to ' . htmlspecialchars($result['segment_label'] ?? 'Diagnoza zakończona') . ' (' . (int)($result['percentage_score'] ?? 0) . '%).

    ' - . '

    ' . htmlspecialchars($result['segment_summary'] ?? '') . '

    ' - . '

    Priorytetowe obszary

      ' . $weakest . '
    ' - . '

    Rekomendowane kolejne kroki

      ' . $recommendations . '
    ' - . '

    Jeśli chcesz omówić wynik i ustalić priorytety zmian, wróć do podsumowania: ' . htmlspecialchars($resultUrl) . '.

    ' + $content = '

    Dziękujemy za wypełnienie diagnozy. Obecny poziom dojrzałości operacyjnej Twojej firmy to ' . diagnostic_email_escape($result['segment_label'] ?? 'Diagnoza zakończona') . '.

    ' + . '' + . '' + . '' + . '' + . '
    ' + . '
    Wynik łączny
    ' + . '
    ' . (int)($result['percentage_score'] ?? 0) . '%
    ' + . '
    ' . diagnostic_email_escape($result['segment_summary'] ?? '') . '
    ' + . '
    ' + . '

    Priorytetowe obszary do poprawy

    ' + . $priorityCards + . '

    Rekomendowane kolejne kroki

    ' + . '
      ' . $recommendations . '
    ' + . '
    ' + . '
    Chcesz omówić wynik z zespołem?
    ' + . '
    Wróć do podsumowania diagnozy, aby ustalić priorytety zmian i zamówić kolejny krok.
    ' + . 'Otwórz podsumowanie diagnozy' . '
    '; + + return diagnostic_email_document( + 'Raport z diagnozy dojrzałości procesowej', + 'Twój wynik diagnozy i rekomendowane kolejne kroki są gotowe.', + $content + ); } function diagnostic_build_report_text(array $attempt): string @@ -391,6 +500,126 @@ function diagnostic_send_report_email(array $attempt): array return MailService::sendMail($attempt['email'], $subject, $html, $text); } +function diagnostic_admin_setting_get(string $key): ?string +{ + $stmt = db()->prepare('SELECT setting_value FROM diagnostic_settings WHERE setting_key = :setting_key LIMIT 1'); + $stmt->execute([':setting_key' => $key]); + $value = $stmt->fetchColumn(); + if ($value === false || $value === null) { + return null; + } + return trim((string)$value); +} + +function diagnostic_admin_setting_set(string $key, ?string $value): void +{ + $stmt = db()->prepare( + 'INSERT INTO diagnostic_settings (setting_key, setting_value) + VALUES (:setting_key, :setting_value) + ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value), updated_at = CURRENT_TIMESTAMP' + ); + $stmt->bindValue(':setting_key', $key, PDO::PARAM_STR); + if ($value === null || $value === '') { + $stmt->bindValue(':setting_value', null, PDO::PARAM_NULL); + } else { + $stmt->bindValue(':setting_value', $value, PDO::PARAM_STR); + } + $stmt->execute(); +} + +function diagnostic_admin_notification_config(): array +{ + $configuredEmail = diagnostic_admin_setting_get('admin_notification_email') ?? ''; + if ($configuredEmail !== '' && !filter_var($configuredEmail, FILTER_VALIDATE_EMAIL)) { + $configuredEmail = ''; + } + + $fallbackEmail = trim((string)(getenv('MAIL_TO') ?: '')); + if ($fallbackEmail !== '' && !filter_var($fallbackEmail, FILTER_VALIDATE_EMAIL)) { + $fallbackEmail = ''; + } + + $effectiveEmail = $configuredEmail !== '' ? $configuredEmail : $fallbackEmail; + $source = $configuredEmail !== '' ? 'panel' : ($fallbackEmail !== '' ? 'env' : 'none'); + + return [ + 'configured_email' => $configuredEmail, + 'fallback_email' => $fallbackEmail, + 'effective_email' => $effectiveEmail, + 'source' => $source, + ]; +} + +function diagnostic_admin_notification_email(): ?string +{ + $config = diagnostic_admin_notification_config(); + return $config['effective_email'] !== '' ? $config['effective_email'] : null; +} + +function diagnostic_send_consultation_notification(array $attempt): array +{ + $recipient = diagnostic_admin_notification_email(); + if ($recipient === null) { + return ['success' => false, 'error' => 'Brak skonfigurowanego adresu powiadomień administratora', 'reason' => 'missing_recipient']; + } + + $phone = trim((string)($attempt['contact_phone'] ?? '')); + if ($phone === '') { + return ['success' => false, 'error' => 'Brak numeru telefonu do przekazania', 'reason' => 'missing_phone']; + } + + $result = $attempt['result'] ?? []; + $score = isset($result['percentage_score']) ? (int)$result['percentage_score'] . '%' : '—'; + $segment = (string)($result['segment_label'] ?? '—'); + $requestedAt = (string)($attempt['consultation_requested_at'] ?? date('Y-m-d H:i:s')); + $respondentEmail = trim((string)($attempt['email'] ?? '')); + $marketingConsent = ((int)($attempt['marketing_consent'] ?? 0)) === 1 ? 'Tak' : 'Nie'; + + $subject = 'Nowa prośba o konsultację po diagnozie'; + $html = diagnostic_email_document( + $subject, + 'Użytkownik zakończył diagnozę i poprosił o kontakt telefoniczny.', + '

    Użytkownik zakończył diagnozę i poprosił o dalszą konsultację.

    ' + . '
    ' + . '
    Telefon kontaktowy
    ' + . '
    ' . diagnostic_email_escape($phone) . '
    ' + . '
    ' + . diagnostic_email_data_table([ + 'ID próby' => (string)(int)($attempt['id'] ?? 0), + 'E-mail respondenta' => $respondentEmail !== '' ? $respondentEmail : '—', + 'Data zgłoszenia' => $requestedAt, + 'Wynik diagnozy' => $score, + 'Segment' => $segment, + 'Zgoda marketingowa' => $marketingConsent, + ]) + ); + + $text = "Nowa prośba o kontakt + +" + . "ID próby: " . (int)($attempt['id'] ?? 0) . " +" + . "E-mail respondenta: " . ($respondentEmail !== '' ? $respondentEmail : '—') . " +" + . "Telefon kontaktowy: {$phone} +" + . "Data zgłoszenia: {$requestedAt} +" + . "Wynik diagnozy: {$score} +" + . "Segment: {$segment} +" + . "Zgoda marketingowa: {$marketingConsent} +"; + + $opts = []; + if ($respondentEmail !== '' && filter_var($respondentEmail, FILTER_VALIDATE_EMAIL)) { + $opts['reply_to'] = $respondentEmail; + } + + return MailService::sendMail($recipient, $subject, $html, $text, $opts); +} + function diagnostic_flash_set(string $type, string $message): void { $_SESSION['diagnostic_flash'] = ['type' => $type, 'message' => $message]; diff --git a/diagnostic.php b/diagnostic.php index 324cef2..74412a7 100644 --- a/diagnostic.php +++ b/diagnostic.php @@ -98,7 +98,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } diagnostic_save_contact_phone((int)$attempt['id'], $phone); - diagnostic_flash_set('success', 'Dziękujemy. Numer telefonu został zapisany — skontaktujemy się w sprawie dalszej konsultacji.'); + $updatedAttempt = diagnostic_get_attempt((int)$attempt['id']) ?? $attempt; + $notificationResult = diagnostic_send_consultation_notification($updatedAttempt); + + if (!empty($notificationResult['success'])) { + diagnostic_flash_set('success', 'Dziękujemy. Numer telefonu został zapisany, a administrator otrzymał powiadomienie e-mail o prośbie o kontakt.'); + } elseif (($notificationResult['reason'] ?? '') === 'missing_recipient') { + diagnostic_flash_set('warning', 'Numer telefonu został zapisany, ale adres powiadomień administratora nie jest jeszcze ustawiony w panelu administracyjnym.'); + } else { + error_log('Consultation notification email failed: ' . ($notificationResult['error'] ?? 'unknown')); + diagnostic_flash_set('warning', 'Numer telefonu został zapisany, ale nie udało się wysłać powiadomienia e-mail do administratora.'); + } + header('Location: /diagnostic.php?view=result'); exit; } diff --git a/mail/MailService.php b/mail/MailService.php index d801067..570132d 100644 --- a/mail/MailService.php +++ b/mail/MailService.php @@ -43,6 +43,8 @@ class MailService $mail = new PHPMailer\PHPMailer\PHPMailer(true); try { + $mail->CharSet = 'UTF-8'; + $mail->Encoding = PHPMailer\PHPMailer\PHPMailer::ENCODING_BASE64; $mail->isSMTP(); $mail->Host = $cfg['smtp_host'] ?? ''; $mail->Port = (int)($cfg['smtp_port'] ?? 587); @@ -158,6 +160,8 @@ class MailService { $mail = new PHPMailer\PHPMailer\PHPMailer(true); try { + $mail->CharSet = 'UTF-8'; + $mail->Encoding = PHPMailer\PHPMailer\PHPMailer::ENCODING_BASE64; $mail->isSMTP(); $mail->Host = $cfg['smtp_host'] ?? ''; $mail->Port = (int)($cfg['smtp_port'] ?? 587);