Skip to content

Всё для WordPress, веб-разработки — и не только

🚀 Как подключиться к Contact Form 7 перед отправкой формы

🚀 Как подключиться к Contact Form 7 перед отправкой формы

Contact Form 7 установлен на миллионах сайтов WordPress. Он прост, надёжен и бесплатен. Но как только стандартного поведения перестаёт хватать, нужно валидировать поле, подставить динамического получателя или отменить письмо при условии, начинается зона «как это вообще делается».

Документация CF7 описывает хуки коротко. На форумах и Stack Overflow лежат сниппеты под старые версии плагина, и код пятилетней давности молча ломается на современном CF7. Разбираемся, какие хуки актуальны сегодня и как к ним подключаться без сюрпризов.

💡 Быстрый обзор:

  • главный хук wpcf7_before_send_mail, точка входа для любой логики перед отправкой.
  • пропустить письмо можно фильтром wpcf7_skip_mail или одной строкой skip_mail: on в настройках формы.
  • получить введённые данные, через объект $submission и метод get_posted_data().
  • сменить получателя динамически, через set_properties() на объекте формы.
  • для валидации полей используйте wpcf7_validate с фильтром по типу поля.

Зачем нужны хуки перед отправкой

Стандартный сценарий CF7 выглядит так: пользователь заполняет поля, нажимает «Отправить», плагин шлёт письмо на фиксированный адрес. Всё. Добавить логику внутрь этого процесса без хуков невозможно.

Хуки wpcf7_before_send_mail и сопутствующие фильтры позволяют вклиниться в цепочку ровно в тот момент, когда данные уже проверены, но письмо ещё не ушло. Вы можете:

  • проверить кастомное условие и отменить отправку;
  • сохранить данные формы в базу или внешний API;
  • сменить получателя письма на лету;
  • модифицировать содержимое письма;
  • запустить интеграцию с CRM или рассылкой.

Все примеры ниже работают с Contact Form 7 версии 5.2 и выше. Если ваш плагин старше, сначала обновите: старые сигнатуры хуков несовместимы.

wpcf7_before_send_mail, точка входа

С версии 5.2 сигнатура хука изменилась. Раньше передавался только объект формы, теперь, три аргумента:

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}

Файл для размещения: functions.php активной темы или плагин Code Snippets. Приоритет 10 подходит для большинства задач; поднимайте до 1, если ваш callback должен отработать раньше других, или опускайте до 99, если после.

И обратите внимание: хук, фильтр, не экшн. Возвращать нужно объект $contact_form (или WPCF7_ContactForm), иначе цепочка прервётся некорректно.

Пропуск отправки письма

Самый частый кейс: не слать уведомление, если поле заполнено определённым образом. Для этого есть два пути.

Через фильтр wpcf7_skip_mail

Коротко и чисто. Фильтр принимает булево значение и объект формы:

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}

Метод WPCF7_Submission::get_instance() даёт доступ к данным отправки внутри этого фильтра. Без него get_posted_data() недоступен, $submission не передаётся напрямую в wpcf7_skip_mail.

Через Additional Settings

Если логика проще некуда, всегда пропускать письмо для конкретной формы, идите в админку. Вкладка формы «Дополнительные настройки» (Additional Settings), одна строка:

1skip_mail: on

Никакого кода. Плагин прерывает отправку письма сразу после валидации, а пользователь видит сообщение об успешной отправке. Для демо-форм и тестовых окружений, идеально.

Альтернатива с тем же эффектом: demo_mode: on. Разница в том, что demo_mode полностью симулирует успех без касания почтовой подсистемы, а skip_mail пропускает только шаг отправки, сохраняя всё остальное поведение.

Получение введённых данных

Объект $submission (третий аргумент wpcf7_before_send_mail) даёт полный доступ к тому, что отправил пользователь:

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}

Ключи полей ('your-name', 'your-email'), это значения атрибута name в шорткодах формы. Стандартные теги [text* your-name], [email* your-email] дают ключи без префиксов, просто копируйте то, что после пробела.

Важно: get_posted_data() возвращает сырые данные до обработки почтовыми шаблонами. Спецсимволы, переносы строк, всё как ввёл пользователь. Санитизируйте перед сохранением в базу через sanitize_text_field() или аналоги.

Динамическая смена получателя

Предположим, письмо должно уходить разным менеджерам в зависимости от выбранной в форме темы. Меняем свойство recipient на лету:

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}

Методы get_properties() и set_properties() работают с массивом настроек формы, включая секцию mail. Менять можно не только получателя, но и тему (subject), тело (body), дополнительные заголовки (additional_headers) и отправителя (sender).

Код размещайте там же, functions.php темы или через Code Snippets. Перед выкаткой на боевой сайт протестируйте на staging: опечатка в ключе массива mail молча проигнорируется, и письмо уйдёт на стандартный адрес.

Валидация полей перед отправкой

CF7 даёт фильтр wpcf7_validate, который срабатывает до wpcf7_before_send_mail. Он принимает объект WPCF7_Validation и позволяет добавить ошибку, форма не уйдёт, пока пользователь не исправит:

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}

Фильтр именуется по схеме wpcf7_validate_<тип поля>: text* для обязательных текстовых, email* для email, textarea* для текстовых областей. Без звёздочки, для необязательных полей. $tag, объект с параметрами поля формы, включая name.

Ошибка добавляется методом invalidate($tag, 'текст ошибки'). Текст, произвольный, выводится под полем формы. Для AJAX-отправки (по умолчанию в CF7) сообщение появляется без перезагрузки страницы.

⁉️🤔 Частые вопросы

В чём разница между wpcf7_before_send_mail и wpcf7_mail_sent?

wpcf7_before_send_mail срабатывает до отправки письма, в нём можно отменить письмо, сменить получателя, модифицировать данные. wpcf7_mail_sent, экшн после успешной отправки. Используйте его для логирования, запуска вебхуков или записи в базу, когда письмо уже гарантированно ушло. Если вам нужно прервать процесс, только before_send_mail.

Можно ли подключиться к хуку без правки functions.php?

Да, через плагин Code Snippets (бесплатный, в репозитории WordPress). Он даёт интерфейс для добавления PHP-сниппетов без касания темы. Сниппеты можно включать и выключать поштучно, удобно для отладки. Альтернатива: WPCode (бывший Insert Headers and Footers), тоже бесплатный и с контролем scope, сниппет можно привязать к конкретной форме через conditional logic.

Как проверить, что хук вообще срабатывает?

Простейший способ: error_log( 'HOOK FIRED' ) внутри callback-функции и просмотр wp-content/debug.log при включённом WP_DEBUG. Для быстрой отладки без логов, временно подмените return $contact_form на wp_die('Хук работает'). На боевом сайте так не делайте, только на локальном или staging-окружении.

Что делать, если после обновления CF7 код перестал работать?

Первым делом сверьте сигнатуру хука. В версии 5.2 количество аргументов wpcf7_before_send_mail выросло с 1 до 3. Если ваш callback объявлен без параметра $submission, добавьте. Второе: проверьте, возвращает ли функция объект $contact_form. Третье: откройте лог ошибок PHP, CF7 молча игнорирует фатальные ошибки внутри хуков, форма отправляется как обычно, а вы не видите проблемы.

Есть ли способ подписаться на несколько форм одним хуком?

Да, wpcf7_before_send_mail срабатывает для всех форм. Внутри callback фильтруйте по ID: $contact_form->id() вернёт число, совпадающее с ID шорткода формы (например, [contact-form-7 id="42"]). Сравните с нужными ID и выполняйте разную логику через if или switch.

Что ставить в функции темы, а что вынести в плагин

Хуки CF7 технически работают из любого места: functions.php, Code Snippets, MU-плагин. Но есть практический расклад, который сэкономит нервы при смене темы или обновлении.

Код, завязанный на бизнес-логику (смена получателя, интеграция с CRM, сохранение в базу), выносите в отдельный плагин или Code Snippets. Причина проста: смена темы не должна ломать маршрутизацию клиентских писем. Валидацию полей, которая часто завязана на вёрстку и классы темы, держите в functions.php, она потеряет смысл при сменё темы в любом случае.

Сниппеты объёмом больше 30 строк оформляйте в MU-плагин (wp-content/mu-plugins/cf7-custom.php). MU-плагины не отключаются из админки, выполняются до обычных плагинов и не требуют активации, просто положили файл, и код работает.

Для совсем изолированных кейсов (одна форма, один сайт), functions.php + дочерняя тема. Меньше файлов, меньше путаницы.

Эти же принципы применимы к любому другому плагину форм, будь то WPForms, Gravity Forms или Fluent Forms. Хуки у каждого свои, но архитектурный подход «бизнес-логика отдельно, презентационная логика с темой» универсален.

На видео, пошаговый разбор кастомной валидации полей CF7 с живым примером в админке WordPress. Подход совместим с описанным выше фильтром wpcf7_validate, так что смотрите как наглядное дополнение к коду из раздела валидации.