Простая контактная форма с валидацией полей
Я хочу создать очень простую контактную форму, которую можно выводить с помощью шорткода. У меня есть следующий код, но он не выполняет проверку корректно и даже позволяет отправить форму без заполнения данных. Может кто-нибудь объяснить, как правильно реализовать валидацию формы? Также я хотел бы добавить honeypot-ловушку, так как считаю их более эффективными, чем капчи, но я новичок в PHP. Любая помощь будет очень полезна.
function html_form_code() {
echo '<form action="' . esc_url( $_SERVER['REQUEST_URI'] ) . '" method="post">';
echo '<p>';
echo 'Ваше имя (обязательно) <br />';
echo '<input type="text" name="cf-name" pattern="[a-zA-Z0-9 ]+" value="' . ( isset( $_POST["cf-name"] ) ? esc_attr( $_POST["cf-name"] ) : '' ) . '" size="40" />';
echo '</p>';
echo '<p>';
echo 'Ваш Email (обязательно) <br />';
echo '<input type="email" name="cf-email" value="' . ( isset( $_POST["cf-email"] ) ? esc_attr( $_POST["cf-email"] ) : '' ) . '" size="40" />';
echo '</p>';
echo '<p>';
echo 'Ваше сообщение (обязательно) <br />';
echo '<textarea rows="10" cols="35" name="cf-message">' . ( isset( $_POST["cf-message"] ) ? esc_attr( $_POST["cf-message"] ) : '' ) . '</textarea>';
echo '</p>';
echo '<input type="text" name="content" id="content" value="" class="hpot" />';
echo '<p><input type="submit" name="cf-submitted" value="Отправить"/></p>';
echo '</form>';
}
function deliver_mail() {
$errors = new WP_Error();
if ( isset( $_POST[ 'content' ] ) && $_POST[ 'content' ] !== '' ) {
$errors->add( 'cheater', 'Извините, это поле не должно быть заполнено. Вы пытаетесь обмануть систему?' );
}
if ( isset( $_POST[ 'cf-name' ] ) && $_POST[ 'cf-name' ] == '' ) {
$errors->add('error', 'Пожалуйста, укажите ваше имя.' );
}
if ( isset( $_POST[ 'cf-email' ] ) && $_POST[ 'cf-email' ] == '' ) {
$errors->add('error', 'Пожалуйста, укажите корректный email.' );
}
if ( isset( $_POST[ 'cf-message' ] ) && $_POST[ 'cf-message' ] == '' ) {
$errors->add('error', 'Пожалуйста, напишите ваше сообщение.' );
}
if ( empty( $errors->errors ) ){
deliver_mail();
}
else {
echo 'Пожалуйста, заполните обязательные поля';
}
// Если нажата кнопка отправки, отправляем email
if ( isset( $_POST['cf-submitted'] ) ) {
// Санитизация значений формы
$name = sanitize_text_field( $_POST["cf-name"] );
$email = sanitize_email( $_POST["cf-email"] );
$message = esc_textarea( $_POST["cf-message"] );
// Получаем email администратора
$to = get_option( 'admin_email' );
$headers = "From: $name <$email>" . "\r\n";
// Если email успешно отправлен, выводим сообщение об успехе
if ( wp_mail( $to, $message, $headers ) ) {
echo '<div class=cf-success>';
echo '<p>Спасибо за обращение, '. $name .', наш сотрудник свяжется с вами в ближайшее время.</p>';
echo '</div>';
} else {
echo '<div class=cf-error>';
echo '<p>Произошла непредвиденная ошибка</p>';
echo '</div>';
}
}
}
function cf_contact_form() {
ob_start();
deliver_mail();
html_form_code();
return ob_get_clean();
}
add_shortcode( 'contact_form', 'cf_contact_form' );
Будет ли это работать как в примере выше? Спасибо.
Также я получаю эту ошибку при попытке проверить поля:
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 262144 bytes) in /Applications/MAMP/htdocs/centenary-framework/wp-content/themes/cent_framework/assets/inc/core/contact-form.php on line 147
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 262144 bytes) in /Applications/MAMP/htdocs/centenary-framework/wp-includes/load.php on line 671
Я не совсем понимаю, что это означает. =(
РЕДАКТИРОВАНИЕ
Эти проблемы теперь исправлены. Я публикую рабочий код ниже в надежде, что это поможет кому-то в будущем. Большое спасибо всем, кто помог!!
// Разметка формы
function html_form_code()
{
?>
<form action="<?php esc_url($_SERVER['REQUEST_URI']);
?>" method="post">
<p>Ваше имя (обязательно)<br />
<input type="text" name="cf-name" pattern="[a-zA-Z0-9 ]+" value="<?php isset($_POST['cf-name']) ? esc_attr($_POST['cf-name']) : '';
?>" size="40" />
</p>
<p>Ваш Email (обязательно)<br />
<input type="email" name="cf-email" value="<?php isset($_POST['cf-email']) ? esc_attr($_POST['cf-email']) : '';
?>" size="40" />
</p>
<p>Ваше сообщение (обязательно)<br />
<textarea rows="10" cols="35" name="cf-message"><?php isset($_POST['cf-message']) ? esc_attr($_POST['cf-message']) : '';
?></textarea>
</p>
<p><input type="submit" name="cf-submitted" value="Отправить"/></p>
</form>
<?php
}
// Валидация формы
function my_validate_form()
{
$errors = new WP_Error();
if (isset($_POST[ 'content' ]) && $_POST[ 'content' ] !== '') {
$errors->add('cheater', 'Извините, это поле не должно быть заполнено. Вы пытаетесь обмануть систему?');
}
if (isset($_POST[ 'cf-name' ]) && $_POST[ 'cf-name' ] == '') {
$errors->add('name_error', 'Пожалуйста, укажите ваше имя.');
}
if (isset($_POST[ 'cf-email' ]) && $_POST[ 'cf-email' ] == '') {
$errors->add('email_error', 'Пожалуйста, укажите корректный email.');
}
if (isset($_POST[ 'cf-message' ]) && $_POST[ 'cf-message' ] == '') {
$errors->add('message_error', 'Пожалуйста, напишите ваше сообщение.');
}
return $errors;
}
// Отправка формы
function deliver_mail($args = array())
{
// Этот массив $default инициализирует значения по умолчанию, которые будут перезаписаны нашим массивом $args
// Мы можем добавить больше ключей по мере необходимости, и это удобный способ видеть параметры нашей функции
// Значения будут перезаписаны только если ключи присутствуют в $args
// Используется функция WP wp_parse_args()
$defaults = array(
'name' => '',
'email' => '',
'message' => '',
'to' => get_option('admin_email'), // получаем email администратора
);
$args = wp_parse_args($args, $defaults);
$headers = "From: {$args['name']} <{$args['email']}>"."\r\n";
// Отправка email возвращает true при успехе, false при ошибке
if (wp_mail($args['to'], $args['message'], $headers)) {
return;
} else {
return false;
}
}
// Санитизация полей формы
function my_sanitize_field($input)
{
return trim(stripslashes(sanitize_text_field($input)));
}
// Сообщение формы
function my_form_message()
{
global $errors;
if (is_wp_error($errors) && empty($errors->errors)) {
echo '<div class="cf-success">';
echo '<p>Спасибо за обращение, '.$_POST['cf-name'].', наш сотрудник свяжется с вами в ближайшее время.</p>';
echo '</div>';
// Очищаем $_POST после отправки
$_POST = '';
} else {
if (is_wp_error($errors) && !empty($errors->errors)) {
$error_messages = $errors->get_error_messages();
foreach ($error_messages as $k => $message) {
echo '<div class="cf-error '.$k.'">';
echo '<p>'.$message.'</p>';
echo '</div>';
}
}
}
}
// Шорткод формы
add_shortcode('contact_form', 'cf_contact_form');
function cf_contact_form()
{
ob_start();
my_form_message();
html_form_code();
return ob_get_clean();
}
// Валидация ошибок
add_action('init', 'my_cf_form');
function my_cf_form()
{
if (isset($_POST['cf-submitted'])) {
global $errors;
$errors = my_validate_form();
if (empty($errors->errors)) {
$args = array(
'name' => my_sanitize_field($_POST['cf-name']),
'email' => my_sanitize_field($_POST['cf-email']),
'message' => my_sanitize_field($_POST['cf-message']),
);
deliver_mail($args);
} else {
return $errors;
}
}
}
У вас отсутствует механизм валидации.
Логика должна быть примерно следующей:
- Отправка формы
- Проверка отправленных полей ($_POST) на соответствие ожидаемым значениям
- Если всё в порядке - отправка
- Если что-то не соответствует требованиям, зарегистрировать ошибку (можно использовать WP_Error()) и перестроить форму с отображением сообщения об ошибке (и, возможно, с восстановлением предыдущих "правильных" значений полей).
Я вижу, что вы санируете ввод, но не проверяете, соответствуют ли ваши входные данные ожидаемым значениям (например, валидный email, телефон, длина имени и т.д.).
Вы отправляете email независимо от того, соответствуют ли поля ожидаемым значениям. Ваше условие else выведет ошибку ТОЛЬКО если wp_mail()
завершится неудачей, а не если ваши поля содержат проверенные значения или нет.
Для добавления "медовой ловушки" (honey pot) просто добавьте скрытое поле в форму, которое должно оставаться пустым.
Например, в HTML:
<input type="text" name="content" id="content" value="" class="hpot" />
Затем при валидации формы ожидайте, что это поле будет пустым.
Используя класс WP_Error
, вы можете добавлять ошибки в объект для последующего информирования пользователя.
$errors = new WP_Error();
if ( isset( $_POST[ 'content' ] ) && $_POST[ 'content' ] !== '' ) {
$errors->add( 'cheater', 'Извините, это поле не должно быть заполнено. Вы пытаетесь обмануть систему?' );
}
Приведённая выше проверка на PHP - это способ, которым вы можете валидировать свою форму. Вы просто добавляете несколько условий if
с ожидаемыми значениями формы (конечно, это можно расширить до функций валидации ввода). Затем, если вы используете класс WP_Error, добавляя в объект найденные ошибки, вам остаётся только сделать финальную проверку перед отправкой.
if ( empty( $errors->errors ) ){
deliver_mail();
}
else {
// Здесь вы можете использовать переменную $_POST для повторного заполнения формы принятыми
// значениями (вам потребуется обновить функцию html_form_code(), чтобы она принимала аргумент)
// или просто перезагрузить страницу контактов с отображением сообщения об ошибке.
}
РЕДАКТИРОВАНИЕ
Вот более полный пример
CSS
Добавьте это в ваш CSS, чтобы поле действительно не отображалось в браузере
.hpot {
display: none;
}
PHP
Вот альтернативный способ написания вашей HTML-функции, который легче читать
function html_form_code() { ?>
<form action="<?php esc_url( $_SERVER['REQUEST_URI'] ); ?>" method="post">
<p>Ваше имя (обязательно)<br />
<input type="text" name="cf-name" pattern="[a-zA-Z0-9 ]+" value="<?php isset( $_POST["cf-name"] ) ? esc_attr( $_POST["cf-name"] ) : ''; ?>" size="40" />
</p>
<p>Ваш Email (обязательно)<br />
<input type="email" name="cf-email" value="<?php isset( $_POST["cf-email"] ) ? esc_attr( $_POST["cf-email"] ) : ''; ?>" size="40" />
</p>
<p>Ваше сообщение (обязательно)<br />
<textarea rows="10" cols="35" name="cf-message"><?php isset( $_POST["cf-message"] ) ? esc_attr( $_POST["cf-message"] ) : ''; ?></textarea>
</p>
<p><input type="submit" name="cf-submitted" value="Отправить"/></p>
</form>
<?php }
Ваша функция deliver_mail не должна реагировать на $_POST
и не должна заниматься санацией. Замечание: использование email пользователя в качестве заголовка from может вызвать проблемы с некоторыми провайдерами, потому что email отправляется с вашего домена, но с адресом отправителя из другого домена (может быть помечен как спам). Используйте адрес из вашего домена (например no-reply@example.com) и укажите email пользователя в теле письма (в сообщении). Также вы можете установить его в поле reply-to
для удобства.
function deliver_mail( $args = array() ) {
// Этот массив $default используется для инициализации значений по умолчанию,
// которые будут перезаписаны нашим массивом $args.
// Мы можем добавить больше ключей по мере необходимости, и это удобный способ
// увидеть, какие параметры мы используем в нашей функции.
// Значения будут перезаписаны только если соответствующие ключи присутствуют в $args.
// Здесь используется функция WP wp_parse_args().
$defaults = array(
'name' => '',
'email' => '',
'message' => '',
'to' => get_option( 'admin_email' ), // получаем email администратора
);
$args = wp_parse_args( $args, $defaults );
$headers = "From: {$args['name']} <{$args['email']}>" . "\r\n";
// Отправка email возвращает true при успехе, false в противном случае
if( wp_mail( $args['to'], $args['message'], $headers ) ) {
return;
}
else {
return false;
}
}
Ваша функция валидации
function my_validate_form() {
$errors = new WP_Error();
if ( isset( $_POST[ 'content' ] ) && $_POST[ 'content' ] !== '' ) {
$errors->add( 'cheater', 'Извините, это поле не должно быть заполнено. Вы пытаетесь обмануть систему?' );
}
if ( isset( $_POST[ 'cf-name' ] ) && $_POST[ 'cf-name' ] == '' ) {
$errors->add('name_error', 'Пожалуйста, укажите корректное имя.' );
}
if ( isset( $_POST[ 'cf-email' ] ) && $_POST[ 'cf-email' ] == '' ) {
$errors->add('email_error', 'Пожалуйста, укажите корректный email.' );
}
if ( isset( $_POST[ 'cf-message' ] ) && $_POST[ 'cf-message' ] == '' ) {
$errors->add('message_error', 'Пожалуйста, напишите ваше сообщение.' );
}
return $errors;
}
Ваша функция санации. Вот общая функция санации, удаляющая пробелы и экранирующая HTML, но это может быть сложнее в зависимости от ваших полей ввода. Для ваших целей этого должно быть достаточно.
function my_sanitize_field( $input ){
return trim( stripslashes( sanitize_text_field ( $input ) ) );
}
Отображение сообщений об успехе/ошибке, вы можете использовать это для получения объекта WP_Error
function my_form_message(){
global $errors;
if( is_wp_errors( $errors ) && empty( $errors->errors ) ){
echo '<div class="cf-success">';
echo '<p>Спасибо за обращение, '. $_POST['cf-name'] .', наш сотрудник свяжется с вами в ближайшее время.</p>';
echo '</div>';
// Очищаем $_POST, так как email уже отправлен
$_POST = '';
}
else {
if( is_wp_errors( $errors ) && ! empty( $errors->errors ) ){
$error_messages = $errors->get_error_messages();
foreach( $error_messages as $k => $message ){
echo '<div class="cf-error ' . $k . '">';
echo '<p>' . $message . '</p>';
echo '</div>';
}
}
}
И наконец, ваша функция шорткода
add_shortcode( 'contact_form', 'cf_contact_form' );
function cf_contact_form() {
ob_start();
my_form_message();
html_form_code();
return ob_get_clean();
}
И подключение к init
для прослушивания $_POST
перед отрисовкой формы и вывода $errors
, если они найдены, или отправки, если всё в порядке.
add_action( 'init', 'my_cf_form');
function my_cf_form(){
if( isset( $_POST['cf-submitted'] ) ) {
global $errors;
$errors = my_validate_form();
if( empty( $errors->errors ) ){
$args = array(
'name' => my_sanitize_field( $_POST['cf-name'] ),
'email' => my_sanitize_field( $_POST['cf-email'] ),
'message' => my_sanitize_field( $_POST['cf-message'] ),
);
deliver_mail( $args );
}
else {
return $errors;
}
}
}
Не забывайте всегда добавлять префиксы к вашим функциям, чтобы избежать конфликтов с именами функций других плагинов.

Извините за глупый вопрос, но где мне выполнять проверки? Спасибо

Если вы новичок в PHP, вы не идиот, вам просто нужно этому научиться :P - Что касается места для проверок, вам нужно изменить функцию deliver_mail(). Вы не слушаете там свой $_POST
. На самом деле, ваша функция должна делать только одну вещь (или иметь только одну логику), поэтому если ваша функция используется для отправки писем, то вы не проверяете там содержимое (понимаете?). Так что вы можете проверить $_POST
прямо перед валидацией формы. Я обновлю свой ответ с примером кода.

Я обновил свой код, как я предполагаю, он должен выглядеть, но видимо это не так, потому что он всё ещё не работает. lol Я также получил ошибку памяти, когда отправил форму и затем обновил страницу снова, так что, наверное, я неправильно завершаю выполнение кода или что-то в этом роде?

Также при обновлении страницы после отправки формы поля остаются заполненными информацией, это правильно? Я думал, что санитизация формы должна удалять пользовательские данные из полей. Или я неправильно понимаю использование функции sanitize?

Хорошо, это оказалось более объемным редактированием, чем ожидалось, но думаю, это может быть хорошим справочным материалом для вас. Обратите внимание, что код не тестировался, поэтому если найдете ошибки, дайте знать, и мы попробуем их исправить. Проверьте и сообщите мне. Что касается заполненных полей после отправки — это происходит потому, что вы не очищаете переменную $_POST
и используете её для заполнения формы. Мой код должен решить эту проблему.

Вау! Это серьезный ответ, если я когда-либо видел такие. Думаю, вы правы, он определенно будет очень полезен для других, кто его найдет. Спасибо за все время, которое вы потратили на это! Это действительно помогло. У меня один вопрос: что делает переменная $k? Еще раз спасибо!

$k
— это ключ массива $errors->errors
, также известный как слаг ошибки. Вспомните, когда вы устанавливали сообщение об ошибке: $errors->add('name_error', 'Пожалуйста, укажите корректное имя.');
— здесь $k
будет равно name_error
. Фактически, это код ошибки, назначенный для конкретной ошибки. Я добавил его как HTML-класс, чтобы при необходимости можно было легче стилизовать разные ошибки по-разному. Рад, что этот ответ помог.

Извините, что снова беспокою. У меня синтаксическая ошибка в переменной $headers — неожиданный пробел, ожидается строка или переменная. Я убрал кавычки вокруг имени и email, но тогда получаю неожиданный конец файла, что, как я понимаю, связано с незакрытой функцией my_form_message(). Я закрыл её, но теперь получаю фатальную ошибку из-за is_wp_errors, и теперь я совсем запутался =S

Трудно сказать наверняка отсюда, но я вижу проблему в определении вашей переменной $headers
. Поскольку вы используете имя переменной (массива) внутри строки, вам нужно явно указать PHP, где заканчивается переменная, с помощью фигурных скобок { }
. Попробуйте так, и всё должно заработать: $headers = "From: {$args['name']} <{$args['email']}>\r\n";

Поскольку вы новичок в PHP, вот хороший пример простого серверного honeypot.
Вам нужно добавить скрытое текстовое поле в вашу форму и проверять, не заполнено ли оно, чтобы отлавливать спам.
HTML формы
<label for="honeypot" class="bot">Оставьте пустым, если вы человек</label>
<input type="text" id="honeypot" name="honeypot" class="bot" />
CSS
.bot { display:none }
PHP (упрощённый)
if ( $_POST() ) {
$name = strip_tags(urldecode(trim($_POST['name'])));
// ИЛИ
$name = test_input($_POST['name']);
$honeypot = $_POST('honeypot');
// Проверяет только, заполнено ли поле.
if ($honeypot){
$msg = "Вы бот";
} else {
$msg = "Успешно";
// Дополнительная обработка
if ($name){
mail(...)
}
}
echo $msg;
}
После небольшого поиска я также нашёл эту функцию, которую вы можете использовать.
function test_input($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data);
return $data;
}

Спасибо за ваше сообщение. Это определенно полезно, моя основная проблема — отсутствие проверки формы. В текущем виде она позволяет отправить форму независимо от того, заполнил ли я что-то или нет. И меня беспокоит, что она не проверяет на наличие инъекций строк и т.д. Это главное, что меня волнует. Нужно ли следовать тому же принципу, что и для "honey pot"? Еще раз спасибо.

Я обновлю PHP с дополнительными проверками, но вы никогда не должны доверять пользовательскому вводу и должны санировать/проверять его всеми возможными способами.

Спасибо. Я хочу сделать форму максимально безопасной, но, как я уже говорил, я довольно новичок в PHP, поэтому я осознаю эти риски, но не знаю, как реализовать меры безопасности. Я знаю, что можно проверять поля ввода, чтобы предотвратить попытки взлома формы и т.д., но не знаю, как это сделать. Еще раз спасибо за помощь.

Вы сохраняете это в базу данных или просто получаете по email? Судя по исходному сообщению, вы делаете только последнее.
