В этой заметке я поделюсь готовым кодом, который добавляет возможность задавать миниатюры для элементов таксономий, как встроенных (метки, рубрики), так и произвольных. Код протестирован и 7 раз улучшен - все для вас, мои дорогие
Чтобы добавить, или загрузить и добавить, или изменить картинку элемента таксономии, нужно кликнуть на саму картинку - она по совместительству является кнопкой. При загрузке открывается стандартное окно медиафайлов и картинка загружается в медиатеку WordPress. Но в отличии от картинок записей, она не привязывается к термину - она не привязывается ни к чему и это минус. Ну, пока так.
По умолчанию код работает для всех публичных таксономий (рубрики, метки и существующие произвольные таксономии). В коде можно указать конкретные таксы для которых он должен работать.
Вот что мы получим в итоге:
Изменение миниатюры элемента таксономии на странице редактирования. Миниатюра для элемента таксономии при создании термина. Также здесь видно как выглядят миниатюры в таблице терминов. Добавление или изменение миниатюры элемента таксономии на старанице редактирования термина Как выглядит медиа окно, если нажать на добавление/изменение миниатюры.
В сети много старых и некачественных пособий по этой теме. В некоторых из них данные сохраняются в таблицу опций, а не в метаполя. Напомню, что метаполя для элементов таксономий появились с версии WP 4.4 и все данные терминов, нужно сохранять именно туда, потому что это правильно и удобно.
Код
GitHub
<?php
namespace Kama;
use WP_Term;
/**
* Ability to upload images for terms (elements of taxonomies: categories, tags).
*
* Examples of usage: https://github.com/doiftrue/Term_Meta_Image
*
* @author Kama (wp-kama.com)
*
* @version 3.5.2
*/
interface WP_Term_Image_Interface {
/**
* @param array|string $args String can be passed when call it from WP hook directly.
* {@see: static::$defautl_args}.
*
* @return WP_Term_Image Instance of class by specified parameters.
*/
public static function init( $args = [] );
/**
* @param int|WP_Term $term The term which image you want to get.
*
* @return int WP attachment image ID or 0 if no image.
*/
public static function get_image_id( $term );
}
class WP_Term_Image implements WP_Term_Image_Interface {
private $args;
/**
* Default parameters that can be changed during class initialization.
*
* @var array
*/
private static $default_args = [
// For which taxonomies to include code. The default is for all public ones.
'taxonomies' => [],
// URL of the empty image
'noimage_src' => "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 1000 1000'%3E%3Cpath fill='%23bbb' d='M430 660 l0 -90 -90 0 -90 0 0 -70 0 -70 90 0 90 0 0 -85 0 -85 70 0 70 0 0 85 0 85 90 0 90 0 0 70 0 70 -90 0 -90 0 0 90 0 90 -70 0 -70 0 0 -90z'%3E%3C/path%3E%3C/svg%3E",
];
/**
* The name of the meta key for the term where the ID of the attachment is stored.
*
* @var string
*/
private static $meta_key = '_thumbnail_id';
/**
* The name of the meta key for the attachment post (label which term the picture belongs to).
*
* @var string
*/
private static $attach_meta_key = 'image_of_term';
private function __construct(){}
public static function init( $args = [] ){
static $inst;
$args = array_filter( (array) $args ); // may be null if called directly from wp hook
$args = array_intersect_key( $args, self::$default_args ); // leave allowed only
$inst_key = md5( serialize( $args ) );
if( empty( $inst[ $inst_key ] ) ){
$inst[ $inst_key ] = new self();
$inst[ $inst_key ]->set_args( $args );
$inst[ $inst_key ]->register_hooks();
}
return $inst[ $inst_key ];
}
private function set_args( array $args = [] ){
$this->args = $args + self::$default_args;
if( ! $this->args['taxonomies'] ){
$this->args['taxonomies'] = get_taxonomies( [ 'public' => true ], 'names' );
}
}
private function register_hooks(){
foreach( $this->args['taxonomies'] as $taxname ){
add_action( "{$taxname}_add_form_fields", [ $this, '_add_term__image_fields' ], 10 );
add_action( "{$taxname}_edit_form_fields", [ $this, '_update_term__image_fields' ], 10, 2 );
add_action( "created_{$taxname}", [ $this, '_create_term__handler' ], 10, 2 );
add_action( "edited_{$taxname}", [ $this, '_updated_term__handler' ], 10 );
// table columns
add_filter( "manage_edit-{$taxname}_columns", [ WP_Term_Image_Table_Columns::class, '_add_image_column' ] );
add_filter( "manage_{$taxname}_custom_column", [ WP_Term_Image_Table_Columns::class, '_fill_image_column' ], 10, 3 );
}
}
/**
* @param int|WP_Term $term The term which image you want to get.
*
* @return int 0 if no image.
*/
public static function get_image_id( $term ){
return (int) get_term_meta( is_object( $term ) ? $term->term_id : $term, self::$meta_key, true );
}
/**
* Fields at term creation.
*
* @param string $taxonomy
*
* @return void
*/
public function _add_term__image_fields( $taxonomy ){
// add the media styles, if they are not present
wp_enqueue_media();
add_action( 'admin_print_footer_scripts', [ $this, '_add_script' ], 99 );
$this->_css();
?>
<div class="form-field term-group term_image_wrapper_js">
<label><?php _e( 'Image', 'default' ) ?></label>
<div class="term__image__wrapper">
<button type="button" class="termeta_img_button termeta_img_button_js">
<img width="100" height="100" alt="" src="<?= $this->args['noimage_src'] ?>">
</button>
<button type="button" class="button button-secondary termeta_img_remove_js"><?php _e( 'Remove', 'default' ) ?></button>
</div>
<input type="hidden" id="term_imgid" name="term_imgid" value="">
</div>
<?php
}
/**
* Fields when editing a term.
*
* @param WP_Term $term
* @param string $taxonomy
*
* @return void
*/
public function _update_term__image_fields( $term, $taxonomy ){
wp_enqueue_media();
add_action( 'admin_print_footer_scripts', [ $this, '_add_script' ], 99 );
$image_id = self::get_image_id( $term );
$image_url = $image_id
? wp_get_attachment_image_url( $image_id, 'thumbnail' )
: $this->args['noimage_src'];
$this->_css();
?>
<tr class="form-field term-group-wrap term_image_wrapper_js">
<th scope="row"><?php _e( 'Image', 'default' ) ?></th>
<td>
<div class="term__image__wrapper">
<button type="button" class="termeta_img_button termeta_img_button_js"><img src="<?= $image_url ?>" alt=""></button>
<button type="button" class="button button-secondary termeta_img_remove_js"><?php _e( 'Remove', 'default' ) ?></button>
</div>
<input type="hidden" id="term_imgid" name="term_imgid" value="<?= $image_id ?>">
</td>
</tr>
<?php
}
private function _css(){
?>
<style>
.termeta_img_button{ display:inline-block; margin-right:1em;
border:0; padding:0; cursor:pointer; /* reset */
}
.termeta_img_button img{ display:block; float:left; margin:0; padding:0;
width:100px; height:100px;
background:rgba(0, 0, 0, .07);
}
.termeta_img_button:hover img{ opacity:.8; }
.termeta_img_button:after{ content:''; display:table; clear:both; }
</style>
<?php
}
public function _add_script(){
// выходим если не на нужной странице таксономии
//$cs = get_current_screen();
//if( ! in_array($cs->base, array('edit-tags','term')) || ! in_array($cs->taxonomy, (array) $this->for_taxes) )
// return;
$title = __( 'Featured Image', 'default' );
$button_txt = __( 'Set featured image', 'default' );
?>
<script>
document.addEventListener( 'DOMContentLoaded', function(){
const imgwrap = document.querySelector( '.term_image_wrapper_js' )
if( ! imgwrap ){
return;
}
const addButton = imgwrap.querySelector( '.termeta_img_button_js' )
const delButton = imgwrap.querySelector( '.termeta_img_remove_js' )
const imgInput = imgwrap.querySelector( '#term_imgid' )
let frame
// add / edit
addButton.addEventListener( 'click', function( ev ){
ev.preventDefault();
if( frame ){
frame.open();
return;
}
// задаем media frame
frame = wp.media.frames.questImgAdd = wp.media( {
states: [
new wp.media.controller.Library( {
title : '<?= $title ?>',
library : wp.media.query( { type: 'image' } ),
multiple: false
//date: false
} )
],
button: {
text: '<?= $button_txt ?>' // Set the text of the button.
}
} );
// select
frame.on( 'select', function(){
let selected = frame.state().get( 'selection' ).first().toJSON();
if( selected ){
imgInput.value = selected.id;
let src = selected.sizes.thumbnail ? selected.sizes.thumbnail.url : selected.url
imgwrap.querySelector( 'img' ).setAttribute( 'src', src );
}
} );
// open media-popup
frame.on( 'open', function(){
if( imgInput.value )
frame.state().get( 'selection' ).add( wp.media.attachment( imgInput.value ) );
} );
frame.open();
} );
// remove
delButton.addEventListener( 'click', function(){
imgInput.value = '';
imgwrap.querySelector( 'img' ).setAttribute( 'src', '<?= str_replace( "'", "\'", $this->args['noimage_src'] ) ?>' );
} );
} );
</script>
<?php
}
// Save the form field
public function _create_term__handler( $term_id, $tt_id ){
$attach_id = isset( $_POST['term_imgid'] ) ? (int) $_POST['term_imgid'] : 0;
if( ! $attach_id ){
return;
}
update_term_meta( $term_id, self::$meta_key, $attach_id );
self::up_attach_term_id( $attach_id, 'add', $term_id );
}
// Update the form field value
public function _updated_term__handler( $term_id ){
if( ! isset( $_POST['term_imgid'] ) ){
return;
}
$old_attach_id = self::get_image_id( $term_id );
$attach_id = (int) $_POST['term_imgid'];
// update metas
if( $attach_id ){
update_term_meta( $term_id, self::$meta_key, $attach_id );
self::up_attach_term_id( $attach_id, 'add', $term_id );
}
else{
delete_term_meta( $term_id, self::$meta_key );
}
// delete attachment
$old_attach = get_post( $old_attach_id );
if( $old_attach && (int) $old_attach_id !== (int) $attach_id ){
$old_attach_term_ids = self::up_attach_term_id( $old_attach_id, 'remove', $term_id );
// Вложение не прикреплено к посту или оно прикреплено к другому термину
if( ! $old_attach_term_ids && ! $old_attach->post_parent ){
wp_delete_attachment( $old_attach_id );
}
}
}
/**
* Updates post-meta field of specified attachment (post) - adds or removes term id from the field.
*
* @param int $attach_id
* @param string $action add|remove.
* @param int $term_id Term id to add/remove to attachment.
*
* @return array term ids updated fo attachment.
*/
private static function up_attach_term_id( $attach_id, $action, $term_id ){
$term_ids = wp_parse_list( get_post_meta( $attach_id, self::$attach_meta_key, true ) );
// add
if( 'add' === $action ){
$term_ids[] = $term_id;
}
// remove
else {
$term_ids = array_diff( $term_ids, [ $term_id ] );
}
// join - save as: ,12,34,54,
$term_ids = array_unique( (array) $term_ids );
$term_ids = array_map( 'sanitize_text_field', $term_ids );
$joined_term_ids = $term_ids ? ','. implode( ',', $term_ids ) .',' : '';
update_post_meta( $attach_id, self::$attach_meta_key, $joined_term_ids );
return $term_ids;
}
}
class WP_Term_Image_Table_Columns {
/**
* Adds an image column to the term table. Method for WP hook.
*
* @param array $columns
*
* @return array|string[]
*/
public static function _add_image_column( $columns ){
// fix column width
add_action( 'admin_notices', function(){
echo '<style>.column-image{ width:50px; text-align:center; }</style>';
} );
// column has no name
return array_slice( $columns, 0, 1 ) + [ 'image' => '' ] + $columns;
}
public static function _fill_image_column( $string, $column_name, $term_id ){
if( 'image' === $column_name ){
$image_id = WP_Term_Image::get_image_id( $term_id );
$string = $image_id
? sprintf( '<img src="%s" width="50" height="50" alt="" style="border-radius:4px;" />',
wp_get_attachment_image_url( $image_id, 'thumbnail' ) )
: '';
}
return $string;
}
}
Использование
Подключение
Подключите php файл WP_Term_Image.php
:
require_once __DIR__ . '/WP_Term_Image.php';
Или используйте Composer:
composer require doiftrue/wp_term_image
Инициализация
Базовая без передачи параметров:
add_action( 'admin_init', [ \Kama\WP_Term_Image::class, 'init' ] );
С передачей параметров:
add_action( 'admin_init', 'kama_wp_term_image' );
function kama_wp_term_image(){
// Укажем для какой таксономии нужна возможность устанавливать картинки.
// Можно не указывать, тогда возможность будет автоматом добавлена для всех публичных таксономий.
\Kama\WP_Term_Image::init( [
'taxonomies' => [ 'post_tag' ],
] );
}
Вывод картинки элемента таксономии во фронтенде
Бэкграунд для добавления картинки у нас есть, теперь осталось понять как получать такие картинки терминов в лицевой части сайта.
Делается это через родную функцию WordPress get_term_meta() , которая напомню еще раз существует с версии 4.4.
ID картинок (вложений) сохраняется в метаполе термина _thumbnail_id
, его и будем получать.
// получаем ID термина на странице термина
$term_id = get_queried_object_id();
// получим ID картинки из метаполя термина
$image_id = get_term_meta( $term_id, '_thumbnail_id', 1 );
// ссылка на полный размер картинки по ID вложения
$image_url = wp_get_attachment_image_url( $image_id, 'full' );
// выводим картинку на экран
echo '<img src="'. $image_url .'" alt="" />';
Если нужен не полный размер меняем full на нужный размер в wp_get_attachment_image_url() . Если нужны другие данные картинки, используем другую функцию получения вложения по ID .
Плагины для вставки миниатюр таксономий
Не кодом единым жив программист - есть еще плагины:
Taxonomy Thumbnail - по коду все круто, только его там много. Поддержка версий WP до 4,4 (когда была добавлена таблица метаданных таксономий)...
WP Multiple Taxonomy Images - код плагина очень похож на код из этой статьи - его не много, только то что нужно. Фишка плагина в том, что он позволяет задать сразу несколько миниатюр для термина.
Taxonomy Images - самый рейтинговый плагин по этому вопросу. Плагин в целом неплохой, но кода много, «лишнего» много, кажется. Если лишнее не нужно, используйте лучше предыдущие плагины или код из этой статьи.