Получение терминов get_terms по типу записи
У меня есть два пользовательских типа записей 'country' и 'city' и общая таксономия 'flag'.
Если я использую:
<?php $flags = get_terms('flag', 'orderby=name&hide_empty=0');
Я получаю список всех терминов в таксономии, но мне нужно ограничить список только типом записи 'country'.
Как это можно сделать?
Использование нового решения
<?php
$flags = wpse57444_get_terms('flags',array('parent' => 0,'hide_empty' => 1,'post_types' =>array('country')));
foreach ($flags as $flag) {
$childTerms = wpse57444_get_terms('flags',array('parent' => $flag->term_id,'hide_empty' => 1,'post_types' =>array('country')));
foreach ($childTerms as $childTerm) {
echo $childTerm->name.'<br />';
}
}
?>
Я не могу вывести $childTerm->name. Почему?

К сожалению, это невозможно сделать нативно (пока?). Смотрите тикет в треке: http://core.trac.wordpress.org/ticket/18106
Аналогично, на странице администрирования таксономий количество записей отражает все типы записей. (Я почти уверен, что для этого тоже есть тикет)
http://core.trac.wordpress.org/ticket/14084
Смотрите также эту связанную статью.
Новое решение
После написания решения ниже, я нашел гораздо лучший способ (по крайней мере в том смысле, что можно делать больше) — использовать фильтры, предоставляемые в вызове get_terms()
. Вы можете создать функцию-обертку, которая использует get_terms
и (условно) добавляет фильтр для манипуляции SQL-запросом (чтобы ограничить по типу записи).
Функция принимает те же аргументы, что и get_terms($taxonomies, $args)
. $args
принимает дополнительный аргумент post_types
, который может быть массивом или строкой типов записей.
Но я не могу гарантировать, что все будет работать «как ожидается» (я думаю о подсчете). Похоже, это работает, используя только стандартные $args
для get_terms
.
function wpse57444_get_terms( $taxonomies, $args=array() ){
// Разбираем $args на случай, если это строка запроса.
$args = wp_parse_args($args);
if( !empty($args['post_types']) ){
$args['post_types'] = (array) $args['post_types'];
add_filter( 'terms_clauses','wpse_filter_terms_by_cpt',10,3);
function wpse_filter_terms_by_cpt( $pieces, $tax, $args){
global $wpdb;
// Не использовать подсчет из базы данных
$pieces['fields'] .=", COUNT(*) " ;
// Присоединяем дополнительные таблицы для ограничения по типу записи.
$pieces['join'] .=" INNER JOIN $wpdb->term_relationships AS r ON r.term_taxonomy_id = tt.term_taxonomy_id
INNER JOIN $wpdb->posts AS p ON p.ID = r.object_id ";
// Ограничиваем по типу записи и группируем по term_id для подсчета.
$post_types_str = implode(',',$args['post_types']);
$pieces['where'].= $wpdb->prepare(" AND p.post_type IN(%s) GROUP BY t.term_id", $post_types_str);
remove_filter( current_filter(), __FUNCTION__ );
return $pieces;
}
} // endif post_types set
return get_terms($taxonomies, $args);
}
Использование
$args =array(
'hide_empty' => 0,
'post_types' =>array('country','city'),
);
$terms = wpse57444_get_terms('flag',$args);
Оригинальный обходной путь
Вдохновленный вышеупомянутым тикетом (протестировано, работает у меня)
function wpse57444_filter_terms_by_cpt($taxonomy, $post_types=array() ){
global $wpdb;
$post_types=(array) $post_types;
$key = 'wpse_terms'.md5($taxonomy.serialize($post_types));
$results = wp_cache_get($key);
if ( false === $results ) {
$where =" WHERE 1=1";
if( !empty($post_types) ){
$post_types_str = implode(',',$post_types);
$where.= $wpdb->prepare(" AND p.post_type IN(%s)", $post_types_str);
}
$where .= $wpdb->prepare(" AND tt.taxonomy = %s",$taxonomy);
$query = "
SELECT t.*, COUNT(*)
FROM $wpdb->terms AS t
INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id
INNER JOIN $wpdb->term_relationships AS r ON r.term_taxonomy_id = tt.term_taxonomy_id
INNER JOIN $wpdb->posts AS p ON p.ID = r.object_id
$where
GROUP BY t.term_id";
$results = $wpdb->get_results( $query );
wp_cache_set( $key, $results );
}
return $results;
}
Использование
$terms = wpse57444_filter_terms_by_cpt('flag',array('country','city'));
или
$terms = wpse57444_filter_terms_by_cpt('flag','country');

Это работает, но что я могу сделать со своими $args? То есть... parent=0&orderby=name&hide_empty=0

нет - это должен быть массив: $args = array('parent'=>0,'orderby'=>'name','hide_empty'=>0);
. Я отредактирую это, чтобы разрешить строки запроса...

Куда я могу вставить свои $args в этом примере: $terms = wpse57444_filter_terms_by_cpt('flag',array('country','city'));
?

В этой функции нельзя, только в новом решении: wpse57444_get_terms()

@user1443216 $args
это второй аргумент. Там вы просто указываете wpse57444_get_terms( 'flag', array( 'country', 'city' ) );

@StephenHarris Даже ядро WordPress пытается удалить всё, что похоже на query string. Это только добавляет мусор. Я уже убрал подобную функциональность из всех своих плагинов после разговора с одним из разработчиков ядра.

Если я использую это: $flags = wpse57444_get_terms('flags',array('parent' => 0,'hide_empty' => 1,'post_types' =>array('country')));
foreach ($flags as $flag) {
$childTerms = wpse57444_get_terms('flags',array('parent' => $flag->term_id,'hide_empty' => 1,'post_types' =>array('country')));
foreach ($childTerms as $childTerm) {
echo $childTerm->name.'<br />';
}
}
я не могу вывести $childTerm->name

чтобы получить правильный счетчик, мне пришлось обновить эту строку на:
$pieces['fields'] .= ", COUNT(*) as count ";

До сих пор нет лучшего способа в почти 2019 году? Это печально :)

Приведенное выше решение отлично работает с одним типом записи. С несколькими типами записей у нас возникла проблема с prepare. Похоже, что он не санирует $post_types_str
, что приводит к выводу IN( 'whitepaper, blog' )
вместо IN( 'whitepaper', 'blog' )
. Пожалуйста, ознакомьтесь с этой статьей для обходного решения ссылка

Два пользовательских типа записей 'country' и 'city' и общая таксономия 'flag'. Вам нужно ограничить список типом записей 'country'.
Вот более простое решение:
$posts_in_post_type = get_posts( array(
'fields' => 'ids',
'post_type' => 'country',
'posts_per_page' => -1,
) );
$terms = wp_get_object_terms( $posts_in_post_type, 'flag', array( 'ids' ) ); ?>

Согласно этому тикету в Trac, это рекомендуемый способ: https://core.trac.wordpress.org/ticket/18106#comment:7. Обратите внимание, что это может быть очень медленно, поэтому вам, скорее всего, понадобится реализовать кеширование, если на вашем сайте много записей.

Ответ @stephen-harris выше сработал для меня лишь частично. Если я пытался использовать его дважды на странице, он не работал. Кроме того, идея закапывать mysql-запросы таким образом меня беспокоит — я считаю, что лучше использовать основные методы для достижения решения, чтобы избежать конфликтов с будущими обновлениями WordPress. Вот мое решение, основанное на комментарии #7 в тикете Trac, на который он ссылался:
function get_terms_by_custom_post_type( $post_type, $taxonomy ){
$args = array( 'post_type' => $post_type);
$loop = new WP_Query( $args );
$postids = array();
// Создаем массив ID записей
while ( $loop->have_posts() ) : $loop->the_post();
array_push($postids, get_the_ID());
endwhile;
// Получаем значения таксономии на основе массива ID
$regions = wp_get_object_terms( $postids, $taxonomy );
return $regions;
}
Использование:
$terms = get_terms_by_custom_post_type('country','flag');
Это работает только для одного типа записи и одной таксономии, потому что это именно то, что мне было нужно, но не составит большого труда модифицировать этот код для работы с несколькими значениями.
В том обсуждении на Trac упоминалось, что это решение может плохо масштабироваться, но я работаю в довольно небольших масштабах и не столкнулся с проблемами производительности.

это решение выглядит для меня более "родным" - в любом случае ->
вы должны вызывать "wp_reset_postdata()" сразу после "endwhile" в цикле: https://wordpress.stackexchange.com/questions/144343/wp-reset-postdata-or-wp-reset-query-after-a-custom-loop

[редактирование] Это комментарий к отличному ответу Стивена Харриса.
Он не возвращает никаких терминов при использовании с несколькими типами записей, например так: $flags = wpse57444_get_terms('flags', array('post_types' => array('country','city')));
. Это происходит потому, что $wpdb->prepare санирует строку $post_types_str в p.post_type IN('country,city')
, тогда как должно быть p.post_type IN('country','city')
. Смотрите этот тикет: 11102. Используйте решение из этой темы, чтобы обойти проблему: https://stackoverflow.com/a/10634225

Я также пробовал использовать ответ @Stephen Harris, но запрос, который мне был нужен, оказался довольно сложным для написания в виде одного запроса с использованием частей фильтра.
Кроме того, мне также нужно было использовать эту функцию несколько раз на одной странице, и я решил проблему, объявив функцию wpse_filter_terms_by_cpt
вне обёртки.
В любом случае, ответ @Mark Pruce, на мой взгляд, подходит лучше, по тем же причинам, которые он указал, хотя и требует выполнения ещё одного запроса (и соответствующего цикла) для подготовки аргументов функции wp_get_object_terms
.
