WordPress как на ладони
WordCamp Saint Petersburg 2018 wordpress jino

Регистрация таксономии без привязки к типу записи

Как выяснилось, WordPress не позволяет быстро и просто создать таксономию так, чтобы не привязать её к какому-либо типу записи. Точнее зарегистрировать таксономию без привязки можно, только вот при переходе на страницу создания элементов этой таксономии мы неизбежно будем находится в пункте меню «Записи». А нам нужно создать свой отдельный пункт меню для этой таксономии. Объясню по порядку...

Задача

Нужно хранить данные (строки), с возможностью добавлять к ним еще какие-то данные (заранее неизвестно, код будет расширяться). Дальше эти данные (строки) будут использоваться для пользователей WordPress (у пользователей будет настройка скилы, например, пользователь умеет, готовить, стирать, убирать).

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

Плюсы: регнуть таксономию очень просто и сразу же мы получаем таблицу с пагинацией и поиском, возможность добавлять, изменять, удалять данные, а также возможность расширять данные за счет метаполей. Более того, получаем целый пакет функций WP для вывода элементов этой таксономии. А если, например, сохранять эти данные в отдельную таблицу или в опции, то для управления всем этим нужно было бы писать отдельный код, для всего: начиная с создания страницы в админке и заканчивая функциями вывода элементов. А тут готово все и сразу.

Минусы: лишние неиспользуемые поля в таблице таксономий, а мы на спичках не экономим, поэтому минусов нет!

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

Решение

Регистрируем таксономию. Она нам нужна только для хранения данных, поэтому она будет не публичной (не видна на фронте) и без всяких привычных параметров таксономии:

// создадим таксономию skills
add_action( 'init', function (){
	register_taxonomy( 'skills', null, array(
		//'label'                 => 'Скилы', // определяется параметром $labels->name
		'labels'                => array(
			'name'          => 'Скилы',
			'singular_name' => 'Скил',
			'add_new_item'  => 'Добавить новый Скил',
		),
		'public'                => false,
		'show_ui'               => true, // равен аргументу public
		'show_in_rest'          => false, // добавить в REST API
		'hierarchical'          => false,
		'update_count_callback' => '__return_null',
	) );
}, 20 );

Получаем:

Как видно, все работает, только у таксы нет своего пункта меню и при входе на страницу таксы, мы находимся в разделе «Записи».

Создаем пункт меню под нашу таксу:

## добавим пункт меню таксономии в админ меню
add_action( 'admin_menu', 'add_skills_menu_item' );
function add_skills_menu_item(){
	add_menu_page( 'Скилы', 'Скилы', 'manage_options', "edit-tags.php?taxonomy=skills", null, 'dashicons-awards', 9 );
}

Получаем:

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

Это код ради которого писалась текущая заметка:

## добавим пункт меню таксономии в админ меню
add_action( 'admin_menu', 'add_skills_menu_item' );
function add_skills_menu_item(){
	$taxname = 'skills';

	$is_skills = isset($_GET['taxonomy']) && $_GET['taxonomy'] === $taxname;

	// отменим 'current' для записей (по умолчанию такса туда привязывается, даже если при регистрации таксы не указать тип записи)
	if( $is_skills ) add_filter( 'parent_file', '__return_false' );

	// добавим пункт меню
	$menu_title = 'Скилы';
	add_menu_page( 'Скилы', $menu_title, 'manage_options', "edit-tags.php?taxonomy=$taxname", null, 'dashicons-awards', 9 );

	// поправим некоторые параметры добавленого пункта меню
	$menu_item = & $GLOBALS['menu'][ key(wp_list_filter( $GLOBALS['menu'], [$menu_title] )) ];
	foreach( $menu_item as & $val ){
		// добавим класс 'current' где нужно
		if( false !== strpos($val, 'menu-top') )
			$val = 'menu-top'. ( $is_skills ? ' current' : '' );

		$val = preg_replace('~toplevel_page[^ ]+~', "toplevel_page_$taxname", $val );
	}

}

Получаем:

Вот собственно и все!

-

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

Весь предыдущий код целиком, включая код для доп. задач:

<?php

// создадим таксономию skills
add_action( 'init', function (){
	register_taxonomy( 'skills', null, array(
		//'label'                 => 'Скилы', // определяется параметром $labels->name
		'labels'                => array(
			'name'          => 'Скилы',
			'singular_name' => 'Скил',
			'add_new_item'  => 'Добавить новый Скил',
		),
		'public'                => false,
		'show_ui'               => true, // равен аргументу public
		'show_in_rest'          => false, // добавить в REST API
		'hierarchical'          => false,
		'update_count_callback' => '__return_null',
	) );

	massadd_skills_handler();
}, 20 );

## добавим пункт меню таксономии в админ меню
add_action( 'admin_menu', 'add_skills_menu_item' );
function add_skills_menu_item(){
	$taxname = 'skills';

	$is_skills = isset($_GET['taxonomy']) && $_GET['taxonomy'] === $taxname;

	// отменим 'current' для записей (по умолчанию такса туда привязывается, даже если при регистрации таксы не указать тип записи)
	if( $is_skills ) add_filter( 'parent_file', '__return_false' );

	// добавим пункт меню
	$menu_title = 'Скилы';
	add_menu_page( 'Скилы', $menu_title, 'manage_options', "edit-tags.php?taxonomy=$taxname", null, 'dashicons-awards', 9 );

	// поправим некоторые параметры добавленного пункта меню
	$menu_item = & $GLOBALS['menu'][ key(wp_list_filter( $GLOBALS['menu'], [$menu_title] )) ];
	foreach( $menu_item as & $val ){
		// добавим класс 'current' где нужно
		if( false !== strpos($val, 'menu-top') )
			$val = 'menu-top'. ( $is_skills ? ' current' : '' );

		$val = preg_replace('~toplevel_page[^ ]+~', "toplevel_page_$taxname", $val );
	}

}

## обработка запроса на массовое добавление скилов
function massadd_skills_handler(){
	if( empty($_POST['massadd_skills']) || ! trim($_POST['massadd_skills']) || ! current_user_can('manage_options') )
		return; // только админ

	$new_skills = wp_unslash( trim($_POST['massadd_skills']) );
	$new_skills = array_filter( array_map( 'trim', explode( "\n", $new_skills ) ) );

	$err_names = [];
	foreach( $new_skills as $skill_name ){
		$data = wp_insert_term( $skill_name, 'skills' );
		if( is_wp_error($data) )
			$err_names[ $skill_name ] = $data->get_error_message();
	}

	// сообщение о результате запроса
	add_action( 'admin_notices', function() use ($err_names, $new_skills){
		$added_count = count($new_skills) - count($err_names);
		$message = "<p>Добавлено терминов: $added_count</p>";

		if( $err_names ){
			$message .= '<p style="color:red;">';
			$message .= 'Не удалось добавить: <br>';
			foreach( $err_names as $skill_name => $err_msg )
				$message .= '<b>'. esc_html($skill_name) . "</b>: $err_msg <br>";
			$message .= "</p>";
		}

		echo '<div class="notice notice-success is-dismissible"><div>'. $message .'</div></div>';
	} );

}

## форма массового добавления скилов
add_action( 'skills'.'_add_form', 'massadd_skills_form' );
function massadd_skills_form(){
	if( ! current_user_can('manage_options') ) return; // только админ

	// код выводиться внутри существующей формы, поэтому закроем её и откроем свою
	?>
	</form>

	<form method="POST" action="">
		<div class="form-field massadd-skills-wrap">
			<h2>Массовое добавление скилов</h2>
			<p>Список скилов каждый на новой строке.</p>
			<textarea name="massadd_skills" rows="5" style="width:95%"></textarea>
		</div>
	<?php
	submit_button( 'Добавить скилы массово' );
}

## свои стили на странице таксономии skills и на странице редактирования элемента skills
## спрячем ненужные поля
add_action( 'admin_head', 'hide_unwanted_skill_field' );
function hide_unwanted_skill_field(){
	if( get_current_screen()->id === 'edit-skills' ){
		echo '
		<style>
			.form-field.term-slug-wrap{ display:none; }
			.form-field.term-description-wrap{ display:none; }
		</style>';
	}
}

## Удалим ненужные колонки
add_filter( 'manage_'.'edit-skills'.'_columns', function( $columns ){
	unset( $columns['description'], $columns['posts'] );
	return $columns;
});

Получаем:

Регистрация таксономии без привязки к типу записи 7 комментов
  • Otshelnik-Fm191 cайт: otshelnik-fm.ru

    )) так и надо было вначале предупредить - придется костылять.
    Но я не согласен с этим методом - лучше создать свою таблицу. Писать свой код (чтож поделаешь) - но это правильное решение. Это будет работать быстрей - т.к. мухи и котлеты раздельно. wp_term_taxonomy - имеет структуру как и произвольных полей - любой фильтр по ней - если данных много и обеспечены тормоза. В своей таблице вы всегда типы данных можете задать: чтобы самой БД было проще выбрать по ней.
    В общем - предупреждение - чтобы так не делали нужно повесить.
    Или я неверно понял задачу про 2000 скиллов

    Ответить25 дней назад #
    • Kama5235

      Там 2к элементов ожидается, а не 200к (что тоже не страшно)... wp_term_taxonomy тут вообще при чем? Приведи пример, когда будут тормоза, а так я не понял, какие: типы данных, мухи и котлеты. Демагогией попахивает...

      Это нестандартное и простое решение задачи. К слову, ВП сам подобным макаром размещает пункты nav menu, хотя там все еще хуже если разобраться, зачем же они так решили сделать?

      Ответить25 дней назад #
      • Otshelnik-Fm191 cайт: otshelnik-fm.ru

        в wp_terms - опечатался, не бей ногами. Там он хранить 2000 данных будет? наряду с тем что там еще есть от других

        и да - я неверно понял наверно вашу задачу по скиллам. Вам видней что вы там за скиллы хранить хотите и с какими данными. Можно было и объяснить в каментах

        Демагогией не думал заниматься. Сорян если что не так

        Ответить25 дней назад #
        • Kama5235

          А wp_terms чем не угодил? Там вообще все поля в индексах, хоть миллион строк храни, хоть два... Проблемы кривых запросов будут на любой таблице, включая созданной вручную. И конкретно к запросам ВП связанным с таксономиями это никак не относится... С метаполями да могут быть проблемы, но не когда там 2-5к строк... А когда значительно больше.

          Я немного знаю о чем говорю и пишу... Например, есть у меня сайт в таксах хранятся 200к записей и это именно таксы, на которых крошки строятся и к которым записи прикрепляются, никаких проблем нет в части работы с таблицами и запросами. Там другие проблемы самого ВП, которые видны исключительно в админке при выводе (ну это другая история). 2000 строк для mysql это пустяки, с такими объемами грубо говоря можно вообще без индексов жить... В общем, я не понял твои комменты pardon

          Ответить25 дней назад #
          • Otshelnik-Fm191 cайт: otshelnik-fm.ru

            Ну не понял - потри. Я с самого начала написал что не совсем понял задачу и решил уточнить. Не холивара ради. Как будто я у тебя тут за эти годы главный холиварщик. Не понял нападок.

            Ответить25 дней назад #
            • Kama5235

              Нет никакого холивара вроде бы, дисскуссия. Сорян если что не так.

              Ответить24 дня назад #
  • Иван cайт: creepy1001.xyz

    Спасибо за информацию.

    Ответить13 дней назад #

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