Вопрос или проблема
У меня на сайте есть две настраиваемые таксономии – Страны и Города.
Таксономия Страны содержит более 200 терминов, а таксономия Городов – более 47 000 терминов.
Естественно, редактирование контента с учетом такой большой таксономии является очень трудоемким процессом.
Поэтому возникла задача связать эти две таксономии и загружать список городов только для выбранной страны на экране редактирования (Добавить) записи.
Теперь термины таксономии Городов имеют мета-поле, связанное с Устраной.
У меня есть два метабокса (типа post_category_meta_box) на странице редактирования пользовательского типа записи (например, Событие).
Как я могу реализовать следующие функции:
- по умолчанию для нового контента не отображать список городов, а заменить его на (нулевое) значение – “Сначала выберите страну”
- при выборе страны (post_category_meta_box) – обновить метабокс Городов, указав только города выбранной страны
Похожая задача обсуждалась на wordpress.org, но я не знаю, как она была реализована. Редактирование записи – Фильтрация терминов в метабоксе 1 на основе выбранных терминов метабокса 2
Буду рад любой помощи. Примечание: контент может быть связан с разными странами и несколькими городами.
Вот что я сделал:
- добавил скрипт в окно редактора записи для выбора стран
`
/* Установка связанного процесса с таксономией стран */
function cities_add_admin_scripts( $hook ) {
global $post;
if ( $hook == 'post-new.php' || $hook == 'post.php' ) {
if ( in_array($post->post_type, ['hotel','event','sight','webcamera'])) {
wp_add_inline_script( "jquery",
"(function($){
function city_doing_ajax( countries ){
$.ajax({
url: ajaxurl,
method: 'post',
dataType: 'json',
data: {action: 'city_filter', countries: countries, nonce: '" . wp_create_nonce('myajax-nonce') . "'}
}).done(function(data){
console.log(data);
});
}
$(document).ready(function() {
/* установить начальные значения */
var countries = $('#countrychecklist input[type=\"checkbox\"]:checked').map(function(){ return $(this).val();}).get();
console.log('идентификаторы стран:', countries);
if(countries.length > 0){
city_doing_ajax(countries);
}
$('#countrychecklist input[type=\"checkbox\"]').change(function() {
countries = $('#countrychecklist input[type=\"checkbox\"]:checked').map(function(){ return $(this).val();}).get();
console.log('обновленные идентификаторы стран:', countries);
city_doing_ajax(countries);
});
})})(jQuery);" );
}
}
}
`
Я делаю это дважды. Сразу после загрузки формы, чтобы получить список уже отмеченных стран, и для каждого выбора (снятия выбора) в форме
Далее, я навешиваю обработчик на Ajax-запрос
`
add_action( 'wp_ajax_city_filter', array(
$this, 'city_ajax_action_callback'
) );
`
и код функции обработки Ajax-запроса
`
function city_ajax_action_callback(){
$countries_ids = array_map( 'intval', $_POST['countries'] );
if(!empty( $countries_ids ) ) {
$country_abbr = get_terms([
'taxonomy' => 'country',
'hide_empty' => false,
'fields' => 'all', /* slug not exists as parameter*/
'include' => $countries_ids
]);
if( $country_abbr && ! is_wp_error( $country_abbr ) ){
$countr_codes = [];
foreach ($country_abbr as $term) $countr_codes[] = $term->slug;
**$this->country_codes** = $countr_codes;
add_action('parse_term_query', array($this, 'city_filter_term_query'));
} else {
remove_action('parse_term_query', array($this, 'city_filter_term_query'));
}
wp_send_json_success();
} else {
/* remove_action('parse_term_query', array($this, 'city_filter_term_query')); */
wp_send_json_error();
}
}
`
и последняя функция добавляет наш фильтр на этапе подготовки запроса к таблице терминов
`
function city_filter_term_query( $wp_term_query ){
$taxonomy = $wp_term_query->query_vars['taxonomy'];
if( is_admin() && $taxonomy == 'city' && (! empty($this->country_codes))){
$meta_query = [
'relation' => 'AND',
];
$meta_query[] = [
'key' => 'country_code',
'value' => **$this->country_codes**,
'compare' => 'IN',
];
$wp_term_query->query_vars['meta_query'] = $meta_query;
error_log( print_r(['city_filter_term_query' => $wp_term_query], true ) );
}
}
`
Отмечу, что все это реализовано в классе CityTaxonomy
, где имеется поле (выделенное жирным)
private $country_codes = [];
Мой подход будет следующим:
- Скрыть метабокс редактирования городов, установив
public
в false. - Я бы переопределил, как метабокс для стран должен отображаться, используя параметр
meta_box_cb
. Вам нужно предоставить обратный вызов для функции, которая будет выполнять отображение. - В этой функции сначала отобразите поле для страны, а затем при выборе сделайте AJAX-запрос на ваш сервер, чтобы собрать список городов для указанной страны.
Ответ или решение
Проблема, которую вы хотите решить, заключается в том, чтобы связать две таксономии (Страны и Города) в WordPress и фильтровать список городов в зависимости от выбранной страны на странице редактирования постов. Ниже я приведу шаги, которые позволяют достичь желаемого результата.
Шаг 1: Скрытие Метабокса Городов
Сначала необходимо скрыть метабокс городов. Для этого можно установить параметр public
в значение false
при регистрации таксономии. Однако, лучше всего будет сделать это через фильтрацию метабокса.
add_action('add_meta_boxes', 'remove_city_metabox');
function remove_city_metabox() {
remove_meta_box('citydiv', 'your_custom_post_type', 'normal');
}
Шаг 2: Отображение Метабокса Страны и Создание AJAX Запроса
Создайте новый метабокс для страны с пользовательским выводом. При выборе страны будет выполняться AJAX-запрос, который запрашивает доступные города для выбранной страны.
add_action('add_meta_boxes', 'add_country_metabox');
function add_country_metabox() {
add_meta_box('country_metabox', 'Выберите страну', 'country_metabox_callback', 'your_custom_post_type', 'normal', 'high');
}
function country_metabox_callback($post) {
// Получаем все страны
$countries = get_terms(['taxonomy' => 'country', 'hide_empty' => false]);
?>
<select id="country_select">
<option value="">Сначала выберите страну</option>
<?php foreach ($countries as $country) : ?>
<option value="<?php echo esc_attr($country->term_id); ?>"><?php echo esc_html($country->name); ?></option>
<?php endforeach; ?>
</select>
<div id="city_container">
<p>Сначала выберите страну.</p>
</div>
<script>
jQuery(document).ready(function($) {
$('#country_select').change(function() {
var country_id = $(this).val();
if (country_id) {
$.ajax({
url: ajaxurl,
method: 'POST',
data: {
action: 'filter_cities',
country_id: country_id,
nonce: '<?php echo wp_create_nonce('filter_cities_nonce'); ?>'
},
success: function(response) {
$('#city_container').html(response);
}
});
} else {
$('#city_container').html('<p>Сначала выберите страну.</p>');
}
});
});
</script>
<?php
}
Шаг 3: Обработка AJAX Запроса
Добавьте обработчик AJAX-запроса, чтобы вернуть города, связанные с выбранной страной.
add_action('wp_ajax_filter_cities', 'filter_cities_callback');
function filter_cities_callback() {
check_ajax_referer('filter_cities_nonce', 'nonce');
$country_id = intval($_POST['country_id']);
if (!$country_id) {
wp_send_json_error('Нет выбранной страны');
}
// Получаем города, связанные с выбранной страной
$cities = get_terms([
'taxonomy' => 'city',
'meta_query' => [
[
'key' => 'country_code',
'value' => $country_id,
'compare' => '='
]
],
'hide_empty' => false
]);
if (!empty($cities) && !is_wp_error($cities)) {
$output = '<select id="city_select" name="city_select[]">';
foreach ($cities as $city) {
$output .= '<option value="' . esc_attr($city->term_id) . '">' . esc_html($city->name) . '</option>';
}
$output .= '</select>';
} else {
$output = '<p>Нет доступных городов для этой страны.</p>';
}
wp_send_json_success($output);
}
Шаг 4: Сохранение Выборов
Не забудьте обработать сохранение выбранных городов при сохранении поста. Используйте save_post
хук для этого.
add_action('save_post', 'save_city_selection');
function save_city_selection($post_id) {
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (isset($_POST['city_select'])) {
// Сохраняем выбранные города
update_post_meta($post_id, 'selected_cities', $_POST['city_select']);
}
}
Заключение
Теперь, используя указанный выше код, у вас будет простой интерфейс на странице редактирования поста, который позволяет выбирать страну, а затем отображать только города, относящиеся к выбранной стране. Не забудьте адаптировать код под вашу конкретную реализацию и убедиться, что имена метабоксов и таксономий типичны для вашей темы или плагина.