Загрузка файлов с помощью AJAX для вашего WordPress плагина

16-10-07 Php, WordPress WordPress 1

Загрузка файлов для плагина 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');

Теперь, когда основа плагина готова, мы можем создать новую страницу и вывести на ней нашу форму.

Загрузка файлов с помощью AJAX в WordPress

Так форма будет выглядеть на сайте с темой twentysixteen:

Загрузка файлов с помощью AJAX в WordPress

Если мы не авторизованы, то мы увидим ссылку на страницу входа:

Загрузка файлов с помощью AJAX в WordPress

 

Загрузка файлов с помощью 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 должен выглядеть примерно так:

Загрузка файлов с помощью AJAX в WordPress

Вы так же можете проверить существует ли файл в папке wp-content/uploads. Теперь, когда загрузка работает исправно, можно заняться улучшением нашего плагина:

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

Показываем сообщение в процессе загрузки

Чтобы показать сообщение о том, что файл загружается нам достаточно определить событие beforeSend в AJAX запросе:

beforeSend: function() {
    $imgFile.hide();
    $imgNotice.html('Uploading&hellip;').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&hellip;(' + 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().

Для проверки работы плагина попробуйте загрузить файл и проверьте пришло ли вам уведомление на почту.

Хочешь получать статьи на почту?

Подпишись на обновления!
* Ваш email не будет разглашен/продан. Вы сможете отписаться в любое время.

1 Комментарий

  1. Smi:

    Здравствуйте, спасибо за пример, то что надо.

    Только не пойму как поменять путь куда будет загружаться фотография?

    по умолчанию она загружается в папку uploads/год/месяц
    а я хочу в корень и в папку с номером пользователя

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *