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

Ильдар Сарибжанов | 22.02.2017

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;
}

Теперь точно всё!

Итог

Показал как работать с медиабиблиотекой, попутно расписал как создавать свои метаполя, как правильно подключать свои скрипты и стили в админку. Кстати, рабочий плагин, созданный по мотивам этого поста, можно забрать на гитхабе. Туда же свои предложения по улучшению и исправлению.

Всем рок!