WordPress как на ладони
Наставник Трепачёв Д.П., phphtml.net wordpress jino

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

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+
	 * ver: 1.9.0
	 */
	class Kama_Post_Meta_Box {

		public $opt;
		public $id;

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

		/**
		 * Конструктор
		 * @param array $opt Опции по которым будет строиться метаблок
		 */
		function __construct( $opt ){
			$defaults = array(
				'id'         => '',       // Идент. блока. Используется как префикс для названия метаполя.
										  // Укажите Идент. с начального '_': '_foo', чтобы ID не был префиксом в названии метаполей.
				'title'      => '',       // заголовок блока
				'post_type'  => '',       // строка/массив. Типы записей для которых добавляется блок: array('post','page'). По умолчанию: '' - для всех типов записей.
				'priority'   => 'high',   // Приоритет блока для показа выше или ниже остальных блоков ('high' или 'low').
				'context'    => 'normal', // Место где должен показываться блок ('normal', 'advanced' или 'side').

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

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

				'theme' => 'table',       // тема оформления. Может быть: 'table', 'line' или массив с данными полей (см. изменение опции '$this->opt->theme').
										  // изменить тему можно также через фильтр 'kpmb_theme'

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

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

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

				// тема 'line'
				if( $this->opt->theme === 'line' ){
					$this->opt->theme = array(
						'css'         => '',
						'fields_wrap' => '%s',
						'field_wrap'  => '<p class="%1$s">%2$s</p>',
						'title_patt'  => '<strong class="tit"><label>%s</label></strong>',
						'field_patt'  => '%s',
						'desc_patt'   => '<br><span class="description" style="opacity:0.6;">%s</span>',
					);
				}

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

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

			$this->disabled = false; // по умолчанию всегда включен

			// создадим уникальный 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', array( &$this, 'add_meta_box' ), 10, 2 );
			add_action('save_post', array( &$this, 'meta_box_save' ), 1, 2 );
		}

		function add_meta_box( $post_type, $post ){
			// maybe disable?
			if( in_array( $post_type, array('comment','link')) ) return;
			if( ! current_user_can( get_post_type_object( $post_type )->cap->edit_post, $post->ID ) ) return;

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

			// maybe disable meta_box
			if( $opt->disable_func && is_callable($opt->disable_func) )
				$this->disabled = call_user_func( $opt->disable_func, $post );
			if( $this->disabled ) 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, array( &$this, 'meta_box'), $p_type, $opt->context, $opt->priority );
			}
			else
				add_meta_box( $this->id, $opt->title, array( &$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}", array( &$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(array('<p ','</p>'), array('<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 ) );
			}

			echo ( $this->opt->css ? '<style>'. $this->opt->css .'</style>' : '' ) .
				$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 HTML код
		 */
		function field( $args, $post ){
			$def = array(
			'type'          => '', // тип поля: 'text', 'textarea', 'select', 'checkbox', 'radio',
								   // 'wp_editor', 'hidden' или другое (email, number). По умолчанию 'text'.
			'title'         => '', // заголовок метаполя
			'desc'          => '', // описание для поля
			'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.
			'callback'      => '', // название функции, которая отвечает за вывод поля.
								   // если указана, то ни один параметр не учитывается и за вывод полностью отвечает указанная функция.
								   // Все параметры передаются ей... Получит параметры:
								   // $args - все параметры указанные тут
								   // $post - объект WP_Post текущей записи
								   // $name - название атрибута 'name' (название полей собираются в массив)
								   // $val - атрибут 'value' текущего поля

			'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 - объект поста.

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

			$rg = (object) array_merge( $def, $args );

			// поле отключено для этой записи
			if( $rg->disable_func && is_callable($rg->disable_func) && call_user_func( $rg->disable_func, $post ) )
				return;

			if( ! $rg->type )
				$rg->type = 'text';

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

			$meta_val = get_post_meta( $post->ID, $meta_key, true );

			$rg->val = $meta_val ?: $rg->val;
			if( $rg->output_func && is_callable($rg->output_func) ){
				$rg->val = call_user_func( $rg->output_func, $post, $meta_key, $rg->val );
			}

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

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

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

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

			$_class = $rg->class ? ' class="'. $rg->class .'"' : '';

			$out = '';

			$fn__desc = function() use( $rg ){
				return $rg->desc ? sprintf( $rg->desc_patt, $rg->desc ) : '';
			};
			$fn__field = function($field) use( $rg ){
				return sprintf( $rg->field_patt, $field );
			};

			// произвольная функция...
			if( is_callable( $rg->callback ) ){
				$out .= $_title . $fn__field( call_user_func_array( $rg->callback, array( $args, $post, $name, $rg->val ) ) );
			}
			// wp_editor
			elseif( $rg->type == 'wp_editor' ){
				$ed_args = array_merge( array(
					'textarea_name'    => $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();

				$out .= $_title . $fn__field( $wp_editor . $fn__desc() );
			}
			// textarea
			elseif( $rg->type == 'textarea' ){
				$_style = (false === strpos($rg->attr,'style=')) ? ' style="width:98%;"' : '';
				$out .= $_title . $fn__field('<textarea '. $rg->attr . $_class . $pholder . $_style .'  id="'. $rg->id .'" name="'. $name .'">'. esc_textarea($rg->val) .'</textarea>'. $fn__desc() );
			}
			// select
			elseif( $rg->type == 'select' ){
				$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>';
				}

				$out .= $_title . $fn__field('<select '. $rg->attr . $_class .' id="'. $rg->id .'" name="'. $name .'">' . implode("\n", $_options ) . '</select>' . $fn__desc() );
			}
			// checkbox
			elseif( $rg->type == 'checkbox' ){
				$out .= $_title . $fn__field('
				<label '. $rg->attr . $_class .'>
					<input type="hidden" name="'. $name .'" value="">
					<input type="checkbox" id="'. $rg->id .'" name="'. $name .'" value="'. esc_attr(reset($rg->options) ?: 1) .'" '. checked( $rg->val, (reset($rg->options) ?: 1), 0) .'>
					'.( $rg->desc ?: '' ).'
				</label>');
			}
			// radio
			elseif( $rg->type == 'radio' ){
				$radios = array();
				foreach( $rg->options as $v => $l )
					$radios[] = '<label '. $rg->attr . $_class .'><input type="radio" name="'. $name .'" value="'. $v .'" '. checked($rg->val, $v, 0) .'>'. $l .'</label> ';

				$out .= $_title . $fn__field('<span class="radios">'. implode("\n", $radios ) .'</span>'. $fn__desc() );
			}
			// hidden
			elseif( $rg->type == 'hidden' ){
				$out .= '<input type="'. $rg->type .'" id="'. $rg->id .'" name="'. $name .'" value="'. esc_attr($rg->val) .'" title="'. esc_attr($rg->title) .'">';
			}
			// text
			else {
				$_style = ($rg->type==='text' && false===strpos($rg->attr,'style=')) ? ' style="width:100%;"' : '';

				$out .= $_title . $fn__field('<input '. $rg->attr . $_class  . $pholder . $_style .' type="'. $rg->type .'" id="'. $rg->id .'" name="'. $name .'" value="'. esc_attr($rg->val) .'">'. $fn__desc() );
			}

			return $out;
		}

		/**
		 * Сохраняем данные, при сохранении поста
		 * @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 ) ) || // не подходящий тип записи
				! current_user_can( get_post_type_object( $post->post_type )->cap->edit_post, $post_id )      // нет права редакт. запись
			)
				return;

			// Оставим только поля текущего класса (защиты от подмены поля)
			$__key_prefix = $this->__key_prefix();
			$fields_data  = array();
			foreach( $this->opt->fields as $_key => $_val ) $fields_data[ $__key_prefix . $_key ] = $_val;
			$fields_names  = array_keys($fields_data);
			$save_metadata = array_intersect_key( $save_metadata, array_flip($fields_names) );

			// Очистка
			if(1){
				// своя функция очистки
				if( is_callable($this->opt->save_sanitize) ){
					$save_metadata = call_user_func_array( $this->opt->save_sanitize, array( $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 );
							elseif( $type === 'password' ) $value = $value;
							// wp_editor, textarea
							elseif( in_array( $type, array('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 .'_';
		}

	}
}

/**
Changelog:

1.9.0 - Новые параметры полей: 'output_func', 'update_func' и 'disable_func'.
1.8.0 - Баг с выводом поля, когда используется своя функция...
	  - Параметр 'sanitize_func' для каждого поля. Чтобы можно было указать очистку отдельного поля.
	  - Доработана очистка полей, теперь она опирается на тип поля.
1.7.0 - ADD: if set field 'options' parametr it becomes checkbox 'value' attribute.
	  - remove extract() call in field() method
1.6.0 - FIX: closure remove bug
1.5.0 - ADD: theme support. New parametr 'theme'. where you can set 'table' or 'line' themes.
1.3.0 - ADD: 'disable_func' parametr - its allow to disable/enable metabox by any conditions inside edit post screen.
	  - FIX: field 'options' parametr for <select> element: now you can set numeric array keys as option value, ex: 'options' => array( 23=>'Name', 5=>'Name 2' )
	  - CHG: instance ID and instances save algorithm
*/
к началу

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

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

new Kama_Post_Meta_Box( array(
	'id'         => '',       // Идент. блока. Используется как префикс для названия метаполя.
							  // Укажите Идент. с начального '_': '_foo', чтобы ID не был префиксом в названии метаполей.
	'title'      => '',       // заголовок блока
	'post_type'  => '',       // строка/массив. Типы записей для которых добавляется блок: array('post','page'). По умолчанию: '' - для всех типов записей.
	'priority'   => 'high',   // Приоритет блока для показа выше или ниже остальных блоков ('high' или 'low').
	'context'    => 'normal', // Место где должен показываться блок ('normal', 'advanced' или 'side').

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

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

	'theme' => 'table',       // тема оформления. Может быть: 'table', 'line' или массив с данными полей (см. изменение опции '$this->opt->theme').
							  // изменить тему можно также через фильтр 'kpmb_theme'

	// Массив метаполей, которые будут выводиться
	'fields'     => array(
		// Каждое поле указывается в виде массива, где ключ - это название метаполя, а значение - это массив данных о поле
		// реальное название метаполя будет выглядеть как: {id}_{meta_key}
		'meta_key' => array(
			'type'          => '', // тип поля: 'text', 'textarea', 'select', 'checkbox', 'radio',
								   // 'wp_editor', 'hidden' или другое (email, number). По умолчанию 'text'.
			'title'         => '', // заголовок метаполя
			'desc'          => '', // описание для поля
			'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.
			'callback'      => '', // название функции, которая отвечает за вывод поля.
								   // если указана, то ни один параметр не учитывается и за вывод полностью отвечает указанная функция.
								   // Все параметры передаются ей... Получит параметры:
								   // $args - все параметры указанные тут
								   // $post - объект WP_Post текущей записи
								   // $name - название атрибута 'name' (название полей собираются в массив)
								   // $val - атрибут 'value' текущего поля

			'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_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
Kama_Post_Meta_Box: создаем метаполя для записей 43 комментария
Вопросы 4 Все
  • Алексей

    Добрый день, что то запутался с отключение метабокса по условию на страницах, как по по id понятно, а вот как по это сделать по слагу не пойму, с помощью is_page(), что то не получается

    Ответить2 месяца назад #
    • Алексей

      разобрался через, реализовал через get_post()

      Ответить2 месяца назад #

Здравствуйте, !

Ваш комментарий