WordPress как на ладони
Недорогой хостинг для сайтов на WordPress: wordpress.jino.ru

Kama_Post_Meta_Box — создаем метаполя для записей

В этой статье я поделись классом Kama Post Meta Box, с помощью которого можно быстро создавать метаполя для записей, просто указав их в виде массива. Получается своего рода конструктор. К тому же, этот вариант автоматически очищает данные при сохранении и в некоторых случаях может защитить от взлома сайта.

Статью на эту тему я уже писал: «Блок произвольных полей в админке WordPress своими руками». Правда было это давно, но статья по-прежнему актуальна и может пригодится, когда нужно создать произвольные поля для записи, без использования плагинов. Однако в том варианте все нужно делать вручную, включая создание html каждого поля формы — это не удобно. Также, тот вариант требует определенных знаний: умение работать с хуками и т.д.

Покажу на примере, как легко можно создать произвольные поля для записей. Допустим, нам нужно создать 4 SEO поля: title, description, keywords и robots для всех типов записей. Для создания метабокса нужно вызвать класс Kama_Post_Meta_Box с передачей ему параметров:

class_exists('Kama_Post_Meta_Box') && new Kama_Post_Meta_Box( array(
	'id'     => '_seo',
	'title'  => 'SEO поля',
	'fields' => array(
		'title' => array(
			'type'=>'text',    'title'=>'Title',       'desc'=>'Заголовок страницы (рекомендуется 70 символов)', 'attr'=>'style="width:99%;"'
		),
		'description' => array(
			'type'=>'textarea','title'=>'Description', 'desc'=>'Описание страницы (рекомендуется 160 символов)', 'attr'=>'style="width:99%;"'
		),
		'keywords' => array(
			'type'=>'text',    'title'=>'Keywords',    'desc'=>'Ключевые слова для записи',       'attr'=>'style="width:99%;"'
		),
		'robots' => array(
			'type'=>'radio',   'title'=>'Robots',      'options' => array(''=>'index,follow', 'noindex,nofollow'=>'noindex,nofollow')
		),
	),
) );

В результате в админке на страницах редактирования записей любого типа (post, page, ...) получим такой метабокс:

Создание произвольных полей для записи

Удобно? По-моему очень!

Чтобы такой код работал нужно подключить в functions.php (или куда-то еще) следующий код (PHP класс)

меню

Класс: Kama_Post_Meta_Box

GitHub
<?php

if( ! class_exists('Kama_Post_Meta_Box') ) :

	/**
	 * Создает блок произвольных полей для указанных типов записей.
	 *
	 * Возможные параметры класса, смотрите в: Kama_Post_Meta_Box::__construct()
	 * Возможные параметры для каждого поля, смотрите в: Kama_Post_Meta_Box::field()
	 *
	 * При сохранении, очищает каждое поле, через: wp_kses() или sanitize_text_field().
	 * Функцию очистки можно заменить через хук 'kpmb_save_sanitize_{$id}' и
	 * также можно указать название функции очистки в параметре 'save_sanitize'.
	 * Если указать функции очистки и в параметре, и в хуке, то будут работать обе!
	 * Обе функции очистки получают данные: $metas - все метаполя, $post_id - ID записи.
	 *
	 * Блок выводиться и метаполя сохраняются для юзеров с правом редактировать текущую запись.
	 *
	 * PHP: 5.3+
	 *
	 * @changlog https://github.com/doiftrue/Kama_Post_Meta_Box/blob/master/changelog.md
	 *
	 * @version 1.9.7
	 */
	class Kama_Post_Meta_Box {

		public $opt;

		public $id;

		static $instances = array(); // сохраняет ссылки на все экземпляры

		/**
		 * Конструктор
		 *
		 * @param array $opt Опции по которым будет строиться метаблок
		 */
		function __construct( $opt ){

			$defaults = [
				'id'         => '',       // динетификатор блока. Используется как префикс для названия метаполя.
				// начните идент. с '_': '_foo', чтобы ID не был префиксом в названии метаполей.

				'title'      => '',       // заголовок блока
				'desc'       => '',       // описание для метабокса. Можно указать функцию/замыкание, она получит $post. С версии 1.9.1
				'post_type'  => '',       // строка/массив. Типы записей для которых добавляется блок: array('post','page').
				// По умолчанию: '' - для всех типов записей.
				'post_type_feature' => '', // строка. возможность которая должна быть у типа записи,
				// чтобы метабокс отобразился. См. post_type_supports()
				'post_type_options' => '', // массив. опции типа записи, которые должны быть у типа записи чтобы метабокс отобразился.
				// см. первый параметр get_post_types()

				'priority'   => 'high',   // Приоритет блока для показа выше или ниже остальных блоков ('high' или 'low').
				'context'    => 'normal', // Место где должен показываться блок ('normal', 'advanced' или 'side').

				'disable_func'  => '',    // функция отключения метабокса во время вызова самого метабокса.
				// Если вернет что-либо кроме false/null/0/array(), то метабокс будет отключен. Передает объект поста.

				'cap'           => '',    // название права пользователя, чтобы показывать метабокс.

				'save_sanitize' => '',    // Функция очистки сохраняемых в БД полей. Получает 2 параметра:
				// $metas - все поля для очистки и $post_id

				'theme' => 'table',       // тема оформления: 'table', 'line'.
				// ИЛИ массив паттернов полей: css, fields_wrap, field_wrap, title_patt, field_patt, desc_patt.
				// Массив указывается так: [ 'desc_patt' => '<div>%s</div>' ] (за овнову будет взята тема line)
				// Массив указывается так: [ 'table' => [ 'desc_patt' => '<div>%s</div>' ] ] (за овнову будет взята тема table)
				// ИЛИ изменить тему можно через фильтр 'kp_metabox_theme' - удобен для общего изменения темы для всех метабоксов.

				// метаполя. Параметры смотрите ниже в методе field()
				'fields'     => [
					'foo' => [ 'title' =>'Первое метаполе' ],
					'bar' => [ 'title' =>'Второе метаполе' ],
				],
			];

			$this->opt = (object) array_merge( $defaults, $opt );

			// хуки инициализации, вешается на хук init чтобы текущий пользователь уже был установлен
			add_action( 'init', [ $this, 'init_hooks' ], 20 );
		}

		function init_hooks(){
			// может метабокс отключен по праву
			if( $this->opt->cap && ! current_user_can( $this->opt->cap ) )
				return;

			// темы оформления
			if( 'theme_options' ){

				$opt_theme = & $this->opt->theme;

				$def_opt_theme = [
					'line' => [
						// CSS стили всего блока. Например: '.postbox .tit{ font-weight:bold; }'
						'css'         => '',
						// '%s' будет заменено на html всех полей
						'fields_wrap' => '%s',
						// '%2$s' будет заменено на html поля (вместе с заголовком, полем и описанием)
						'field_wrap'  => '<p class="%1$s">%2$s</p>',
						// '%s' будет заменено на заголовок
						'title_patt'  => '<strong class="tit"><label>%s</label></strong>',
						// '%s' будет заменено на HTML поля (вместе с описанием)
						'field_patt'  => '%s',
						// '%s' будет заменено на текст описания
						'desc_patt'   => '<br><span class="description" style="opacity:0.6;">%s</span>',
					],
					'table' => [
						'css'         => '.kpmb-table td{ padding:.6em .5em; } .kpmb-table tr:hover{ background:rgba(0,0,0,.03); }',
						'fields_wrap' => '<table class="form-table kpmb-table">%s</table>',
						'field_wrap'  => '<tr class="%1$s">%2$s</tr>',
						'title_patt'  => '<td style="width:10em;" class="tit">%s</td>',
						'field_patt'  => '<td class="field">%s</td>',
						'desc_patt'   => '<br><span class="description" style="opacity:0.8;">%s</span>',
					],
				];

				if( is_string($opt_theme) ){
					$def_opt_theme = $def_opt_theme[ $opt_theme ];
				}
				// позволяет изменить отдельные поля темы оформелния метабокса
				else {
					$opt_theme_key = key( $opt_theme ); // индекс массива

					// в индексе указана не тема: [ 'desc_patt' => '<div>%s</div>' ]
					if( ! in_array( $opt_theme_key, array_keys($def_opt_theme) ) ){
						$def_opt_theme = $def_opt_theme['line']; // основа темы
					}
					// в индексе указана тема: [ 'table' => [ 'desc_patt' => '<div>%s</div>' ] ]
					else {
						$def_opt_theme = $def_opt_theme[ $opt_theme_key ]; // основа темы
						$opt_theme     = $opt_theme[ $opt_theme_key ];
					}
				}

				$opt_theme = is_array( $opt_theme ) ? array_merge( $def_opt_theme, $opt_theme ) : $def_opt_theme;

				// для изменения темы через фильтр
				$opt_theme = apply_filters( 'kp_metabox_theme', $opt_theme, $this->opt );

				// переменные theme в общие параметры.
				// Если в параметрах уже есть переменная, то она остается как есть (это позволяет изменить отдельный элемент темы).
				foreach( $opt_theme as $kk => $vv ){
					if( ! isset($this->opt->{$kk}) )
						$this->opt->{$kk} = $vv;
				}
			}

			// создадим уникальный ID объекта
			$_opt = (array) clone $this->opt;
			// удалим (очистим) все closure
			array_walk_recursive( $_opt, function(&$val, $key){
				if( $val instanceof Closure ) $val = '';
			});
			$this->id = substr( md5(serialize($_opt)), 0, 7 ); // ID экземпляра

			// сохраним ссылку на экземпляр, чтобы к нему был доступ
			self::$instances[ $this->opt->id ][ $this->id ] = & $this;

			add_action( 'add_meta_boxes', [ $this, 'add_meta_box' ], 10, 2 );
			add_action( 'save_post', [ $this, 'meta_box_save' ], 1, 2 );
		}

		function add_meta_box( $post_type, $post ){

			$opt = $this->opt; // short love

			// может отключить метабокс?
			if(
				in_array( $post_type, [ 'comment','link' ] )
				|| ! current_user_can( get_post_type_object( $post_type )->cap->edit_post, $post->ID )
				|| ( $opt->post_type_feature && ! post_type_supports( $post_type, $opt->post_type_feature ) )
				|| ( $opt->post_type_options && ! in_array( $post_type, get_post_types( $opt->post_type_options, 'names', 'or' ) ) )
				|| ( $opt->disable_func && is_callable($opt->disable_func) && call_user_func( $opt->disable_func, $post ) )
			)
				return;

			$p_types = $opt->post_type ?: $post_type;

			// if WP < 4.4
			if( is_array($p_types) && version_compare( $GLOBALS['wp_version'], '4.4', '<' ) ){
				foreach( $p_types as $p_type )
					add_meta_box( $this->id, $opt->title, [ $this, 'meta_box' ], $p_type, $opt->context, $opt->priority );
			}
			else
				add_meta_box( $this->id, $opt->title, [ $this, 'meta_box' ], $p_types, $opt->context, $opt->priority );

			// добавим css класс к метабоксу
			// apply_filters( "postbox_classes_{$page}_{$id}", $classes );
			add_filter( "postbox_classes_{$post_type}_{$this->id}", [ $this, '_postbox_classes_add' ] );
		}

		/**
		 * Выводит код блока
		 * @param object $post Объект записи
		 */
		function meta_box( $post ){
			$fields_out = $hidden_out = '';

			foreach( $this->opt->fields as $key => $args ){
				if( ! $key || ! $args ) continue; // пустое поле

				if( empty($args['title_patt']) ) $args['title_patt'] = @ $this->opt->title_patt ?: '%s';
				if( empty($args['desc_patt'])  ) $args['desc_patt']  = @ $this->opt->desc_patt  ?: '%s';
				if( empty($args['field_patt']) ) $args['field_patt'] = @ $this->opt->field_patt ?: '%s';

				$args['key'] = $key;

				$field_wrap = & $this->opt->field_wrap;
				if( @ $args['type'] == 'wp_editor' )  $field_wrap = str_replace( [ '<p ','</p>' ], [ '<div ','</div><br>' ], $field_wrap );

				if( @ $args['type'] == 'hidden' )
					$hidden_out .= $this->field( $args, $post );
				else
					$fields_out .= sprintf( $field_wrap, $key .'_meta', $this->field( $args, $post ) );

			}

			$metabox_desc = '';
			if( $this->opt->desc )
				$metabox_desc = is_callable($this->opt->desc) ? call_user_func($this->opt->desc, $post) : '<p class="description">'. $this->opt->desc .'</p>';

			echo ( $this->opt->css ? '<style>'. $this->opt->css .'</style>' : '' ) .
			     $metabox_desc .
			     $hidden_out .
			     sprintf( (@ $this->opt->fields_wrap ?: '%s'), $fields_out ) .
			     '<div class="clear"></div>';
		}

		/**
		 * Выводит отдельные мета поля.
		 *
		 * @param string  $name  Атрибут name.
		 * @param array   $args  Параметры поля.
		 * @param object  $post  Объект текущего поста.
		 *
		 * @return string|null HTML код
		 */
		function field( $args, $post ){

			$var = (object) []; // внутренние переменные этой фукнции, будут переданы в методы
			$rg  = (object) array_merge( [
				'type'          => '', // тип поля: textarea, select, checkbox, radio, image, wp_editor, hidden, sep_*.
				// Или базовые: text, email, number, url, tel, color, password, date, month, week, range.
				// 'sep' - визуальный разделитель, для него нужно указать `title` и можно указать `'attr'=>'style="свои стили"'`.
				// 'sep' - чтобы удобнее указывать тип 'sep' начните ключ поля с `sep_`: 'sep_1' => [ 'title'=>'Разделитель' ].
				// Для типа `image` можно указать тип сохраняемого значения в `options`: 'options'=>'url'. По умолчанию тип = id.
				// По умолчанию 'text'.

				'title'         => '', // заголовок метаполя
				'desc'          => '', // описание для поля. Можно указать функцию/замыкание, она получит параметры: $post, $meta_key, $val, $name.
				'placeholder'   => '', // атрибут placeholder
				'id'            => '', // атрибут id. По умолчанию: $this->opt->id .'_'. $key
				'class'         => '', // атрибут class: добавляется в input, textarea, select. Для checkbox, radio в оборачивающий label
				'attr'          => '', // любая строка, будет расположена внутри тега. Для создания атрибутов. Пр: style="width:100%;"
				'val'           => '', // значение по умолчанию, если нет сохраненного.
				'options'       => '', // массив: array('значение'=>'название') - варианты для типов 'select', 'radio'.
				// Для 'wp_editor' стенет аргументами.
				// Для 'checkbox' станет значением атрибута value: <input type="checkbox" value="{options}">.
				// Для 'image' определяет тип сохраняемого в метаполе значения: id (ID вложения), url (url вложения).

				'callback'      => '', // название функции, которая отвечает за вывод поля.
				// если указана, то ни один параметр не учитывается и за вывод полностью отвечает указанная функция.
				// Все параметры передаются ей... Получит параметры: $args, $post, $name, $val

				'sanitize_func' => '', // функция очистки данных при сохранении - название функции или Closure.
				// Укажите 'none', чтобы не очищать данные...
				// работает, только если не установлен глобальный параметр 'save_sanitize'...
				// получит параметр $value - сохраняемое значение поля.

				'output_func'   => '', // функция обработки значения, перед выводом в поле.
				// получит параметры: $post, $meta_key, $value - объект записи, ключ, значение метаполей.

				'update_func'   => '', // функция сохранения значения в метаполя.
				// получит параметры: $post, $meta_key, $value - объект записи, ключ, значение метаполей.

				'disable_func'  => '', // функция отключения поля.
				// Если не false/null/0/array() - что-либо вернет, то поле не будет выведено.
				// Получает парамтры: $post, $meta_key

				'cap'           => '', // название права пользователя, чтобы видеть и изменять поле.

				// служебные
				'key'           => '', // Обязательный! Автоматический
				'title_patt'    => '', // Обязательный! Автоматический
				'field_patt'    => '', // Обязательный! Автоматический
				'desc_patt'     => '', // Обязательный! Автоматический
			], $args );

			if( $rg->cap && ! current_user_can( $rg->cap ) )
				return null;

			if( 'sep_' === substr($rg->key, 0, 4) ) $rg->type = 'sep';
			if( ! $rg->type )                       $rg->type = 'text';

			$var->meta_key = $this->_key_prefix() . $rg->key;

			// поле отключено
			if( $rg->disable_func && is_callable($rg->disable_func) && call_user_func( $rg->disable_func, $post, $var->meta_key ) )
				return null;

			// meta_val
			$rg->val = get_post_meta( $post->ID, $var->meta_key, true ) ?: $rg->val;
			if( $rg->output_func && is_callable($rg->output_func) )
				$rg->val = call_user_func( $rg->output_func, $post, $var->meta_key, $rg->val );

			$var->name = $this->id . "_meta[$var->meta_key]";

			$rg->id  = $rg->id ?: ( $this->opt->id .'_'. $rg->key );

			// при табличной теме, td заголовка должен выводиться всегда!
			if( false !== strpos($rg->title_patt, '<td ') )
				$var->title = sprintf( $rg->title_patt, $rg->title ) . ($rg->title ? ' ' : '');
			else
				$var->title = $rg->title ? sprintf( $rg->title_patt, $rg->title ) .' ' : '';

			$rg->options = (array) $rg->options;

			$var->pholder = $rg->placeholder ? ' placeholder="'. esc_attr($rg->placeholder) .'"' : '';
			$var->class = $rg->class ? ' class="'. $rg->class .'"' : '';

			$fn__desc = function() use ( $rg, $post, $var ){
				if( ! $rg->desc ) return '';
				$desc = is_callable( $rg->desc ) ? call_user_func_array($rg->desc, [ $post, $var->meta_key, $rg->val, $var->name ] ) : $rg->desc;
				return sprintf( $rg->desc_patt, $desc );
			};

			$fn__field = function( $field ) use ( $rg ){
				return sprintf( $rg->field_patt, $field );
			};

			// произвольная функция
			if( is_callable( $rg->callback ) )
				$out = $var->title . $fn__field( call_user_func_array( $rg->callback, [ $args, $post, $var->name, $rg->val, $rg, $var ] ) );
			// произвольный метод
			// вызов метода `$this->field__{FIELD}()` (для возможности расширить этот класс)
			elseif( method_exists( $this, "field__$rg->type") )
				$out = $this->{"field__$rg->type"}( $rg, $var, $post, $fn__desc, $fn__field );
			// text, email, number, url, tel, color, password, date, month, week, range
			else
				$out = $this->field__default( $rg, $var, $post, $fn__desc, $fn__field );

			return $out;
		}

		// textarea
		function field__textarea( $rg, $var, $post, $fn__desc, $fn__field ){
			$_style = (false === strpos($rg->attr,'style=')) ? ' style="width:98%;"' : '';

			return $var->title . $fn__field('<textarea '. $rg->attr . $var->class . $var->pholder . $_style .'  id="'. $rg->id .'" name="'. $var->name .'">'. esc_textarea($rg->val) .'</textarea>'. $fn__desc() );
		}

		// select
		function field__select( $rg, $var, $post, $fn__desc, $fn__field ){
			$is_assoc = ( array_keys($rg->options) !== range(0, count($rg->options) - 1) ); // ассоциативный или нет?
			$_options = array();
			foreach( $rg->options as $v => $l ){
				$_val       = $is_assoc ? $v : $l;
				$_options[] = '<option value="'. esc_attr($_val) .'" '. selected($rg->val, $_val, 0) .'>'. $l .'</option>';
			}

			return $var->title . $fn__field('<select '. $rg->attr . $var->class .' id="'. $rg->id .'" name="'. $var->name .'">' . implode("\n", $_options ) . '</select>' . $fn__desc() );
		}

		// radio
		function field__radio( $rg, $var, $post, $fn__desc, $fn__field ){
			$radios = array();
			foreach( $rg->options as $v => $l )
				$radios[] = '<label '. $rg->attr . $var->class .'><input type="radio" name="'. $var->name .'" value="'. $v .'" '. checked($rg->val, $v, 0) .'>'. $l .'</label> ';

			return $var->title . $fn__field('<span class="radios">'. implode("\n", $radios ) .'</span>'. $fn__desc() );
		}

		// checkbox
		function field__checkbox( $rg, $var, $post, $fn__desc, $fn__field ){
			return $var->title . $fn__field('
				<label '. $rg->attr . $var->class .'>
					<input type="hidden" name="'. $var->name .'" value="">
					<input type="checkbox" id="'. $rg->id .'" name="'. $var->name .'" value="'. esc_attr(reset($rg->options) ?: 1) .'" '. checked( $rg->val, (reset($rg->options) ?: 1), 0) .'>
					'.( $rg->desc ?: '' ).'
				</label>');
		}

		// sep
		function field__sep( $rg, $var, $post, $fn__desc, $fn__field ){
			$_style = 'font-weight:600; ';
			if( preg_match( '/style="([^"]+)"/', $rg->attr, $mm ) ) $_style .= $mm[1];

			if( false !== strpos( $rg->field_patt, '<td' ) )
				return str_replace( '<td ', '<td colspan="2" style="padding:1em .5em; '. $_style .'"', $fn__field( $rg->title ) );
			else
				return '<span style="display:block; padding:1em 0; font-size:110%; '. $_style .'">'. $rg->title .'</span>';
		}

		// hidden
		function field__hidden( $rg, $var, $post, $fn__desc, $fn__field ){
			return '<input type="'. $rg->type .'" id="'. $rg->id .'" name="'. $var->name .'" value="'. esc_attr($rg->val) .'" title="'. esc_attr($rg->title) .'">';
		}

		// text, email, number, url, tel, color, password, date, month, week, range
		function field__default( $rg, $var, $post, $fn__desc, $fn__field ){
			$_style   = ( $rg->type === 'text' && false === strpos($rg->attr, 'style=') ) ? ' style="width:100%;"' : '';

			return $var->title . $fn__field( '<input '. $rg->attr . $var->class  . $var->pholder . $_style .' type="'. $rg->type .'" id="'. $rg->id .'" name="'. $var->name .'" value="'. esc_attr($rg->val) .'">'. $fn__desc() );
		}

		// wp_editor
		function field__wp_editor( $rg, $var, $post, $fn__desc, $fn__field ){
			$ed_args = array_merge( [
				'textarea_name'    => $var->name, //нужно указывать!
				'editor_class'     => $rg->class,
				// изменяемое
				'wpautop'          => 1,
				'textarea_rows'    => 5,
				'tabindex'         => null,
				'editor_css'       => '',
				'teeny'            => 0,
				'dfw'              => 0,
				'tinymce'          => 1,
				'quicktags'        => 1,
				'media_buttons'    => false,
				'drag_drop_upload' => false,
			], $rg->options );

			ob_start();
			wp_editor( $rg->val, $rg->id, $ed_args );
			$wp_editor = ob_get_clean();

			return $var->title . $fn__field( $wp_editor . $fn__desc() );
		}

		// image
		function field__image( $rg, $var, $post, $fn__desc, $fn__field ){

			wp_enqueue_media();

			static $once;
			if( ! $once && $once = 1 ){
				add_action( 'admin_print_footer_scripts', function(){
					?>
					<script>
					$('.kmb_img_wrap').each(function(){

						var frame,
							$wrap = $(this),
							$img   = $wrap.find('img'),
							$input = $wrap.find('input[type="hidden"]');

						$wrap.on( 'click', '.set_img', function(){

							var post_id = $(this).data('post_id') || null

							//if( frame && frame.post_id === post_id ){
							//	frame.open();
							//	return;
							//}

							frame = wp.media.frames.kmbframe = wp.media({
								title   : '<?= __( 'Add Media' ) ?>',
								// Library WordPress query arguments.
								library : {
									type       : 'image',
									uploadedTo : post_id
								},
								multiple: false,
								button: {
									text: '<?= __( 'Apply' ) ?>'
								}
							});

							frame.on( 'select', function() {
								attachment = frame.state().get('selection').first().toJSON();
								$img.attr( 'src', attachment.url );

								$wrap.data('usetype') === 'url' ? $input.val( attachment.url ) : $input.val( attachment.id );
							});

							frame.on( 'open', function(){
								if( $input.val() )
									frame.state().get('selection').add( wp.media.attachment( $input.val() ) );
							});

							frame.open();
							//frame.post_id = post_id // save
						});

						$wrap.on( 'click', '.del_img', function(){
							$img.attr( 'src', '' );
							$input.val('');
						});
					})
					</script>
					<?php
				}, 99 );
			}

			$usetype = $rg->options ? $rg->options[0] : 'id'; // может быть: id, url

			if( ! $src = is_numeric($rg->val) ? wp_get_attachment_url( $rg->val ) : $rg->val )
				$src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';

			ob_start();
			?>
			<span class="kmb_img_wrap" data-usetype="<?= esc_attr($usetype) ?>" style="display:flex; align-items:center;">
				<img src="<?= esc_url($src) ?>" style="max-height:100px; max-width:100px; margin-right:1em;" alt="">
				<span>
					<input class="set_img button button-small" type="button" value="<?= __('Set image') ?>" />
					<input class="set_img button button-small" type="button" data-post_id="<?= $post->ID ?>" value="<?= __( 'Uploaded to this post' ) ?>" />
					<input class="del_img button button-small" type="button" value="<?= __('Remove')?>" />

					<input type="hidden" name="<?= $var->name ?>" value="<?= esc_attr($rg->val) ?>">
				</span>
			</span>
			<?php
			$field = ob_get_clean();

			return $var->title . $fn__field( $field );
		}

		/**
		 * Сохраняем данные, при сохранении поста
		 * @param  integer $post_id ID записи
		 * @return boolean  false если проверка не пройдена
		 */
		function meta_box_save( $post_id, $post ){

			if(	! ( $save_metadata = @ $_POST["{$this->id}_meta"] )                                        // нет данных
			       || ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE  )                                           // выходим, если это автосохр.
			       || ! wp_verify_nonce( $_POST['_wpnonce'], 'update-post_'. $post_id )                          // nonce проверка
			       || ( $this->opt->post_type && ! in_array( $post->post_type, (array) $this->opt->post_type ) ) // не подходящий тип записи
			)
				return null;

			// оставим только поля текущего класса (защиты от подмены поля)
			$_key_prefix = $this->_key_prefix();
			$fields_data = array();
			foreach( $this->opt->fields as $_key => $rg ){
				$meta_key = $_key_prefix . $_key;

				// недостаточно прав
				if( !empty($rg['cap']) && ! current_user_can( $rg['cap'] ) )
					continue;

				// пропускаем отключенные поля
				if( !empty($rg['disable_func']) && is_callable($rg['disable_func']) && call_user_func( $rg['disable_func'], $post, $meta_key ) )
					continue;

				$fields_data[ $meta_key ] = $rg;
			}
			$fields_names  = array_keys( $fields_data );
			$save_metadata = array_intersect_key( $save_metadata, array_flip($fields_names) );


			// Очистка
			if( 'sanitize' ){
				// своя функция очистки
				if( is_callable($this->opt->save_sanitize) ){
					$save_metadata = call_user_func_array( $this->opt->save_sanitize, [ $save_metadata, $post_id, $fields_data ] );
					$sanitized = true;
				}
				// хук очистки
				if( has_filter("kpmb_save_sanitize_{$this->opt->id}") ){
					$save_metadata = apply_filters("kpmb_save_sanitize_{$this->opt->id}", $save_metadata, $post_id, $fields_data );
					$sanitized = true;
				}
				// если нет функции и хука очистки, то чистим все поля с помощью wp_kses() или sanitize_text_field()
				if( empty($sanitized) ){

					foreach( $save_metadata as $meta_key => & $value ){
						// есть функция очистки отдельного поля
						if( !empty($fields_data[$meta_key]['sanitize_func']) && is_callable($fields_data[$meta_key]['sanitize_func']) ){
							$value = call_user_func( $fields_data[$meta_key]['sanitize_func'], $value );
						}
						// не чистим
						elseif( @ $fields_data[$meta_key]['sanitize_func'] === 'none' ){}
						// не чистим - видимо это произвольная функция вывода полей, которая сохраняет массив
						elseif( is_array($value) ){}
						// нет функции очистки отдельного поля
						else {

							$type = !empty($fields_data[$meta_key]['type']) ? $fields_data[$meta_key]['type'] : 'text';

							if(0){}
							elseif( $type === 'number' )
								$value = floatval( $value );
							elseif( $type === 'email' )
								$value = sanitize_email( $value );
							// wp_editor, textarea
							elseif( in_array( $type, [ 'wp_editor','textarea' ], true ) )
								$value = addslashes( wp_kses( stripslashes( $value ), 'post' ) ); // default ?
							// text, radio, checkbox, color, date, month, tel, time, url
							else
								$value = sanitize_text_field( $value );
						}
					}
					unset($value); // $value используется ниже, поэтому он должен быть пустой, а не ссылкой...

				}
			}

			// Сохраняем
			foreach( $save_metadata as $meta_key => $value ){
				// если есть функция сохранения
				if( !empty($fields_data[$meta_key]['update_func']) && is_callable($fields_data[$meta_key]['update_func']) ){
					call_user_func( $fields_data[$meta_key]['update_func'], $post, $meta_key, $value );
				}
				else {
					// удаляем поле, если значение пустое. 0 остается...
					if( ! $value && ($value !== '0') )
						delete_post_meta( $post_id, $meta_key );
					else
						update_post_meta( $post_id, $meta_key, $value ); // add_post_meta() работает автоматически
				}
			}
		}

		function _postbox_classes_add( $classes ){
			$classes[] = "kama_meta_box_{$this->opt->id}";
			return $classes;
		}

		function _key_prefix(){
			return ($this->opt->id{0} == '_') ? '' : $this->opt->id .'_';
		}

	}

endif;


меню

Передаваемые параметры

Прежде чем переходить к остальным примерам, рассмотрим все параметры, которые понимает класс:

new Kama_Post_Meta_Box( array(

	'id'         => '',       // динетификатор блока. Используется как префикс для названия метаполя.
	// начните идент. с '_': '_foo', чтобы ID не был префиксом в названии метаполей.

	'title'      => '',       // заголовок блока
	'desc'       => '',       // описание для метабокса. Можно указать функцию/замыкание, она получит $post. С версии 1.9.1
	'post_type'  => '',       // строка/массив. Типы записей для которых добавляется блок: array('post','page').
	// По умолчанию: '' - для всех типов записей.
	'post_type_feature' => '', // строка. возможность которая должна быть у типа записи,
	// чтобы метабокс отобразился. См. post_type_supports()
	'post_type_options' => '', // массив. опции типа записи, которые должны быть у типа записи чтобы метабокс отобразился.
	// см. первый параметр get_post_types()

	'priority'   => 'high',   // Приоритет блока для показа выше или ниже остальных блоков ('high' или 'low').
	'context'    => 'normal', // Место где должен показываться блок ('normal', 'advanced' или 'side').

	'disable_func'  => '',    // функция отключения метабокса во время вызова самого метабокса.
	// Если вернет что-либо кроме false/null/0/array(), то метабокс будет отключен. Передает объект поста.

	'cap'           => '',    // название права пользователя, чтобы показывать метабокс.

	'save_sanitize' => '',    // Функция очистки сохраняемых в БД полей. Получает 2 параметра: $metas - все поля для очистки и $post_id

	'theme' => 'table',       // тема оформления: 'table', 'line'.
	// ИЛИ массив паттернов полей: css, fields_wrap, field_wrap, title_patt, field_patt, desc_patt.
	// Массив указывается так: [ 'desc_patt' => '<div>%s</div>' ] (за овнову будет взята тема line)
	// Массив указывается так: [ 'table' => [ 'desc_patt' => '<div>%s</div>' ] ] (за овнову будет взята тема table)
	// ИЛИ изменить тему можно через фильтр 'kp_metabox_theme' - удобен для общего изменения темы для всех метабоксов.

	// Массив метаполей, которые будут выводиться
	'fields'     => array(

		// Каждое поле указывается в виде массива, где ключ - это название метаполя, а значение - это массив данных о поле
		// реальное название метаполя будет выглядеть как: {id}_{meta_key}
		'meta_key' => array(

			'type'          => '', // тип поля: textarea, select, checkbox, radio, image, wp_editor, hidden, sep_*.
			// Или базовые: text, email, number, url, tel, color, password, date, month, week, range.
			// 'sep' - визуальный разделитель, для него нужно указать `title` и можно указать `'attr'=>'style="свои стили"'`.
			// 'sep' - чтобы удобнее указывать тип 'sep' начните ключ поля с `sep_`: 'sep_1' => [ 'title'=>'Разделитель' ].
			// Для типа `image` можно указать тип сохраняемого значения в `options`: 'options'=>'url'. По умолчанию тип = id.
			// По умолчанию 'text'.

			'title'         => '', // заголовок метаполя
			'desc'          => '', // описание для поля. Можно указать функцию/замыкание, она получит параметры: $post, $meta_key, $val, $name.
			'placeholder'   => '', // атрибут placeholder
			'id'            => '', // атрибут id. По умолчанию: $this->opt->id .'_'. $key
			'class'         => '', // атрибут class: добавляется в input, textarea, select. Для checkbox, radio в оборачивающий label
			'attr'          => '', // любая строка, будет расположена внутри тега. Для создания атрибутов. Пр: style="width:100%;"
			'val'           => '', // значение по умолчанию, если нет сохраненного.
			'options'       => '', // массив: array('значение'=>'название') - варианты для типов 'select', 'radio'.
			// Для 'wp_editor' стенет аргументами.
			// Для 'checkbox' станет значением атрибута value: <input type="checkbox" value="{options}">.
			// Для 'image' определяет тип сохраняемого в метаполе значения: id (ID вложения), url (url вложения).

			'callback'      => '', // название функции, которая отвечает за вывод поля.
			// если указана, то ни один параметр не учитывается и за вывод полностью отвечает указанная функция.
			// Все параметры передаются ей... Получит параметры: $args, $post, $name, $val

			'sanitize_func' => '', // функция очистки данных при сохранении - название функции или Closure.
			// Укажите 'none', чтобы не очищать данные...
			// работает, только если не установлен глобальный параметр 'save_sanitize'...
			// получит параметр $value - сохраняемое значение поля.

			'output_func'   => '', // функция обработки значения, перед выводом в поле.
			// получит параметры: $post, $meta_key, $value - объект записи, ключ, значение метаполей.

			'update_func'   => '', // функция сохранения значения в метаполя.
			// получит параметры: $post, $meta_key, $value - объект записи, ключ, значение метаполей.

			'disable_func'  => '', // функция отключения поля.
			// Если не false/null/0/array() - что-либо вернет, то поле не будет выведено.
			// Получает парамтры: $post, $meta_key

			'cap'           => '', // название права пользователя, чтобы видеть и изменять поле.

		),
		'meta_key2' => array( /*...*/ ),
		'meta_key3' => array( /*...*/ ),
		// ...
	),
) );
меню

Заметки

Названия произвольных полей

Создаваемые произвольные поля будут иметь название/ключ, состоящий из объединения основного ID и указанного ключа метаполя: {id}_{meta_key} (смотрите пример ниже).

Самый просто способ узнать название произвольного поля — это посмотреть в исходный код. Для этого фокусируемся на нужном поле, кликаем правой кнопкой и смотрим значение атрибута name в исходном коде элемента:

Как узнать название метаполя

Получение произвольных полей

Получать созданные поля для использования в темах и плагинах нужно стандартной функцией WordPress: get_post_meta():

// получим значение поля 'my_meta_key' у записи 25
$my_filed = get_post_meta( 25, 'my_meta_key', 1 );
echo $my_filed;

Права

  • Блок/метабокс будет показан только пользователям, у которых есть право редактировать текущую запись.

  • Сохранение метаполей будет работать только для пользователей, которые могут редактировать текущую запись.
меню

Примеры создания различных произвольных полей

#1 Демонстрация создания всех видов метаполей

Этот пример показывает как создавать все поддерживаемые типы метаполей: 'text', 'textarea', 'select', 'checkbox', 'radio', 'wp_editor', 'hidden' и другие: 'email', 'number', 'phone', 'password' и т.д. (обрабатываются как поле 'text').

class_exists('Kama_Post_Meta_Box') && new Kama_Post_Meta_Box(
	array(
		'id'         => 'my',
		'title'      => 'Мои произвольные поля',
		'fields'     => array(
			'text_field'      => array(
				'title' => 'Текстовое поле'
			),
			'number_field'    => array(
				'type'=>'number', 'title'=>'Числовое поле', 'desc'=>'Число от 0 до 5', 'attr'=>'min="0" max="5"'
			),
			'textarea_field'  => array(
				'type'=>'textarea', 'title'=>'Большое текстовое поле', 'desc'=>'Описание чего-либо. Можно использовать html теги.'
			),
			'select_field'    => array(
				'type'=>'select', 'title'=>'Выберите значение', 'options'=>array(''=>'Ничего не выбрано', 'val_1'=>'Выбор 1', 'val_2'=>'Выбор 2') 
			),
			'select_field2'   => array(
				'type'=>'select', 'title'=>'Выберите значение 2', 'options'=>array('Выбор 1', 'Выбор 2'), 'desc'=>'Выбор, где не указывается значение value для тегов option'
			),
			'checkbox_field'  => array(
				'type'=>'checkbox', 'title'=>'Галочка', 'desc'=>'отметьте, если хотите :)' 
			),
			'checkbox_field2' => array(
				'type'=>'checkbox', 'desc'=>'< только описание для галочки, без заголовка' 
			),
			'radio_field'     => array(
				'type'=>'radio', 'title'=>'Переключатель', 'desc'=>'Выберите одно из значений', 'options'=>array(''=>'Ничего не выбрано', 'good'=>'хорошо', 'bad'=>'плохо') 
			),
			'radio_field2'    => array(
				'type'=>'radio', 'desc'=>'Переключатель без заголовка', 'options'=>array(''=>'Не выбрано', 'good'=>'хорошо', 'bad'=>'плохо') 
			),
			'wp_editor_field' => array(
				'type'=>'wp_editor', 'title'=>'Текстовое поле с редактором TinyMce'
			),
			'wp_editor_field2' => array(
				'type'=>'wp_editor', 'title'=>'Текстовое поле с редактором WordPress, без TinyMce', 'options'=>array('tinymce'=>0) // список настроек: http://wp-kama.ru/function/wp_editor
			),
			'hidden_field' => array(
				'type'=>'hidden', 'val'=>'foo'
			),
			'' => array('type'=>'text', 'title'=>''), // заготовка
		),
	)
);

В результате, появиться такой метаблок на странице редактирования любого типа записи.

post-meta-fields

А при сохранении будут созданы, такие произвольные поля:

post-meta-fields2

меню

#2 Блоки для указанных типов записей

Когда нужно создать метабокс с метаполями только для указанных типов записи, укажите параметр 'post_type' => array('post','page') в котором перечислите нужные типы записей.

class_exists('Kama_Post_Meta_Box') && new Kama_Post_Meta_Box(
	array(
		'id'         => 'my',
		'title'      => 'Мои произвольные поля',
		'post_type'  => array('page', 'my_type'), // показывать только на страницах типа: page и my_type
		'fields'     => array(
			'text_field' => array( 'title' => 'Текстовое поле' ),
		),
	)
);

#3 Своя функция вывода поля

Когда возможностей класса не достаточно и нужно создать поле, которое будет иметь какой-то особенный вывод, используйте параметр callback для создаваемого поля.

В этом случае можно настроить вывод поля как угодно. Для примера давайте создадим несколько полей, которые будут храниться в поле special_field в виде массива.

<?php
class_exists('Kama_Post_Meta_Box') && new Kama_Post_Meta_Box(
	array(
		'id'         => 'my',
		'title'      => 'Мои произвольные поля',
		'fields'     => array(
			'special_field' => array(
				'title'=>'Текстовое поле', 'callback'=>'special_field_out_function',
				// функция очистки полей
				'sanitize_func'=>function($array){ return array_map('sanitize_text_field', $array); }
			),
		),
	)
);

// функция вывода для поля
function special_field_out_function( $args, $post, $name, $val ){
	/*
		$args = Array
		(
			[type] => 
			[title] => Текстовое поле
			[desc] => 
			[placeholder] => 
			[id] => 
			[class] => 
			[attr] => 
			[val] => 
			[options] => 
			[callback] => special_field_out_function
			[key] => special_field
			[field_patt] => %s
			[title_patt] => <strong class="tit">%s</strong>
			[desc_patt] => <br><span class="description" style="opacity:0.6;">%s</span>

		)

		$post = Объект записи

		$name = 92a9f92_meta[my_special_field]

		$val = Текущее значение метаполя
	*/
	ob_start();
	?>
	<div class="special_field_wrap">
		Ящик 1: <input type="text" name="<?= $name ?>[box1]" value="<?= esc_attr( @ $val['box1'] ) ?>">
		Ящик 2: <input type="text" name="<?= $name ?>[box2]" value="<?= esc_attr( @ $val['box2'] ) ?>">
		Ящик 3: <input type="text" name="<?= $name ?>[box3]" value="<?= esc_attr( @ $val['box3'] ) ?>">
	</div>
	<?php
	return ob_get_clean();
}

В результате, получим такой метабокс:

Произвольный вывод метаполя

Все данные будут храниться в поле my_special_field в виде массива array( 'box1'=>'яблоки', 'box1'=>'апельсины', 'box1'=>'груши' ).

меню

#4 Точные названия полей без префикса

Когда нужно, чтобы названия метаполей были точно такие же какие вы указали, добавьте в начало id нижнее подчеркивание _.

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

Например, у вас уже есть метаполя: foo, bar, views, title и для них нужно создать метабокс:

class_exists('Kama_Post_Meta_Box') && new Kama_Post_Meta_Box(
	array(
		'id'         => '_my', // "_" - значит, что id не будет добавляться в название поля
		'title'      => 'Мои точные произвольные поля',
		'fields'     => array(
			'foo' => array( 'title' => 'Поле foo' ),
			'bar' => array( 'title' => 'Поле bar' ),
			'views' => array( 'title' => 'Поле views' ),
			'title' => array( 'title' => 'Поле title' ),
		),
	)
);

Получим такой метабокс:

Точные произвольные поля

меню

#5 Очистка значений перед сохранением

Класс автоматически очищает все поля и защищает от XSS атак. Но иногда может быть нужно, очистить определенное поле как-то особенно. В этом случае укажите навзание функции очистки в параметре 'save_sanitize' или используйте фильтр "kpmb_save_sanitize_{id}". Если указана функция или хук очистки, то класс никак не очищает сохраняемые данные, очистка всех полей должна быть в вашей функции очистки.

Допустим, мы создаем поле в котором все символы должны быть прописными, а если они указаны как строчные, то автоматом преобразуем их в прописные и сохраним:

class_exists('Kama_Post_Meta_Box') && new Kama_Post_Meta_Box(
	array(
		'id'     => 'my',
		'title'  => 'Мои произвольные поля',
		'save_sanitize' => 'my_metabox_sanitize_function',
		'fields' => array(
			'foo_field' => array( 'title'=>'Некое поле' ),
			'for_esc' => array( 'title'=>'Специальное поле', 'desc'=>'Поле которое должно содержать только прописные символы.' ),
		),
	)
);

// функция очистки всех полей
function my_metabox_sanitize_function( $metas, $post_id ){
	/*
		$metas = сохраняемые поля в массиве
		$post = ID записи
	*/

	// очищаем нужное поле
	foreach( $metas as $key => & $val ){
		// наше поле
		if( $key == 'my_for_esc' )
			$val = mb_strtoupper( $val );
		// все остальные поля
		else
			$val = sanitize_text_field( $val );
	}

	return $metas;
}

В результате получим:

post-meta-fields7

Еще один пример функции очистки

Такой подход используется в классе, если не указать функцию очистки. Он очищает с помощью wp_kses() и обрабатывает массив и все вложенные массивы.

function save_sanitize_function( $metas, $post_id ){
	array_walk_recursive( $metas, function( & $val, $key ){
		// очистка отдельного поля my_video - разрешим оставить в нем тег iframe
		if( $key == 'my_video' ){
			$val = addslashes( wp_kses( stripslashes( $val ), array(
				'iframe' => array( 'src' => true, 'width' => true, 'height' => true, 'frameborder' => true, 'marginwidth' => true, 'marginheight' => true, 'scrolling' => true, 'title' => true, ),
			) ) );
		}
		// has tags
		elseif( false !== strpos($val, '<') )
			$val = addslashes( wp_kses( stripslashes( $val ), 'post' ) ); // default ?
		else
			$val = sanitize_text_field( $val );
	} );

	return $metas;
}
меню

#6 Темы оформления: настройка html и css каждого поля

Можно указать как должны выводиться поля в метабоксе. Для этого есть параметр theme - тема оформления.

В этом параметре можно указать строку или массив:

  • table (по умолчанию) - поля будут выведены в табличной форме.
  • line - поля будут выведены линиями. Во всю ширину метабокса, где идет заголовок поля, а под ним само поле.
  • array() - указывая массив, вы можете сами определить все виды оборачивающих тегов для каждого элемента поля. В массиве можно указать следующие параметры:

    • css — CSS стили метабокса. Например: '.my_field{ margin:1em; }'
    • fields_wrap — формат обёртки всего метаблока (всех полей)
    • field_wrap — формат обёртки поля (вместе с заголовком и полем)
    • title_patt — формат обёртки заголовка поля
    • field_patt — формат обёртки самого поля (только input...)
    • desc_patt — формат обёртки описания поля
class_exists('Kama_Post_Meta_Box') && new Kama_Post_Meta_Box(
	array(
		'id'         => 'my',
		'title'      => 'Мои произвольные поля',

		'theme' => array(
			'css'        => '.my_field_wrap{ margin-bottom:1em; } .my_field_desc{ opacity:0.5; } .my_field_tit{ font-weight:bold; margin-bottom:.3em; }',
			'fields_wrap' => '%s',
			'field_wrap' => '<div class="my_field_wrap %1$s">%2$s</div>', // '%2$s' будет заменено на html поля
			'title_patt' => '<div class="my_field_tit">%s</div>', // '%s' будет заменено на заголовок
			'field_patt'  => '%s',
			'desc_patt'  => '<span class="my_field_desc"> %s</span>', // '%s' будет заменено на текст описания
		),

		'fields'     => array(
			'text_field'      => array(
				'title' => 'Текстовое поле'
			),
			'number_field'    => array(
				'type'=>'number', 'title'=>'Числовое поле', 'desc'=>'Число от 0 до 5', 'attr'=>'min="0" max="5"'
			),
			'textarea_field'  => array(
				'type'=>'textarea', 'title'=>'Большое текстовое поле', 'desc'=>'Описание чего-либо. Можно использовать html теги.'
			),
			'select_field'    => array(
				'type'=>'select', 'title'=>'Выберите значение', 'options'=>array(''=>'Ничего не выбрано', 'val_1'=>'Выбор 1', 'val_2'=>'Выбор 2') 
			),
			'checkbox_field'  => array(
				'type'=>'checkbox', 'title'=>'Галочка', 'desc'=>'отметьте, если хотите :)' 
			),
			'radio_field'     => array(
				'type'=>'radio', 'title'=>'Переключатель', 'desc'=>'Выберите одно из значений', 'options'=>array(''=>'Ничего не выбрано', 'good'=>'хорошо', 'bad'=>'плохо') 
			),
			'wp_editor_field2' => array(
				'type'=>'wp_editor', 'title'=>'Текстовое поле с редактором WordPress, без TinyMce', 'options'=>array('tinymce'=>0) // список настроек: http://wp-kama.ru/function/wp_editor
			),
			'' => array('type'=>'text', 'title'=>''), // заготовка
		),
	)
);

В результате получим такой метабокс:

post-meta-fields8

меню

#7 Блоки с одинаковыми id

Класс позволяет создавать два и более разных блока с одинаковыми id. Следующий пример создаст 2 разных блока метаполя которых будут иметь одинаковый префикс: my_:

if( class_exists('Kama_Post_Meta_Box') ){
	new Kama_Post_Meta_Box( array(
		'id'         => 'my',
		'title'      => 'Мои произвольные поля',
		'fields'     => array(
			'text_field' => array( 'title' => 'Текстовое поле' ), // название поля: my_text_field
		),
	) );
	new Kama_Post_Meta_Box( array(
		'id'         => 'my',
		'title'      => 'Мои произвольные поля 2',
		'fields'     => array(
			'foo_field' => array( 'title' => 'Текстовое поле' ), // название поля: my_foo_field
		),
	) );
}

#8 Отключение метабокса по условию

Иногда нужно включить метабокс для одной рубрики, но не включать для другой. Для таких "поздних" проверок отключения метабокса, есть параметр: disable_func.

В этот параметр нужно передать название функции, которая будет срабатывать перед выводом метабокса и отключать его, если срабатывает описанное в функции условие. Также, вместо названия функции, можно передать анонимную функцию (замыкание). Далее, если указанная функция, что-нибудь возвращает, то метабокс будет отключен.

А теперь примеры.

меню

#1. Метабокс записи для указанной рубрики

Допустим нам нужно вывести метабокс, только если запись находится в рубрике с ID = 2, т.е. его нужно скрыть для всех рубрик кроме 2:

new Kama_Post_Meta_Box( array(
	'id'        => 'mybox',
	'title'     => 'Виден для рубрики 2',
	'post_type' => 'post',
	'disable_func' => function($post){
		if( ! in_category(2, $post) ) return 'отключить';
	},
	'fields'    => array(
		'fname' => array( 'type'=>'text', 'title'=>'Поле-поле' ),
	),
) );

#2. Метабокс записи для всех рубрик кроме указанной

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

new Kama_Post_Meta_Box( array(
	'id'        => 'mybox',
	'title'     => 'Виден для всех рубрик, кроме 2',
	'post_type' => 'post',
	'disable_func' => function($post){
		if( in_category(2, $post) ) return 'отключить';
	},
	'fields'    => array(
		'fname' => array( 'type'=>'text', 'title'=>'Поле-поле' ),
	),
) );

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

меню

Опрос

меню

Как вы создаете произвольные поля?

  • Добавить ответ
меню

Плагины

  1. Крутой плагин для создания метаполей: Meta Box

  2. Хороший плагин для создания метаполей: Custom Field Suite

  3. Еще один хороший, но огромный: Advanced Custom Fields
67 комментов
Полезные 6 Все
  • petrozavodsky785 cайт: alkoweb.ru

    вот это уже прикольная штука , плюсую

    Ответить3.3 года назад #
  • Petrovodsky cайт: alkoweb.ru

    можно я форкну эту штуку ?

    Ответить3.2 года назад #
  • petrozavodsky785 cайт: alkoweb.ru

    да конечно сделаю как в GPL завещали

    Ответить3.2 года назад #
  • Анжелика cайт: club-animal.ru

    Вау, очень интересная, нужная статья, обязательно беру на заметку, надо будет на досуге сделать. Прочитала, думаю вроде поняла, как начну делать думаю там уж разберусь наверняка smile

    Ответить3.2 года назад #
  • WP_Panda158 cайт: wp-panda.com

    Гуд, но есть куда расти.

    1. Добавить параметр repeat - для динамического добавления полей
    2. По скольку теперь есть term_meta, стоит добавить выбор для чего создавать поля, для терминов или для постов.
    3. метод field разделить на 2, сам проход по $type, сделать отдельным методом, обозвав его как нибудь, типа generate_fields
    4. Предыдущий пункт позволит легко внести, комбинированное поле, типа group с возможностью динамического добавления.

    И на сладкое, все упускают такой приятный параметр как суффикс, а он очень помогает для наглядности администрирования

    Ответить3.2 года назад #
    • Kama7527

      Спасибо за коммент,

      Добавить параметр repeat - для динамического добавления полей

      Не понял и как это будет работать?

      По скольку теперь есть term_meta

      Я думаю тут отдельный аналогичный класс нужен, впрочем он у меня уже есть, но я его не тестировал нормально, позже выложу наверное.

      метод field разделить на 2, сам проход

      Это не так то просто сделать, там придется все переменные передать... Что по сути является созданием почти такого же метода. А практической надобности я чет не понял, что значит комбинированные поля? В группы собирать их что ли и давать название группе? Если так, то может проще создать 2 метабокса разных? нет?

      И на сладкое, все упускают такой приятный параметр как суффикс

      При все уважении, вообще не вижу в нем смысла. Одни минусы: дополнительная настройка, которая будет только путаться, дополнительный код, который опять же будет путать и мешаться. Такую пометку можно написать например в title, как в WP и делается, например "Уставной капитал (тыс.Р)". Зачем усложнять, надо наоборот упрощать...

      Другой вариант, если скажем метабокс подразумевает все поля, где очень удобны будут именно суффексы, то в их качестве можно использовать desc изменив для него шаблон вывода в desc_patt

      Ответить3.2 года назад #
      • WP_Panda158 cайт: wp-panda.com

        Не понял и как это будет работать?

        Вот так

        Я думаю тут отдельный аналогичный класс нужен

        В том то и дело, что основа у них совершенно одинаковая,отличия не большие.
        Просто в метод рендера передаю где сейчас нахожусь, ориентируясь на get_current_screen()->base для понимания с чем работать.

        А практической надобности я чет не понял, что значит комбинированные поля? В группы собирать их что ли и давать название группе?

        Отвечу картинкой.

        При все уважении, вообще не вижу в нем смысла. Одни минусы:

        Это только так кажется, есть один огромный плюс, это сильно упрощает администрирование.

        Вот так может выглядеть кипа параметров текстинпут у меня)

        array(
        				'id'    => 'field_1',
        				'type'  => 'text',
        				'title' => 'Текстинпут',
        				'suff'  => 'SUFF',
        				'sub'   => 'Дополнительный заголовок поля',
        				'preff' => 'PREFF',
        				'desc'  => 'Дополнительное описание поля',
        				'help'  => 'Дополнительная текстовая всплывающая подсказка.',
        				'param' => array(
        					'width'    => 12,
        					'sortable' => true,
        					'repeat'   => true
        				)
        			),

        А вот так оно будет на экране

        И это я про placeholder забыл и default_val smile

        1
        Ответить3.2 года назад #
        • Артём

          А можно увидеть код как это всё реализовано. Это как раз то, что я ищу. Заранее спасибо smile

          Ответить2.7 года назад #
  • WPShop5 cайт: wpshop.ru

    Это великолепно smile

    Там в коде есть if(1){ - это с тестирования осталось? строку не скажу, поиском легко ищется.

    Про repeat - видимо имелось в виду, например, ингредиенты. Когда заранее неизвестно, сколько полей будет?

    Ответить3.2 года назад #
    • Kama7527

      Там в коде есть if(1){

      Нет, это просто для удобного чтения кода и сворачивания блока с IDE...
      Я это свой прием частенько использовать стал - удобно как-то.

      Ответить3.2 года назад #
  • lincaseidhe76 cайт: lincaseidhe.ru

    Кама, еще вопрос по данным... Если я сделаю вывод полей своей функцией, как у тебя в примере 3:

    <div class="special_field_wrap">
    		<h4><?= $args['title'] ?></h4>
    		Ящик 1: <input type="text" name="<?= $name ?>[box1]" value="<?= esc_attr( @ $val['box1'] ) ?>">
    		Ящик 2: <input type="text" name="<?= $name ?>[box2]" value="<?= esc_attr( @ $val['box2'] ) ?>">
    		Ящик 3: <input type="text" name="<?= $name ?>[box3]" value="<?= esc_attr( @ $val['box3'] ) ?>">
    	</div>

    Как данные в БД хранятся? (возвращаются массивом....)

    И если я после этого буду юзать WP_Query с поиском по произвольным полям?

    $args = array(
    	'meta_query' => array(
    		'relation' => 'OR',
    		array(
    			'key' => 'color',
    			'value' => 'blue'
    		),
    		array(
    			'key' => 'price',
    			'value' => 20
    		)
    	)
    );

    Будут ли учитываться отдельные значения? например сейчас у меня такой вывод(print_r):

     Array
    (
    	[box1] => Ю.Бегишевские
    	[box2] => Тобольск
    )
    

    Если я задам поиск:

    $args = array(
    	'meta_query' => array(
    		array(
    			'key' => 'my_key',
    			'value' => 'Тобольск'
    		),
    	)
    );

    он мне вернет запись?

    Ответить3.1 года назад #
    • Kama7527

      Нет, в БД значение в виде сериализованного массива храниться. Если нужно отдельно, то надо отдельные поля создавать. Если при этом нужна своя функция, то прописывай поля отдельно - для каждого поля отдельная функция вывода, если полей много, то можно взять за основу метод класса field и подправить его под себя.

      Правда я не понимаю, почему просто не использовать класс как есть, может проще просто там указать поля и не нужна отдельная функция?

      Ответить3.1 года назад #
      • lincaseidhe76 cайт: lincaseidhe.ru

        Дело в том, что на разные записи, будет разное количество полей. на одну запись 5, на вторую 20... вообще, в идеале, хотелось бы сделать, что-то наподобие добавления полей через кнопку +, как в комменте выше... но опять же, не охота тянуть какие-нибудь плагины сторонние, с кучей лишнего хлама, наподобие этого - https://metabox.io/docs/.... а самому пока знаний JS не хватает, чтоб реализовать..... ты там выше писал про свой класс, который у тебя есть нетестированый... еще не выложил?

        Ответить3.1 года назад #
        • lincaseidhe76 cайт: lincaseidhe.ru

          хотя наверное проще будет определить максимальное кол-во полей и не париться....

          Ответить3.1 года назад #
  • Евгений

    Здравствуйте, можете подсказать в чем может быть проблема. Подключил данный класс. Поля выводятся как нужно. Но при сохранении выдается ошибка:
    "Fatal error: Using $this when not in object context in ...\kama-post-meta-box.class.php on line 310"
    310 строка это: return $this->__key_prefix() . $key;

    1
    Ответить3.1 года назад #
    • Kama7527

      Версию 1.2 попробуйте. Спасибо за коммент там ошибка была thank_you

      Ответить3.1 года назад #
  • А как сделать поиск по этим мэта полям, как например по тэгам? например я формирую поисковый запрос таким образом /?s=&cat=1&m=2011 Что нужно дописать еще чтобы искало по полям?
    /?s=&cat=1&m=2011&moe_meta_pole=tralala

    Ответить3 года назад #
    • Kama7527

      Это через создание запросов с помощью WP_Query делается, смотри параметр meta_query

      Ответить3 года назад #
  • Сергей cайт: newwebmaster.ru

    Хороший класс, приятно видеть таких профи в WordPress. Но мне как то ближе пока работать с CMB2. На досуге побалуюсь и с вашим классом.

    1
    Ответить3 года назад #
Здравствуйте, !     Войти . Зарегистрироваться