Как получить записи из двух категорий с помощью WP_Query?

2 февр. 2014 г., 14:45:05
Просмотры: 21.7K
Голосов: 5

Я использую WP_Query для создания секций 'последние записи' и 'популярные записи' на главной странице. Я пытаюсь вывести 5 записей из 2 категорий (9 и 11), но отображаются только записи из категории 9.

Вот PHP-код, который я использую для 'последних записей'-

<ul>
        <?php 

        $cat = array(9,11);
        $showposts = 5;
        $paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
        $args=array(
            'category__in' => $cat, 
            'showposts' => $showposts,
            'paged' => $paged,
            'orderby' => 'post_date',
            'order' => 'DESC',
            'post_status' => 'publish',
           );

        $the_query = new WP_Query ( $args ); //запрос

        $i = 0;while ($the_query->have_posts() ) : $the_query->the_post(); //начало цикла
        ?>

        <?php
        if($i==0){ //Задает вывод для первой записи
        ?>  
            <li class="first-news">
                <div class="post-thumbnail"><a href="<?php the_permalink(); ?>"><?php the_post_thumbnail(350,187); ?></a></div>
                <h2 class="post-box-title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
                <p class="post-meta"><span class="tie-date"><?php the_time('F jS, Y') ?></span></p>
                <div class="entry"><?php the_excerpt(); ?></div>
                <div><a class="more-link" href="<?php the_permalink(); ?>">Читать далее »</a></div>

            </li>


        <?php
            $i++;
             } else { ?>

                <li class="other-news rar">
                <div class="post-thumbnail"><a href="<?php the_permalink(); ?>"><?php the_post_thumbnail(145,93); ?></a></div>
                <h3 class="post-box-title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
                <p class="post-meta"><span class="tie-date"><?php the_time('F jS, Y') ?></span></p>
           </li>

        <?php } endwhile; //конец цикла ?>
        <?php wp_reset_postdata(); // сброс запроса ?>
        </ul> 

Есть предложения?

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

Хм, похоже, это должно работать. Что произойдет, если попробовать запросить только категорию 11?

Rarst Rarst
2 февр. 2014 г. 15:24:41

Пожалуйста, проверьте не только запрос 5 постов - возможно, первые пять принадлежат только категории 9.

fischi fischi
2 февр. 2014 г. 16:19:50

'showposts' устарел. Попробуйте использовать 'posts_per_page' вместо этого.

Mayeenul Islam Mayeenul Islam
2 февр. 2014 г. 17:23:02

Ой =/ параметр posts_per_page у меня не работал

user3205234 user3205234
3 февр. 2014 г. 16:12:37

Когда функции объявляются устаревшими, это значит, что они перестанут работать в будущих обновлениях WordPress?

user3205234 user3205234
3 февр. 2014 г. 16:12:59

@user3205234 Когда функции объявляются устаревшими, это значит, что они перестанут работать в будущих обновлениях WordPress? - В общем, да. Но WordPress имеет обширную поддержку старых версий, поэтому пока это не заметно. Однако в будущем WordPress может прекратить поддержку и может перенести их в плагины для конкретных версий.

Mayeenul Islam Mayeenul Islam
26 апр. 2015 г. 09:34:21
Показать остальные 1 комментариев
Все ответы на вопрос 3
0

По моему опыту, использование фильтров 'posts_*' ('posts_request', 'posts_where'...) в комбинации с str_replace / preg_replace ненадежно и негибко:

Ненадежно, потому что если другой фильтр модифицирует один из этих фильтров, в лучшем случае можно получить неожиданные результаты, в худшем — ошибки SQL.

Негибко, потому что изменение аргумента, например, 'include_children' для категорий, или повторное использование кода для, скажем, 3 терминов вместо 2 требует много работы.

Кроме того, адаптация кода для мультисайтовой совместимости требует ручного редактирования SQL.

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

А производительность можно улучшить с помощью некоторых трюков кэширования...

Мое предложение:

  1. написать функцию, которая использует usort для сортировки постов, полученных из разных запросов (например, по одному на термин)
  2. написать функцию, которая при первом запуске выполняет отдельные запросы, объединяет результаты, сортирует их, кэширует и возвращает. При последующих запросах просто возвращает закэшированные результаты
  3. обрабатывать инвалидацию кэша при необходимости

##Код##

Сначала функция для сортировки постов:

function my_date_terms_posts_sort( Array $posts, $order = 'DESC' ) {
  if ( ! empty( $posts ) ) {
    usort( $posts, function( WP_Post $a, WP_Post $b ) use ( $order ) {
      $at = (int) mysql2date( 'U', $a->post_date );
      $bt = (int) mysql2date( 'U', $b->post_date );
      $orders = strtoupper($order) === 'ASC' ? array( 1, -1 ) : array( -1, 1 );
      return $at === $bt ? 0 : ( $at > $bt ) ? $orders[0] : $orders[1];
    } );
  }
  return $posts;
}

Затем функция для получения "свежих" (не кэшированных) результатов:

function my_fresh_terms_get_posts( $args, $terms, $tax_query_args = NULL ) {
  $posts = array();
  // нам нужно знать хотя бы таксономию
  if ( ! is_array( $tax_query_args ) || ! isset( $tax_query_args['taxonomy'] ) ) return;
  // обрабатываем базовый tax_query
  $base_tax_query = isset( $args['tax_query'] ) ? $args['tax_query'] : array();
  // выполняем запрос для каждого термина
  foreach ( $terms as $term ) {
    $term_tax_query = wp_parse_args( array(
      'terms' => array( $term ),
      'field' => is_numeric( $term ) ? 'term_id' : 'slug'
    ), $tax_query_args );
    $args['tax_query'] = array_merge( $base_tax_query, array($term_tax_query) );
    $q = new WP_Query( $args ); 
    if ( $q->have_posts() ) {
      // объединяем полученные посты в массив $posts
      // предотвращаем дубликаты, используя ID как ключи массива
      $ids = wp_list_pluck( $q->posts, 'ID' );
      $keyed = array_combine( $ids, array_values( $q->posts ) );
      $posts += $keyed;
    }
  }
  return $posts;
}

Теперь функция, которая проверяет кэш и возвращает его, если он есть, или возвращает "свежие" результаты

function my_terms_get_posts( $args, $terms, $tax_query_args = NULL, $order = 'DESC' ) {
  // нам нужно знать хотя бы таксономию
  if ( ! is_array( $tax_query_args ) || ! isset( $tax_query_args['taxonomy'] ) ) return;
  $tax = $tax_query_args['taxonomy'];
  // получаем закэшированные результаты
  $cached = get_transient( "my_terms_get_posts_{$tax}" );
  if ( ! empty( $cached ) ) return $cached;
  // нет закэшированных результатов, получаем "свежие" посты
  $posts = my_fresh_terms_get_posts( $args, $terms, $tax_query_args );
  if ( ! empty($posts) ) {
    // сортируем посты и кэшируем их
    $posts = my_date_terms_posts_sort( $posts, $order );
    set_transient( "my_terms_get_posts_{$tax}",  $posts, DAY_IN_SECONDS );
  }
  return $posts;
}

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

add_action( 'set_object_terms', function( $object_id, $terms, $tt_ids, $taxonomy ) {
  $taxonomies = get_taxonomies( array( 'object_type' => array('post') ), 'names' );
  if ( in_array( $taxonomy, (array) $taxonomies ) ) {
    delete_transient( "my_terms_get_posts_{$taxonomy}" );
  }
}, 10, 4 );

##Использование##

// количество постов для каждого термина
// общее количество полученных постов будет равно этому числу x количество переданных терминов
// в функцию my_terms_get_posts
$perterm = 5;

// сначала определяем общие аргументы:
$paged = ( get_query_var('paged') ) ? get_query_var('paged') : 1;
$args = array(
  'posts_per_page' => $perterm,
  'paged' => $paged,
);

// аргументы таксономии
$base_tax_args = array(
  'taxonomy' => 'category'
);
$terms = array( 9, 11 ); // можно также использовать слаги

// получаем посты
$posts = my_terms_get_posts( $args, $terms, $base_tax_args );

// цикл
if ( ! empty( $posts ) ) {
  foreach ( $posts as $_post ) {
    global $post;
    setup_postdata( $_post );
    //-------------------------> здесь идет код цикла    
  }
  wp_reset_postdata();
}

Функции достаточно гибкие, чтобы использовать сложные запросы, даже запросы для дополнительных таксономий:

Например:

$args = array(
  'posts_per_page' => $perterm,
  'paged' => $paged,
  'post_status' => 'publish',
  'tax_query' => array(
     'relation' => 'AND',
     array(
       'taxonomy' => 'another_taxonomy',
       'terms' => array( 'foo', 'bar' ),
       'field' => 'id'
     )
   )
);

$base_tax_args = array(
  'taxonomy' => 'category',
  'include_children' => FALSE
);

$terms = array( 'a-category', 'another-one' );

$posts = my_terms_get_posts( $args, $terms, $base_tax_args );

Таким образом, 'tax_query', установленный в массиве $args, будет динамически объединен с аргументами таксономии в $base_tax_args для каждого из терминов в массиве $terms.

Также можно сортировать посты в порядке возрастания:

$posts = my_terms_get_posts( $args, $terms, $base_tax_args, 'ASC' );

Обратите внимание:

  1. ВАЖНО: Если некоторые посты принадлежат более чем одной категории из переданных в функцию (например, в случае OP некоторые посты имеют и категорию 9, и 11), количество полученных постов будет не таким, как ожидалось, потому что функция вернет такие посты только один раз
  2. Код требует PHP 5.3+
19 мар. 2014 г. 22:34:00
2

Согласно Кодексу:

Показать записи из нескольких категорий (Отображать записи, которые принадлежат этим категориям, используя ID категорий):

$query = new WP_Query( 'cat=9,11' );

И, в разделе параметров пагинации Кодекса указано, что параметр 'showposts' устарел и заменён на 'posts_per_page'.

posts_per_page (int) - количество записей для отображения на странице (доступно с версии 2.1, заменил параметр showposts).

2 февр. 2014 г. 17:29:44
Комментарии

Спасибо за ответ, я попробую ваш вариант после того, как опробую способ от s_ha_dum. (ранее posts per page почему-то не работал для меня)

user3205234 user3205234
3 февр. 2014 г. 16:08:46

@user3205234: Вам стоит использовать posts_per_page, но это не решит проблему, которая, как я понимаю, у вас есть — когда одна из категорий заполняет лимит постов до того, как появится другая категория.

s_ha_dum s_ha_dum
3 февр. 2014 г. 16:11:46
5

То, о чем вы спрашиваете, почти дублирует этот вопрос: Как создать собственный вложенный meta_query, используя posts_where / posts_join?

Проблема, как отмечает @fischi, почти наверняка заключается в том, что результаты берутся из одной категории или другой и достигают лимита постов до того, как будут представлены обе. Чтобы это работало, вам нужен UNION. WP_Query не поддерживает такую логику, но с помощью хуков можно добиться желаемого.

$cat = 9; // ваша первая категория
$showposts = 5; // фактическое количество результатов в два раза больше этого значения
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$args=array(
  'cat' => $cat, 
  'posts_per_page' => $showposts,
  'paged' => $paged,
  'orderby' => 'post_date',
  'order' => 'DESC',
  'post_status' => 'publish',
);

function create_cat_union($clauses) {
  remove_filter('posts_request','create_cat_union',1);
  $clauses = str_replace('SQL_CALC_FOUND_ROWS','',$clauses,$scfr);

  $scfr = (0 < $scfr) ? 'SQL_CALC_FOUND_ROWS' : '';

  $pattern = 'wp_term_relationships.term_taxonomy_id IN \(([0-9,]+)\)';
  $clause2 = preg_replace('|'.$pattern.'|','wp_term_relationships.term_taxonomy_id IN (11)',$clauses); // измените это

  return "SELECT {$scfr} u.* FROM (({$clauses}) UNION ({$clause2})) as u";
}
add_filter('posts_request','create_cat_union',1,2);

$the_query = new WP_Query ( $args ); // запрос
var_dump($the_query->posts);

Несколько замечаний: WP_Query обработает аргумент категории и найдет дочерние категории, так что первый UNION будет включать все дочерние элементы категории 9. Я не дублировал эту логику для категории 11. Если вам нужна эта функциональность, вы можете обратиться к исходному коду WP_Query и воспроизвести эффект.

2 февр. 2014 г. 17:56:28
Комментарии

Ой, извините, мне следовало поискать ответ получше =/

user3205234 user3205234
3 февр. 2014 г. 16:05:40

Не стоит извинений. Упомянутый вопрос приблизил бы вас к ответу, и если ваши знания PHP/MySQL на уровне, вы могли бы догадаться, но это не совсем дубликат.

s_ha_dum s_ha_dum
3 февр. 2014 г. 16:07:39

Спасибо за ответ, я попробую. Раньше не слышал о UNION, для меня это что-то новое, что стоит изучить. Хотя самые новые 5 записей точно из обеих категорий.

user3205234 user3205234
3 февр. 2014 г. 16:07:43

Ах, мой MySQL пока не слишком хорош — мне еще многому нужно учиться! Спасибо за помощь =)

user3205234 user3205234
3 февр. 2014 г. 16:09:42

Даже если "пять самых новых" будут из обеих категорий, то без использования UNION вы получите 3 из одной и 2 из другой, или 4 из одной и 1 из другой — это неравномерное распределение из каждой категории.

s_ha_dum s_ha_dum
3 февр. 2014 г. 16:55:09