Initial commit

This commit is contained in:
2026-09-03 10:39:26 +02:00
commit 496e8bdb27
85 changed files with 8134 additions and 0 deletions
+632
View File
@@ -0,0 +1,632 @@
<?php
declare(strict_types=1);
/**
* SoftSPM - obsługa formularzy strony.
*
* Pośrednik między formularzami a api.softspm.pl/send_email_api.php.
* Klucz API zostaje na serwerze; przeglądarka nigdy go nie widzi. Adresat
* jest stały (config.php), więc endpointu nie da się użyć do wysyłki
* dowolnej wiadomości pod dowolny adres.
*
* Tryby pracy:
* GET ?action=token&form=<id> -> wydaje podpisany token formularza
* GET ?action=confirm&form=<id>&token=.. -> potwierdza przesunięcie suwaka
* POST (JSON) -> waliduje i wysyła zgłoszenie
*/
$config = require __DIR__ . '/config.php';
header('Content-Type: application/json; charset=utf-8');
header('X-Content-Type-Options: nosniff');
header('Cache-Control: no-store');
header('Referrer-Policy: same-origin');
// ---------------------------------------------------------------------------
// Definicje formularzy. Serwer przyjmuje wyłącznie te pola - cokolwiek innego
// przyjdzie w JSON-ie, jest ignorowane.
// ---------------------------------------------------------------------------
function spm_forms(): array
{
return [
'newsletter' => [
'lang' => 'pl',
'label' => 'Newsletter',
'subject' => 'Zapis do newslettera',
'fields' => [
'email' => ['label' => 'E-mail', 'type' => 'email', 'required' => true, 'max' => 190],
],
],
'newsletter-en' => [
'lang' => 'en',
'label' => 'Newsletter (EN)',
'subject' => 'Kontakt z wersji angielskiej',
'fields' => [
'email' => ['label' => 'E-mail', 'type' => 'email', 'required' => true, 'max' => 190],
],
],
'szkolenia' => [
'lang' => 'pl',
'label' => 'Zgłoszenie - szkolenia i webinaria',
'subject' => 'Zapytanie o szkolenie',
'fields' => [
'imie' => ['label' => 'Imię i nazwisko', 'type' => 'text', 'required' => true, 'max' => 120],
'firma' => ['label' => 'Przedsiębiorstwo', 'type' => 'text', 'required' => true, 'max' => 160],
'email' => ['label' => 'E-mail', 'type' => 'email', 'required' => true, 'max' => 190],
'telefon' => ['label' => 'Telefon', 'type' => 'text', 'required' => false, 'max' => 40],
'temat' => ['label' => 'Interesujący temat', 'type' => 'text', 'required' => false, 'max' => 160],
'wiadomosc' => ['label' => 'Wiadomość', 'type' => 'textarea', 'required' => false, 'max' => 4000],
],
],
'konsulting' => [
'lang' => 'pl',
'label' => 'Zgłoszenie - konsulting',
'subject' => 'Prośba o rozmowę wstępną',
'fields' => [
'imie' => ['label' => 'Imię i nazwisko', 'type' => 'text', 'required' => true, 'max' => 120],
'firma' => ['label' => 'Przedsiębiorstwo', 'type' => 'text', 'required' => true, 'max' => 160],
'email' => ['label' => 'E-mail', 'type' => 'email', 'required' => true, 'max' => 190],
'telefon' => ['label' => 'Telefon', 'type' => 'text', 'required' => false, 'max' => 40],
'zakres' => ['label' => 'Interesujący zakres', 'type' => 'text', 'required' => false, 'max' => 160],
'wiadomosc' => ['label' => 'Opis sytuacji', 'type' => 'textarea', 'required' => false, 'max' => 4000],
],
],
];
}
// Treść zgody utrwalana razem ze zgłoszeniem - dowód, na co konkretnie
// zgodził się nadawca w dniu wysyłki.
function spm_consent_text(string $lang): string
{
if ($lang === 'en') {
return 'I consent to SoftSPM processing my personal data in order to be contacted '
. 'about commercial and marketing matters, including offers of training, '
. 'consulting and software. I may withdraw this consent at any time '
. 'by writing to info@softspm.com.';
}
return 'Wyrażam zgodę na przetwarzanie moich danych osobowych przez SoftSPM w celu '
. 'kontaktu handlowego i marketingowego, w tym przedstawienia oferty szkoleń, '
. 'konsultingu i aplikacji. Zgodę mogę wycofać w każdej chwili, pisząc na '
. 'info@softspm.com.';
}
// ---------------------------------------------------------------------------
// Narzędzia
// ---------------------------------------------------------------------------
function spm_respond(int $status, array $payload): void
{
http_response_code($status);
echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
function spm_fail(int $status, string $code, string $message): void
{
spm_respond($status, ['ok' => false, 'error' => $code, 'message' => $message]);
}
function spm_b64u(string $bin): string
{
return rtrim(strtr(base64_encode($bin), '+/', '-_'), '=');
}
function spm_sign(string $payload, string $secret): string
{
return spm_b64u(hash_hmac('sha256', $payload, $secret, true));
}
/** Token ma postać: rodzaj.formularz.nonce.czas.podpis */
function spm_issue_token(string $kind, string $form, string $nonce, string $secret): string
{
$payload = $kind . '.' . $form . '.' . $nonce . '.' . time();
return $payload . '.' . spm_sign($payload, $secret);
}
/** @return array{nonce:string, ts:int}|null */
function spm_read_token(string $token, string $kind, string $form, string $secret): ?array
{
$parts = explode('.', $token);
if (count($parts) !== 5) {
return null;
}
[$gotKind, $gotForm, $nonce, $ts, $sig] = $parts;
if ($gotKind !== $kind || $gotForm !== $form || !ctype_digit($ts)) {
return null;
}
if (preg_match('/^[A-Za-z0-9_-]{8,64}$/', $nonce) !== 1) {
return null;
}
$payload = $gotKind . '.' . $gotForm . '.' . $nonce . '.' . $ts;
if (!hash_equals(spm_sign($payload, $secret), $sig)) {
return null;
}
return ['nonce' => $nonce, 'ts' => (int) $ts];
}
function spm_storage(array $config, string $sub): string
{
$dir = rtrim($config['storage_dir'], '/\\') . '/' . $sub;
if (!is_dir($dir)) {
@mkdir($dir, 0770, true);
}
// Katalog roboczy nie ma prawa być czytany przez przeglądarkę.
$guard = rtrim($config['storage_dir'], '/\\') . '/.htaccess';
if (!is_file($guard)) {
@file_put_contents($guard, "Require all denied\n<IfModule !mod_authz_core.c>\nDeny from all\n</IfModule>\n");
}
return $dir;
}
/** Jednorazowość tokenu: nonce raz zużyty nie przechodzi drugi raz. */
function spm_nonce_used(array $config, string $nonce): bool
{
$dir = spm_storage($config, 'nonce');
$file = $dir . '/' . hash('sha256', $nonce) . '.used';
if (is_file($file)) {
return true;
}
@file_put_contents($file, (string) time());
// Sprzątanie: co jakiś czas kasujemy wpisy starsze niż czas życia tokenu.
if (random_int(1, 25) === 1) {
$limit = time() - (int) $config['max_token_age'] - 3600;
foreach ((glob($dir . '/*.used') ?: []) as $old) {
if (@filemtime($old) < $limit) {
@unlink($old);
}
}
}
return false;
}
function spm_client_ip(): string
{
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
return is_string($ip) ? $ip : '0.0.0.0';
}
/** Limit zgłoszeń z jednego adresu IP - godzinowy i dobowy. */
function spm_rate_limited(array $config, string $ip): bool
{
$dir = spm_storage($config, 'rate');
$file = $dir . '/' . hash('sha256', $ip . '|' . $config['token_secret']) . '.json';
$now = time();
$stamps = [];
if (is_file($file)) {
$raw = @file_get_contents($file);
if (is_string($raw) && $raw !== '') {
$decoded = json_decode($raw, true);
if (is_array($decoded)) {
$stamps = array_values(array_filter($decoded, static fn($t) => is_int($t) && $t > $now - 86400));
}
}
}
$lastHour = count(array_filter($stamps, static fn(int $t) => $t > $now - 3600));
if ($lastHour >= (int) $config['rate_limit']['per_hour'] || count($stamps) >= (int) $config['rate_limit']['per_day']) {
return true;
}
$stamps[] = $now;
@file_put_contents($file, json_encode($stamps), LOCK_EX);
return false;
}
/** Zgłoszenie musi pochodzić ze strony SoftSPM, nie z obcego skryptu. */
function spm_origin_ok(array $config): bool
{
$candidates = [];
foreach (['HTTP_ORIGIN', 'HTTP_REFERER'] as $header) {
if (!empty($_SERVER[$header]) && is_string($_SERVER[$header])) {
$host = parse_url($_SERVER[$header], PHP_URL_HOST);
if (is_string($host) && $host !== '') {
$candidates[] = strtolower($host);
}
}
}
if ($candidates === []) {
return false;
}
$allowed = array_map('strtolower', (array) $config['allowed_hosts']);
foreach ($candidates as $host) {
if (in_array($host, $allowed, true)) {
return true;
}
}
return false;
}
function spm_log(array $config, string $line): void
{
$dir = spm_storage($config, 'log');
@file_put_contents(
$dir . '/' . gmdate('Y-m') . '.log',
gmdate('c') . ' ' . $line . "\n",
FILE_APPEND | LOCK_EX
);
}
/** Pojedyncza linia tekstu: bez znaków sterujących, bez łamania wiersza. */
function spm_clean_line(string $value): string
{
$value = str_replace(["\r", "\n", "\t"], ' ', $value);
$value = preg_replace('/[\x00-\x1F\x7F]/u', '', $value) ?? '';
return trim(preg_replace('/\s{2,}/u', ' ', $value) ?? '');
}
function spm_clean_text(string $value): string
{
$value = str_replace("\r\n", "\n", $value);
$value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $value) ?? '';
return trim($value);
}
// ---------------------------------------------------------------------------
// Start obsługi żądania
// ---------------------------------------------------------------------------
$method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
if ($method === 'OPTIONS') {
http_response_code(204);
exit;
}
if ($config['token_secret'] === '' || $config['api_key'] === '') {
spm_log($config, 'config-missing');
spm_fail(503, 'not_configured', 'Formularz nie jest jeszcze skonfigurowany. Napisz na info@softspm.com.');
}
$forms = spm_forms();
// --- GET: wydanie tokenu i potwierdzenie suwaka ----------------------------
if ($method === 'GET') {
$action = isset($_GET['action']) && is_string($_GET['action']) ? $_GET['action'] : '';
$form = isset($_GET['form']) && is_string($_GET['form']) ? $_GET['form'] : '';
if (!isset($forms[$form])) {
spm_fail(400, 'unknown_form', 'Nieznany formularz.');
}
if (!spm_origin_ok($config)) {
spm_fail(403, 'bad_origin', 'Żądanie spoza strony SoftSPM.');
}
if ($action === 'token') {
$nonce = spm_b64u(random_bytes(12));
spm_respond(200, [
'ok' => true,
'token' => spm_issue_token('t', $form, $nonce, $config['token_secret']),
]);
}
if ($action === 'confirm') {
$token = isset($_GET['token']) && is_string($_GET['token']) ? $_GET['token'] : '';
$data = spm_read_token($token, 't', $form, $config['token_secret']);
if ($data === null) {
spm_fail(400, 'bad_token', 'Token formularza jest nieprawidłowy. Odśwież stronę.');
}
if (time() - $data['ts'] > (int) $config['max_token_age']) {
spm_fail(400, 'expired', 'Formularz był otwarty zbyt długo. Odśwież stronę.');
}
spm_respond(200, [
'ok' => true,
'proof' => spm_issue_token('c', $form, $data['nonce'], $config['token_secret']),
]);
}
spm_fail(400, 'bad_action', 'Nieznana operacja.');
}
if ($method !== 'POST') {
spm_fail(405, 'method_not_allowed', 'Dozwolone są wyłącznie żądania POST.');
}
// --- POST: właściwa wysyłka -------------------------------------------------
// Formularz wysłany bez JavaScriptu leci tu jako zwykły POST przeglądarki.
// Zamiast JSON-a z błędem pokazujemy stronę, którą da się przeczytać.
$contentType = strtolower((string) ($_SERVER['CONTENT_TYPE'] ?? ''));
if (strpos($contentType, 'application/json') === false) {
http_response_code(400);
header('Content-Type: text/html; charset=utf-8');
echo '<!doctype html><html lang="pl"><head><meta charset="utf-8">'
. '<title>Formularz SoftSPM</title></head><body style="font:16px/1.5 system-ui,sans-serif;padding:2rem;max-width:40rem">'
. '<h1 style="font-size:1.25rem">Formularz wymaga włączonego JavaScriptu</h1>'
. '<p>Napisz do nas bezpośrednio na <a href="mailto:info@softspm.com">info@softspm.com</a>. '
. 'Przepraszamy za kłopot.</p>'
. '<p><a href="javascript:history.back()">Wróć do formularza</a></p>'
. '</body></html>';
exit;
}
$raw = file_get_contents('php://input');
if (!is_string($raw) || trim($raw) === '') {
spm_fail(400, 'empty_payload', 'Puste żądanie.');
}
$input = json_decode($raw, true);
if (!is_array($input)) {
spm_fail(400, 'invalid_json', 'Nieprawidłowy format danych.');
}
$form = isset($input['form']) && is_string($input['form']) ? $input['form'] : '';
if (!isset($forms[$form])) {
spm_fail(400, 'unknown_form', 'Nieznany formularz.');
}
$def = $forms[$form];
$lang = $def['lang'];
$isEn = $lang === 'en';
if (!spm_origin_ok($config)) {
spm_log($config, 'reject origin form=' . $form);
spm_fail(403, 'bad_origin', $isEn ? 'Request did not come from the SoftSPM site.' : 'Żądanie spoza strony SoftSPM.');
}
// Pułapka na boty: pole ukryte przed człowiekiem. Wypełnione = automat.
// Odpowiadamy sukcesem, żeby nie podpowiadać botowi, co go zdradziło.
$trap = isset($input['firma_www']) && is_string($input['firma_www']) ? trim($input['firma_www']) : '';
if ($trap !== '') {
spm_log($config, 'trap form=' . $form . ' ip=' . hash('sha256', spm_client_ip()));
spm_respond(200, ['ok' => true, 'message' => $isEn ? 'Message sent.' : 'Wiadomość wysłana.']);
}
// Token formularza + potwierdzenie suwakiem.
$token = isset($input['token']) && is_string($input['token']) ? $input['token'] : '';
$proof = isset($input['proof']) && is_string($input['proof']) ? $input['proof'] : '';
$tokenData = spm_read_token($token, 't', $form, $config['token_secret']);
$proofData = spm_read_token($proof, 'c', $form, $config['token_secret']);
if ($tokenData === null || $proofData === null || !hash_equals($tokenData['nonce'], $proofData['nonce'])) {
spm_log($config, 'reject token form=' . $form);
spm_fail(400, 'bad_token', $isEn
? 'Verification failed. Refresh the page and slide the control again.'
: 'Weryfikacja nie powiodła się. Odśwież stronę i przesuń suwak ponownie.');
}
$now = time();
if ($now - $tokenData['ts'] > (int) $config['max_token_age'] || $now - $proofData['ts'] > (int) $config['max_proof_age']) {
spm_fail(400, 'expired', $isEn
? 'The form has expired. Refresh the page and try again.'
: 'Formularz stracił ważność. Odśwież stronę i spróbuj ponownie.');
}
$minSlide = (int) $config['min_slide_seconds'];
if ($now - $tokenData['ts'] < (int) $config['min_fill_seconds']
|| $proofData['ts'] < $tokenData['ts']
|| ($minSlide > 0 && $proofData['ts'] - $tokenData['ts'] < $minSlide)) {
spm_log($config, 'reject speed form=' . $form);
spm_fail(429, 'too_fast', $isEn
? 'That was too quick. Try again in a moment.'
: 'Zgłoszenie wysłano zbyt szybko. Spróbuj ponownie za chwilę.');
}
if (spm_nonce_used($config, $tokenData['nonce'])) {
spm_fail(409, 'already_sent', $isEn
? 'This form has already been sent. Refresh the page to send another message.'
: 'To zgłoszenie zostało już wysłane. Odśwież stronę, aby wysłać kolejne.');
}
// Zgoda marketingowa - wymagana, sprawdzana także po stronie serwera.
$consent = $input['zgoda'] ?? false;
if ($consent !== true && $consent !== 'true' && $consent !== 1 && $consent !== '1' && $consent !== 'on') {
spm_fail(400, 'consent_required', $isEn
? 'Consent to be contacted is required.'
: 'Zgoda na kontakt jest wymagana.');
}
if (spm_rate_limited($config, spm_client_ip())) {
spm_log($config, 'reject rate form=' . $form . ' ip=' . hash('sha256', spm_client_ip()));
spm_fail(429, 'rate_limited', $isEn
? 'Too many messages from this address. Please write to info@softspm.com.'
: 'Zbyt wiele zgłoszeń z tego adresu. Napisz na info@softspm.com.');
}
// --- Walidacja pól ----------------------------------------------------------
$values = [];
$replyTo = '';
foreach ($def['fields'] as $name => $rule) {
$value = $input[$name] ?? '';
if (!is_string($value)) {
$value = '';
}
$value = $rule['type'] === 'textarea' ? spm_clean_text($value) : spm_clean_line($value);
if ($value === '') {
if (!empty($rule['required'])) {
spm_fail(400, 'missing_field', $isEn
? sprintf('Field "%s" is required.', $rule['label'])
: sprintf('Pole „%s" jest wymagane.', $rule['label']));
}
$values[$name] = '';
continue;
}
if (mb_strlen($value) > (int) $rule['max']) {
spm_fail(400, 'field_too_long', $isEn
? sprintf('Field "%s" is too long.', $rule['label'])
: sprintf('Pole „%s" jest za długie.', $rule['label']));
}
if ($rule['type'] === 'email') {
if (filter_var($value, FILTER_VALIDATE_EMAIL) === false) {
spm_fail(400, 'invalid_email', $isEn
? 'The e-mail address is not valid.'
: 'Adres e-mail jest nieprawidłowy.');
}
$replyTo = $value;
}
$values[$name] = $value;
}
// --- Budowa wiadomości ------------------------------------------------------
// UWAGA: send_email_api.php sanityzuje treść przed osadzeniem jej we własnym
// szablonie HTML (manual, punkt 7). Znaczniki wysłane stąd dotarłyby do skrzynki
// jako widoczny tekst "<p style=...>", więc wysyłamy czysty tekst.
//
// Nie wiemy, czy szablon po drugiej stronie zamienia znaki nowej linii na <br>,
// dlatego układ jest odporny na oba warianty: każda pozycja to osobna linia
// "Etykieta: wartość", która pozostaje czytelna nawet po sklejeniu w akapit.
$topic = '';
foreach (['temat', 'zakres'] as $key) {
if (!empty($values[$key])) {
$topic = $values[$key];
break;
}
}
$subject = trim($config['subject_prefix'] . ' ' . $def['subject']);
if ($topic !== '') {
$subject .= ' - ' . $topic;
}
if (mb_strlen($subject) > 250) {
$subject = mb_substr($subject, 0, 247) . '...';
}
$lines = [];
$lines[] = $isEn ? 'New message from softspm.com' : 'Nowe zgłoszenie ze strony softspm.com';
$lines[] = ($isEn ? 'Form: ' : 'Formularz: ') . $def['label'];
if ($replyTo !== '') {
$lines[] = ($isEn ? 'Reply to: ' : 'Odpowiedz na: ') . $replyTo;
}
$lines[] = '';
$lines[] = $isEn ? '--- DETAILS ---' : '--- DANE Z FORMULARZA ---';
foreach ($def['fields'] as $name => $rule) {
$value = $values[$name] ?? '';
if ($value === '') {
continue;
}
if ($rule['type'] === 'textarea') {
// Treść wieloliniowa dostaje własny blok, każdy wiersz z wcięciem,
// żeby po sklejeniu akapitu dało się odróżnić ją od pozostałych pól.
$lines[] = $rule['label'] . ':';
foreach (explode("\n", $value) as $row) {
$lines[] = ' ' . $row;
}
continue;
}
$lines[] = $rule['label'] . ': ' . $value;
}
$lines[] = '';
$lines[] = $isEn ? '--- CONSENT GIVEN ON SUBMISSION ---' : '--- ZGODA UDZIELONA PRZY WYSYŁCE ---';
$lines[] = spm_consent_text($lang);
$lines[] = '';
$lines[] = $isEn ? '--- METADATA ---' : '--- METRYKA ---';
$lines[] = ($isEn ? 'Page: ' : 'Strona: ')
. (isset($input['page']) && is_string($input['page']) ? spm_clean_line(mb_substr($input['page'], 0, 300)) : '-');
$lines[] = ($isEn ? 'Date (UTC): ' : 'Data (UTC): ') . gmdate('Y-m-d H:i:s');
$lines[] = ($isEn ? 'IP address: ' : 'Adres IP: ') . spm_client_ip();
$message = implode("\n", $lines);
// Zostawiamy zapas na szablon po stronie API (limit endpointu to 20000 znaków).
if (mb_strlen($message) > 15000) {
$message = mb_substr($message, 0, 15000) . "\n[...]";
}
// --- Wysyłka przez api.softspm.pl ------------------------------------------
// Adresat pochodzi wyłącznie z konfiguracji serwera. Formularz może mieć
// własną skrzynkę (config['recipients']), inaczej idzie na adres domyślny.
$recipient = (string) $config['recipient'];
if (isset($config['recipients'][$form]) && is_string($config['recipients'][$form]) && $config['recipients'][$form] !== '') {
$recipient = $config['recipients'][$form];
}
if (filter_var($recipient, FILTER_VALIDATE_EMAIL) === false) {
spm_log($config, 'config-bad-recipient form=' . $form);
spm_fail(503, 'not_configured', $isEn
? 'The form is not configured yet. Please write to info@softspm.com.'
: 'Formularz nie jest jeszcze skonfigurowany. Napisz na info@softspm.com.');
}
$payload = json_encode([
'to' => $recipient,
'subject' => $subject,
'message' => $message,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($payload === false) {
spm_fail(500, 'encode_failed', $isEn ? 'Could not prepare the message.' : 'Nie udało się przygotować wiadomości.');
}
$headers = [
'Content-Type: application/json',
'Authorization: Bearer ' . $config['api_key'],
];
$status = 0;
$response = '';
$netError = '';
if (function_exists('curl_init')) {
$ch = curl_init($config['api_url']);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 8,
CURLOPT_TIMEOUT => (int) $config['timeout'],
]);
$body = curl_exec($ch);
if ($body === false) {
$netError = curl_error($ch);
} else {
$response = (string) $body;
}
$status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
} else {
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => implode("\r\n", $headers),
'content' => $payload,
'timeout' => (int) $config['timeout'],
'ignore_errors' => true,
],
]);
$body = @file_get_contents($config['api_url'], false, $context);
if ($body === false) {
$netError = 'stream request failed';
} else {
$response = (string) $body;
foreach ($http_response_header ?? [] as $line) {
if (preg_match('#^HTTP/\S+\s+(\d{3})#', $line, $m) === 1) {
$status = (int) $m[1];
}
}
}
}
if ($status !== 200) {
// Szczegóły zostają w logu serwera, klient dostaje komunikat bez detali.
spm_log($config, sprintf(
'send-failed form=%s http=%d err=%s resp=%s',
$form,
$status,
$netError !== '' ? $netError : '-',
substr($response, 0, 300)
));
spm_fail(502, 'send_failed', $isEn
? 'The message could not be sent. Please write to info@softspm.com.'
: 'Nie udało się wysłać wiadomości. Napisz na info@softspm.com.');
}
spm_log($config, 'sent form=' . $form . ' ip=' . hash('sha256', spm_client_ip()));
spm_respond(200, [
'ok' => true,
'message' => $isEn
? 'Thank you - your message has been sent. We reply within one working day.'
: 'Dziękujemy - wiadomość została wysłana. Odpowiadamy w ciągu jednego dnia roboczego.',
]);