Загрузка файлов с помощью AJAX для вашего WordPress плагина
Загрузка файлов для плагина WordPress — не самая простая задача. Необходимо предоставить пользователю простой и понятный интерфейс, а также правильно обрабатывать загружаемые файлы, чтобы не создавать уязвимости.
Для того, чтобы не писать полностью собственный загрузчик файлов, мы будем использовать функционал WordPress, а именно файл async-upload.php, расположенный в папке wp-admin.
Используя этот файл мы получаем ряд преимуществ. Во-первых это стандартный файл для загрузки медиа файлов в WordPress, таким образом мы можем быть уверены в том, что код работает правильно. Так же в этом файле проводятся все необходимые проверки прав доступа и нам не придется делать это самостоятельно.
Требования
Чтобы использовать файл async-upload.php нужно следовать следующим правилам.
- Атрибут name поля для загрузки файла должен быть async-upload
- Защитный ключ, который мы отправляем в AJAX запросе, должен использовать стандартное имя _wpnonce, а значение его — результат работы функции wp_create_nonce(‘media-form’)
- В AJAX запросе мы должны отправить ключ action со значением upload-attachment, таким образом будет вызвана нужная нам функция wp_ajax_upload_attachment
Если мы будем следовать этим правилам, WordPress сможет корректно обработать наш AJAX запрос.
Плагин
Лучший способ продемонстрировать возможность загрузки файлов в WordPress — создать плагин. Мы создадим простой плагин, который позволяет зарегистрированным пользователям загружать изображения.
Так как цель этой статьи — демонстрация загрузки файлов с помощью AJAX, функционал плагина будет довольно скромным, наш плагин будет:
- Позволять администратору вставлять форму загрузки в любую страницу, используя шорткод.
- Показывать зарегистрированным пользователям форму загрузки файла с помощью AJAX.
- Отправлять уведомление о загрузке администратору.
Что можно добавить в плагин:
- Хранить информацию о загрузках в базе данных.
- Показывать список файлов в интерфейсе админа.
- Позволять не зарегистрированным пользователям загружать.
Создаем плагин
Как обычно, для создания нового WordPress плагина — создайте папку в директории wp-content/plugins. Будем использовать имя my-upload-plugin и префикс mup_ для всех собственных функций.
Далее, создайте главный файл плагина с таким же именем, как у папки. Также создадим папку js, в которой будет храниться файл script.js.
Должна получиться следующая структура:
wp-content/ |-- plugins/ |-- my-upload-plugin/ |-- js/ | |-- script.js |--my-upload-plugin.php
Напишем заголовок плагина в главном файле my-upload-plugin.php, осле этого можно перейти на страницу с плагинами и активировать его.
<?php /* Plugin Name: Simple Uploader Plugin URI: http://easy-code.ru Description: Демонстрация AJAX загрузки в WordPress Version: 0.1.0 Author: Author Name Author URI: http://easy-code.ru */
Подключение скрипта
Чтобы использовать script.js, необходимо подключить скрипт:
function mup_load_scripts() { wp_enqueue_script('image-form-js', plugin_dir_url( __FILE__ ) . 'js/script.js', array('jquery'), '0.1.0', true); } add_action('wp_enqueue_scripts', 'mup_load_scripts');
Нам нужно будет «локализовать» этот скрипт, так как в нем используются данные, которые могут меняться в зависимости от конкретной сессии. Для этого мы будем использовать функцию wp_localize_script(). Нам нужно изменить три переменные: ссылки на файлы admin-ajax.php и async-upload.php, а также защитный ключ, который генерирует функция wp_create_nonce().
Теперь код для подключения скрипта выглядит так:
function mup_load_scripts() { wp_enqueue_script('image-form-js', plugin_dir_url( __FILE__ ) . 'js/script.js', array('jquery'), '0.1.0', true); $data = array( 'upload_url' => admin_url('async-upload.php'), 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('media-form') ); wp_localize_script( 'image-form-js', 'mup_config', $data ); } add_action('wp_enqueue_scripts', 'mup_load_scripts');
Регистрируем шорткод для вывода формы
Для вывода формы загрузки файла мы будем использовать шорткод, таким образом мы сможем вставить форму где захотим и нам не придется каждый раз вставлять одну и ту же HTML разметку.
Нашей форме потребуется:
- Текстовое поле для ввода имени.
- Текстовое поля для ввода почты.
- Поле для файла с именем async-upload.
- Несколько div’ов для показа сообщений.
Также, если пользователь не авторизован, вместо формы для загрузки мы будем показывать форму для входа.
function mup_image_form_html(){ ob_start(); ?> <?php if ( is_user_logged_in() ): ?> <p class="form-notice"></p> <form action="" method="post" class="image-form"> <?php wp_nonce_field('image-submission'); ?> <p><input type="text" name="user_name" placeholder="Your Name" required></p> <p><input type="email" name="user_email" placeholder="Your Email Address" required></p> <p class="image-notice"></p> <p><input type="file" name="async-upload" class="image-file" accept="image/*" required></p> <input type="hidden" name="image_id"> <input type="hidden" name="action" value="image_submission"> <div class="image-preview"></div> <hr> <p><input type="submit" value="Submit"></p> </form> <?php else: ?> <p>Please <a href="<?php echo esc_url( wp_login_url( get_permalink() ) ); ?>">login</a> first to submit your image.</p> <?php endif; ?> <?php $output = ob_get_clean(); return $output; } add_shortcode('image_form', 'mup_image_form_html');
Этот код регистрирует новый шорткод image_form. Мы используем функции управления буфером вывода, и можем писать HTML код внутри PHP функции. С помощью атрибута accept мы разрешаем загружать только изображения. В функцию wp_login_url() мы передаем ссылку на текущую страницу (get_permalink()), таким образом пользователь будет перенаправлен обратно в случае успешного входа.
Разрешаем определенным ролям загружать файлы
Чтобы наш плагин работал, нам надо разрешить пользователям с ролью subscriber загружать файлы, так как по-умолчанию роль subscriber не имеет прав на загрузку файлов.
function mup_allow_subscriber_to_uploads() { $subscriber = get_role('subscriber'); if ( ! $subscriber->has_cap('upload_files') ) { $subscriber->add_cap('upload_files'); } } add_action('admin_init', 'mup_allow_subscriber_to_uploads');
Теперь, когда основа плагина готова, мы можем создать новую страницу и вывести на ней нашу форму.
Так форма будет выглядеть на сайте с темой twentysixteen:
Если мы не авторизованы, то мы увидим ссылку на страницу входа:
Загрузка файлов с помощью AJAX
Теперь можно заняться реализацией основной задачи плагина.
В файле script.js объявим нужные переменные:
(function($) { $(document).ready(function() { var $formNotice = $('.form-notice'); var $imgForm = $('.image-form'); var $imgNotice = $imgForm.find('.image-notice'); var $imgPreview = $imgForm.find('.image-preview'); var $imgFile = $imgForm.find('.image-file'); var $imgId = $imgForm.find('[name="image_id"]'); }); })(jQuery);
Эти переменные пригодятся нам в дальнейшем.
Мы будем отслеживать событие change у поля для выбора файла и если пользователь выберет файл, мы начнем AJAX загрузку.
$imgFile.on('change', function(e) { e.preventDefault(); var formData = new FormData(); formData.append('action', 'upload-attachment'); formData.append('async-upload', $imgFile[0].files[0]); formData.append('name', $imgFile[0].files[0].name); formData.append('_wpnonce', su_config.nonce); $.ajax({ url: su_config.upload_url, data: formData, processData: false, contentType: false, dataType: 'json', type: 'POST', success: function(resp) { console.log(resp); } }); });
Теперь отвлечемся немного от кода и попробуем загрузить файл. Используя инструменты разработчика (зависит от вашего браузера), проверим вывод в консоли. Успешный ответ от файла async-upload.php должен выглядеть примерно так:
Вы так же можете проверить существует ли файл в папке wp-content/uploads. Теперь, когда загрузка работает исправно, можно заняться улучшением нашего плагина:
- Покажем прогресс загрузки или уведомление во время загрузки файла.
- Покажем загруженное изображение в случае успеха или сообщение в случае ошибки.
- Добавим возможность загрузить новое изображение взамен старого.
Показываем сообщение в процессе загрузки
Чтобы показать сообщение о том, что файл загружается нам достаточно определить событие beforeSend в AJAX запросе:
beforeSend: function() { $imgFile.hide(); $imgNotice.html('Uploading…').show(); },
Мы используем пустой div с классом image-notice для показа сообщения и прячем файловый инпут.
Мы также можем показать процент загрузки файла. Для этого надо переопределить стандартный jQuery объект xhr. Добавьте этот код в объявление $.ajax:
xhr: function() { var myXhr = $.ajaxSettings.xhr(); if ( myXhr.upload ) { myXhr.upload.addEventListener( 'progress', function(e) { if ( e.lengthComputable ) { var perc = ( e.loaded / e.total ) * 100; perc = perc.toFixed(2); $imgNotice.html('Uploading…(' + perc + '%)'); } }, false ); } return myXhr; }
В браузерах, которые поддерживают эту функцию будет показан прогресс загрузки файла, а в тех, которые не поддерживают — ничего не произойдет.
Вывод изображения после успешной загрузки
В зависимости от ответа, который пришлет файл async-upload.php, мы показываем пользователю разные сообщения. Но если ключ success равен true, то мы можем показать загруженное изображение и спрятать поле выбора файла. Если произошла ошибка мы покажем сообщение об ошибке.
success: function(resp) { if ( resp.success ) { $imgNotice.html('Successfully uploaded.'); var img = $('<img>', { src: resp.data.url }); $imgId.val( resp.data.id ); $imgPreview.html( img ).show(); } else { $imgNotice.html('Fail to upload image. Please try again.'); $imgFile.show(); $imgId.val(''); } }
$imgId — скрытый инпут, в который записывается ID загруженного изображения, он пригодится нам дальше.
Заменяем старое изображение новым
Предоставим пользователю возможность заменить ранее загруженное изображение новым.
Для этого в случае успеха покажем немного другое сообщение:
$imgNotice.html('Successfully uploaded. <a href="#" class="btn-change-image">Change?</a>');
Теперь у нас есть ссылка с классом btn-change-image, на которую мы повесим обработчик события click. При нажатии на нее мы удалим текущее изображение, сообщение о загрузке и снова покажем поле для выбора файла.
$imgForm.on( 'click', '.btn-change-image', function(e) { e.preventDefault(); $imgNotice.empty().hide(); $imgFile.val('').show(); $imgId.val(''); $imgPreview.empty().hide(); });
Нам также необходимо перезаписать значение файлового инпута, чтобы событие change сработало снова.
$imgFile.on('click', function() { $(this).val(''); $imgId.val(''); });
Завершаем плагин
Для полноценной работы формы на нужно отслеживать событие submit и отправлять AJAX запрос:
$imgForm.on('submit', function(e) { e.preventDefault(); var data = $(this).serialize(); $.post( su_config.ajax_url, data, function(resp) { if ( resp.success ) { $formNotice.css('color', 'green'); $imgForm[0].reset(); $imgNotice.empty().hide(); $imgPreview.empty().hide(); $imgId.val(''); $imgFile.val('').show(); } else { $formNotice.css('color', 'red'); } $formNotice.html( resp.data.msg ); }); });
Этот код использует встроенный в WordPress обработчик AJAX запросов. В случае успешного запроса мы удалим показанную картинку и поменяем цвет уведомления на зеленый. В случае ошибки поменяем цвет на красный.
Теперь откроем главный файл плагина и добавим обработчик для нашего AJAX запроса:
add_action('wp_ajax_image_submission', 'mup_image_submission_cb');
В нашем примере мы просто вышлем, уведомление администратору о новой загрузке.
function mup_image_submission_cb() { check_ajax_referer('image-submission'); $user_name = filter_var( $_POST['user_name'],FILTER_SANITIZE_STRING ); $user_email = filter_var( $_POST['user_email'], FILTER_VALIDATE_EMAIL ); $image_id = filter_var( $_POST['image_id'], FILTER_VALIDATE_INT ); if ( ! ( $user_name && $user_email && $image_id ) ) { wp_send_json_error( array('msg' => 'Validation failed. Please try again later.') ); } $to = get_option('admin_email'); $subject = 'New image submission!'; $message = sprintf( 'New image submission from %s (%s). Link: %s', $user_name, $user_email, wp_get_attachment_url( $image_id ) ); $result = wp_mail( $to, $subject, $message ); if ( $result ) { wp_send_json_error( array('msg' => 'Email failed to send. Please try again later.') ); } else { wp_send_json_success( array('msg' => 'Your submission successfully sent.') ); } }
Для отправки сообщения об успешной или не очень загрузке используются функции wp_send_json_error() и wp_send_json_success().
Для проверки работы плагина попробуйте загрузить файл и проверьте пришло ли вам уведомление на почту.
Здравствуйте, спасибо за пример, то что надо.
Только не пойму как поменять путь куда будет загружаться фотография?
по умолчанию она загружается в папку uploads/год/месяц
а я хочу в корень и в папку с номером пользователя