Две связанные таксономии. Как отфильтровать термины во второй таксономии в зависимости от выбранного термина в первой таксономии на странице редактирования поста?

Вопрос или проблема

У меня на сайте есть две настраиваемые таксономии – Страны и Города.

Таксономия Страны содержит более 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 = [];

Мой подход будет следующим:

  1. Скрыть метабокс редактирования городов, установив public в false.
  2. Я бы переопределил, как метабокс для стран должен отображаться, используя параметр meta_box_cb. Вам нужно предоставить обратный вызов для функции, которая будет выполнять отображение.
  3. В этой функции сначала отобразите поле для страны, а затем при выборе сделайте 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']);
    }
}

Заключение

Теперь, используя указанный выше код, у вас будет простой интерфейс на странице редактирования поста, который позволяет выбирать страну, а затем отображать только города, относящиеся к выбранной стране. Не забудьте адаптировать код под вашу конкретную реализацию и убедиться, что имена метабоксов и таксономий типичны для вашей темы или плагина.

Оцените материал
Добавить комментарий

Капча загружается...