Возможно ли получить get_terms по таксономии И типу записи?
У меня есть 2 пользовательских типа записей 'bookmarks' и 'snippets' и общая таксономия 'tag'. Я могу сгенерировать список всех терминов в таксономии с помощью get_terms(), но не могу понять, как ограничить список по типу записи. По сути, я ищу что-то вроде этого:
get_terms(array('taxonomy' => 'tag', 'post_type' => 'snippet'));
Есть ли способ достичь этого? Буду признателен за идеи!
Да, я использую WP 3.1.1

Так получилось, что мне понадобилось нечто подобное для проекта, над которым я работаю. Я просто написал запрос для выборки всех записей пользовательского типа, затем проверил, какие термины моей таксономии они используют.
Затем я получил все термины этой таксономии с помощью get_terms()
и использовал только те, которые были в обоих списках, обернул это в функцию, и всё было готово.
Но потом мне понадобилось больше, чем просто ID: мне нужны были названия, поэтому я добавил новый аргумент $fields
, чтобы можно было указать функции, что возвращать. Затем я понял, что get_terms
принимает множество аргументов, а моя функция ограничивалась только терминами, используемыми в типе записи, поэтому я добавил ещё одно условие if
, и вот что получилось:
Функция:
/* Получить термины, ограниченные типом записи
@ $taxonomies - (string|array) (обязательно) Таксономии, из которых нужно получить термины.
@ $args - (string|array) Все возможные аргументы get_terms http://codex.wordpress.org/Function_Reference/get_terms
@ $post_type - (string|array) Типы записей, к которым нужно ограничить термины
@ $fields - (string) Что возвращать (по умолчанию all). Допустимые значения: ID, name, all, get_terms.
Если вы хотите использовать аргументы get_terms, то $fields должен быть установлен в 'get_terms'
*/
function get_terms_by_post_type($taxonomies,$args,$post_type,$fields = 'all'){
$args = array(
'post_type' => (array)$post_type,
'posts_per_page' => -1
);
$the_query = new WP_Query( $args );
$terms = array();
while ($the_query->have_posts()){
$the_query->the_post();
$curent_terms = wp_get_object_terms( $post->ID, $taxonomy);
foreach ($curent_terms as $t){
//избегаем дубликатов
if (!in_array($t,$terms)){
$terms[] = $c;
}
}
}
wp_reset_query();
//возвращаем массив объектов терминов
if ($fields == "all")
return $terms;
//возвращаем массив ID терминов
if ($fields == "ID"){
foreach ($terms as $t){
$re[] = $t->term_id;
}
return $re;
}
//возвращаем массив названий терминов
if ($fields == "name"){
foreach ($terms as $t){
$re[] = $t->name;
}
return $re;
}
// получаем термины с аргументами get_terms
if ($fields == "get_terms"){
$terms2 = get_terms( $taxonomies, $args );
foreach ($terms as $t){
if (in_array($t,$terms2)){
$re[] = $t;
}
}
return $re;
}
}
Использование:
Если вам нужен только список ID терминов:
$terms = get_terms_by_post_type('tag','','snippet','ID');
Если вам нужен только список названий терминов:
$terms = get_terms_by_post_type('tag','','snippet','name');
Если вам нужен список объектов терминов:
$terms = get_terms_by_post_type('tag','','snippet');
А если вам нужно использовать дополнительные аргументы get_terms, такие как: orderby, order, hierarchical ...
$args = array('orderby' => 'count', 'order' => 'DESC', 'hide_empty' => 1);
$terms = get_terms_by_post_type('tag',$args,'snippet','get_terms');
Наслаждайтесь!
Обновление:
Чтобы исправить подсчёт терминов для определённого типа записи, измените:
foreach ($current_terms as $t){
//избегаем дубликатов
if (!in_array($t,$terms)){
$terms[] = $t;
}
}
на:
foreach ($current_terms as $t){
//избегаем дубликатов
if (!in_array($t,$terms)){
$t->count = 1;
$terms[] = $t;
}else{
$key = array_search($t, $terms);
$terms[$key]->count = $terms[$key]->count + 1;
}
}

не будет ли лучше использовать (array) $args
вместо списка из 4 переменных $vars? Это позволит не заботиться о порядке передачи аргументов, например так: get_terms_by_post_type( $args = array( 'taxonomies', 'args', 'post_type', 'fields' => 'all') )
, а затем обращаться к ним внутри функции через $args['taxonomies']
. Это поможет избежать передачи пустых значений и необходимости запоминать порядок аргументов. Также я бы рекомендовал использовать одинарные кавычки вместо двойных. Я заметил, что они могут быть до пяти раз быстрее.

@kaiser - Строки в двойных кавычках требуют парсинга, тогда как значения в одинарных кавычках всегда трактуются буквально. Когда вы используете переменные внутри строки, имеет смысл и вполне допустимо применять двойные кавычки, но для строк без переменных идеальнее использовать одинарные (так как они не требуют парсинга) и они немного быстрее (в большинстве случаев речь идет о миллисекундах).

@t31os - Совершенно верно. Я всё же предпочитаю 'это моё настроение: '.$value
вместо "это моё настроение: $value"
из-за читаемости. Что касается скорости: разница не небольшая - я замерял до пяти раз. И если вы используете двойные кавычки по всему шаблону, они быстро накапливаются при большом количестве запросов. В любом случае, хорошо, что вы это прояснили.

@t31os После обсуждения я снова измерил скорость работы "
по сравнению с '
и оказался неправ. Разница настолько незначительна, что её никто не заметит.

Отличный вопрос и содержательные ответы.
Мне очень понравился подход @jessica с использованием фильтра terms_clauses, так как он расширяет функцию get_terms очень логичным способом.
Мой код является продолжением её идеи, с добавлением SQL от @braydon для уменьшения дубликатов. Также он позволяет использовать массив post_types:
/**
* my_terms_clauses
*
* фильтр для clauses в terms
*
* @param $clauses array
* @param $taxonomy string
* @param $args array
* @return array
**/
function my_terms_clauses($clauses, $taxonomy, $args)
{
global $wpdb;
if ($args['post_types'])
{
$post_types = implode("','", array_map('esc_sql', (array) $args['post_types']));
// разрешаем массивы
if ( is_array($args['post_types']) ) {
$post_types = implode( "','", $args['post_types'] );
}
$clauses['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";
$clauses['where'] .= " AND p.post_type IN ('". $post_types. "') GROUP BY t.term_id";
}
return $clauses;
}
add_filter('terms_clauses', 'my_terms_clauses', 99999, 3);
Поскольку get_terms не имеет условия GROUP BY, мне пришлось добавить его в конец WHERE. Обратите внимание, что приоритет фильтра установлен очень высоким, чтобы он всегда выполнялся последним.

Вам следует заменить $post_types = $args['post_types'];
на $post_types = implode("','", array_map('esc_sql', (array) $args['post_types']));
и убрать esc_sql()
из условия IN
, иначе в этом условии появятся \'
между типами записей при указании нескольких типов в $args['post_types']

Единственное решение, которое позволило мне использовать get_terms и сработало для меня в 2022 году

Вот ещё один способ сделать что-то подобное с одним SQL-запросом:
static public function get_terms_by_post_type( $taxonomies, $post_types ) {
global $wpdb;
$query = $wpdb->prepare(
"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 p.post_type IN('%s') AND tt.taxonomy IN('%s')
GROUP BY t.term_id",
join( "', '", $post_types ),
join( "', '", $taxonomies )
);
$results = $wpdb->get_results( $query );
return $results;
}

print_r(get_terms_by_post_type(array('category') , array('event') ));
выводит Warning: Missing argument 2 for wpdb::prepare()

Возможно, я ошибаюсь, но навскидку не думаю, что эти 'join' выражения будут работать - то есть, они сработают только если передать массивы с одним значением. Это потому что функция prepare экранирует все сгенерированные одинарные кавычки и будет рассматривать каждый 'join' как одну строку.

Я написал функцию, которая позволяет передавать post_type
в массиве $args
функции get_terms()
:
Спасибо @braydon за написание SQL-запроса.
/**
* terms_clauses
*
* Фильтрует части SQL-запроса для терминов
*
* @param $clauses array
* @param $taxonomy string
* @param $args array
* @return array
**/
function terms_clauses($clauses, $taxonomy, $args)
{
global $wpdb;
if ($args['post_type'])
{
$clauses['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";
$clauses['where'] .= " AND p.post_type='{$args['post_type']}'";
}
return $clauses;
}
add_filter('terms_clauses', 'terms_clauses', 10, 3);

@Bainternet: Спасибо! Мне пришлось немного изменить функцию, так как она не работала (были опечатки). Единственная проблема сейчас - неправильный подсчет терминов. Подсчет не учитывает тип записи, поэтому я не думаю, что можно использовать get_terms() в этом случае.
function get_terms_by_post_type($post_type,$taxonomy,$fields='all',$args){
$q_args = array(
'post_type' => (array)$post_type,
'posts_per_page' => -1
);
$the_query = new WP_Query( $q_args );
$terms = array();
while ($the_query->have_posts()) { $the_query->the_post();
global $post;
$current_terms = get_the_terms( $post->ID, $taxonomy);
foreach ($current_terms as $t){
//избегаем дубликатов
if (!in_array($t,$terms)){
$t->count = 1;
$terms[] = $t;
}else{
$key = array_search($t, $terms);
$terms[$key]->count = $terms[$key]->count + 1;
}
}
}
wp_reset_query();
//возвращаем массив объектов терминов
if ($fields == "all")
return $terms;
//возвращаем массив ID терминов
if ($fields == "ID"){
foreach ($terms as $t){
$re[] = $t->term_id;
}
return $re;
}
//возвращаем массив названий терминов
if ($fields == "name"){
foreach ($terms as $t){
$re[] = $t->name;
}
return $re;
}
// получаем термины с аргументами get_terms
if ($fields == "get_terms"){
$terms2 = get_terms( $taxonomy, $args );
foreach ($terms as $t){
if (in_array($t,$terms2)){
$re[] = $t;
}
}
return $re;
}
}
РЕДАКТИРОВАНО: Добавил исправления. Но почему-то у меня все равно не работает. Подсчет по-прежнему показывает неверное значение.

Это уже другая история, но вы можете вести подсчет, избегая дубликатов в цикле while.

Пожалуйста, не добавляйте продолжения в виде ответов, если только вы специально не отвечаете на свой собственный вопрос. Дополнения следует вносить в исходный вопрос.

@t31os: Ах да, я думал, как добавить дополнение. Не догадался отредактировать свой вопрос. Спасибо!

Избегание дубликатов:
//избегаем дубликатов
$mivalor=$t->term_id;
$arr=array_filter($terms, function ($item) use ($mivalor) {return isset($item->term_id) && $item->term_id == $mivalor;});
if (empty($arr)){
$t->count=1;
$terms[] = $t;
}else{
$key = array_search($t, $terms);
$terms[$key]->count = $terms[$key]->count + 1;
}
