WordPress. Обработка POST-запросов. Часть 1
В процессе загрузки WordPress происходит множество событий. К каждому из этих событий можно привязать функцию, которая выполнит какое-то действие ( action ) или изменит данные ( filter ). Отправка формы не является исключением — мы можем «прицепить» свою функцию к подходящему хуку и обработать POST-данные.
Кодекс WordPress говорит, что данные формы нужно отправлять на admin-post.php в директории wp-admin . А сама форма должна содержать поле action . Посмотрим на исходный код этого скрипта:
if (!defined('WP_ADMIN')) define('WP_ADMIN', true); > if (defined('ABSPATH')) require_once(ABSPATH . 'wp-load.php'); > else require_once(dirname(dirname(__FILE__)) . '/wp-load.php'); > send_origin_headers(); require_once(ABSPATH . 'wp-admin/includes/admin.php'); nocache_headers(); do_action('admin_init'); $action = empty($_REQUEST['action']) ? '' : $_REQUEST['action']; if (!wp_validate_auth_cookie()) // если пользователь не авторизован if (empty($action)) do_action('admin_post_nopriv'); > else do_action('admin_post_nopriv_'.$action); > > else // если пользователь авторизован if (empty($action)) do_action('admin_post'); > else do_action('admin_post_'.$action); > >
- когда пользователь не авторизован
- если action не установлен, происходит событие admin_post_nopriv
- если action установлен, происходит событие admin_post_nopriv_
- если action не установлен, происходит событие admin_post
- если action установлен, происходит событие admin_post_
Все вышеописанные хуки можно применить и для GET запроса, если адрес примерно такой:
http://www.server.com/wp-admin/admin-post.php?action=some_action&data=some_data
Плагин «Форма обратной связи»
Давайте применим полученные знания на практике и создадим плагин, который позволит добавить на сайт форму обратной связи. Создаем директорию tokmakov-feedback и внутри нее — файл tokmakov-feedback.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; > >);
Для начала зарегистрируем шорткод [tokmakov-feedback] :
/* * Регистрируем шорткод [tokmakov-feedback], который позволит * вставить форму обратной связи на страницу или запись блога */ add_shortcode('tokmakov-feedback', function () ob_start(); ?> class="tokmakov-feedback"> class="response"> action=" admin_url('admin-post.php'); ?>" method="post"> type="hidden" name="action" value="tokmakov_feedback" /> type="hidden" name="redirect" value=" get_permalink(); ?>" /> for="name"> Имя type="text" name="name" value="" required /> for="email"> Email type="text" name="email" value="" required /> for="phone"> Телефон type="text" name="phone" value="" /> for="message"> Сообщение name="message" required> type="submit" value="Отправить" /> return ob_get_clean(); >);
У нас есть скрытое поле формы action со значением tokmakov_feedback — значит, нам доступны два хука:
Форму мы должны обработать независимо от того, залогинился пользователь или нет:
/* * Обрабатываем отправленные данные формы обратной связи */ 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() /* * Здесь уже можно обрабатывать массивы $_POST или $_GET */ >
Что ж, давайте напишем функцию, которая будет обрабатывать данные формы и отправлять письмо администратору:
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)) /* * были допущены ошибки при заполнении формы, сохраняем введенные * пользователем данные, чтобы после редиректа снова показать форму, * заполненную введенными ранее даннными и сообщением об ошибке */ $_SESSION['tokmakov_feedback']['success'] = false; $message = 'При заполнении формы были допущены ошибки'; $_SESSION['tokmakov_feedback']['message'] = $message; $_SESSION['tokmakov_feedback']['errors'] = $errors; $_SESSION['tokmakov_feedback']['data'] = $data; > else // отправляем письмо администратору if (tokmakov_sendmail($data)) $_SESSION['tokmakov_feedback']['success'] = true; $message = 'Ваше сообщение успешно отправлено'; $_SESSION['tokmakov_feedback']['message'] = $message; > else $_SESSION['tokmakov_feedback']['success'] = false; $message = 'Произошла ошибка при отправке письма'; $_SESSION['tokmakov_feedback']['message'] = $message; $_SESSION['tokmakov_feedback']['data'] = $data; > > // после отправки формы делаем редирект, чтобы предотвратить // повторную отправку, если пользователь обновит страницу $redirect = home_url(); if (isset($_POST['redirect'])) $redirect = $_POST['redirect']; $redirect = wp_validate_redirect($redirect, home_url()); > wp_redirect($redirect); die(); >
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; >
Как видите, функция проверяет корректность данных и записывает результат проверки в сессию. Чтобы после редиректа показать результаты проверки пользователю. Если форма содержит ошибки, в сессию будут также записаны введенные пользователем данные. Будет некрасиво, если пользователь потратил время, чтобы написать сообщение, а мы это сообщение потеряем при проверке.
А теперь доработаем функцию, которая регистрирует шорткод. Кроме самой формы, она будет показывать сообщения об ошибках или сообщение об успешной отправке:
/* * Регистрируем шорткод [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(); ?> class="tokmakov-feedback"> class="response"> if (isset($_SESSION['tokmakov_feedback'])): ?> $_SESSION['tokmakov_feedback']['message']; ?> if (isset($_SESSION['tokmakov_feedback']['errors'])): ?> foreach ($_SESSION['tokmakov_feedback']['errors'] as $error): ?> $error; ?> endforeach; ?> endif; ?> unset($_SESSION['tokmakov_feedback']); ?> endif; ?> action=" admin_url('admin-post.php'); ?>" method="post"> type="hidden" name="action" value="tokmakov_feedback" /> type="hidden" name="redirect" value=" get_permalink(); ?>" /> Имя type="text" name="name" value=" $name; ?>" required /> E-mail type="text" name="email" value=" $email; ?>" required /> Телефон type="text" name="phone" value=" $phone; ?>" /> Сообщение name="message" required> $message; ?> type="submit" value="Отправить" /> return ob_get_clean(); >);
Поскольку мы используем суперглобальный массив $_SESSION , давайте стартанем сессию при наступлении события init :
/* * Запускаем сессию, чтобы передавать сообщение о результате * обработки формы обратной связи после перезагрузки страницы */ add_action('init', function () if (session_id() == '') session_start(); > >);
Если при проверке данных формы были обнаружены ошибки, элемент массива $_SESSION с ключом tokmakov_feedback будет содержать вот что:
Array ( [success] => false [message] => При заполнении формы были допущены ошибки [errors] => Array ( [0] => Не заполнено обязательное поле «E-mail» ) [data] => Array ( [name] => Евгений [email] => [phone] => [message] => Какое-то сообщение ) )
Если данные формы были успешно отправлены, этот элемент массива будет таким:
Array ( [success] => true [message] => Ваше сообщение успешно отправлено )
Скачать плагин «Форма обратной связи» можно здесь.
Action load post php
I think the way an array of attachments works is kind of cumbersome. Usually the PHP guys are right on the money, but this is just counter-intuitive. It should have been more like:
Array
(
[0] => Array
(
[name] => facepalm.jpg
[type] => image/jpeg
[tmp_name] => /tmp/phpn3FmFr
[error] => 0
[size] => 15476
)Anyways, here is a fuller example than the sparce one in the documentation above:
foreach ( $_FILES [ «attachment» ][ «error» ] as $key => $error )
$tmp_name = $_FILES [ «attachment» ][ «tmp_name» ][ $key ];
if (! $tmp_name ) continue;$name = basename ( $_FILES [ «attachment» ][ «name» ][ $key ]);
if ( $error == UPLOAD_ERR_OK )
if ( move_uploaded_file ( $tmp_name , «/tmp/» . $name ) )
$uploaded_array [] .= «Uploaded file ‘» . $name . «‘.
\n» ;
else
$errormsg .= «Could not move uploaded file ‘» . $tmp_name . «‘ to ‘» . $name . «‘
\n» ;
>
else $errormsg .= «Upload error. [» . $error . «] on file ‘» . $name . «‘
\n» ;
>
?>Do not use Coreywelch or Daevid’s way, because their methods can handle only within two-dimensional structure. $_FILES can consist of any hierarchy, such as 3d or 4d structure.
The following example form breaks their codes:
As the solution, you should use PSR-7 based zendframework/zend-diactoros.
use Psr \ Http \ Message \ UploadedFileInterface ;
use Zend \ Diactoros \ ServerRequestFactory ;$request = ServerRequestFactory :: fromGlobals ();
if ( $request -> getMethod () !== ‘POST’ ) http_response_code ( 405 );
exit( ‘Use POST method.’ );
>$uploaded_files = $request -> getUploadedFiles ();
if (
!isset( $uploaded_files [ ‘files’ ][ ‘x’ ][ ‘y’ ][ ‘z’ ]) ||
! $uploaded_files [ ‘files’ ][ ‘x’ ][ ‘y’ ][ ‘z’ ] instanceof UploadedFileInterface
) http_response_code ( 400 );
exit( ‘Invalid request body.’ );
>$file = $uploaded_files [ ‘files’ ][ ‘x’ ][ ‘y’ ][ ‘z’ ];
if ( $file -> getError () !== UPLOAD_ERR_OK ) http_response_code ( 400 );
exit( ‘File uploading failed.’ );
>$file -> moveTo ( ‘/path/to/new/file’ );
The documentation doesn’t have any details about how the HTML array feature formats the $_FILES array.
Array
(
[document] => Array
(
[name] => sample-file.doc
[type] => application/msword
[tmp_name] => /tmp/path/phpVGCDAJ
[error] => 0
[size] => 0
)
)Multi-files with HTML array feature —
Array
[type] => Array
(
[documents] => Array
(
[name] => Array
(
[0] => sample-file.doc
[1] => sample-file.doc
)
(
[0] => application/msword
[1] => application/msword
) [tmp_name] => Array
(
[0] => /tmp/path/phpVGCDAJ
[1] => /tmp/path/phpVGCDAJ
)The problem occurs when you have a form that uses both single file and HTML array feature. The array isn’t normalized and tends to make coding for it really sloppy. I have included a nice method to normalize the $_FILES array.
function normalize_files_array ( $files = [])
foreach( $files as $index => $file )
if (! is_array ( $file [ ‘name’ ])) $normalized_array [ $index ][] = $file ;
continue;
>foreach( $file [ ‘name’ ] as $idx => $name ) $normalized_array [ $index ][ $idx ] = [
‘name’ => $name ,
‘type’ => $file [ ‘type’ ][ $idx ],
‘tmp_name’ => $file [ ‘tmp_name’ ][ $idx ],
‘error’ => $file [ ‘error’ ][ $idx ],
‘size’ => $file [ ‘size’ ][ $idx ]
];
>?>
The following is the output from the above method.
Array
[documents] => Array
(
[document] => Array
(
[0] => Array
(
[name] => sample-file.doc
[type] => application/msword
[tmp_name] => /tmp/path/phpVGCDAJ
[error] => 0
[size] => 0
)
(
[0] => Array
(
[name] => sample-file.doc
[type] => application/msword
[tmp_name] => /tmp/path/phpVGCDAJ
[error] => 0
[size] => 0
) [1] => Array
(
[name] => sample-file.doc
[type] => application/msword
[tmp_name] => /tmp/path/phpVGCDAJ
[error] => 0
[size] => 0
)