Работа с медиабиблиотекой WordPress

WordPress уже давно вырос из штанов «блогового движка», и теперь осваивает просторы тру-кул-продакшн системы. Скажем прямо, справляется с переменным успехом, все-таки CMS была создана в те времена, когда только рождался Php 5, и до сих пор пытается сохранять обратную совместимость.
Если «правильно готовить», то на выходе можно получить годный продукт, в плане пользовательского интерфейса в админке все достаточно интуитивно, и за пару дней можно понять принципы работы.
Вступление
Для одного коммерческого проекта, потребовалось создать плагин с выбором файлов из медиабиблиотеки. Подобная задача решается несколькими вариантами: можно сделать обычное текстовое поле, куда писать ссылку на файл, можно в это текстовое поле писать id файла из медиабиблиотеки — но мне захотелось сделать красиво. В WP (WordPress) есть уже готовый функционал работы с файлами, добавление их в библиотеку и выбор. Нужно только подцепиться к этому АПИ.
Задача
Чтобы было проще понять, поставлю конкретную проблему, которую героически преодолею: нужно в каждую публикацию типа post добавить кнопку прикрепления файла из библиотеки. Границы четкие и ясные. Поехали!
Код
Технически, ничто не мешает все нижеописанные куски php писать напрямую в файл functions.php в тему, но мне кажется, что то, что решаю, несколько больше чем функция темы, а скорее даже наоборот, вообще к функциям темы не имеет отношения, поэтому, я рекомендую создать отдельный плагин.
Допустим, входной файл плагина создан, и пока он чистый, кроме служебных строк, которые расскажут движку, что это плагин и его можно включать и выключать. Для начала, нужно подключить АПИ
function my_plugin_enqueue_media() {
wp_enqueue_media();
}
add_action( 'admin_enqueue_scripts', 'my_plugin_enqueue_media' );
Эти строки подключают все стили и скрипты связанные с работой медиабиблиотеки.
А теперь начинается магия. Следи за руками и постарайся не отставать за ходом мысли.
В папку с новый плагином (конечно, папку можно не создавать, но тогда магии не произойдет) нужно положить некоторый js-файлик, о содержимом которого поговорим чуть позже. Будет неплохо, если он будет правильно подключен. Для этого немного модифицируем вышеописанный код.
function my_plugin_enqueue_media() {
// Подключение АПИ для работы с медиабиблиотекой
wp_enqueue_media();
// Скрипт для выбора файла
wp_enqueue_script('add-one-media.js', plugins_url('/js/add-one-media.js', __FILE__), array('jquery'));
}
add_action( 'admin_enqueue_scripts', 'my_plugin_enqueue_media' );
В функции wp_enqueue_script третьим параметром передается массив зависимостей. Да, я до сих пор использую уже не мэйнстримовый jquery, не нравится, ничто не мешает использовать что-то другое. Сам движок уже содержит jquery, поэтому незачем тянуть свою версию. Обожаю логически структурировать расположение файлов, поэтому js положу в отдельную папку для js, а вдруг потом это все обрастет чем-то большим, пусть уж сразу лежит там где положено.
Добавим саму кнопку для прикрепления файла к записи. Реализуем это через механизм метаполей. Надеюсь что такое хук в WP не надо объяснять? В противном случае, тебе еще рано так глубоко погружаться в кишки WP. Или просто копируй, и пусть оно работает как истинная магия.
/**
* Создание блока метаполей для постов
*/
function add_one_meta()
{
add_meta_box('add_one_meta', 'Прикрепленный файл', 'add_one_meta_view', 'post');
}
add_action('add_meta_boxes', 'add_one_meta');
/**
* HTML код блока
*/
function add_one_meta_view()
{
global $post;
// Если это пост отличный от необходимого, уйдем отсюда, и ничего не отобразим
if ($post->post_type != 'post') {
return;
}
// Используем nonce для верификации
wp_nonce_field(plugin_basename(__FILE__), 'add_one_nonce');
// Заберем значение прикрепленного файла
$add_file_id = get_post_meta($post->ID, 'add_file_id', true);
// Ссылка на добавление файлов, если js отколючен
$upload_link = esc_url(get_upload_iframe_src('null', $post->ID));
// Поле для выбора файла
echo '
<div class="custom_field_itm">
<div class="js-add-wrap">';
if ($add_file_id) :
$file_info = get_post($add_file_id);
$file_icon = wp_get_attachment_image($add_file_id, 'thumbnail', true);
echo '<div class="add_file js-add_file_itm">
<input type="hidden" name="add_file_id" value="' . $add_file_id . '" />
<div class="add_file_icon">' . $file_icon . '</div>
<p class="add_file_name">' . $file_info->post_title . '</p>
<a href="#" class="button button-primary button-large js-add-file-remove">Открепить файл</a>
</div>';
endif;
echo '</div><br/>
<a href="' . $upload_link . '" class="button button-primary button-large js-add-file">Добавить файл</a>
</div>';
}
На выходе получается такое

Как не трудно догадаться, в метаполе планируется хранить только id вложения, а зачем что-то большее? Имея id можно получить хоть черта лысого, было бы желание. Сохраняется все это вот так:
/**
* Сохраняем данные, когда пост сохраняется
*
* @param $post_id
* @param $post
*
* @return mixed
*/
function omsu_doc_save_postdata($post_id, $post)
{
// Если это пост отличный от документа, уйдем отсюда
if ($post->post_type != 'post') {
return $post_id;
}
// проверяем nonce нашей страницы, потому что save_post может быть вызван с другого места.
if ( ! isset($_POST['add_one_nonce']) || ! wp_verify_nonce($_POST['add_one_nonce'],
plugin_basename(__FILE__))
) {
return $post_id;
}
// проверяем, если это автосохранение ничего не делаем с данными нашей формы.
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return $post_id;
}
// проверяем разрешено ли пользователю указывать эти данные
if ('page' == $_POST['post_type'] && ! current_user_can('edit_page', $post_id)) {
return $post_id;
} elseif ( ! current_user_can('edit_post', $post_id)) {
return $post_id;
}
// Сохраним прикрепленный файл
$add_file_id = (int)$_POST['add_file_id'];
update_post_meta($post_id, 'add_file_id', $add_file_id);
return $post_id;
}
add_action('save_post', 'omsu_doc_save_postdata', 10, 2);
Куча проверок и защиты и 2 строки непосредственно сохранения. Кажется, что уже много написано, но к сути пока не подошел. А вся сила, брат, в JavaScript. Так-то! Вот мы и пришли к нему. Блин! Когда ты уже знаешь как это делать, все кажется таким очевидным, но на момент изучения потратил полдня на формулирование запроса и еще полдня на поиск ответа. А сейчас вот так просто я раскрыл все таинства этого действа, которые по сути уместились в 27 строчек кода. И отчего знания не приходят в голову сразу как только становятся необходимы?
jQuery(function ( $ ) {
var frame;
// Прикрепить файл к редакции
$(document).on('click', '.js-add-file', function ( event ) {
event.preventDefault();
// Если окно загрузки уже доступно, просто откроем его
if( frame ) {
frame.open();
return;
}
// В противном случае, создадим новое
frame = wp.media({
title : 'Выберите файл',
button : {
text: 'Использовать этот файл'
},
multiple: false // Если нужна возможность крепить одним махом несколько файлов
});
// показать инфу о прикрепленном файле
frame.on('select', function () {
// Получим объект со всей информацией о выбранном файле
var attachment = frame.state().get('selection').first().toJSON();
$('.js-add-wrap').html('<div class="add_file js-add_file_itm">' +
'<input type="hidden" name="add_file_id" value="' + attachment.id + '" />' +
'<div class="add_file_icon"><img src="' + attachment.icon + '" alt="" /></div>' +
'<p class="add_file_name">' + attachment.title + '</p>' +
'<a href="#" class="button button-primary button-large js-add-file-remove">Открепить файл</a>' +
'</div>');
});
// Откроем файл
frame.open();
});
});
Вот как бы и всё. Теперь нет проблем, чтобы крепить файлы к посту. Можно запуливать в продакшен и радоваться ровно до того момента, пока не потребуется открепить файл =) К счастью это совсем не проблема.
Внутрь замыкания добавляем 4 строчки кода, и теперь плагин умеет откреплять файлы.
// отцепить приложенный файл от редакции
$(document).on('click', '.js-add-file-remove', function ( event ) {
event.preventDefault();
$(this).closest('.js-add_file_itm').remove();
});
Чтобы добавить немного красивостей (хотя я бы не рекомендовал вам в области «красивостей» консультироваться с человеком взращенным ComicSans’ом) добавим немного css. В самую первую функцию, где подключаются скрипты, допишем еще строку с подключением стилей.
wp_enqueue_style('add-one-media', plugins_url('/css/add-one-media.css', __FILE__));
А в css, например, такие стили:
.add_file {
position: relative;
overflow: hidden;
}
.add_file_icon {
position: relative;
float: left;
margin: 0 10px 10px 0;
}
.add_file_name {
position: relative;
margin: 0 0 10px;
}
Теперь точно всё!
Итог
Показал как работать с медиабиблиотекой, попутно расписал как создавать свои метаполя, как правильно подключать свои скрипты и стили в админку. Кстати, рабочий плагин, созданный по мотивам этого поста, можно забрать на гитхабе. Туда же свои предложения по улучшению и исправлению.
Всем рок!