Получение терминов get_terms по типу записи

4 июл. 2012 г., 19:29:23
Просмотры: 33.6K
Голосов: 24

У меня есть два пользовательских типа записей '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. Почему?

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

Можешь пояснить немного подробнее?

TheDeadMedic TheDeadMedic
4 июл. 2012 г. 19:42:36
Все ответы на вопрос 5
10
20

К сожалению, это невозможно сделать нативно (пока?). Смотрите тикет в треке: 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');
4 июл. 2012 г. 20:00:56
Комментарии

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

user1443216 user1443216
4 июл. 2012 г. 22:11:24

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

Stephen Harris Stephen Harris
4 июл. 2012 г. 22:44:51

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

user1443216 user1443216
4 июл. 2012 г. 22:59:45

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

Stephen Harris Stephen Harris
4 июл. 2012 г. 23:21:09

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

kaiser kaiser
4 июл. 2012 г. 23:21:12

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

kaiser kaiser
4 июл. 2012 г. 23:22:25

Если я использую это: $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

user1443216 user1443216
7 июл. 2012 г. 17:03:36

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

locomo locomo
9 мар. 2018 г. 23:34:12

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

trainoasis trainoasis
14 дек. 2018 г. 00:20:26

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

Redox Redox
4 нояб. 2019 г. 10:51:21
Показать остальные 5 комментариев
1

Два пользовательских типа записей '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' ) ); ?>
7 нояб. 2017 г. 14:44:29
Комментарии

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

Jules Jules
11 нояб. 2020 г. 16:25:33
1

Ответ @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 упоминалось, что это решение может плохо масштабироваться, но я работаю в довольно небольших масштабах и не столкнулся с проблемами производительности.

7 мар. 2017 г. 18:52:27
Комментарии

это решение выглядит для меня более "родным" - в любом случае ->

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

Thomas Fellinger Thomas Fellinger
22 нояб. 2017 г. 12:04:47
0

[редактирование] Это комментарий к отличному ответу Стивена Харриса.

Он не возвращает никаких терминов при использовании с несколькими типами записей, например так: $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

13 февр. 2013 г. 16:42:10
0

Я также пробовал использовать ответ @Stephen Harris, но запрос, который мне был нужен, оказался довольно сложным для написания в виде одного запроса с использованием частей фильтра.

Кроме того, мне также нужно было использовать эту функцию несколько раз на одной странице, и я решил проблему, объявив функцию wpse_filter_terms_by_cpt вне обёртки.

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

3 авг. 2017 г. 12:31:57