exec( "CREATE TABLE IF NOT EXISTS diagnostic_attempts ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, session_token VARCHAR(64) NOT NULL UNIQUE, email VARCHAR(190) NOT NULL, marketing_consent TINYINT(1) NOT NULL DEFAULT 0, status VARCHAR(20) NOT NULL DEFAULT 'in_progress', current_step INT UNSIGNED NOT NULL DEFAULT 1, answers_json LONGTEXT NULL, result_json LONGTEXT NULL, report_subject VARCHAR(255) NULL, report_html LONGTEXT NULL, report_text LONGTEXT NULL, report_generated_at DATETIME NULL, ai_summary_text TEXT NULL, ai_summary_generated_at DATETIME NULL, email_report_status VARCHAR(20) NOT NULL DEFAULT 'pending', email_report_error TEXT NULL, contact_phone VARCHAR(40) NULL, consultation_requested_at DATETIME NULL, started_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, completed_at DATETIME NULL, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_status (status), INDEX idx_email (email) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" ); if (!diagnostic_schema_column_exists('diagnostic_attempts', 'report_subject')) { db()->exec("ALTER TABLE diagnostic_attempts ADD COLUMN report_subject VARCHAR(255) NULL AFTER result_json"); } if (!diagnostic_schema_column_exists('diagnostic_attempts', 'report_html')) { db()->exec("ALTER TABLE diagnostic_attempts ADD COLUMN report_html LONGTEXT NULL AFTER report_subject"); } if (!diagnostic_schema_column_exists('diagnostic_attempts', 'report_text')) { db()->exec("ALTER TABLE diagnostic_attempts ADD COLUMN report_text LONGTEXT NULL AFTER report_html"); } if (!diagnostic_schema_column_exists('diagnostic_attempts', 'report_generated_at')) { db()->exec("ALTER TABLE diagnostic_attempts ADD COLUMN report_generated_at DATETIME NULL AFTER report_text"); } if (!diagnostic_schema_column_exists('diagnostic_attempts', 'ai_summary_text')) { db()->exec("ALTER TABLE diagnostic_attempts ADD COLUMN ai_summary_text TEXT NULL AFTER report_generated_at"); } if (!diagnostic_schema_column_exists('diagnostic_attempts', 'ai_summary_generated_at')) { db()->exec("ALTER TABLE diagnostic_attempts ADD COLUMN ai_summary_generated_at DATETIME NULL AFTER ai_summary_text"); } if (!diagnostic_schema_column_exists('diagnostic_attempts', 'contact_phone')) { db()->exec("ALTER TABLE diagnostic_attempts ADD COLUMN contact_phone VARCHAR(40) NULL AFTER email_report_error"); } 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 { $stmt = db()->prepare( 'SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = :schema AND TABLE_NAME = :table_name AND COLUMN_NAME = :column_name' ); $stmt->execute([ ':schema' => DB_NAME, ':table_name' => $tableName, ':column_name' => $columnName, ]); return (int)$stmt->fetchColumn() > 0; } function diagnostic_meta(string $pageTitle, ?string $fallbackDescription = null): array { $projectName = $_SERVER['PROJECT_NAME'] ?? 'Przegląd Procesów'; $projectDescription = trim((string)($_SERVER['PROJECT_DESCRIPTION'] ?? '')); return [ 'title' => $pageTitle . ' · ' . $projectName, 'description' => $projectDescription !== '' ? $projectDescription : ($fallbackDescription ?? 'Diagnoza dojrzałości procesowej dla właścicieli firm i liderów operacyjnych.'), 'image' => $_SERVER['PROJECT_IMAGE_URL'] ?? '', 'project_name' => $projectName, ]; } function diagnostic_admin_entry_filename(): string { return 'admin-vr0heltmao.php'; } function diagnostic_admin_path(array $query = []): string { $path = '/' . diagnostic_admin_entry_filename(); if ($query === []) { return $path; } $queryString = http_build_query($query); return $queryString !== '' ? $path . '?' . $queryString : $path; } function diagnostic_brand_home_url(): string { return 'https://doktorbiznes.pl/'; } function diagnostic_brand_logo_public_path(): string { return '/assets/pasted-20260411-194222-a875bba4.png'; } function diagnostic_brand_logo_file_path(): string { return __DIR__ . '/../assets/pasted-20260411-194222-a875bba4.png'; } function diagnostic_brand_logo_alt(): string { return 'DoktorBiznes.pl — Gdy Twój biznes potrzebuje trafnej diagnozy'; } function diagnostic_brand_logo_anchor(string $className = '', bool $openInNewTab = true): string { $classes = trim('doctor-biznes-logo-link ' . preg_replace('/[^a-zA-Z0-9_\- ]+/', '', $className)); $target = $openInNewTab ? ' target="_blank" rel="noopener noreferrer"' : ''; return '' . '' . ''; } function diagnostic_brand_logo_email_src(): string { static $src = null; if ($src !== null) { return $src; } $path = diagnostic_brand_logo_file_path(); if (!is_file($path) || !is_readable($path)) { $src = ''; return $src; } $data = file_get_contents($path); if ($data === false || $data === '') { $src = ''; return $src; } $src = 'data:image/png;base64,' . base64_encode($data); return $src; } function diagnostic_flat_questions(): array { $definition = diagnostic_quiz_definition(); $questions = []; foreach ($definition['sections'] as $sectionIndex => $section) { foreach ($section['questions'] as $questionIndex => $question) { $questions[] = [ 'id' => $question['id'], 'text' => $question['text'], 'reverse' => !empty($question['reverse']), 'section_id' => $section['id'], 'section_name' => $section['name'], 'section_order' => $sectionIndex, 'question_order' => $questionIndex, ]; } } return $questions; } function diagnostic_question_map(): array { $map = []; foreach (diagnostic_flat_questions() as $question) { $map[$question['id']] = $question; } return $map; } function diagnostic_find_question(string $questionId): ?array { $map = diagnostic_question_map(); return $map[$questionId] ?? null; } function diagnostic_answer_label(int $value): string { $definition = diagnostic_quiz_definition(); return $definition['answer_scale'][$value] ?? 'Nieznana odpowiedź'; } function diagnostic_segment_for_percentage(int $percentage): array { $definition = diagnostic_quiz_definition(); foreach ($definition['segments'] as $segment) { if ($percentage >= $segment['min'] && $percentage <= $segment['max']) { return $segment; } } return end($definition['segments']); } function diagnostic_create_attempt(string $email, bool $marketingConsent): int { $stmt = db()->prepare( 'INSERT INTO diagnostic_attempts (session_token, email, marketing_consent, status, current_step, answers_json, result_json, email_report_status) VALUES (:session_token, :email, :marketing_consent, :status, :current_step, :answers_json, :result_json, :email_report_status)' ); $stmt->execute([ ':session_token' => bin2hex(random_bytes(16)), ':email' => $email, ':marketing_consent' => $marketingConsent ? 1 : 0, ':status' => 'in_progress', ':current_step' => 1, ':answers_json' => json_encode([], JSON_UNESCAPED_UNICODE), ':result_json' => null, ':email_report_status' => 'pending', ]); $id = (int)db()->lastInsertId(); $_SESSION['diagnostic_attempt_id'] = $id; return $id; } function diagnostic_clear_attempt_session(): void { unset($_SESSION['diagnostic_attempt_id']); } function diagnostic_current_attempt(): ?array { $attemptId = isset($_SESSION['diagnostic_attempt_id']) ? (int)$_SESSION['diagnostic_attempt_id'] : 0; if ($attemptId <= 0) { return null; } return diagnostic_get_attempt($attemptId); } function diagnostic_get_attempt(int $attemptId): ?array { $stmt = db()->prepare('SELECT * FROM diagnostic_attempts WHERE id = :id LIMIT 1'); $stmt->execute([':id' => $attemptId]); $attempt = $stmt->fetch(); if (!$attempt) { return null; } $attempt['answers'] = diagnostic_decode_json_map($attempt['answers_json'] ?? ''); $attempt['result'] = diagnostic_decode_json_map($attempt['result_json'] ?? ''); return $attempt; } function diagnostic_decode_json_map(?string $json): array { if (!$json) { return []; } $decoded = json_decode($json, true); return is_array($decoded) ? $decoded : []; } function diagnostic_save_answer(int $attemptId, string $questionId, int $selectedValue, int $currentStep): void { $attempt = diagnostic_get_attempt($attemptId); if (!$attempt) { return; } $answers = $attempt['answers']; $answers[$questionId] = $selectedValue; $stmt = db()->prepare( 'UPDATE diagnostic_attempts SET answers_json = :answers_json, current_step = :current_step, updated_at = NOW() WHERE id = :id' ); $stmt->execute([ ':answers_json' => json_encode($answers, JSON_UNESCAPED_UNICODE), ':current_step' => $currentStep, ':id' => $attemptId, ]); } function diagnostic_update_step(int $attemptId, int $currentStep): void { $stmt = db()->prepare('UPDATE diagnostic_attempts SET current_step = :current_step, updated_at = NOW() WHERE id = :id'); $stmt->execute([':current_step' => $currentStep, ':id' => $attemptId]); } function diagnostic_normalize_phone(string $phone): string { $phone = trim(preg_replace('/\s+/', ' ', $phone) ?? ''); return $phone; } function diagnostic_phone_is_valid(string $phone): bool { if ($phone === '' || strlen($phone) > 40) { return false; } if (!preg_match('/^[0-9+()\-\s]+$/', $phone)) { return false; } $digitsOnly = preg_replace('/\D+/', '', $phone) ?? ''; $digitCount = strlen($digitsOnly); return $digitCount >= 7 && $digitCount <= 15; } function diagnostic_save_contact_phone(int $attemptId, string $phone): void { $stmt = db()->prepare( 'UPDATE diagnostic_attempts SET contact_phone = :contact_phone, consultation_requested_at = NOW(), updated_at = NOW() WHERE id = :id' ); $stmt->execute([ ':contact_phone' => $phone, ':id' => $attemptId, ]); } function diagnostic_effective_answer_value(array $question, array $answers): int { $value = (int)($answers[$question['id']] ?? 0); return !empty($question['reverse']) ? 3 - $value : $value; } function diagnostic_calculate_result(array $answers): array { $definition = diagnostic_quiz_definition(); $maxScore = count(diagnostic_flat_questions()) * 3; $totalScore = 0; $sectionScores = []; foreach ($definition['sections'] as $sectionIndex => $section) { $sectionScore = 0; $questionCount = count($section['questions']); foreach ($section['questions'] as $question) { $sectionScore += diagnostic_effective_answer_value($question, $answers); } $sectionMax = $questionCount * 3; $sectionPercentage = $sectionMax > 0 ? (int)round(($sectionScore / $sectionMax) * 100) : 0; $sectionScores[] = [ 'section_id' => $section['id'], 'section_name' => $section['name'], 'score' => $sectionScore, 'max_score' => $sectionMax, 'percentage' => $sectionPercentage, 'order' => $sectionIndex, ]; $totalScore += $sectionScore; } $ranked = $sectionScores; usort($ranked, static fn(array $a, array $b): int => $b['percentage'] <=> $a['percentage']); $strongest = array_slice($ranked, 0, 3); $weakest = array_slice(array_reverse($ranked), 0, 3); usort($sectionScores, static fn(array $a, array $b): int => $a['order'] <=> $b['order']); foreach ($sectionScores as &$sectionScore) { unset($sectionScore['order']); } unset($sectionScore); foreach ($strongest as &$area) { unset($area['order']); } unset($area); foreach ($weakest as &$area) { unset($area['order']); } unset($area); $percentage = $maxScore > 0 ? (int)round(($totalScore / $maxScore) * 100) : 0; $segment = diagnostic_segment_for_percentage($percentage); return [ 'total_score' => $totalScore, 'max_score' => $maxScore, 'percentage_score' => $percentage, 'segment_key' => $segment['key'], 'segment_label' => $segment['label'], 'segment_summary' => $segment['summary'], 'recommendations' => $segment['recommendations'], 'section_scores' => $sectionScores, 'strongest_areas' => $strongest, 'weakest_areas' => $weakest, 'completed_questions' => count($answers), 'total_questions' => count(diagnostic_flat_questions()), ]; } function diagnostic_finalize_attempt(int $attemptId): ?array { $attempt = diagnostic_get_attempt($attemptId); if (!$attempt) { return null; } $result = diagnostic_calculate_result($attempt['answers']); $stmt = db()->prepare( 'UPDATE diagnostic_attempts SET status = :status, current_step = :current_step, result_json = :result_json, report_subject = NULL, report_html = NULL, report_text = NULL, report_generated_at = NULL, ai_summary_text = NULL, ai_summary_generated_at = NULL, completed_at = NOW(), updated_at = NOW() WHERE id = :id' ); $stmt->execute([ ':status' => 'completed', ':current_step' => count(diagnostic_flat_questions()), ':result_json' => json_encode($result, JSON_UNESCAPED_UNICODE), ':id' => $attemptId, ]); $attempt = diagnostic_get_attempt($attemptId); $emailResult = diagnostic_send_report_email($attempt); 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_brand_marker_html(): string { $src = diagnostic_brand_logo_email_src(); if ($src === '') { return '
DoktorBiznes.pl
'; } return '
' . '' . '' . diagnostic_email_escape(diagnostic_brand_logo_alt()) . '' . '' . '
'; } function diagnostic_email_document(string $subject, string $preheader, string $contentHtml): string { $safeSubject = diagnostic_email_escape($subject); $safePreheader = diagnostic_email_escape($preheader); return '' . '' . $safeSubject . '' . '' . '
' . $safePreheader . '
' . '' . '
' . '' . '' . '' . '' . '
' . diagnostic_email_brand_marker_html() . '
' . $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_email_list(array $items, string $accentColor = '#111827'): string { $html = ''; } function diagnostic_report_limit(string $value, int $maxLength): string { $value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/u', '', $value) ?? $value; $value = str_replace(["\r\n", "\r"], "\n", $value); $value = preg_replace('/[ \t]+/u', ' ', $value) ?? $value; $value = preg_replace("/\n{3,}/u", "\n\n", $value) ?? $value; $value = trim($value); if ($maxLength > 0) { if (function_exists('mb_substr')) { $value = mb_substr($value, 0, $maxLength, 'UTF-8'); } else { $value = substr($value, 0, $maxLength); } $value = trim($value); } return $value; } function diagnostic_report_list(array $items, int $maxItems = 3, int $maxLength = 220): array { $normalized = []; foreach ($items as $item) { if (!is_scalar($item)) { continue; } $line = diagnostic_report_limit((string)$item, $maxLength); if ($line !== '') { $normalized[] = $line; } if (count($normalized) >= $maxItems) { break; } } return $normalized; } function diagnostic_question_answer_rows(array $attempt): array { $rows = []; $questions = diagnostic_flat_questions(); $answers = $attempt['answers'] ?? []; foreach ($questions as $question) { $value = $answers[$question['id']] ?? null; if ($value === null || $value === '') { continue; } $rows[] = [ 'section' => $question['section_name'], 'question' => $question['text'], 'answer_value' => (int)$value, 'answer_label' => diagnostic_answer_label((int)$value), 'reverse' => !empty($question['reverse']), ]; } return $rows; } function diagnostic_default_report_copy(array $attempt): array { $result = $attempt['result'] ?? []; $strengths = []; foreach (($result['strongest_areas'] ?? []) as $area) { $strengths[] = sprintf( '%s (%d%%) — to obszar, który już daje Ci relatywnie stabilny fundament operacyjny.', (string)($area['section_name'] ?? 'Mocny obszar'), (int)($area['percentage'] ?? 0) ); } $priorities = []; foreach (($result['weakest_areas'] ?? []) as $area) { $priorities[] = sprintf( '%s (%d%%) — tutaj najszybciej pojawiają się tarcia, opóźnienia lub zależność od ręcznego nadzoru.', (string)($area['section_name'] ?? 'Priorytet'), (int)($area['percentage'] ?? 0) ); } $nextSteps = []; foreach (($result['recommendations'] ?? []) as $item) { $nextSteps[] = (string)$item; } $segmentLabel = (string)($result['segment_label'] ?? 'Diagnoza zakończona'); $segmentSummary = diagnostic_report_limit((string)($result['segment_summary'] ?? ''), 420); $priorityNames = array_map( static fn(array $area): string => (string)($area['section_name'] ?? ''), (array)($result['weakest_areas'] ?? []) ); $priorityNames = array_values(array_filter($priorityNames, static fn(string $name): bool => trim($name) !== '')); $whatItMeans = 'Największy potencjał poprawy widać obecnie w obszarach: ' . ($priorityNames ? implode(', ', $priorityNames) : 'organizacji codziennej pracy') . '.'; return [ 'subject' => 'Twój raport diagnozy dojrzałości procesowej', 'preheader' => 'Gotowe: wynik diagnozy i rekomendowane kolejne kroki dla Twojej firmy.', 'intro' => 'Dziękujemy za wypełnienie diagnozy. Poniżej znajdziesz krótkie podsumowanie wyniku oraz rekomendowane następne kroki.', 'executive_summary' => $segmentSummary !== '' ? $segmentSummary : 'Masz już gotowy wynik diagnozy oraz wskazanie obszarów, które warto uporządkować w pierwszej kolejności.', 'what_it_means' => $whatItMeans, 'strengths' => diagnostic_report_list($strengths), 'priorities' => diagnostic_report_list($priorities), 'next_steps' => diagnostic_report_list($nextSteps), 'closing' => '', 'result_overview' => diagnostic_default_result_overview($attempt), ]; } function diagnostic_default_result_overview(array $attempt): string { $result = $attempt['result'] ?? []; $score = (int)($result['percentage_score'] ?? 0); $segmentLabel = (string)($result['segment_label'] ?? 'Diagnoza zakończona'); $segmentSummary = diagnostic_report_limit((string)($result['segment_summary'] ?? ''), 380); $strongest = array_map( static fn(array $area): string => sprintf('%s (%d%%)', (string)($area['section_name'] ?? 'Mocny obszar'), (int)($area['percentage'] ?? 0)), array_slice((array)($result['strongest_areas'] ?? []), 0, 2) ); $strongest = array_values(array_filter($strongest, static fn(string $item): bool => trim($item) !== '')); $weakest = array_map( static fn(array $area): string => sprintf('%s (%d%%)', (string)($area['section_name'] ?? 'Priorytet'), (int)($area['percentage'] ?? 0)), array_slice((array)($result['weakest_areas'] ?? []), 0, 2) ); $weakest = array_values(array_filter($weakest, static fn(string $item): bool => trim($item) !== '')); $recommendations = diagnostic_report_list((array)($result['recommendations'] ?? []), 2, 180); $parts = []; $parts[] = 'Wynik ' . $score . '% klasyfikuje firmę w segmencie „' . $segmentLabel . '”.'; if ($segmentSummary !== '') { $parts[] = $segmentSummary; } if ($strongest !== []) { $parts[] = 'Najmocniej wypadają obecnie: ' . implode(', ', $strongest) . '.'; } if ($weakest !== []) { $parts[] = 'Najwięcej uporządkowania wymagają: ' . implode(', ', $weakest) . '.'; } if ($recommendations !== []) { $parts[] = 'Najbardziej praktyczny kolejny krok to: ' . implode(' ', $recommendations); } return diagnostic_report_limit(implode(' ', $parts), 1000); } function diagnostic_ai_report_prompt_payload(array $attempt): array { $result = $attempt['result'] ?? []; return [ 'quiz' => [ 'title' => diagnostic_quiz_definition()['quiz']['title'] ?? 'Diagnoza dojrzałości procesowej', 'language' => 'pl', ], 'recipient' => [ 'email' => (string)($attempt['email'] ?? ''), ], 'result' => [ 'score_percent' => (int)($result['percentage_score'] ?? 0), 'segment_label' => (string)($result['segment_label'] ?? ''), 'segment_summary' => (string)($result['segment_summary'] ?? ''), 'recommendations' => array_values((array)($result['recommendations'] ?? [])), 'strongest_areas' => array_values((array)($result['strongest_areas'] ?? [])), 'weakest_areas' => array_values((array)($result['weakest_areas'] ?? [])), 'section_scores' => array_values((array)($result['section_scores'] ?? [])), ], 'answers' => diagnostic_question_answer_rows($attempt), ]; } function diagnostic_normalize_report_copy(array $candidate, array $defaults): array { $copy = $defaults; foreach (['subject' => 140, 'preheader' => 180, 'intro' => 420, 'executive_summary' => 650, 'what_it_means' => 520, 'closing' => 320, 'result_overview' => 1000] as $field => $maxLength) { if (!empty($candidate[$field]) && is_scalar($candidate[$field])) { $normalized = diagnostic_report_limit((string)$candidate[$field], $maxLength); if ($normalized !== '') { $copy[$field] = $normalized; } } } foreach (['strengths', 'priorities', 'next_steps'] as $field) { if (!empty($candidate[$field]) && is_array($candidate[$field])) { $normalizedList = diagnostic_report_list($candidate[$field]); if ($normalizedList !== []) { $copy[$field] = $normalizedList; } } } return $copy; } function diagnostic_generate_ai_report_copy(array $attempt): array { $defaults = diagnostic_default_report_copy($attempt); $payload = diagnostic_ai_report_prompt_payload($attempt); try { $response = LocalAIApi::createResponse( [ 'input' => [ [ 'role' => 'system', 'content' => 'Jesteś konsultantem operacyjnym B2B. Tworzysz krótkie, konkretne i profesjonalne podsumowania e-mail po polsku na podstawie danych z quizu. Odpowiadaj wyłącznie poprawnym JSON-em UTF-8 bez markdownu i bez HTML. Zachowaj rzeczowy ton. Nie wymyślaj faktów wykraczających poza dane. Zwróć dokładnie pola: subject, preheader, intro, executive_summary, what_it_means, strengths, priorities, next_steps, closing, result_overview. strengths/priorities/next_steps mają być tablicami 2-3 krótkich punktów. result_overview ma być jednym spójnym opisem wyniku na stronę podsumowania, maksymalnie 1000 znaków.' ], [ 'role' => 'user', 'content' => "Przygotuj treść raportu e-mail dla klienta na podstawie tych danych quizu. Dane wejściowe JSON:\n" . json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) ], ], ], [ 'poll_interval' => 3, 'poll_timeout' => 120, ] ); if (!empty($response['success'])) { $decoded = LocalAIApi::decodeJsonFromResponse($response); if (is_array($decoded)) { return [ 'copy' => diagnostic_normalize_report_copy($decoded, $defaults), 'source' => 'ai', ]; } error_log('Diagnostic AI report generation returned unparsable payload.'); } else { error_log('Diagnostic AI report generation failed: ' . (string)($response['error'] ?? $response['message'] ?? 'unknown_error')); } } catch (Throwable $exception) { error_log('Diagnostic AI report generation failed: ' . $exception->getMessage()); } return [ 'copy' => $defaults, 'source' => 'fallback', ]; } function diagnostic_build_report_result_overview_html(string $overview): string { $overview = diagnostic_report_limit(trim($overview), 1000); if ($overview === '') { return ''; } return '
' . '
Co ten wynik mówi o Twojej organizacji
' . '
' . diagnostic_email_escape($overview) . '
' . '
'; } function diagnostic_build_report_result_overview_text(string $overview): string { $overview = diagnostic_report_limit(trim($overview), 1000); if ($overview === '') { return ''; } return "Co ten wynik mówi o Twojej organizacji:\n" . $overview; } function diagnostic_build_report_html(array $attempt, array $copy): string { $result = $attempt['result'] ?? []; $strengthsHtml = diagnostic_email_list($copy['strengths'] ?? []); $prioritiesHtml = diagnostic_email_list($copy['priorities'] ?? []); $nextStepsHtml = diagnostic_email_list($copy['next_steps'] ?? []); $resultOverviewHtml = diagnostic_build_report_result_overview_html((string)($copy['result_overview'] ?? '')); $content = '

' . diagnostic_email_escape((string)($copy['intro'] ?? '')) . '

' . '' . '' . '' . '' . '
' . '
Wynik łączny
' . '
' . (int)($result['percentage_score'] ?? 0) . '%
' . '
' . diagnostic_email_escape((string)($result['segment_label'] ?? 'Diagnoza zakończona')) . '
' . '
' . diagnostic_email_escape((string)($copy['executive_summary'] ?? '')) . '
' . '
' . $resultOverviewHtml . '
' . '
Co ten wynik oznacza
' . '
' . diagnostic_email_escape((string)($copy['what_it_means'] ?? '')) . '
' . '
' . '

Mocne strony

' . $strengthsHtml . '

Priorytety na teraz

' . $prioritiesHtml . '

Rekomendowane kolejne kroki

' . $nextStepsHtml; return diagnostic_email_document( (string)($copy['subject'] ?? 'Raport z diagnozy dojrzałości procesowej'), (string)($copy['preheader'] ?? 'Twój wynik diagnozy i rekomendowane kolejne kroki są gotowe.'), $content ); } function diagnostic_build_report_text(array $attempt, array $copy): string { $result = $attempt['result'] ?? []; $resultOverviewText = diagnostic_build_report_result_overview_text((string)($copy['result_overview'] ?? '')); $lines = []; $lines[] = 'DoktorBiznes.pl'; $lines[] = diagnostic_brand_home_url(); $lines[] = ''; $lines[] = (string)($copy['subject'] ?? 'Raport z diagnozy dojrzałości procesowej'); $lines[] = 'Wynik: ' . ($result['segment_label'] ?? 'Diagnoza zakończona') . ' (' . (int)($result['percentage_score'] ?? 0) . '%)'; $lines[] = ''; $lines[] = (string)($copy['intro'] ?? ''); $lines[] = ''; $lines[] = (string)($copy['executive_summary'] ?? ''); if ($resultOverviewText !== '') { $lines[] = ''; $lines[] = $resultOverviewText; } $lines[] = ''; $lines[] = 'Co ten wynik oznacza:'; $lines[] = (string)($copy['what_it_means'] ?? ''); $lines[] = ''; $lines[] = 'Mocne strony:'; foreach (($copy['strengths'] ?? []) as $item) { $lines[] = '- ' . $item; } $lines[] = ''; $lines[] = 'Priorytety na teraz:'; foreach (($copy['priorities'] ?? []) as $item) { $lines[] = '- ' . $item; } $lines[] = ''; $lines[] = 'Rekomendowane kolejne kroki:'; foreach (($copy['next_steps'] ?? []) as $item) { $lines[] = '- ' . $item; } return diagnostic_report_limit(rtrim(implode("\n", $lines)), 10000); } function diagnostic_strip_legacy_report_cta(array $package): array { $subject = diagnostic_report_limit((string)($package['subject'] ?? ''), 255); $html = (string)($package['html'] ?? ''); $text = (string)($package['text'] ?? ''); if ($html !== '') { $html = preg_replace( '~
.*?Otwórz podsumowanie diagnozy
~su', '', $html ) ?? $html; $html = str_replace( 'background:linear-gradient(135deg,#111827 0%,#1f2937 100%);color:#ffffff;', 'background:linear-gradient(135deg,#38a7e8 0%,#1d88d4 46%,#0e5fb5 100%);color:#ffffff;', $html ); $html = preg_replace( '~
  • \s*([^<].*?)
  • ~u', '
  • $1
  • ', $html ) ?? $html; $html = preg_replace( '~
    DOKTOR BIZNES
    \s*~u', '', $html, 1 ) ?? $html; $html = preg_replace( '~
    Gdy Twój biznes potrzebuje trafnej diagnozy
    \s*~u', '', $html, 1 ) ?? $html; $html = preg_replace( '~
    Przegląd procesów
    \s*~u', '', $html, 1 ) ?? $html; $html = str_replace( '', '', $html ); $html = str_replace( '', '', $html ); $html = preg_replace( '~
    \s*\s*]+>\s*\s*
    ~u', '', $html ) ?? $html; $html = preg_replace( '~
    DoktorBiznes(?:\.pl)?
    ~u', '', $html ) ?? $html; $html = preg_replace( '~()(?:\s*]*>\s*)*~u', '$1' . diagnostic_email_brand_marker_html(), $html, 1 ) ?? $html; if (strpos($html, 'Przejdź do bloga DoktorBiznes.pl') === false && strpos($html, 'DoktorBiznes.pl') === false) { $html = preg_replace( '~()~u', '$1' . diagnostic_email_brand_marker_html(), $html, 1 ) ?? $html; } } if ($text !== '') { $text = preg_replace('/^.*omówić wynik z zespołem.*$/miu', '', $text) ?? $text; $text = preg_replace('/^DOKTOR BIZNES\s*$/miu', '', $text) ?? $text; $text = preg_replace('/^DoktorBiznes\.pl\s*$/miu', '', $text) ?? $text; $text = preg_replace('/^Gdy Twój biznes potrzebuje trafnej diagnozy\s*$/miu', '', $text) ?? $text; $text = preg_replace('~^https?://(?:www\.)?doktorbiznes\.pl/?\s*$~miu', '', $text) ?? $text; $text = 'DoktorBiznes.pl' . "\n" . diagnostic_brand_home_url() . "\n\n" . ltrim($text); $text = preg_replace('/\n{3,}/', "\n\n", $text) ?? $text; $text = trim($text); } return [ 'subject' => $subject, 'html' => $html, 'text' => $text, ]; } function diagnostic_store_report_content(int $attemptId, array $package): void { $stmt = db()->prepare( "UPDATE diagnostic_attempts SET report_subject = :report_subject, report_html = :report_html, report_text = :report_text, report_generated_at = NOW(), ai_summary_text = :ai_summary_text, ai_summary_generated_at = CASE WHEN :ai_summary_text = '' THEN ai_summary_generated_at ELSE NOW() END, updated_at = NOW() WHERE id = :id" ); $summary = diagnostic_report_limit((string)($package['result_overview'] ?? ''), 1000); $stmt->execute([ ':report_subject' => (string)($package['subject'] ?? ''), ':report_html' => (string)($package['html'] ?? ''), ':report_text' => (string)($package['text'] ?? ''), ':ai_summary_text' => $summary, ':id' => $attemptId, ]); } function diagnostic_cached_report_content(array $attempt): ?array { $package = diagnostic_strip_legacy_report_cta([ 'subject' => (string)($attempt['report_subject'] ?? ''), 'html' => (string)($attempt['report_html'] ?? ''), 'text' => (string)($attempt['report_text'] ?? ''), ]); $subject = $package['subject']; $html = $package['html']; $text = $package['text']; $resultOverview = diagnostic_report_limit((string)($attempt['ai_summary_text'] ?? ''), 1000); if ($subject === '' || $html === '' || $text === '') { return null; } if ($resultOverview === '') { return null; } $overviewHtml = diagnostic_build_report_result_overview_html($resultOverview); $overviewText = diagnostic_build_report_result_overview_text($resultOverview); if ($overviewHtml !== '' && strpos($html, 'Co ten wynik mówi o Twojej organizacji') === false) { $needle = '
    '; if (strpos($html, $needle) !== false) { $html = str_replace($needle, $overviewHtml . $needle, $html); } else { return null; } } if ($overviewText !== '' && strpos($text, 'Co ten wynik mówi o Twojej organizacji:') === false) { $needle = "\n\nCo ten wynik oznacza:\n"; if (strpos($text, $needle) !== false) { $text = str_replace($needle, "\n\n" . $overviewText . $needle, $text); } else { return null; } } if (!empty($attempt['id']) && ( $subject !== (string)($attempt['report_subject'] ?? '') || $html !== (string)($attempt['report_html'] ?? '') || $text !== (string)($attempt['report_text'] ?? '') )) { diagnostic_store_report_content((int)$attempt['id'], [ 'subject' => $subject, 'html' => $html, 'text' => $text, 'result_overview' => $resultOverview, ]); } return [ 'success' => true, 'subject' => $subject, 'html' => $html, 'text' => $text, 'result_overview' => $resultOverview, 'source' => 'cache', ]; } function diagnostic_generate_report_content(array $attempt): array { if (empty($attempt['result'])) { return ['success' => false, 'error' => 'Brak wyniku potrzebnego do wygenerowania raportu.']; } $ai = diagnostic_generate_ai_report_copy($attempt); $copy = $ai['copy'] ?? diagnostic_default_report_copy($attempt); return [ 'success' => true, 'subject' => (string)($copy['subject'] ?? 'Raport z diagnozy dojrzałości procesowej'), 'html' => diagnostic_build_report_html($attempt, $copy), 'text' => diagnostic_build_report_text($attempt, $copy), 'result_overview' => diagnostic_report_limit((string)($copy['result_overview'] ?? ''), 1000), 'source' => (string)($ai['source'] ?? 'fallback'), ]; } function diagnostic_resolve_result_overview(array $attempt): string { $cached = diagnostic_report_limit((string)($attempt['ai_summary_text'] ?? ''), 1000); if ($cached !== '') { return $cached; } $report = diagnostic_resolve_report_content($attempt, true); if (!empty($report['success'])) { $generated = diagnostic_report_limit((string)($report['result_overview'] ?? ''), 1000); if ($generated !== '') { return $generated; } } return diagnostic_default_result_overview($attempt); } function diagnostic_resolve_report_content(array $attempt, bool $forceRegenerate = false): array { if (!$forceRegenerate) { $cached = diagnostic_cached_report_content($attempt); if ($cached !== null) { return $cached; } } $generated = diagnostic_generate_report_content($attempt); if (!empty($generated['success']) && !empty($attempt['id'])) { diagnostic_store_report_content((int)$attempt['id'], $generated); } return $generated; } function diagnostic_send_report_email(array $attempt, bool $forceRegenerate = false): array { if (empty($attempt['email']) || !filter_var((string)$attempt['email'], FILTER_VALIDATE_EMAIL)) { return ['success' => false, 'error' => 'Brak poprawnego adresu e-mail klienta']; } $report = diagnostic_resolve_report_content($attempt, $forceRegenerate); if (empty($report['success'])) { return $report; } return MailService::sendMail( $attempt['email'], (string)$report['subject'], (string)$report['html'], (string)$report['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_mask_secret(string $value, int $visibleTail = 4): string { $normalized = preg_replace('/\s+/', '', trim($value)) ?? ''; if ($normalized === '') { return ''; } $length = function_exists('mb_strlen') ? mb_strlen($normalized) : strlen($normalized); if ($length <= $visibleTail) { return str_repeat('•', max($length, 8)); } $tail = function_exists('mb_substr') ? mb_substr($normalized, -$visibleTail) : substr($normalized, -$visibleTail); return str_repeat('•', max($length - $visibleTail, 8)) . $tail; } function diagnostic_openai_api_key_is_valid(string $value): bool { $normalized = preg_replace('/\s+/', '', trim($value)) ?? ''; if ($normalized === '' || strlen($normalized) < 20) { return false; } return preg_match('/^[A-Za-z0-9_\-]+$/', $normalized) === 1; } function diagnostic_admin_openai_key_config(): array { $configuredKey = diagnostic_admin_setting_get('openai_api_key') ?? ''; $configuredKey = preg_replace('/\s+/', '', $configuredKey) ?? ''; if ($configuredKey !== '' && !diagnostic_openai_api_key_is_valid($configuredKey)) { $configuredKey = ''; } return [ 'configured' => $configuredKey !== '', 'masked_key' => $configuredKey !== '' ? diagnostic_mask_secret($configuredKey) : '', ]; } function diagnostic_admin_openai_api_key(): ?string { $config = diagnostic_admin_openai_key_config(); if (empty($config['configured'])) { return null; } $key = diagnostic_admin_setting_get('openai_api_key') ?? ''; $key = preg_replace('/\s+/', '', $key) ?? ''; return $key !== '' ? $key : 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]; } function diagnostic_flash_get(): ?array { if (empty($_SESSION['diagnostic_flash'])) { return null; } $flash = $_SESSION['diagnostic_flash']; unset($_SESSION['diagnostic_flash']); return is_array($flash) ? $flash : null; } function diagnostic_admin_credentials(): array { return [ 'username' => getenv('DIAGNOSTIC_ADMIN_USER') ?: 'admin', 'password' => getenv('DIAGNOSTIC_ADMIN_PASS') ?: 'admin123', 'using_default' => !getenv('DIAGNOSTIC_ADMIN_USER') && !getenv('DIAGNOSTIC_ADMIN_PASS'), ]; } function diagnostic_admin_is_authenticated(): bool { return !empty($_SESSION['diagnostic_admin_authenticated']); } function diagnostic_admin_login(string $username, string $password): bool { $credentials = diagnostic_admin_credentials(); $valid = hash_equals($credentials['username'], $username) && hash_equals($credentials['password'], $password); if ($valid) { $_SESSION['diagnostic_admin_authenticated'] = true; } return $valid; } function diagnostic_admin_logout(): void { unset($_SESSION['diagnostic_admin_authenticated']); } function diagnostic_admin_attempts(): array { $stmt = db()->query('SELECT * FROM diagnostic_attempts ORDER BY started_at DESC'); $rows = $stmt->fetchAll(); foreach ($rows as &$row) { $row['answers'] = diagnostic_decode_json_map($row['answers_json'] ?? ''); $row['result'] = diagnostic_decode_json_map($row['result_json'] ?? ''); } unset($row); return $rows; } function diagnostic_admin_stats(): array { $attempts = diagnostic_admin_attempts(); $total = count($attempts); $completed = 0; $averageScore = 0; $totalScore = 0; foreach ($attempts as $attempt) { if (($attempt['status'] ?? '') === 'completed') { $completed++; $totalScore += (int)($attempt['result']['percentage_score'] ?? 0); } } $completionRate = $total > 0 ? (int)round(($completed / $total) * 100) : 0; $averageScore = $completed > 0 ? (int)round($totalScore / $completed) : 0; return [ 'total_attempts' => $total, 'completed_attempts' => $completed, 'completion_rate' => $completionRate, 'average_score' => $averageScore, ]; } function diagnostic_admin_export_csv(): string { $rows = diagnostic_admin_attempts(); $stream = fopen('php://temp', 'r+'); fputcsv($stream, ['ID próby', 'E-mail', 'Telefon', 'Data prośby o kontakt', 'Status', 'Rozpoczęto', 'Zakończono', 'Wynik %', 'Segment', 'Zgoda marketingowa', 'Raport e-mail']); foreach ($rows as $row) { fputcsv($stream, [ $row['id'], $row['email'], $row['contact_phone'] ?? '', $row['consultation_requested_at'] ?? '', $row['status'], $row['started_at'], $row['completed_at'], $row['result']['percentage_score'] ?? '', $row['result']['segment_label'] ?? '', ((int)$row['marketing_consent']) === 1 ? 'Tak' : 'Nie', $row['email_report_status'], ]); } rewind($stream); $csv = stream_get_contents($stream) ?: ''; fclose($stream); return $csv; }