Skip to content

Wszystko o WordPressie, tworzeniu stron — i nie tylko

🚀 Jak podłączyć się do Contact Form 7 przed wysłaniem formularza

🚀 Jak podłączyć się do Contact Form 7 przed wysłaniem formularza

Contact Form 7 jest zainstalowany na milionach stron WordPress. Jest prosty, niezawodny i bezpłatny. Jednak gdy standardowe zachowanie przestaje wystarczać, trzeba zwalidować pole, podstawić dynamicznego odbiorcę lub anulować wysyłkę pod warunkiem, zaczyna się strefa „jak to w ogóle zrobić".

Dokumentacja CF7 opisuje hooki skrótowo. Na forach i Stack Overflow leżą snippet'y pod stare wersje wtyczki, a kod sprzed pięciu lat po cichu psuje się na współczesnym CF7. Rozbierzmy na części, które hooki są dziś aktualne i jak się do nich podłączać bez niespodzianek.

💡 Szybki przegląd:

  • główny hook wpcf7_before_send_mail, punkt wejścia dla dowolnej logiki przed wysyłką.
  • pominięcie maila można zrealizować filtrem wpcf7_skip_mail lub jedną linijką skip_mail: on w ustawieniach formularza.
  • pobranie wprowadzonych danych przez obiekt $submission i metodę get_posted_data().
  • dynamiczna zmiana odbiorcy przez set_properties() na obiekcie formularza.
  • do walidacji pól należy używać wpcf7_validate z filtrem według typu pola.

Do czego służą hooki przed wysyłką

Standardowy scenariusz CF7 wygląda tak: użytkownik wypełnia pola, klika „Wyślij", wtyczka wysyła mail na stały adres. To wszystko. Dodanie logiki wewnątrz tego procesu bez hooków jest niemożliwe.

Hooki wpcf7_before_send_mail i towarzyszące filtry pozwalają wpiąć się w łańcuch dokładnie w momencie, gdy dane są już zweryfikowane, ale mail jeszcze nie poszedł. Można:

  • sprawdzić niestandardowy warunek i anulować wysyłkę;
  • zapisać dane formularza do bazy danych lub zewnętrznego API;
  • zmienić odbiorcę maila w locie;
  • zmodyfikować treść maila;
  • uruchomić integrację z CRM lub newsletterem.

Wszystkie przykłady poniżej działają z Contact Form 7 w wersji 5.2 i wyższej. Jeśli Pana/Pani wtyczka jest starsza, najpierw proszę zaktualizować: stare sygnatury hooków są niekompatybilne.

Wpcf7_before_send_mail, punkt wejścia

Od wersji 5.2 sygnatura hooka uległa zmianie. Wcześniej przekazywany był tylko obiekt formularza, teraz są trzy argumenty:

1add_filter( 'wpcf7_before_send_mail', 'cf7_before_send_handler', 10, 3 );
2
3function cf7_before_send_handler( $contact_form, $abort, $submission ) {
4 // $contact_form — объект WPCF7_ContactForm
5 // $abort — флаг отмены (true/false)
6 // $submission — объект WPCF7_Submission с данными
7
8 // ваша логика здесь
9
10 return $contact_form;
11}

Plik do umieszczenia: functions.php aktywnego motywu lub wtyczka Code Snippets. Priorytet 10 pasuje do większości zadań; proszę podnieść do 1, jeśli Pana/Pani callback ma zadziałać przed innymi, lub obniżyć do 99, jeśli po.

I proszę zwrócić uwagę: hook jest filtrem, nie akcją. Należy zwrócić obiekt $contact_form (lub WPCF7_ContactForm), w przeciwnym razie łańcuch przerwie się nieprawidłowo.

Pomijanie wysyłki maila

Najczęstszy przypadek: nie wysyłać powiadomienia, jeśli pole jest wypełnione w określony sposób. Są na to dwie drogi.

Przez filtr wpcf7_skip_mail

Krótko i czysto. Filtr przyjmuje wartość logiczną i obiekt formularza:

1add_filter( 'wpcf7_skip_mail', 'maybe_skip_mail', 10, 2 );
2
3function maybe_skip_mail( $skip_mail, $contact_form ) {
4 $submission = WPCF7_Submission::get_instance();
5
6 if ( $submission ) {
7 $data = $submission->get_posted_data();
8
9 // не шлём письмо, если поле your-reason равно 'test'
10 if ( 'test' === ( $data['your-reason'] ?? '' ) ) {
11 $skip_mail = true;
12 }
13 }
14
15 return $skip_mail;
16}

Metoda WPCF7_Submission::get_instance() daje dostęp do danych wysyłki wewnątrz tego filtra. Bez niej get_posted_data() jest niedostępne, $submission nie jest przekazywany bezpośrednio do wpcf7_skip_mail.

Przez Additional Settings

Jeśli logika jest prostsza niż prosta, zawsze pomijać mail dla konkretnego formularza, proszę iść do panelu administracyjnego. Zakładka formularza „Ustawienia dodatkowe" (Additional Settings), jedna linijka:

1skip_mail: on

Zero kodu. Wtyczka przerywa wysyłkę maila natychmiast po walidacji, a użytkownik widzi komunikat o pomyślnym wysłaniu. Dla formularzy demo i środowisk testowych, idealne.

Alternatywa z tym samym efektem: demo_mode: on. Różnica polega na tym, że demo_mode całkowicie symuluje sukces bez dotykania podsystemu pocztowego, a skip_mail pomija tylko krok wysyłki, zachowując całą resztę zachowania.

Pobieranie wprowadzonych danych

Obiekt $submission (trzeci argument wpcf7_before_send_mail) daje pełny dostęp do tego, co wysłał użytkownik:

1add_filter( 'wpcf7_before_send_mail', 'cf7_read_form_data', 10, 3 );
2
3function cf7_read_form_data( $contact_form, $abort, $submission ) {
4 // все поля разом
5 $posted = $submission->get_posted_data();
6
7 // конкретное поле (ключ — name атрибут тега в форме)
8 $user_name = $submission->get_posted_data( 'your-name' );
9 $user_email = $submission->get_posted_data( 'your-email' );
10
11 // ID поста, с которого отправлена форма
12 $post_id = $submission->get_meta( 'container_post_id' );
13
14 // ID формы
15 $form_id = $contact_form->id();
16
17 // сохраняем в лог или внешний сервис
18 if ( $user_email ) {
19 // например, добавить подписчика в рассылку
20 }
21
22 return $contact_form;
23}

Klucze pól ('your-name', 'your-email') to wartości atrybutu name w shortkodach formularza. Standardowe tagi [text* your-name], [email* your-email] dają klucze bez prefiksów, proszę po prostu skopiować to, co jest po spacji.

Ważne: get_posted_data() zwraca surowe dane przed przetworzeniem przez szablony pocztowe. Znaki specjalne, znaki nowej linii, wszystko tak, jak wprowadził użytkownik. Proszę sanityzować przed zapisem do bazy przez sanitize_text_field() lub analogiczne funkcje.

Dynamiczna zmiana odbiorcy

Załóżmy, że mail ma iść do różnych menedżerów w zależności od wybranego w formularzu tematu. Zmieniamy właściwość recipient w locie:

1add_filter( 'wpcf7_before_send_mail', 'cf7_dynamic_recipient', 10, 3 );
2
3function cf7_dynamic_recipient( $contact_form, $abort, $submission ) {
4 $data = $submission->get_posted_data();
5 $department = $data['your-department'] ?? '';
6
7 $recipients = [
8 'sales' => '[email protected]',
9 'support' => '[email protected]',
10 'billing' => '[email protected]',
11 ];
12
13 if ( isset( $recipients[ $department ] ) ) {
14 $props = $contact_form->get_properties();
15 $props['mail']['recipient'] = $recipients[ $department ];
16 $contact_form->set_properties( $props );
17 }
18
19 return $contact_form;
20}

Metody get_properties() i set_properties() pracują z tablicą ustawień formularza, włącznie z sekcją mail. Zmieniać można nie tylko odbiorcę, ale i temat (subject), treść (body), dodatkowe nagłówki (additional_headers) i nadawcę (sender).

Kod proszę umieszczać tam samo, functions.php motywu lub przez Code Snippets. Przed wdrożeniem na stronę produkcyjną proszę przetestować na stagingu: literówka w kluczu tablicy mail zostanie po cichu zignorowana i mail pójdzie na standardowy adres.

Walidacja pól przed wysyłką

CF7 daje filtr wpcf7_validate, który uruchamia się przed wpcf7_before_send_mail. Przyjmuje on obiekt WPCF7_Validation i pozwala dodać błąd, formularz nie pójdzie, dopóki użytkownik nie poprawi:

1add_filter( 'wpcf7_validate_text*', 'cf7_custom_text_validation', 10, 2 );
2add_filter( 'wpcf7_validate_email*', 'cf7_custom_email_validation', 10, 2 );
3
4function cf7_custom_text_validation( $result, $tag ) {
5 $field_name = $tag->name;
6
7 if ( 'your-message' === $field_name ) {
8 $value = $_POST[ $field_name ] ?? '';
9
10 if ( mb_strlen( $value ) < 20 ) {
11 $result->invalidate( $tag, 'Сообщение должно быть не короче 20 символов.' );
12 }
13 }
14
15 return $result;
16}
17
18function cf7_custom_email_validation( $result, $tag ) {
19 $value = $_POST[ $tag->name ] ?? '';
20
21 if ( $value && ! str_contains( $value, '@' ) ) {
22 $result->invalidate( $tag, 'Укажите корректный email.' );
23 }
24
25 return $result;
26}

Filtr nazywa się według schematu wpcf7_validate_<тип поля>: text* dla obowiązkowych tekstowych, email* dla email, textarea* dla obszarów tekstowych. Bez gwiazdki, dla pól nieobowiązkowych. $tag to obiekt z parametrami pola formularza, włącznie z name.

Błąd dodaje się metodą invalidate($tag, 'текст ошибки'). Tekst jest dowolny, wyświetla się pod polem formularza. Dla wysyłki AJAX (domyślnie w CF7) komunikat pojawia się bez przeładowania strony.

⁉️🤔 Często zadawane pytania

Jaka jest różnica między wpcf7_before_send_mail a wpcf7_mail_sent?

wpcf7_before_send_mail uruchamia się przed wysłaniem maila, można w nim anulować mail, zmienić odbiorcę, zmodyfikować dane. wpcf7_mail_sent to akcja po pomyślnym wysłaniu. Proszę jej używać do logowania, uruchamiania webhooków lub zapisu do bazy, gdy mail już na pewno poszedł. Jeśli trzeba przerwać proces, tylko before_send_mail.

Czy można podłączyć się do hooka bez edycji functions.php?

Tak, przez wtyczkę Code Snippets (bezpłatna, w repozytorium WordPress). Daje ona interfejs do dodawania snippetów PHP bez dotykania motywu. Snippety można włączać i wyłączać pojedynczo, wygodne do debugowania. Alternatywa: WPCode (dawniej Insert Headers and Footers), również bezpłatna i z kontrolą zakresu, snippet można powiązać z konkretnym formularzem przez logikę warunkową.

Jak sprawdzić, czy hook w ogóle się uruchamia?

Najprostszy sposób: error_log( 'HOOK FIRED' ) wewnątrz funkcji callback i podgląd wp-content/debug.log przy włączonym WP_DEBUG. Do szybkiego debugowania bez logów proszę tymczasowo podmienić return $contact_form na wp_die('Хук работает'). Na stronie produkcyjnej proszę tak nie robić, tylko na lokalnym lub stagingowym środowisku.

Co robić, jeśli po aktualizacji CF7 kod przestał działać?

W pierwszej kolejności proszę porównać sygnaturę hooka. W wersji 5.2 liczba argumentów wpcf7_before_send_mail wzrosła z 1 do 3. Jeśli Pana/Pani callback jest zadeklarowany bez parametru $submission, proszę dodać. Po drugie: proszę sprawdzić, czy funkcja zwraca obiekt $contact_form. Po trzecie: proszę otworzyć log błędów PHP, CF7 po cichu ignoruje błędy fatalne wewnątrz hooków, formularz wysyła się normalnie, a problemu nie widać.

Czy jest sposób, by podpiąć się pod kilka formularzy jednym hookiem?

Tak, wpcf7_before_send_mail uruchamia się dla wszystkich formularzy. Wewnątrz callbacka proszę filtrować po ID: $contact_form->id() zwróci liczbę, która pokrywa się z ID shortkodu formularza (na przykład [contact-form-7 id="42"]). Proszę porównać z potrzebnymi ID i wykonywać różną logikę przez if lub switch.

Co umieszczać w funkcjach motywu, a co wydzielić do wtyczki

Hooki CF7 technicznie działają z dowolnego miejsca: functions.php, Code Snippets, MU-plugin. Ale istnieje praktyczny podział, który oszczędzi nerwów przy zmianie motywu lub aktualizacji.

Kod związany z logiką biznesową (zmiana odbiorcy, integracja z CRM, zapis do bazy) proszę wydzielać do osobnej wtyczki lub Code Snippets. Powód jest prosty: zmiana motywu nie powinna psuć routingu maili klientów. Walidację pól, która często jest związana z układem i klasami motywu, proszę trzymać w functions.php, straci ona sens przy zmianie motywu w każdym razie.

Snippety o objętości ponad 30 linijek proszę umieszczać w MU-plugin (wp-content/mu-plugins/cf7-custom.php). MU-pluginy nie wyłączają się z panelu administracyjnego, wykonują się przed zwykłymi wtyczkami i nie wymagają aktywacji, wystarczy wrzucić plik i kod działa.

Dla całkowicie izolowanych przypadków (jeden formularz, jedna strona), functions.php + motyw potomny. Mniej plików, mniej zamieszania.

Te same zasady mają zastosowanie do każdej innej wtyczki formularzy, czy to WPForms, Gravity Forms czy Fluent Forms. Hooki każda ma swoje, ale architektoniczne podejście „logika biznesowa osobno, logika prezentacji z motywem" jest uniwersalne.

Na wideo, krok po kroku omówienie niestandardowej walidacji pól CF7 z żywym przykładem w panelu administracyjnym WordPress. Podejście jest kompatybilne z opisanym wyżej filtrem wpcf7_validate, proszę więc oglądać jako wizualne uzupełnienie do kodu z sekcji walidacji.