Пользовательская таксономия, get_the_terms, вывод в порядке родитель > потомок
У меня есть иерархическая пользовательская таксономия, которую я могу отобразить с помощью 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>
Заранее спасибо

Возможно, существуют более эффективные способы сделать это, но всегда можно использовать три простых цикла 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' ) );
ПРИМЕЧАНИЕ: Тройной перебор одного и того же массива может показаться не самым умным решением, но с другой стороны — это быстрое, простое и читаемое решение, которое легко расширять и поддерживать.

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

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

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

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

Довольно старая тема, но всё ещё актуальная, на мой взгляд, поскольку это по-прежнему вызывает сложности.
Я использую эту рекурсивную функцию, которая принимает два массива по ссылке. Она создаёт массив со следующей структурой:
[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 ) );
Это единственное решение, которое я нашёл, сохраняющее объекты терминов и работающее с любым уровнем вложенности.

Хотя подход 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}";
}

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

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

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

У меня была ситуация, когда запись могла быть помечена несколькими группами категорий, а также несколькими дочерними элементами внутри родительских категорий, поэтому я хотел, чтобы моя иерархия отражала это. И мне нужен был всего несколько строк кода:
$terms = get_the_terms($id, 'department_categories');
foreach($terms as $key => $term){
if($term->parent != 0){
$terms[$term->parent]->children[] = $term;
unset($terms[$key]);
}
}
По сути, после того как код находит родителя категории, он перемещает её в объект родителя как дочерний элемент, а затем удаляет из исходной позиции в массиве. Я протестировал это с использованием нескольких соседних элементов, дочерних элементов и категорий разного уровня.
Надеюсь, это кому-то пригодится, если вы ищете логическое решение, а не "плагин"!

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

Вдохновленный ответом 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>';
}
}
И это будет выглядеть примерно так:
Удачи!

Спасибо, 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";
}

У меня есть несколько рекурсивных функций, которые я использую в зависимости от потребностей. Обеим функциям нужен список терминов, начальный 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);
}
}
