Как показать иерархический список терминов?
У меня есть иерархическая таксономия под названием 'geographical locations'. Она содержит континенты на первом уровне, а затем страны для каждого из них. Пример:
Европа
- Ирландия
- Испания
- Швеция
Азия
- Лаос
- Таиланд
- Вьетнам
и т.д.
Используя get_terms() я смог вывести полный список терминов, но континенты перемешиваются со странами в один плоский список.
Как можно вывести иерархический список, как показано выше?

Я понимаю, что это очень старый вопрос, но если вам нужно построить реальную структуру терминов, этот метод может быть вам полезен:
/**
* Рекурсивно сортирует массив терминов таксономии иерархически. Дочерние категории будут
* размещены внутри элемента 'children' их родительского термина.
* @param Array $cats объекты терминов таксономии для сортировки
* @param Array $into результирующий массив для размещения
* @param integer $parentId текущий родительский ID для размещения
*/
function sort_terms_hierarchically(Array &$cats, Array &$into, $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);
}
}
Использование:
$categories = get_terms('my_taxonomy_name', array('hide_empty' => false));
$categoryHierarchy = array();
sort_terms_hierarchically($categories, $categoryHierarchy);
var_dump($categoryHierarchy);

Это действительно очень хорошо. Я бы изменил одну вещь: $into[$cat->term_id] = $cat;
на $into[] = $cat;
Использование ID термина в качестве ключа массива неудобно (вы не можете легко получить первый элемент, используя ключ 0) и бессмысленно (вы уже храните объект $cat
и можете получить id через свойство term_id
.

Если вы, как и я, пытаетесь применить эту функцию к подуровню категорий, вам нужно передать ID уровня, на котором вы находитесь, чтобы это работало. Но работает это отлично, спасибо @popsi.

Используйте wp_list_categories
с аргументом 'taxonomy' => 'taxonomy'
- эта функция предназначена для создания иерархических списков категорий, но также поддерживает использование пользовательской таксономии.
Пример из Codex:
Отображение терминов в пользовательской таксономии
Если список отображается плоским (без иерархии), возможно, вам просто нужно добавить немного CSS, чтобы добавить отступы к спискам и сделать видимой их иерархическую структуру.

Я не знаю готовой функции, которая делает то, что вам нужно, но вы можете создать что-то подобное:
<ul>
<?php $hiterms = get_terms("my_tax", array("orderby" => "slug", "parent" => 0)); ?>
<?php foreach($hiterms as $key => $hiterm) : ?>
<li>
<?php echo $hiterm->name; ?>
<?php $loterms = get_terms("my_tax", array("orderby" => "slug", "parent" => $hiterm->term_id)); ?>
<?php if($loterms) : ?>
<ul>
<?php foreach($loterms as $key => $loterm) : ?>
<li><?php echo $loterm->name; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
Я не тестировал этот код, но вы можете понять суть. Приведенный выше код даст вам только два уровня вложенности.
РЕДАКТИРОВАНИЕ: Ах да, вы можете использовать wp_list_categories() для достижения нужного результата.

На самом деле это довольно полезно, так как мне нужно добавить пользовательские ссылки (с параметром GET) для ссылок на термины, что, похоже, невозможно при использовании wp_list_categories()
.

Да, этот метод даст вам больше контроля над выводом. Но вы также можете сделать замену в выводе wp_list_categories()
, чтобы добавить ваши GET-параметры. Или еще лучше - создать фильтр для функции, чтобы добавить нужные элементы. Не спрашивайте меня, как это сделать, я сам пока не разобрался :(

Я бы предложил использовать пользовательский обходчик категорий вместе с wp_list_categories
, если вам нужен больший контроль над выводом - это сделает ваш код более переиспользуемым.

Следующий код создаст выпадающий список с терминами, но также может генерировать любой другой элемент/структуру путем редактирования переменной $outputTemplate и строк с str_replace:
function get_terms_hierarchical($terms, $output = '', $parent_id = 0, $level = 0) {
//Шаблон вывода
$outputTemplate = '<option value="%ID%">%PADDING%%NAME%</option>';
foreach ($terms as $term) {
if ($parent_id == $term->parent) {
//Замена переменных шаблона
$itemOutput = str_replace('%ID%', $term->term_id, $outputTemplate);
$itemOutput = str_replace('%PADDING%', str_pad('', $level*12, ' '), $itemOutput);
$itemOutput = str_replace('%NAME%', $term->name, $itemOutput);
$output .= $itemOutput;
$output = get_terms_hierarchical($terms, $output, $term->term_id, $level + 1);
}
}
return $output;
}
$terms = get_terms('taxonomy', array('hide_empty' => false));
$output = get_terms_hierarchical($terms);
echo '<select>' . $output . '</select>';

Я использовал код @popsi, который работал очень хорошо, и сделал его более эффективным и удобочитаемым:
/**
* Рекурсивно сортирует массив терминов таксономии иерархически. Дочерние категории будут
* размещены в элементе 'children' их родительского термина.
* @param Array $cats объекты терминов таксономии для сортировки
* @param integer $parentId текущий ID родителя для группировки
*/
function sort_terms_hierarchicaly(Array $cats, $parentId = 0)
{
$into = [];
foreach ($cats as $i => $cat) {
if ($cat->parent == $parentId) {
$cat->children = sort_terms_hierarchicaly($cats, $cat->term_id);
$into[$cat->term_id] = $cat;
}
}
return $into;
}
Использование:
$sorted_terms = sort_terms_hierarchicaly($terms);

Когда я искал то же самое, но для получения терминов одного поста, в итоге я собрал это, и это работает для меня.
Что делает этот код:
• Получает все термины таксономии для конкретного поста.
• Для иерархической таксономии с двумя уровнями (например, уровень1: 'страна' и уровень2: 'города') создает заголовок h4 с первым уровнем, за которым следует список ul второго уровня, и так для всех элементов первого уровня.
• Если таксономия не иерархическая, создается только список ul со всеми элементами.
Вот код (я писал его для себя, поэтому постарался сделать его максимально универсальным, но...):
function finishingLister($heTerm){
$myterm = $heTerm;
$terms = get_the_terms($post->ID,$myterm);
if($terms){
$count = count($terms);
echo '<h3>'.$myterm;
echo ((($count>1)&&(!endswith($myterm, 's')))?'s':"").'</h3>';
echo '<div class="'.$myterm.'Wrapper">';
foreach ($terms as $term) {
if (0 == $term->parent) $parentsItems[] = $term;
if ($term->parent) $childItems[] = $term;
};
if(is_taxonomy_hierarchical( $heTerm )){
foreach ($parentsItems as $parentsItem){
echo '<h4>'.$parentsItem->name.'</h4>';
echo '<ul>';
foreach($childItems as $childItem){
if ($childItem->parent == $parentsItem->term_id){
echo '<li>'.$childItem->name.'</li>';
};
};
echo '</ul>';
};
}else{
echo '<ul>';
foreach($parentsItems as $parentsItem){
echo '<li>'.$parentsItem->name.'</li>';
};
echo '</ul>';
};
echo '</div>';
};
};
Итак, в итоге вы вызываете функцию так (очевидно, замените my_taxonomy на свою таксономию): finishingLister('my_taxonomy');
Я не утверждаю, что это идеально, но, как я уже сказал, это работает для меня.

У меня была эта проблема, и ни одно из решений здесь не сработало для меня по той или иной причине.
Вот моя обновленная и рабочая версия.
function locationSelector( $fieldName ) {
$args = array('hide_empty' => false, 'hierarchical' => true, 'parent' => 0);
$terms = get_terms("locations", $args);
$html = '';
$html .= '<select name="' . $fieldName . '"' . 'class="chosen-select ' . $fieldName . '"' . '>';
foreach ( $terms as $term ) {
$html .= '<option value="' . $term->term_id . '">' . $term->name . '</option>';
$args = array(
'hide_empty' => false,
'hierarchical' => true,
'parent' => $term->term_id
);
$childterms = get_terms("locations", $args);
foreach ( $childterms as $childterm ) {
$html .= '<option value="' . $childterm->term_id . '">' . $term->name . ' > ' . $childterm->name . '</option>';
$args = array('hide_empty' => false, 'hierarchical' => true, 'parent' => $childterm->term_id);
$granchildterms = get_terms("locations", $args);
foreach ( $granchildterms as $granchild ) {
$html .= '<option value="' . $granchild->term_id . '">' . $term->name . ' > ' . $childterm->name . ' > ' . $granchild->name . '</option>';
}
}
}
$html .= "</select>";
return $html;
}
Использование:
$selector = locationSelector('locationSelectClass');
echo $selector;

Это решение менее эффективно, чем код от @popsi, так как делает новый запрос для каждого термина, но зато его проще использовать в шаблоне. Если ваш сайт использует кэширование, вы, как и я, можете не обращать внимания на небольшую дополнительную нагрузку на базу данных.
Вам не нужно подготавливать массив, который будет рекурсивно заполняться терминами. Вы просто вызываете функцию так же, как вызывали бы get_terms() (неустаревшая форма с единственным аргументом в виде массива). Она возвращает массив объектов WP_Term
с дополнительным свойством children
.
function get_terms_tree( Array $args ) {
$new_args = $args;
$new_args['parent'] = $new_args['parent'] ?? 0;
$new_args['fields'] = 'all';
// Термины для текущего уровня
$terms = get_terms( $new_args );
// Дочерние элементы каждого термина на этом уровне
foreach( $terms as &$this_term ) {
$new_args['parent'] = $this_term->term_id;
$this_term->children = get_terms_tree( $new_args );
}
return $terms;
}
Использование простое:
$terms = get_terms_tree([ 'taxonomy' => 'my-tax' ]);

Последний удар по этой теме (аналогично приведённым выше ответам) с некоторой доработкой... позволяющий также преобразовать список в иерархически-плоский с помощью параметра depth
.
Дополнительно эта функция использует clone
, чтобы избежать изменения исходного массива.
/**
* Сортирует список терминов иерархически или иерархически-плоско.
*/
function sort_terms_hierarchically( $terms, $parent_id = 0, $flat = FALSE, $depth = 0 ) {
$data = [];
foreach ( $terms as $term ) {
if ( $parent_id != $term->parent ) continue;
$data_term = clone $term;
$data_term->depth = $depth;
$children = sort_terms_hierarchically( $terms, $term->term_id, $flat, $depth + 1 );
if ( ! $flat ) $data_term->children = $children;
$data[] = $data_term;
if ( $flat ) foreach( $children as $child ) $data[] = $child;
}
return $data;
}
function sort_terms_hierarchically_flat( $terms, $parent_id = 0 ) {
return sort_terms_hierarchically( $terms, $parent_id, TRUE );
}
Использование выглядит следующим образом:
$terms = get_terms( [
'taxonomy' => 'regions',
'hide_empty' => FALSE,
] );
$terms_sorted = sort_terms_hierarchically( $terms );
$terms_sorted_flat = sort_terms_hierarchically_flat( $terms );

Убедитесь, что в вызов get_terms()
передаётся параметр hierarchical=true
.
Обратите внимание, что hierarchical=true
используется по умолчанию, поэтому главное — убедиться, что это значение не было переопределено на false
.

Привет, Chip, да, параметр 'hierarchical' по умолчанию установлен в 'true'.

Комментируешь ответ, оставленный почти два года назад? Серьёзно? На самом деле, это и есть предложенный ответ, даже если сформулирован как вопрос. Может, мне стоит отредактировать его, чтобы он звучал как утверждение, а не как вопрос?

Здесь у меня четырехуровневый выпадающий список с скрытым первым пунктом
<select name="lokalizacja" id="ucz">
<option value="">Все локации</option>
<?php
$excluded_term = get_term_by('slug', 'podroze', 'my_travels_places');
$args = array(
'orderby' => 'slug',
'hierarchical' => 'true',
'exclude' => $excluded_term->term_id,
'hide_empty' => '0',
'parent' => $excluded_term->term_id,
);
$hiterms = get_terms("my_travels_places", $args);
foreach ($hiterms AS $hiterm) :
echo "<option value='".$hiterm->slug."'".($_POST['my_travels_places'] == $hiterm->slug ? ' selected="selected"' : '').">".$hiterm->name."</option>\n";
$loterms = get_terms("my_travels_places", array("orderby" => "slug", "parent" => $hiterm->term_id,'hide_empty' => '0',));
if($loterms) :
foreach($loterms as $key => $loterm) :
echo "<option value='".$loterm->slug."'".($_POST['my_travels_places'] == $loterm->slug ? ' selected="selected"' : '')."> - ".$loterm->name."</option>\n";
$lo2terms = get_terms("my_travels_places", array("orderby" => "slug", "parent" => $loterm->term_id,'hide_empty' => '0',));
if($lo2terms) :
foreach($lo2terms as $key => $lo2term) :
echo "<option value='".$lo2term->slug."'".($_POST['my_travels_places'] == $lo2term->slug ? ' selected="selected"' : '')."> - ".$lo2term->name."</option>\n";
endforeach;
endif;
endforeach;
endif;
endforeach;
?>
</select>
<label>Выберите тип места</label>
<select name="rodzaj_miejsca" id="woj">
<option value="">Все типы</option>
<?php
$theterms = get_terms('my_travels_places_type', 'orderby=name');
foreach ($theterms AS $term) :
echo "<option value='".$term->slug."'".($_POST['my_travels_places_type'] == $term->slug ? ' selected="selected"' : '').">".$term->name."</option>\n";
endforeach;
?>
</select>

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