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ścicielroot:www-data. api/config.phprobi na tym plikurequire, 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:
- zmienne środowiskowe
SOFTSPM_MAIL_API_KEY,SOFTSPM_FORM_SECRET /etc/hydronexis/config.php(stałe albo klucze tablicy - patrz wyżej)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.jsonprzezakademia_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.htmlo 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.