Files
2026-09-03 21:57:48 +02:00

10 KiB

Formularze SoftSPM - konfiguracja i uruchomienie

Wszystkie formularze strony (newsletter PL, kontakt EN, zgłoszenie na szkolenie, rozmowa konsultingowa) wysyłają dane przez send.php, który dopiero woła https://api.softspm.pl/send_email_api.php zgodnie z doc/manual_send_email.html.

Dlaczego przez pośrednika, a nie prosto z przeglądarki: endpoint autoryzuje się kluczem API w nagłówku Authorization. Klucz wysłany z JavaScriptu byłby widoczny w źródle strony dla każdego, kto naciśnie Ctrl+U - i każdy mógłby nim wysyłać maile na dowolny adres. Klucz zostaje na serwerze; przeglądarka rozmawia wyłącznie z send.php.

Wymagania: PHP 7.4 lub nowszy, rozszerzenie curl (jest fallback na file_get_contents, ale cURL jest zalecany).


1. Co dopisać w /etc/hydronexis/config.php

api/config.php czyta ten plik automatycznie. Obsługiwane są dwa zapisy - wybierz ten, który pasuje do tego, co już masz w pliku.

Wariant A - plik definiuje stałe (najczęstszy)

// --- formularze softspm.com -------------------------------------------
define('SOFTSPM_MAIL_API_KEY',   'tutaj_klucz_do_send_email_api');
define('SOFTSPM_FORM_SECRET',    'tutaj_losowy_ciag_min_32_znaki');
define('SOFTSPM_FORM_RECIPIENT', 'pzators@go2.pl');

Wariant B - plik zwraca tablicę (return [...])

return [
    // ... to, co już tam jest ...

    // --- formularze softspm.com ---------------------------------------
    'softspm_mail_api_key'   => 'tutaj_klucz_do_send_email_api',
    'softspm_form_secret'    => 'tutaj_losowy_ciag_min_32_znaki',
    'softspm_form_recipient' => 'pzators@go2.pl',
];

Akademia Wodociągowca (harmonogram w sekcji 04)

Tabela terminów na stronie głównej i na szkolenia.html pobiera webinary i kursy z publicznego API Akademii. Dopisz klucz wygenerowany w panelu Akademii (Ustawienia -> Klucze API - pokazywany tylko raz):

// wariant ze stałymi
define('SOFTSPM_AKADEMIA_API_KEY', 'tutaj_klucz_z_panelu_akademii');

// wariant z tablicą
'softspm_akademia_api_key' => 'tutaj_klucz_z_panelu_akademii',

Adres API domyślnie to https://akademiawodociagowca.pl/api/public/v1. Zmieniasz go tylko wtedy, gdy instancja stoi pod innym adresem:

define('SOFTSPM_AKADEMIA_API_URL', 'https://inna-domena/api/public/v1');

Bez tego klucza nic się nie psuje: api/schedule.php zwraca source: "error", a w tabeli zostają statyczne wiersze wpisane w HTML.

Wpisy opcjonalne

// Domeny, z których wolno wysyłać formularze. Bez tego wpisu obowiązuje
// lista z api/config.php (softspm.com, www.softspm.com, localhost).
define('SOFTSPM_FORM_HOSTS', ['softspm.com', 'www.softspm.com']);

// Osobna skrzynka dla wybranego formularza. Identyfikatory formularzy:
// newsletter, newsletter-en, szkolenia, konsulting
define('SOFTSPM_FORM_RECIPIENTS', ['szkolenia' => 'szkolenia@softspm.com']);

Skąd wziąć SOFTSPM_FORM_SECRET

To nie jest klucz do niczego zewnętrznego - to sekret, którym send.php podpisuje własne tokeny antybotowe. Wygeneruj dowolny losowy ciąg:

php -r "echo bin2hex(random_bytes(32)), PHP_EOL;"

Zmiana tej wartości unieważnia formularze otwarte w tej chwili w przeglądarkach; nic poza tym się nie dzieje.

Uwagi

  • Plik musi być czytelny dla użytkownika, na którym działa PHP (zwykle www-data), i nieczytelny dla świata: chmod 640, właściciel root:www-data.
  • api/config.php robi na tym pliku require, więc plik musi być poprawnym PHP. Jeśli wykonuje jakieś operacje przy dołączeniu (łączenie z bazą i tym podobne), wykonają się także przy wysyłce formularza.
  • Ścieżkę można zmienić zmienną środowiskową SOFTSPM_SHARED_CONFIG.

2. Kolejność szukania ustawień

Wygrywa pierwsze znalezione:

  1. zmienne środowiskowe SOFTSPM_MAIL_API_KEY, SOFTSPM_FORM_SECRET
  2. /etc/hydronexis/config.php (stałe albo klucze tablicy - patrz wyżej)
  3. api/config.local.php - tylko do pracy lokalnej, wzór w config.local.sample.php

Adresat (recipient) i lista domen działają tak samo, z tym że wartość z /etc/hydronexis/config.php nadpisuje domyślną z api/config.php.

3. Katalog roboczy

send.php zakłada przy pierwszym uruchomieniu api/storage/ na zużyte tokeny, liczniki adresów IP i log. Katalog dostaje .htaccess z Require all denied. Na serwerze innym niż Apache zablokuj ten katalog samodzielnie - nie powinien być dostępny z zewnątrz. Musi być zapisywalny dla PHP:

mkdir -p /sciezka/do/strony/api/storage
chown www-data:www-data /sciezka/do/strony/api/storage
chmod 750 /sciezka/do/strony/api/storage

W logu (api/storage/log/RRRR-MM.log) zapisujemy tylko fakt wysyłki, nazwę formularza i skrót adresu IP - bez treści zgłoszeń i bez danych osobowych.

4. Format wiadomości: czysty tekst, nie HTML

send_email_api.php sanityzuje treść przed osadzeniem jej we własnym szablonie (manual, punkt 7). Znaczniki wysłane z send.php docierają więc do skrzynki jako widoczny tekst <p style="...">. Dlatego wiadomości budujemy z czystego tekstu i nie wolno tu wstawiać HTML-a.

Układ jest odporny na to, czy szablon po drugiej stronie zamienia znaki nowej linii na <br>, czy nie: każda pozycja to osobna linia Etykieta: wartość, która zostaje czytelna nawet po sklejeniu w jeden akapit.

Nowe zgłoszenie ze strony softspm.com
Formularz: Zgłoszenie - szkolenia i webinaria
Odpowiedz na: jan.kowalski@pwik.example.pl

--- DANE Z FORMULARZA ---
Imię i nazwisko: Jan Kowalski
Przedsiębiorstwo: PWiK Testowo
E-mail: jan.kowalski@pwik.example.pl
Telefon: 600 100 200
Interesujący temat: Prawo wodne i wniosek taryfowy
Wiadomość:
  Sześć osób z działu rozliczeń.
  Najlepiej pierwszy kwartał.

--- ZGODA UDZIELONA PRZY WYSYŁCE ---
Wyrażam zgodę na przetwarzanie moich danych osobowych przez SoftSPM (...)

--- METRYKA ---
Strona: https://softspm.com/szkolenia.html#zgloszenie
Data (UTC): 2026-09-01 09:12:33
Adres IP: 203.0.113.7

Jeśli w otrzymanym mailu linie nadal są sklejone w jeden akapit, znaczy to, że szablon endpointu nie zamienia \n na <br>. send_email_api.php jest wasz, więc najczystszą poprawką jest tam jedno nl2br() na sanityzowanej treści - nic po stronie tej strony nie wymaga wtedy zmian.

5. Harmonogram z Akademii Wodociągowca

schedule.php pobiera GET /webinars i GET /courses z API Akademii (dokumentacja: akademiawodociagowca/docs/api_public_v1.html), scala je w jedną listę i oddaje stronie jako JSON. Klucz X-API-Key zostaje na serwerze - punkt 9 dokumentacji Akademii wprost tego wymaga.

  • Cache: odpowiedź leży w api/storage/cache/schedule.json przez akademia_cache_ttl (domyślnie 300 s, zgodnie z zaleceniem 1-5 minut). Pusty wynik i błąd trzymane są krócej, maksymalnie minutę.
  • Kolejność: webinary z terminem rosnąco, potem kursy na żądanie.
  • Linki: akceptujemy wyłącznie adresy prowadzące do domeny Akademii - gdyby API zwróciło cokolwiek innego, link jest pomijany.
  • Limit: ?limit= (1-40). Strona główna prosi o 8 pozycji, szkolenia.html o 12.

Odpowiedź ma pole source:

source Znaczenie Co widzi użytkownik
api Są pozycje Tabela, nagłówki i notatka podmienione na dane z Akademii; kolumna wraca z „Termin" na „Data"
empty API odpowiedziało pustą tablicą Statyczne wiersze z HTML-a zostają nietknięte
error Brak klucza albo API nie odpowiada Jak wyżej

Statyczne wiersze to pełnoprawny plan awaryjny: działają bez JavaScriptu, bez klucza i przy niedostępnym API. Strona nigdy nie pokazuje pustej tabeli. Są w index.html (8 pozycji) i szkolenia.html (11 pozycji) - jeśli chcesz zmienić te tematy zastępcze, edytujesz je wprost w HTML.

Test:

curl -s "https://softspm.com/api/schedule.php?limit=5" | head -c 400

Oczekiwane: {"ok":true,"source":"api",...} z tablicą items.

6. Zabezpieczenia przed botami

Suwak sam z siebie niczego nie gwarantuje - jest elementem interfejsu, a nie kryptografią. Blokady, które faktycznie odsiewają automaty, działają na serwerze i składają się na całość:

Warstwa Co robi
Token podpisany HMAC Wysyłka wymaga tokenu wydanego wcześniej przez send.php. Skrypt musi wykonać dwa dodatkowe żądania, nie jeden POST.
Potwierdzenie suwakiem Osobne, podpisane potwierdzenie wydawane dopiero po przesunięciu suwaka; musi mieć ten sam identyfikator co token.
Jednorazowość Każdy token przechodzi raz. Powtórka tego samego żądania dostaje HTTP 409.
Bramka czasowa Minimum 3 sekundy między pierwszym dotknięciem formularza a wysyłką (min_fill_seconds). Automat robi to w ułamku sekundy.
Pole-pułapka Ukryte pole firma_www. Wypełnione = bot. Odpowiadamy sukcesem, żeby nie podpowiadać, co go zdradziło.
Kontrola pochodzenia Origin/Referer musi wskazywać na domenę z listy.
Limit adresu IP 5 zgłoszeń na godzinę, 20 na dobę (rate_limit w config.php).
Stały adresat Adres odbiorcy pochodzi wyłącznie z konfiguracji serwera, nigdy z formularza - endpoint nie jest przekaźnikiem spamu.

Jeśli mimo to zacznie przechodzić spam, następnym krokiem jest Cloudflare Turnstile albo hCaptcha - obie działają na tej samej zasadzie co obecny token, więc podmiana dotknie tylko send.php i assets/forms.js.

7. Test po wdrożeniu

# 1. Token - powinien zwrócić {"ok":true,"token":"t.szkolenia...."}
curl -s -H "Referer: https://softspm.com/szkolenia.html" \
  "https://softspm.com/api/send.php?action=token&form=szkolenia"

# 2. Bez nagłówka Referer/Origin - powinno być HTTP 403
curl -s -o /dev/null -w '%{http_code}\n' \
  "https://softspm.com/api/send.php?action=token&form=szkolenia"

# 3. GET na nieznany formularz - HTTP 400
curl -s -o /dev/null -w '%{http_code}\n' -H "Referer: https://softspm.com/" \
  "https://softspm.com/api/send.php?action=token&form=cokolwiek"

# 4. POST bez tokenu - HTTP 400 (bad_token)
curl -s -H "Content-Type: application/json" -H "Origin: https://softspm.com" \
  -d '{"form":"szkolenia","zgoda":true,"imie":"Test"}' \
  "https://softspm.com/api/send.php"

Jeśli krok 1 zwraca {"error":"not_configured"}, PHP nie widzi klucza albo sekretu - sprawdź uprawnienia /etc/hydronexis/config.php.

Test całości: otwórz formularz w przeglądarce, wypełnij, przesuń suwak, zaznacz zgodę i wyślij. Wiadomość powinna dojść na adres z SOFTSPM_FORM_RECIPIENT, a w api/storage/log/ pojawi się wpis sent.