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

Регистрация таксономии без привязки к типу записи
Click here to view original web page at wp-kama.ru

Как выяснилось, 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,
		'show_in_quick_edit'    => false, // по умолчанию значение show_ui
		'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' для записей (по умолчанию такса туда привязывается, даже если при регистрации таксы не указать тип записи)
	$is_skills && add_filter( 'parent_file', function($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' для записей (по умолчанию такса туда привязывается, даже если при регистрации таксы не указать тип записи)
	$is_skills && add_filter( 'parent_file', function($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_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;
});