Иногда стандартных полей WordPress для медиафайлов недостаточно. В этой статье я расскажу, как добавить собственное поле в окно редактирования вложения (attachment) — то самое, что появляется при открытии медиафайла в библиотеке. Мы не просто добавим поле, а сделаем это через полноценный class-плагин с сохранением значения на сервере и кастомной обвязкой HTML в окне WordPress Media Library.
Общая идея
WordPress не предлагает «официального» удобного API для кастомизации UI в медиа-библиотеке, но его Backbone.js-архитектура позволяет перехватывать ключевые методы. Мы пойдём немного нестандартным путём: «захватим» метод render()
у wp.media.view.Attachment.Details.TwoColumn
, чтобы внедрить свою логику и HTML.
Структура плагина
Мы создаём плагин-класс с хуками:
admin_init
: добавляем кастомное поле через фильтрattachment_fields_to_edit
.wp_ajax_save-attachment
: добавляем обработчик сохранения данных.- Отдельный JS-скрипт, который цепляется к интерфейсу медиа-библиотеки и управляет полем.
Добавляем поле и nonce на сервере
Я покажу как добавляются сами actions и фильтры. Важен как приоритет так и core action, через которые они добавляются.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<?php class MyPlugin { public function __construct() { add_action('wp_ajax_save-attachment', [$this, 'fields_to_save'], 0); add_action('admin_init', [$this, 'admin_init']); } public function admin_init() { add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']); add_filter('attachment_fields_to_edit', [$this, 'attachment_fields_to_edit'], 10, 2); } public function enqueue_admin_assets($hook) { $screen = get_current_screen(); // необязательная проверка, чтобы не вставлять скрипт // туда в админке, где он не нужен. if ($hook !== 'upload.php') return; wp_enqueue_script('myplugin-class', plugin_dir_url(__FILE__) . 'plugin.js', ['jquery'], 1.0, true); } } ... } |
В методе attachment_fields_to_edit добавим поле и nonce, которые WordPress передаёт в JavaScript.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public function attachment_fields_to_edit($form_fields, $post) { $data = get_post_meta($post->ID, '_my_meta_field', true); $form_fields['my_field'] = [ 'label' => 'Метка', 'input' => 'textarea', 'value' => $data, 'extra_rows' => [ 'nonce' => wp_nonce_field( // действие для nonce 'my_field_update_action_' . $post->ID, // имя для nonce 'my_field_update', true, // Output referer? false // Echo? ), ], ]; return $form_fields; } |
Это было не сложно, дальше разбираемся с JS.
Перехват рендера окна вложения
В JS-файле мы переопределяем метод render()
у представления вложения. Это позволяет вставить свой HTML и инициализировать кастомную логику:
plugin.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
(function ($) { // Переопределим обработку открытия редактора вложения if (wp.media !== undefined) wp.media.view.Attachment.Details.TwoColumn.prototype.render = function() { const result = wp.media.view .Attachment .Details .TwoColumn .__super__ .render .apply(this, arguments); // тут мы узнаём ID вложения const id = this.model.get('id'); // Вставим полезную нагрузку: редактор, обработчики, и т.п. setTimeout(() => { // например, найдем наше поле и картинку const dataSourceElm = $(`.media-modal .attachment-compat textarea[name="attachments[${id}][my_field]"]`).get(0); const imgElm = $('.media-modal .thumbnail-image img.details-image').get(0); ... // в итоге когда нужно будет записать данные // к примеру, пользователь что то поменял, и вы // обновляете своё поле, то будем вызывать // некую функцию сохранения данных: const mySaveFunc = (id, newValue) => { ... // код далее в статье }; // на будет ждать id вложения и новое значение поля // mySaveFunc( ID, YOURNEWVALUE ); }, 100); return result; }; } (jQuery)); |
Этот блок был наверное самым сложным в этой схеме. Обещаю, дальше будет проще.
Обработка сохранения
WordPress вызывает save-attachment
при сохранении полей. Мы уже подключились к этому ajax-хуку в конструкторе класса-плагина, вот код обработчика:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public function fields_to_save() { if (isset($_POST['id'], $_POST['changes'], $_POST['changes']['my_field'], $_POST['changes']['my_field_update_nonce'])) { $newValue = wp_unslash($_POST['changes']['my_field']); $nonce = $_POST['changes']['my_field_update_nonce']; if (wp_verify_nonce($nonce, 'my_field_update_action_' . $_POST['id'])) { update_post_meta($_POST['id'], '_my_meta_field', $newValue); } } } |
Данные полей придут в массиве $_POST[‘changes’]. Проверим nonce и запишем поле.
Триггерим сохранение через JavaScript
Поскольку поле у нас планируется как скрытое (можно именно скрытое создать, но я убираю его стилями), и пользователь его напрямую не редактирует — потому WordPress не будет считать его изменённым. Чтобы переломить эту ситуацию, мы вручную триггерим сохранение. Давайте вернемся к началу статьи и доопределим функцию сохранения данных:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
const mySaveFunc = (id, newValue) => { // вытащим объект attachment const attachment = wp.media.model.Attachment.get(id); // найдем nonce значение const nonce = $('.nonce input[name="my_field_update"]').val(); if (!nonce) { console.error('Nonce is not found for the field!'); } attachment.fetch().then(() => { // сначала сбрасываем значение, чтобы система увидела "изменение" attachment.set({ 'my_field': '[]', 'my_field_update_nonce': '' }); // а теперь установим правильные значения attachment.set({ 'my_field': newValue, 'my_field_update_nonce': nonce, }); // передадим данные на сервер attachment.save().then(() => { console.log('Data saved!'); }); }); } |
Такой подход даёт гибкий контроль над UI и серверной логикой. Хотя он требует вмешательства в внутренности wp.media
, результат — полностью интегрированное поле в редактор медиафайлов.