WordPress. Обработка POST-запросов. Часть 2

11.08.2019

Теги: AJAXCMSHookJSONPOSTWeb-разработкаПлагинСобытиеФорма

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

/*
 * Подключаем файл стилей, чтобы красиво оформить нашу форму
 */
add_action('wp_print_styles', function () {
    wp_enqueue_style(
        'tokmakov-feedback-css',
        plugin_dir_url(__FILE__) . 'style.css'
    );
});
.tokmakov-feedback {
    padding: 10px;
    margin: 10px 0;
    background-color: #f5f5f5;
    border: 1px solid #e3e3e3;
}
    .tokmakov-feedback label {
        display: block;
        font-weight: normal;
    }
    .tokmakov-feedback span {
        display: block;
    }
    .tokmakov-feedback input[type="text"] {
        width: 50%;
    }
    .tokmakov-feedback textarea {
        height: 10rem;
        display: block;
        width: 100%;
    }
    .tokmakov-feedback label img {
        vertical-align: bottom;
    }
    .tokmakov-feedback input[type="submit"] {
        margin-top: 1rem;
    }
    .tokmakov-feedback p, .tokmakov-feedback ul {
        margin-top: 0;
    }

Как отправлять AJAX-запросы

Кодекс WordPress говорит, что AJAX-запросы нужно отправлять на admin-ajax.php в директории wp-admin. А в отправляемых данных должно быть поле action. Посмотрим на исходный код этого скрипта (только то, что нам интересно):

<?php
define('DOING_AJAX', true);

if (!defined('WP_ADMIN')) {
    define('WP_ADMIN', true);
}

// загрузка ядра WordPress
require_once(dirname(dirname(__FILE__)) . '/wp-load.php');

// без action работать не будет
if (empty($_REQUEST['action'])) {
    wp_die('0', 400);
}

require_once(ABSPATH . 'wp-admin/includes/admin.php');

do_action('admin_init');

if (is_user_logged_in()) {
    // если пользователь авторизован
    do_action('wp_ajax_' . $_REQUEST['action']);
} else {
    // если пользователь не авторизован
    do_action('wp_ajax_nopriv_' . $_REQUEST['action']);
}
wp_die('0');

Все очень просто:

  • когда пользователь авторизован, происходит событие wp_ajax_{action}
  • когда пользователь не авторизован, происходит событие wp_ajax_nopriv_{action}

Так что обработать данные формы, когда они отправлются с использованием AJAX, мы можем так:

/*
 * Обработка данных формы при использовании XmlHttpRequest (AJAX)
 */
if (wp_doing_ajax()) {
    add_action('wp_ajax_tokmakov_feedback', 'tokmakov_process_feedback_form');
    add_action('wp_ajax_nopriv_tokmakov_feedback', 'tokmakov_process_feedback_form');
    // когда пришел ajax-запрос c данными формы, нам больше ничего
    // делать не надо, так что прерываем работу плагина и выходим
    return;
}

Обратите внимание, что при обработке AJAX-запроса нам не нужно подключать css и js файлы, регистрировать шорткод, стартовать сессию. А вот функцию, которая обрабатывает обычный POST-запрос или AJAX-запрос, мы доработаем.

Отправляем и обрабатываем AJAX-запрос

Хорошо, давайте теперь напишем js-код, который будет отправлять AJAX-запрос с данными формы:

jQuery(document).ready(function($) {
    /*
     * Скрываем блок, где будут результаты, чтобы потом красиво показать
     */
    $('.tokmakov-feedback .response').hide();
    /*
     * Добавляем loader, который будем показывать в момент отправки
     */
    var attr = {'src': tokmakov_feedback.loader, 'class': 'loader'};
    var $image = $('<img>', attr).hide();
    $('.tokmakov-feedback form').append($image);
    /*
     * Подписываемся на событие submit, отменяем обычную отправку формы
     */
    $('.tokmakov-feedback form').on('submit', function(e) {
        // отменяем обычную отправку формы
        e.preventDefault();
        // сама форма, блок с ответом, loader и кнопка отправки
        var $form = $(this),
            $response = $form.siblings('.response'),
            $loader = $form.children('.loader'),
            $submit = $form.children('input[type=submit]');
        // блокируем кнопку submit и показываем loader
        $submit.prop('disabled', true);
        $loader.show();
        // передаем форму не объектом jQuery, а как DOM-элемент
        var data = new FormData($form[0]);
        // отправляем данные формы
        $.ajax({
            url: tokmakov_feedback.handler,
            type: $form.attr('method'),
            data: data,
            contentType: false, // убираем форматирование данных по умолчанию
            processData: false, // убираем преобразование строк по умолчанию
            dataType: 'json',
            success: function(response) {
                // разблокируем кнопку submit и скрываем loader
                $submit.prop('disabled', false);
                $loader.hide();
                // создаем абзац текста с ответом сервера
                $('<p>').text(response.message).appendTo($response);
                // если были ошибки, создаем список ошибок
                if (response.errors) {
                    var $errors = $('<ul>').appendTo($response);
                    response.errors.forEach(function(item) {
                        $('<li>').text(item).appendTo($errors);
                    });
                }
                // показываем ответ и ошибки с использованмием анимации
                $response
                    .slideDown(500)
                    .delay(2000)
                    .slideUp(500, function () {
                        $(this).empty();
                        // если сообщение отправлено, очищаем форму
                        if (response.success) {
                            $form[0].reset();
                        }
                    });
            }
        });
    });
});

И внесем изменения в php-код плагина:

<?php
/*
Plugin Name: Форма обратной связи
Plugin URI: https://tokmakov.msk.ru
Description: Регистрирует шорткод [tokmakov-feedback] для формы обратной связи.
Version: 1.0
Author: Евгений Токмаков
Author URI: https://tokmakov.msk.ru
*/

register_activation_hook(__FILE__, function() {
    // проверяем права пользователя на активацию плагинов
    if (!current_user_can('activate_plugins')) {
        return;
    }
});

register_deactivation_hook(__FILE__, function() {
    // проверяем права пользователя на деактивацию плагинов
    if (!current_user_can('deactivate_plugins')) {
        return;
    }
});

/*
 * Обработка данных формы при использовании XmlHttpRequest (AJAX)
 */
if (wp_doing_ajax()) {
    add_action('wp_ajax_tokmakov_feedback', 'tokmakov_process_feedback_form');
    add_action('wp_ajax_nopriv_tokmakov_feedback', 'tokmakov_process_feedback_form');
    // когда пришел ajax-запрос c данными формы, нам больше ничего
    // делать не надо, так что прерываем работу плагина и выходим
    return;
}

/*
 * Запускаем сессию, чтобы передавать сообщение о результате
 * обработки формы обратной связи после перезагрузки страницы
 */
add_action('init', function () {
    if (session_id() == '') {
        session_start();
    }
});

/*
 * Регистрируем шорткод [tokmakov-feedback], который позволит
 * вставить форму загрузки файлов на страницу или запись блога
 */
add_shortcode('tokmakov-feedback', function () {
    $name = '';
    $email = '';
    $phone = '';
    $message = '';
    if (isset($_SESSION['tokmakov_feedback']['data'])) {
        $name = htmlspecialchars($_SESSION['tokmakov_feedback']['data']['name']);
        $email = htmlspecialchars($_SESSION['tokmakov_feedback']['data']['email']);
        $phone = htmlspecialchars($_SESSION['tokmakov_feedback']['data']['phone']);
        $message = htmlspecialchars($_SESSION['tokmakov_feedback']['data']['message']);
    }
    ob_start();
    ?>
    <div class="tokmakov-feedback">
        <div class="response">
            <?php if (isset($_SESSION['tokmakov_feedback'])): ?>
                <p><?= $_SESSION['tokmakov_feedback']['message']; ?></p>
                <?php if (isset($_SESSION['tokmakov_feedback']['errors'])): ?>
                    <ul>
                        <?php foreach ($_SESSION['tokmakov_feedback']['errors'] as $error): ?>
                            <li><?= $error; ?></li>
                        <?php endforeach; ?>
                    </ul>
                <?php endif; ?>
                <?php unset($_SESSION['tokmakov_feedback']); ?>
            <?php endif; ?>
        </div>
        <form action="<?= admin_url('admin-post.php'); ?>" method="post">
            <input type="hidden" name="action" value="tokmakov_feedback" />
            <input type="hidden" name="redirect" value="<?= get_permalink(); ?>" />
            <label>
                <span>Имя</span>
                <input type="text" name="name" value="<?= $name; ?>" />
            </label>
            <label>
                <span>E-mail</span>
                <input type="text" name="email" value="<?= $email; ?>" />
            </label>
            <label>
                <span>Телефон</span>
                <input type="text" name="phone" value="<?= $phone; ?>" />
            </label>
            <label>
                <span>Сообщение</span>
                <textarea name="message"><?= $message; ?></textarea>
            </label>
            <input type="submit" value="Отправить" />
        </form>
    </div>
    <?php
    return ob_get_clean();
});

/*
 * Подключаем js и css файлы, необходимые для работы плагина
 */
add_action('wp_enqueue_scripts', function () {
    wp_enqueue_script(
        'tokmakov-feedback-js',
        plugin_dir_url(__FILE__) . 'script.js',
        ['jquery'],
        null,
        true
    );
    // определяем глобальную js-переменную, которая будет
    // содержать URL, куда отправлять ajax-запросы
    wp_localize_script(
        'tokmakov-feedback-js',
        'tokmakov_feedback',
        [
            'handler' => admin_url('admin-ajax.php'),
            'loader' => plugin_dir_url(__FILE__) . 'loader.gif'
        ]
    );
    wp_enqueue_style(
        'tokmakov-feedback-css',
        plugin_dir_url(__FILE__) . 'style.css'
    );
});

/*
 * Обрабатываем отправленные данные формы обратной связи
 */
add_action('admin_post_nopriv_tokmakov_feedback', 'tokmakov_process_feedback_form');
add_action('admin_post_tokmakov_feedback', 'tokmakov_process_feedback_form');

function tokmakov_process_feedback_form() {
    /*
     * Обрабатываем данные, полученные из формы
     */
    $data['name']    = trim(iconv_substr(strip_tags($_POST['name']), 0, 50));
    $data['email']   = trim(iconv_substr(strip_tags($_POST['email']), 0, 50));
    $data['phone']   = trim(iconv_substr(strip_tags($_POST['phone']), 0, 50));
    $data['message'] = trim(iconv_substr(strip_tags($_POST['message']), 0, 1000));

    // были допущены ошибки при заполнении формы?
    if (empty($data['name'])) {
        $errors[] = 'Не заполнено обязательное поле «Имя»';
    }
    if (empty($data['email'])) {
        $errors[] = 'Не заполнено обязательное поле «E-mail»';
    }
    if (empty($data['message'])) {
        $errors[] = 'Не заполнено обязательное поле «Сообщение»';
    }

    if (!empty($errors)) {
        /*
         * были допущены ошибки при заполнении формы, сохраняем введенные
         * пользователем данные, чтобы после редиректа снова показать форму,
         * заполненную введенными ранее даннными и сообщением об ошибке
         */
        $response['success'] = false;
        $message = 'При заполнении формы были допущены ошибки';
        $response['message'] = $message;
        $response['errors'] = $errors;
        $response['data'] = $data;
    } else {
        // отправляем письмо администратору
        if (tokmakov_sendmail($data)) {
            $response['success'] = true;
            $message = 'Ваше сообщение успешно отправлено';
            $response['message'] = $message;
        } else {
            $response['success'] = false;
            $message = 'Произошла ошибка при отправке письма';
            $response['message'] = $message;
            $response['data'] = $data;
        }
    }

    if (!wp_doing_ajax()) {
        $_SESSION['tokmakov_feedback'] = $response;
        $redirect = home_url();
        if (isset($_POST['redirect'])) {
            $redirect = $_POST['redirect'];
            $redirect = wp_validate_redirect($redirect, home_url());
        }
        wp_redirect($redirect);
        die();
    } else {
        wp_send_json($response);
    }
}

function tokmakov_sendmail($data) {
    $message = 'Имя: ' . $data['name'] . PHP_EOL;
    $message .= 'E-mail: ' . $data['email'] . PHP_EOL;
    if (!empty($data['phone'])) {
        $message .= 'Телефон: ' . $data['phone'] . PHP_EOL;
    }
    $message .= PHP_EOL . 'Сообщение: ' . PHP_EOL . $data['message'] . PHP_EOL;
    $result = wp_mail(
        get_bloginfo('admin_email'),
        'Заполнена форма обратной связи',
        $message
    );
    return $result;
}

Добавляем защиту от роботов

Добавим в форму еще одно поле, в которое надо будет ввести код с картинки. Это будет случайное число в интервале от 1000 до 9999, которое мы сохраним в сессию. А при обработке данных будем сравнивать код из формы с кодом из сессии. Для начала напишем скрипт, который будет формировать картинку с кодом:

<?php
session_start();

// создаем случайное число и сохраняем в сессии
$number = rand(1000, 9999);
$_SESSION['tokmakov_feedback_captcha'] = $number;

// создаем изображение
$img = imagecreatetruecolor(105, 40);

// создаем три цвета: для числа, для тени и для фона
$white = imagecolorallocate($img, 250, 250, 250);
$grey = imagecolorallocate($img, 150, 150, 150);
$black = imagecolorallocate($img, 50, 50, 50);

imagefilledrectangle($img, 0, 0, 105, 40, $black);

// рисуем созданное случайное число
imagettftext($img, 30, 0, 10, 30, $grey, 'font.ttf', rand(1000, 9999));
imagettftext($img, 30, 0, 0, 35, $white, 'font.ttf', $number);

// предотвращаем кеширование браузером
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Expires: ' . date('r'));

// отправляем изображение браузеру
header ('Content-type: image/gif');
imagegif($img);
imagedestroy($img);

И доработаем php-код плагина:

<?php
/*
Plugin Name: Форма обратной связи
Plugin URI: https://tokmakov.msk.ru
Description: Регистрирует шорткод [tokmakov-feedback] для формы обратной связи.
Version: 1.0
Author: Евгений Токмаков
Author URI: https://tokmakov.msk.ru
*/

register_activation_hook(__FILE__, function() {
    // проверяем права пользователя на установку плагинов
    if (!current_user_can('activate_plugins')) {
        return;
    }
});

register_deactivation_hook(__FILE__, function() {
    // проверяем права пользователя на деактивацию плагинов
    if (!current_user_can('deactivate_plugins')) {
        return;
    }
});

/*
 * Обработка данных формы при использовании XmlHttpRequest (AJAX)
 */
if (wp_doing_ajax()) {
    // сессия нужна для передачи защитного кода
    if (session_id() == '') {
        session_start();
    }
    add_action('wp_ajax_tokmakov_feedback', 'tokmakov_process_feedback_form');
    add_action('wp_ajax_nopriv_tokmakov_feedback', 'tokmakov_process_feedback_form');
    // когда пришел ajax-запрос c данными формы, нам больше ничего
    // делать не надо, так что прерываем работу плагина и выходим
    return;
}

/*
 * Запускаем сессию, чтобы передавать сообщение о результате
 * обработки формы обратной связи после перезагрузки страницы
 */
add_action('init', function () {
    if (session_id() == '') {
        session_start();
    }
});

/*
 * Регистрируем шорткод [tokmakov-feedback], который позволит
 * вставить форму загрузки файлов на страницу или запись блога
 */
add_shortcode('tokmakov-feedback', function () {
    $name = '';
    $email = '';
    $phone = '';
    $message = '';
    if (isset($_SESSION['tokmakov_feedback']['data'])) {
        $name = htmlspecialchars($_SESSION['tokmakov_feedback']['data']['name']);
        $email = htmlspecialchars($_SESSION['tokmakov_feedback']['data']['email']);
        $phone = htmlspecialchars($_SESSION['tokmakov_feedback']['data']['phone']);
        $message = htmlspecialchars($_SESSION['tokmakov_feedback']['data']['message']);
    }
    ob_start();
    ?>
    <div class="tokmakov-feedback">
        <div class="response">
            <?php if (isset($_SESSION['tokmakov_feedback'])): ?>
                <p><?= $_SESSION['tokmakov_feedback']['message']; ?></p>
                <?php if (isset($_SESSION['tokmakov_feedback']['errors'])): ?>
                    <ul>
                        <?php foreach ($_SESSION['tokmakov_feedback']['errors'] as $error): ?>
                            <li><?= $error; ?></li>
                        <?php endforeach; ?>
                    </ul>
                <?php endif; ?>
                <?php unset($_SESSION['tokmakov_feedback']); ?>
            <?php endif; ?>
        </div>
        <form action="<?= admin_url('admin-post.php'); ?>" method="post">
            <input type="hidden" name="action" value="tokmakov_feedback" />
            <input type="hidden" name="redirect" value="<?= get_permalink(); ?>" />
            <label>
                <span>Имя</span>
                <input type="text" name="name" value="<?= $name; ?>" />
            </label>
            <label>
                <span>E-mail</span>
                <input type="text" name="email" value="<?= $email; ?>" />
            </label>
            <label>
                <span>Телефон</span>
                <input type="text" name="phone" value="<?= $phone; ?>" />
            </label>
            <label>
                <span>Сообщение</span>
                <textarea name="message"><?= $message; ?></textarea>
            </label>
            <label>
                <span>Защитный код</span>
                <input type="text" name="captcha" value="" />
                <img src="<?= plugin_dir_url(__FILE__) ?>captcha.php" alt="" />
            </label>
            <input type="submit" value="Отправить" />
        </form>
    </div>
    <?php
    return ob_get_clean();
});

/*
 * Подключаем js и css файлы, необходимые для работы плагина
 */
add_action('wp_enqueue_scripts', function () {
    wp_enqueue_script(
        'tokmakov-feedback-js',
        plugin_dir_url(__FILE__) . 'script.js',
        ['jquery'],
        null,
        true
    );
    // определяем глобальную js-переменную, которая будет
    // содержать URL, куда отправлять ajax-запросы
    wp_localize_script(
        'tokmakov-feedback-js',
        'tokmakov_feedback',
        [
            'handler' => admin_url('admin-ajax.php'),
            'loader' => plugin_dir_url(__FILE__) . 'loader.gif'
        ]
    );
    wp_enqueue_style(
        'tokmakov-feedback-css',
        plugin_dir_url(__FILE__) . 'style.css'
    );
});

/*
 * Обрабатываем отправленные данные формы обратной связи
 */
add_action('admin_post_nopriv_tokmakov_feedback', 'tokmakov_process_feedback_form');
add_action('admin_post_tokmakov_feedback', 'tokmakov_process_feedback_form');

function tokmakov_process_feedback_form() {
    /*
     * Обрабатываем данные, полученные из формы
     */
    $data['name']    = trim(iconv_substr(strip_tags($_POST['name']), 0, 50));
    $data['email']   = trim(iconv_substr(strip_tags($_POST['email']), 0, 50));
    $data['phone']   = trim(iconv_substr(strip_tags($_POST['phone']), 0, 50));
    $data['message'] = trim(iconv_substr(strip_tags($_POST['message']), 0, 1000));
    $data['captcha'] = trim(iconv_substr(strip_tags($_POST['captcha']), 0, 4));

    // были допущены ошибки при заполнении формы?
    if (empty($data['name'])) {
        $errors[] = 'Не заполнено обязательное поле «Имя»';
    }
    if (empty($data['email'])) {
        $errors[] = 'Не заполнено обязательное поле «E-mail»';
    }
    if (empty($data['message'])) {
        $errors[] = 'Не заполнено обязательное поле «Сообщение»';
    }
    if (!empty($data['captcha'])) {
        if ($data['captcha'] != $_SESSION['tokmakov_feedback_captcha']) {
            $errors[] = 'Не совпадает защитный код с картинки';
        }
    } else {
        $errors[] = 'Не заполнено обязательное поле «Защитный код»';
    }

    if (!empty($errors)) {
        /*
         * были допущены ошибки при заполнении формы, сохраняем введенные
         * пользователем данные, чтобы после редиректа снова показать форму,
         * заполненную введенными ранее даннными и сообщением об ошибке
         */
        $response['success'] = false;
        $message = 'При заполнении формы были допущены ошибки';
        $response['message'] = $message;
        $response['errors'] = $errors;
        $response['data'] = $data;
    } else {
        // отправляем письмо администратору
        if (tokmakov_sendmail($data)) {
            $response['success'] = true;
            $message = 'Ваше сообщение успешно отправлено';
            $response['message'] = $message;
        } else {
            $response['success'] = false;
            $message = 'Произошла ошибка при отправке письма';
            $response['message'] = $message;
            $response['data'] = $data;
        }
    }

    if (!wp_doing_ajax()) {
        $_SESSION['tokmakov_feedback'] = $response;
        $redirect = home_url();
        if (isset($_POST['redirect'])) {
            $redirect = $_POST['redirect'];
            $redirect = wp_validate_redirect($redirect, home_url());
        }
        wp_redirect($redirect);
        die();
    } else {
        wp_send_json($response);
    }
}

function tokmakov_sendmail($data) {
    $message = 'Имя: ' . $data['name'] . PHP_EOL;
    $message .= 'E-mail: ' . $data['email'] . PHP_EOL;
    if (!empty($data['phone'])) {
        $message .= 'Телефон: ' . $data['phone'] . PHP_EOL;
    }
    $message .= PHP_EOL . 'Сообщение: ' . PHP_EOL . $data['message'] . PHP_EOL;
    $result = wp_mail(
        get_bloginfo('admin_email'),
        'Заполнена форма обратной связи',
        $message
    );
    return $result;
}

Скачать плагин «Форма обратной связи» можно здесь.

Поиск: AJAX • JSON • POST • Web-разработка • Форма • CMS • Событие • Hook • Обратная связь • Плагин • Plugin • Feedback

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.