Возможно ли получить get_terms по таксономии И типу записи?

9 апр. 2011 г., 03:38:03
Просмотры: 27.8K
Голосов: 18

У меня есть 2 пользовательских типа записей 'bookmarks' и 'snippets' и общая таксономия 'tag'. Я могу сгенерировать список всех терминов в таксономии с помощью get_terms(), но не могу понять, как ограничить список по типу записи. По сути, я ищу что-то вроде этого:

get_terms(array('taxonomy' => 'tag', 'post_type' => 'snippet'));

Есть ли способ достичь этого? Буду признателен за идеи!

Да, я использую WP 3.1.1

0
Все ответы на вопрос 7
5
14

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

Затем я получил все термины этой таксономии с помощью 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;
    }
}
9 апр. 2011 г. 04:21:43
Комментарии

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

kaiser kaiser
9 апр. 2011 г. 13:48:50

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

t31os t31os
9 апр. 2011 г. 14:35:20

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

kaiser kaiser
9 апр. 2011 г. 15:25:26

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

kaiser kaiser
24 июл. 2011 г. 18:24:17

+1 отличная функция! 2 опечатки: $taxonomies используется в функции как $taxonomy, а $terms[] = $c; должно быть $terms[] = $t;

Rob Vermeer Rob Vermeer
5 февр. 2012 г. 19:45:27
3
13

Отличный вопрос и содержательные ответы.

Мне очень понравился подход @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. Обратите внимание, что приоритет фильтра установлен очень высоким, чтобы он всегда выполнялся последним.

4 апр. 2015 г. 06:09:25
Комментарии

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

ZalemCitizen ZalemCitizen
28 сент. 2020 г. 10:58:33

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

LBF LBF
28 окт. 2022 г. 00:03:13

Будет ли аргумент hide_empty по-прежнему применяться ко всем записям, а не только к выбранному типу записи?

Adrian B Adrian B
4 окт. 2023 г. 00:43:15
3
12

Вот ещё один способ сделать что-то подобное с одним 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;

}
26 июл. 2011 г. 11:40:10
Комментарии

Да! Это именно то, что мне нужно.

Gavin Hewitt Gavin Hewitt
7 нояб. 2011 г. 03:33:30

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

Anshad Vattapoyil Anshad Vattapoyil
20 июн. 2014 г. 05:20:40

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

Codesmith Codesmith
12 мар. 2019 г. 19:19:43
0
10

Я написал функцию, которая позволяет передавать 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);
8 июн. 2012 г. 08:14:39
1

Мне не удалось заставить аргументы get_terms работать с версией кода Гэвина выше, но в итоге получилось, изменив

$terms2 = get_terms( $taxonomy );

на

$terms2 = get_terms( $taxonomy, $args );

как это было в оригинальной функции от Bainternet.

10 апр. 2011 г. 05:16:13
Комментарии

Исправлено в текущей версии

Gavin Hewitt Gavin Hewitt
10 апр. 2011 г. 14:06:01
5

@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;
    }
}

РЕДАКТИРОВАНО: Добавил исправления. Но почему-то у меня все равно не работает. Подсчет по-прежнему показывает неверное значение.

9 апр. 2011 г. 14:45:03
Комментарии

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

Bainternet Bainternet
9 апр. 2011 г. 16:34:55

Я обновил свой ответ с исправлением подсчета терминов.

Bainternet Bainternet
9 апр. 2011 г. 17:25:26

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

t31os t31os
10 апр. 2011 г. 13:06:44

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

Gavin Hewitt Gavin Hewitt
10 апр. 2011 г. 13:58:36

Как можно вызвать это? print_r(get_terms_by_post_typea(array('event','category','',array())); этот вариант выдает Warning: Invalid argument supplied for foreach() для строки foreach ($current_terms as $t){

Anshad Vattapoyil Anshad Vattapoyil
20 июн. 2014 г. 05:32:21
1

Избегание дубликатов:

//избегаем дубликатов
    $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;
        }
26 февр. 2013 г. 19:10:19
Комментарии

Можете объяснить, почему это решает проблему? Смотрите [ответ].

brasofilo brasofilo
26 февр. 2013 г. 19:38:46