Как получить записи из двух категорий с помощью WP_Query?
Я использую 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>
Есть предложения?
По моему опыту, использование фильтров 'posts_*'
('posts_request'
, 'posts_where'
...) в комбинации с str_replace
/ preg_replace
ненадежно и негибко:
Ненадежно, потому что если другой фильтр модифицирует один из этих фильтров, в лучшем случае можно получить неожиданные результаты, в худшем — ошибки SQL.
Негибко, потому что изменение аргумента, например, 'include_children' для категорий, или повторное использование кода для, скажем, 3 терминов вместо 2 требует много работы.
Кроме того, адаптация кода для мультисайтовой совместимости требует ручного редактирования SQL.
Поэтому иногда, даже если это не самое лучшее решение с точки зрения производительности, более канонический подход оказывается самым гибким и удобным.
А производительность можно улучшить с помощью некоторых трюков кэширования...
Мое предложение:
- написать функцию, которая использует
usort
для сортировки постов, полученных из разных запросов (например, по одному на термин) - написать функцию, которая при первом запуске выполняет отдельные запросы, объединяет результаты, сортирует их, кэширует и возвращает. При последующих запросах просто возвращает закэшированные результаты
- обрабатывать инвалидацию кэша при необходимости
##Код##
Сначала функция для сортировки постов:
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' );
Обратите внимание:
- ВАЖНО: Если некоторые посты принадлежат более чем одной категории из переданных в функцию (например, в случае OP некоторые посты имеют и категорию 9, и 11), количество полученных постов будет не таким, как ожидалось, потому что функция вернет такие посты только один раз
- Код требует PHP 5.3+

Согласно Кодексу:
Показать записи из нескольких категорий (Отображать записи, которые принадлежат этим категориям, используя ID категорий):
$query = new WP_Query( 'cat=9,11' );
И, в разделе параметров пагинации Кодекса указано, что параметр 'showposts'
устарел и заменён на 'posts_per_page'
.
posts_per_page
(int) - количество записей для отображения на странице (доступно с версии 2.1, заменил параметрshowposts
).

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

То, о чем вы спрашиваете, почти дублирует этот вопрос: Как создать собственный вложенный 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
и воспроизвести эффект.

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

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

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