Пользовательская таксономия, get_the_terms, вывод в порядке родитель > потомок

25 дек. 2011 г., 22:21:36
Просмотры: 25.1K
Голосов: 10

У меня есть иерархическая пользовательская таксономия, которую я могу отобразить с помощью print_r(get_the_terms( $post->ID, 'taxonomic_rank' ));:

Array
(
    [46] => stdClass Object
        (
            [term_id] => 46
            [name] => Aplocheilidae
            [slug] => aplocheilidae
            [term_group] => 0
            [term_taxonomy_id] => 53
            [taxonomy] => taxonomic_ranks
            [description] => 
            [parent] => 39
            [count] => 1
            [object_id] => 443
        )

    [47] => stdClass Object
        (
            [term_id] => 47
            [name] => Aplocheilus
            [slug] => aplocheilus
            [term_group] => 0
            [term_taxonomy_id] => 54
            [taxonomy] => taxonomic_ranks
            [description] => 
            [parent] => 46
            [count] => 1
            [object_id] => 443
        )

    [39] => stdClass Object
        (
            [term_id] => 39
            [name] => Cyprinodontiformes
            [slug] => cyprinodontiformes
            [term_group] => 0
            [term_taxonomy_id] => 52
            [taxonomy] => taxonomic_ranks
            [description] => 
            [parent] => 0
            [count] => 1
            [object_id] => 443
        )

)

Эта таксономия всегда будет иметь следующую форму: Отряд (родитель) > Семейство (потомок Отряда) > Подсемейство (потомок Семейства)

Есть ли быстрый и простой способ отображения этих таксономий в правильном порядке, чтобы я мог вывести следующую строку? Отряд: <order>, Семейство: <family>, Подсемейство: <sub-family>

Заранее спасибо

1
Комментарии

Тот, кто поставил мне минус, не могли бы вы объяснить, почему?

turbonerd turbonerd
26 дек. 2011 г. 15:09:39
Все ответы на вопрос 7
5

Возможно, существуют более эффективные способы сделать это, но всегда можно использовать три простых цикла foreach.

Я написал пример функции, которая отлично справляется с задачей и может послужить хорошей отправной точкой:

function print_taxonomic_ranks( $terms = '' ){

    // проверка входных данных
    if ( empty( $terms ) || is_wp_error( $terms ) || ! is_array( $terms ) )
        return;

    // устанавливаем ID переменных в 0 для удобной проверки
    $order_id = $family_id = $subfamily_id = 0;

    // получаем порядок
    foreach ( $terms as $term ) {
        if ( $order_id || $term->parent )
            continue;
        $order_id  = $term->term_id;
        $order     = $term->name;
    }

    // получаем семейство
    foreach ( $terms as $term ) { 
        if ( $family_id || $order_id != $term->parent )
            continue;
        $family_id = $term->term_id;
        $family    = $term->name;
    }

    // получаем подсемейство
    foreach ( $terms as $term ) { 
        if ( $subfamily_id || $family_id != $term->parent ) 
            continue;
        $subfamily_id = $term->term_id;
        $subfamily    = $term->name;
    }

    // вывод
    echo "Порядок: $order, Семейство: $family, Подсемейство: $subfamily";

}

Разместите этот код в вашем файле functions.php и используйте в шаблонах следующим образом:

print_taxonomy_ranks( get_the_terms( $post->ID, 'taxonomic_rank' ) );

ПРИМЕЧАНИЕ: Тройной перебор одного и того же массива может показаться не самым умным решением, но с другой стороны — это быстрое, простое и читаемое решение, которое легко расширять и поддерживать.

27 дек. 2011 г. 16:19:01
Комментарии

Я бесконечно вам благодарен. Это идеальный ответ, и мне жаль, что я не могу дать вам больше репутации!! Если кто-то увидит этот пост, пожалуйста, поставьте +1, чтобы Maugly получил столько наград, сколько он заслуживает :)

turbonerd turbonerd
27 дек. 2011 г. 16:23:02

Не беспокойтесь. Рад, что смог помочь :) Я также обновил код в своем ответе и добавил простую проверку ввода...

Michal Mau Michal Mau
27 дек. 2011 г. 17:10:41

Maugly, спасибо за ваш ответ, это почти именно то, что мне нужно - не могли бы вы подсказать, как использовать это с архивными ссылками, все еще прикрепленными к терминам? Еще раз спасибо

User User
24 янв. 2012 г. 05:55:07

@Adam Посмотри на get_term_link()

Michal Mau Michal Mau
24 янв. 2012 г. 12:36:31

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

Lucas Bustamante Lucas Bustamante
2 июн. 2015 г. 08:20:46
1

Довольно старая тема, но всё ещё актуальная, на мой взгляд, поскольку это по-прежнему вызывает сложности.

Я использую эту рекурсивную функцию, которая принимает два массива по ссылке. Она создаёт массив со следующей структурой: [term_id] => term_object->children->child_terms_array->children->child_terms_array.

<?php
function sort_terms_hierarchically( array &$terms, array &$into, $parent_id = 0 ) {
    foreach ( $terms as $i => $term ) {
        if ( $term->parent == $parent_id ) {
            $into[$term->term_id] = $term;
            unset( $terms[ $i ] );
        }
    }

    foreach ( $into as $top_term ) {
        $top_term->children = array();
        $this->sort_terms_hierarchically( $terms, $top_term->children, $top_term->term_id );
    }

}

$terms = get_the_terms( 'taxslug', $post );
$sorted_terms = array();
sort_terms_hierarchically( $terms, $sorted_terms );

// Выведет вложенные массивы объектов терминов в лог.
error_log( print_r( $sorted_terms, true ) );

Это единственное решение, которое я нашёл, сохраняющее объекты терминов и работающее с любым уровнем вложенности.

20 сент. 2016 г. 15:25:54
Комментарии

Отличный ответ, спасибо. Всё ещё актуально в 2022 году! Есть небольшая синтаксическая ошибка с несоответствием $this-> при вызове функции, но логика идеальна

Ian Ian
7 апр. 2022 г. 17:18:01
4

Хотя подход Maugly кажется немного более читаемым, выполнение цикла 3 раза для одного массива не кажется мне правильным. Вот альтернативный подход, который может быть менее читаемым для некоторых, но работает без трёхкратного прохода по массиву.

function print_taxonomy_ranks( $terms ) {
    // если terms не массив или он пуст, не продолжаем
    if ( ! is_array( $terms ) || empty( $terms ) ) {
        return false;
    }

    foreach ( $terms as $term ) {
        // если термин имеет родителя, устанавливаем дочерний термин как атрибут родительского
        if ( $term->parent != 0 )  {
            $terms[$term->parent]->child = $term;   
        } else {
            // записываем родительский термин
            $parent = $term;
        }
    }

    echo "Порядок: $parent->name, Семейство: {$parent->child->name}, Подсемейство: {$parent->child->child->name}";
}
28 дек. 2011 г. 10:58:36
Комментарии

Отличная работа! Я знал, что это возможно, но тогда не мог до конца разобраться :) Мне нравится ваше решение!

Michal Mau Michal Mau
24 янв. 2012 г. 14:34:23

Предупреждение при downvote: Создание объекта по умолчанию из пустого значения

Brad Dalton Brad Dalton
25 сент. 2020 г. 13:32:36

@Dev не могли бы вы уточнить? О каком объекте идет речь. Хотя ответу уже 9 лет, но мне было бы интересно узнать, на какой объект вы ссылаетесь

Hameedullah Khan Hameedullah Khan
27 сент. 2020 г. 07:14:32

Это сообщение об ошибке, которое я получаю при тестировании. В нем не указано, какой именно объект.

Brad Dalton Brad Dalton
27 сент. 2020 г. 07:35:10
2

У меня была ситуация, когда запись могла быть помечена несколькими группами категорий, а также несколькими дочерними элементами внутри родительских категорий, поэтому я хотел, чтобы моя иерархия отражала это. И мне нужен был всего несколько строк кода:

$terms = get_the_terms($id, 'department_categories');
foreach($terms as $key => $term){
    if($term->parent != 0){
        $terms[$term->parent]->children[] = $term;
        unset($terms[$key]);
    }
}

По сути, после того как код находит родителя категории, он перемещает её в объект родителя как дочерний элемент, а затем удаляет из исходной позиции в массиве. Я протестировал это с использованием нескольких соседних элементов, дочерних элементов и категорий разного уровня.

Надеюсь, это кому-то пригодится, если вы ищете логическое решение, а не "плагин"!

8 июл. 2013 г. 17:57:46
Комментарии

Можешь объяснить, как этим пользоваться?

Lucas Bustamante Lucas Bustamante
2 июн. 2015 г. 08:22:01

@LucasB Попробую вспомнить :P У нас есть пост, и мы знаем, что его $post->ID это $id, а "department_categories" — это таксономия.

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

Kaitlyn McDonald Kaitlyn McDonald
2 июн. 2015 г. 16:22:36
1

Вдохновленный ответом Michal Mau и ответом Manny Fleurmond, вот мое решение: У меня была та же проблема, что и у @Lucas Bustamante: что делать, если у поста две родительские категории?

Мое решение заключалось в создании еще одного массива объектов, сравнивая term_id и проверяя ключ parent

function print_taxonomic_ranks( $terms ){

    if ( ! is_array( $terms ) || empty( $terms ) ) {
        return false;
    }

    $parent_terms = array();

    // получаем только родительские элементы
    foreach ( $terms as $term ) {
        if ($term->parent === 0) {
            $term->child = Array();
            $parent_terms[] = $term;
        }
    }

    // сравниваем и вкладываем
    foreach ( $terms as $term ) {
        if ($term->parent != 0) {
            foreach ($parent_terms as $key => $value) {
                if ($term->parent === $value->term_id) {
                    $parent_terms[$key]->child[] = $term;
                }
            }
        }
    }

    // выводим результаты
    foreach ( $parent_terms as $term ) {

        // родительский элемент
        echo '<span class="d-block">'.$term->name.'';

        if ($term->child) {
            $i = 1;
            foreach ( $term->child as $child ) {
                //echo '<span class="text-danger">'.$i.'</span>';
                echo ($i <= 1)? ": " : "";
                echo '<span class="font-weight-normal">'.$child->name.'</span>';
                echo ($i < count($term->child))? ", " : "";
            $i++;                
            }          
        }
        echo '.<span>';
    }
}

И это будет выглядеть примерно так:

Получение терминов в порядке родительских элементов

Удачи!

7 апр. 2020 г. 01:36:45
Комментарии

Тег <span> не был закрыт в конце кода echo '.<span>' => echo '.</span>'

Mahdi Afzal Mahdi Afzal
3 февр. 2021 г. 15:13:14
0

Спасибо, Maugly,

Вот моя модифицированная версия вашего кода, которая включает постоянные ссылки для терминов, если кому-то это понадобится

function print_show_location( $terms = '' ){

// проверка входных данных
if ( empty( $terms ) || is_wp_error( $terms ) || ! is_array( $terms ) )
    return;

// устанавливаем ID переменных в 0 для удобной проверки 
$country_id = $state_id = $city_id = 0;

// получаем страну
foreach ( $terms as $term ) {
    if ( $country_id || $term->parent )
        continue;
    $country_id  = $term->term_id;
    $country_slug = $term->slug;
    $country = '<a href="'.get_term_link($country_slug, 'location').'">'.$term->name.'</a>';
}

// получаем регион
foreach ( $terms as $term ) { 
    if ( $state_id || $country_id != $term->parent )
        continue;
    $state_id = $term->term_id;
    $state_slug = $term->slug;
    $state = '<a href="'.get_term_link($state_slug, 'location').'">'.$term->name.'</a>';
}

// получаем город
foreach ( $terms as $term ) { 
    if ( $city_id || $state_id != $term->parent ) 
        continue;
    $city_id = $term->term_id;
    $city_slug = $term->slug;
    $city = '<a href="'.get_term_link($city_slug, 'location').'">'.$term->name.'</a>';
}

// вывод
echo "$city, $state - $country";

}
25 янв. 2012 г. 03:10:23
0

У меня есть несколько рекурсивных функций, которые я использую в зависимости от потребностей. Обеим функциям нужен список терминов, начальный ID родителя и начальный (обычно пустой) массив, куда будет помещаться результирующий список.

ПЕРВАЯ ВЕРСИЯ

$terms = [список объектов терминов, используйте get_terms или что-то подобное]
$ordered_terms = array(); // здесь будут упорядоченные термины от корня до последнего потомка

function list_terms_by_parent($parent_id = 0, &$terms, &$ordered_terms){
  $root_parent = $parent_id;

  foreach($terms as $index => $term){
    if($term->parent == (int) $parent_id){
      $ordered_terms[$term->term_id] = $term;
      $root_parent = $term->term_id;
      unset($terms[$index]);
    }
  }

  if(!empty($terms)) list_terms_by_parent($root_parent, $terms, $ordered_terms);
}

ВТОРАЯ ВЕРСИЯ

$term_ids = [должен быть список ID в формате child_id => parent_id]

// быстрый способ получить такой список - использовать WP_Term_query, например:
//$terms_query = new WP_Term_Query(array(
//  'taxonomy' => 'product_categories'
//  ,'object_ids' => $post->ID
//  ,'hide_empty' => false
//  ,'fields' => 'id=>parent'
//));

$ordered_terms = array(); // здесь будут упорядоченные термины от корня до последнего потомка

function list_term_ids_by_parent($parent_id = 0, &$term_ids, &$ordered_terms){
  $child_id = array_search($parent_id, $term_ids);

  if($child_id){
    $ordered_terms[] = $child_id;
    unset($term_ids[$child_id]);
  }

  if(!empty($term_ids)) order_terms($child_id, $term_ids, $ordered_terms);
}

ТРЕТЬЯ ВЕРСИЯ

Это не моя функция, я нашел ее где-то, вероятно здесь или на stackoverflow. Она немного отличается по выводу, так как генерирует каскадный список терминов с одним корневым элементом, у которого есть свойство children, содержащее следующий термин со своим свойством children и так далее..

// $ordered_terms предполагается пустым массивом, как в предыдущих примерах

function sort_terms_hierarchically(&$terms, &$ordered_terms, $parentId = 0){
  foreach($cats as $i => $cat){
    if($cat->parent == $parentId){
      $into[$cat->term_id] = $cat;
      unset($cats[$i]);
    }
  }

  foreach ($into as $topCat) {
    $topCat->children = array();
    sort_terms_hierarchically($cats, $topCat->children, $topCat->term_id);
  }
}
18 нояб. 2019 г. 13:47:32