Вопрос или проблема
Я пытаюсь создать плагин, который сможет загружать файлы прямо в WP. Несмотря на то, что я копировал различные блоки кода из Интернета, он, похоже, упорно не дает мне это сделать.
Способ, которым я пытаюсь это сделать, заключается в использовании XMLHttpRequest к php файлу с wp_handle_upload
. Я прикрепляю элемент ввода файла к formdata
и отправляю его через запрос. Данные, которые отправляются, правильные и в порядке, так как я неоднократно выводил их, чтобы убедиться в этом, но когда они доходят до wp_handle_upload
, выполнение прекращается, и вкладка сети показывает, что произошла ошибка 500.
Я пробовал вызывать wp_handle_upload
только с первым параметром, но он говорит, что отправка формы недействительна. Я также парсил данные файла в новую переменную, как это сделано здесь, но результат остался тем же.
javascript код, который обрабатывает элемент ввода файла:
if ($('#active_p').is(":checked")) personDataActive = 1; // так как checked не является логическим свойством, нам нужно проанализировать его, чтобы получить правильное значение
else personDataActive = 0;
var personDataUsers = new Array;
var personDataPlaces = new Array;
$("input[name=\"check_list_user[]\"]:checked").each(function () {
personDataUsers.push($(this).val());
})
$("input[name=\"check_list_places[]\"]:checked").each(function () {
personDataPlaces.push($(this).val());
})
// в зависимости от значения id, он либо обновит, либо вставит человека
if ($('#id_p').val() > 0) id = $('#id_p').val(); // это новый человек
else id = 0; // это уже существующий человек
let image = $('#profile_picture').prop('files');
const formData = new FormData();
formData.append("profile_picture", image[0]);
formData = new FormData();
formData.append('image', image[0]);
$.ajax({
url: "imageUpload.php",
data: formData,
processData: false,
contentType: false,
type: "POST",
success: function(response){
console.log(response);
},
error: function(response) { console.log("error: " + response)}
});
// передаем все данные как массив, чтобы сделать ajax данные более чистыми. 0 — это id, 1 — имя, 2 — активен, 3 — дата рождения, 4 — внутренний id, 5 — медиа-url, 6 — пользователи и 7 — места
personData = [id, $('#name_p').val(), personDataActive, $('#birthdate_p').val(), $('#internalid_p').val(), personDataUsers, personDataPlaces];
// ajax вызов для добавления нового человека!!!
$.ajax({
url: 'ajaxToPhpCalls.php', // файл, в который следует отправить данные. желательно оставить его в той же папке, чтобы избежать проблем
type: 'POST', // это всегда может быть POST, даже если намерение заключается в получении значений, все-таки лучше указывать предполагаемый тип, чтобы случайно не перезаписать данные
data: { callFunction : "Insert", personData: personData.join(',')}, // мы не можем отправить массив напрямую, нам нужно использовать функцию .join(), чтобы отправить его целиком
success: function(response) {
// ответ может быть использован для отладки, так как вернет те же данные, что и в Postman
//alert("Success");
console.log(response);
// скрыть модальное окно при успешном выполнении, чтобы пользователь получил визуальный сигнал. если неудача, они могут повторно отправить запрос
hideCreateModal();
var total = $("#totalNumber").val();
$("#totalNumber").val(total++); // добавляем нового человека к общему количеству. val()++ не работает вне, может работать внутри ()
//console.log($("#totalNumber").val());
$("body").css("cursor", "default");
resetNewPersonFields();
ResetTable();
$("body").css("cursor", "default");
},
error: function() {alert("Error"); $("body").css("cursor", "default");},
});
imageUpload.php
для загрузки медиа в WP
<?php
// загружаем оба, чтобы убедиться, что функции ядра wordpress загружены
require( dirname(__FILE__) . '/../../../../../wp-config.php' );
require( dirname(__FILE__) . '/../../../../../wp-load.php' );
require_once( ABSPATH . 'wp-admin/includes/file.php' );
// проверяем, есть ли в запросе требуемая переменная
if (! empty($_FILES["image"]))
{
$upload_overrides = array( 'test_form' => FALSE );
$movefile = wp_handle_upload( $_FILES['image'], $upload_overrides);
if ( $movefile && !isset( $movefile['error'] ) ) {
$ufiles = get_post_meta( $post_id, 'my_files', true );
if( empty( $ufiles ) ) $ufiles = array();
$ufiles[] = $movefile;
update_post_meta( $post_id, 'my_files', $ufiles );
}
exit;
}
else return "No files detected";
html для ввода файла
<div id="Imagens" class="tabcontent" style="display: none;">
<div class="form-group">
<label>Изображение</label><br>
<input type="file" id="profile_picture" name="profile_picture"><br>
<label>или</label><br>
<a href="#" id="insertImageAnchor" class="button">Добавить изображение</a><br>
<img id="preview" src="https://cdn-icons-png.flaticon.com/512/983/983213.png" width=350px>
</form>
</div>
</div>
Примечание: В настоящее время не протестировано, но я публикую это здесь по мере написания
PHP может находиться в плагине или в functions.php
вашей темы и даст вам конечную точку по адресу yoursite.com/wp-json/demonipo/v1/profilephoto
, к которой вы можете выполнять POST
запросы через AJAX. Обратите внимание, что предполагается, что только авторизованные пользователи смогут это использовать. Если это не так, измените is_user_logged_in
на __return_true
. Не забудьте обновить постоянные ссылки перед тем, как попробовать использовать это:
PHP:
<?php
add_action( 'rest_api_init', 'demonipo_rest_api_init' );
function demonipo_rest_api_init() : void {
register_rest_route( 'demonipo/v1' , '/profilephoto/', [
'methods' => 'POST',
'callback' => 'demonipo_rest_profilephoto', // функция, которая будет выполнена при вызове
'permission_callback' => 'is_user_logged_in', // только для авторизованных пользователей
] );
}
/**
* Обработка загрузки профиля
*
* @param \WP_REST_Request $request AJAX-запрос со всеми полями, которые WP получил
* @return mixed возвращает WP_Error, если что-то пошло не так, иначе возвращает
* массив с информацией о загруженном файле
*/
function demonipo_rest_profilephoto( $request ) {
// Получаем все параметры, файл и пост, к которому нужно его прикрепить
// Получаем ID поста и проверяем, что он действителен
if ( empty( $request['postid'] ) ) {
return new WP_Error( 'invalid', 'Вы должны указать, какой пост будет обновлен' );
}
$post_id = $request['postid'];
// это действительно пост?
$post = get_post( $post_id );
if ( ! $post instanceof WP_Post ) {
return new WP_Error( 'invalid', 'Пост с указанным ID не найден' );
}
$files = $request->get_file_params();
$headers = $request->get_headers();
if ( empty( $files['profile_picture'] ) ) {
return new WP_Error( 'invalid', 'Профильное фото не найдено!' );
}
$file = $files['profile_picture'];
// Проверьте, что загрузка прошла успешно и действительна:
// подтверждаем, что файл загружен через POST
if ( ! is_uploaded_file( $file['tmp_name'] ) ) {
return new WP_Error( 'error', 'Проверка загруженного файла не прошла!' );
}
// подтверждаем, что нет ошибок с файлом
if (! $file['error'] === UPLOAD_ERR_OK ) {
return new WP_Error( 'error', 'Ошибка загрузки!' . $file['error'] );
}
$att_id = media_handle_upload( 'profile_picture', $post_id );
// если добавление не удалось, верните ошибку, чтобы мы могли понять, что произошло:
if ( is_wp_error( $att_id ) ) {
return $att_id;
}
$new_data = [
'file' => basename( wp_get_attachment_image_url( $att_id, 'full' ) ),
'url' => wp_get_attachment_image_url( $att_id, 'full' ),
'type' => 'image/jpeg',
];
// Все отлично! Обновляем метаданные поста
$ufiles = get_post_meta( $post_id, 'my_files', true );
if( empty( $ufiles ) ) {
$ufiles = [];
}
$ufiles[] = $new_data;
update_post_meta( $post_id, 'my_files', $ufiles );
// здесь верните любые данные, необходимые в ответе
return rest_ensure_response( $new_data );
}
Важные примечания:
- Я заметил, что в PHP использовался
$post_id
, но он нигде не был определен, поэтому не было способа узнать, какой пост нужно обновить. Убедитесь, что вы включили полеpost_id
в ваш форм-дата. wp_handle_upload
перемещает файл в нужное место, но не создаёт вложения, поэтому я заменил его наmedia_handle_upload
. Это недостающая часть, которая нужна для отображения в медиабиблиотеке. Я также установил, чтобы это стало вложением к посту, который вы передаете.- Эта статья была очень информативной при написании этого: https://firxworx.com/blog/wordpress/adding-an-endpoint-to-wordpress-rest-api-for-file-uploads/, вы можете узнать некоторые части проверки ошибок.
- много дополнительной проверки ошибок, она никогда не должна срабатывать, но если это произойдет, REST API вернет это вашему javascript и jQuery выдаст ошибку, которую вы сможете прочитать.
- Сначала я не знал, как получить mimetype, поэтому просто закодировал его как
image/jpeg
. Я также сказал, чтобы он возвращал изображение в полном размере, но WP также создаст миниатюры и т.д. - Если возможно, постарайтесь сохранить ID вложения, а не имя файла/URL, это упростит миграции.
- Эта статья/гист также была информативной, хотя я бы рекомендовал использовать PHP-базированный блок, а не шорткод в наши дни: https://gist.github.com/ahmadawais/0ccb8a32ea795ffac4adfae84797c19a
Этот последний гист содержит JS код, который близок к тому, что вам нужно:
// Проверьте, если файл выбран.
if ( 'undefined' === typeof( jQuery( '#profile_picture' )[0].files[0] ) ) {
alert( 'Выберите файл!' );
return;
}
// Получаем файл из ввода.
var file = jQuery( '#profile_picture' )[0].files[0];
var formData = new FormData();
formData.append( 'file', file );
// TODO: Добавьте ID поста в ваш HTML где-то
var post_id = jQuery( '#post_id' )
formData.append( 'post_id', post_id );
// Отправляем запрос.
jQuery.ajax( {
url: '/wp-json/demonipo/v1/profilephoto',
method: 'POST',
processData: false,
contentType: false,
data: formData
} ).success( function ( response ) {
console.log( 'успех!' );
console.log( response );
} ).error( function( response ) {
console.log( 'ошибка' );
console.log( response );
});
Вам нужно будет добавить скрытый ввод с ID поста, названный post_id
, с таким же ID и именем рядом с этим вводом файла, чтобы это работало.
например.
<input type="hidden" id="post_id" name="post_id" value="<?php ECHO POST ID HERE ?>" />
<input type="file" id="profile_picture" name="profile_picture"><br>
Есть одна вещь, которую я упустил, которую, я думаю, не нужно, это REST API nonce. Обычно для аутентифицированных запросов вам нужно передать nonce, что не сложно, но я не думаю, что вам это нужно. Если да, то API просто сообщит вам, что запрос запрещен, если это так, дайте знать. Как это сделать, описано в некоторых из ссылок, которые я добавил, так что это не слишком много работы.
Я также использовал jQuery
вместо $
, так как jQuery, который поставляется с WordPress, по умолчанию использует режим без конфликтов, но вы можете изменить это.
В качестве общего руководства, вы можете взять случайный PHP файл, как этот:
<?php
// загружаем оба, чтобы убедиться, что функции ядра wordpress загружены
require( dirname(__FILE__) . '/../../../../../wp-config.php' );
require( dirname(__FILE__) . '/../../../../../wp-load.php' );
echo "Привет " . $_GET['name'];
И преобразовать его в красивую URL конечную точку REST API так:
<?php
add_action( 'rest_api_init', 'demonipo_rest_api_init' );
function demonipo_rest_api_init() : void {
register_rest_route( 'demonipo/v1' , '/randomphp/', [
'methods' => 'GET',
'callback' => 'demonipo_randomphp', // функция, которая будет выполнена при вызове
'permission_callback' => '__return_true',
] );
}
function demonipo_randomphp( $request ) {
return 'Привет' . $request['name'];
}
Ответ или решение
При создании плагина для загрузки файлов в WordPress через wp_handle_upload
, вы столкнулись с проблемами, которые могут быть вызваны несколькими факторами. Давайте разберемся, как правильно обрабатывать загрузку файлов и избегать ошибок, таких как "500 Internal Server Error" и "Недопустимая форма отправки".
Анализ и решение проблемы
-
Проверка файлового ввода: Убедитесь, что файл действительно загружается. В вашем случае, вы используете
$_FILES['image']
, однако при отправке AJAX-запроса вы передаете его какprofile_picture
. Соответственно, вам нужно использовать$_FILES['profile_picture']
в вашем PHP-коде.if (!empty($_FILES["profile_picture"])) {
-
Параметры загрузки: В массиве
$upload_overrides
, в параметре'test_form'
вы указываете значениеFALSE
. Это правильный подход, чтобы обойти проверку формы. Однако также рекомендуется убедиться, что все необходимые параметры передаются вwp_handle_upload
. -
Проверка ошибок загрузки: Важно добавить обработку ошибок, чтобы выяснить, что именно идет не так. Используйте
error_log
илиwp_die
, чтобы вывести код ошибки.if (isset($movefile['error'])) { error_log('Upload error: ' . $movefile['error']); wp_die($movefile['error']); }
-
Использование
media_handle_upload
вместоwp_handle_upload
: Чтобы файл не только загружался, но и регистрировался как вложение в медиа-библиотеку, стоит использоватьmedia_handle_upload
.$att_id = media_handle_upload('profile_picture', $post_id); if (is_wp_error($att_id)) { error_log('Media upload error: ' . $att_id->get_error_message()); wp_die($att_id); }
-
Проверка ID записи (
post_id
): Убедитесь, чтоpost_id
передается в AJAX-запросе и корректно извлекается в PHP. Еслиpost_id
не установлен, это может вызвать ошибки.
Пример исправленного кода для PHP
require( dirname(__FILE__) . '/../../../../../wp-config.php' );
require( dirname(__FILE__) . '/../../../../../wp-load.php' );
require_once( ABSPATH . 'wp-admin/includes/file.php' );
if (!empty($_FILES["profile_picture"])) {
$upload_overrides = array('test_form' => false);
$att_id = media_handle_upload('profile_picture', $post_id);
if (is_wp_error($att_id)) {
error_log('Media upload error: ' . $att_id->get_error_message());
wp_die($att_id);
}
// Файл успешно загружен
// Можете вернуть информацию о файле, если это необходимо
wp_send_json_success($att_id);
} else {
wp_send_json_error('No files detected');
}
Рекомендации по JavaScript
В вашем JavaScript-коде убедитесь, что вы правильно передаете нужные данные, включая post_id
:
var post_id = $('#post_id').val();
formData.append('post_id', post_id);
Также проверьте, что загружаемый файл является допустимым:
if (!$('#profile_picture').prop('files').length) {
alert('Select a file!');
return;
}
Заключение
Следуя указаниям выше и реализовав предложенные изменения, вы сможете устранить основные проблемы с загрузкой файлов в WordPress с использованием wp_handle_upload
. Кроме того, не забывайте использовать инструменты отладки и логи для анализа возникающих ошибок, что поможет вам быстрее находить и исправлять проблемы. Удачи в разработке вашего плагина!