Получить все категории и записи в этих категориях
Я ищу решение, которое позволит мне вывести следующее:
Кат 1 Кат 2 Кат 3
Запись 1 Запись 1 Запись 1
Запись 2 Запись 2 Запись 2
Запись 3 Запись 3
Запись 4
РЕДАКТИРОВАНО
Я ищу решение, которое будет требовать только один запрос к базе данных! Поэтому, если в вашем коде есть foreach
, за которым следует new WP_Query
, то это не то, что мне нужно (я планирую разместить это на главной странице своего сайта).
ПЕРЕСМОТР №2
Я никогда не работал с Transient API, пока сегодня не увидел ответ @MikeSchinkel в этом посте. Это вдохновило меня снова пересмотреть данный пост. После некоторых тестов я пришел к следующему:
Время выполнения сократилось с ~0.07 секунд до ~0.002 секунд
Время запросов к базе данных уменьшилось примерно вдвое
С использованием транзиентов выполняется только 2 запроса к БД
Как работает код (обсуждаю только изменения по сравнению с оригинальным кодом из ПЕРЕСМОТРА):
ШАГ 1
Нам нужно сохранить значение $q
в транзиент — это значение содержит список категорий с заголовками постов.
ШАГ 2
Сначала проверяем, существует ли транзиент. Если его нет — создаем. Если существует — извлекаем его данные.
ШАГ 3
Эти данные передаются в цикл foreach
для вывода списка с названиями категорий и заголовками постов.
ШАГ 4
В текущем виде транзиент будет обновляться каждые 12 часов. Этот интервал можно изменить под свои нужды. Однако транзиент нужно удалять и создавать заново при каждом изменении статуса поста (например, черновик → опубликовано, новый пост или удаление поста). Для этого используется delete_transient
, подключенный к хуку transition_post_status
, который срабатывает при каждом изменении статуса поста.
Полный код:
В файле functions.php
add_action( 'transition_post_status', 'publish_new_post', 10, 3 );
function publish_new_post() {
delete_transient( 'category_list' );
}
В шаблоне, где нужно вывести список
<?php
if ( false === ( $q = get_transient( 'category_list' ) ) ) {
$args = array(
'posts_per_page' => -1
);
$query = new WP_Query($args);
$q = array();
while ( $query->have_posts() ) {
$query->the_post();
$a = '<a href="'. get_permalink() .'">' . get_the_title() .'</a>';
$categories = get_the_category();
foreach ( $categories as $key=>$category ) {
$b = '<a href="' . get_category_link( $category ) . '">' . $category->name . '</a>';
}
$q[$b][] = $a; // Создаем массив с названиями категорий и заголовками постов
}
/* Восстанавливаем оригинальные данные поста */
wp_reset_postdata();
set_transient( 'category_list', $q, 12 * HOUR_IN_SECONDS );
}
foreach ($q as $key=>$values) {
echo $key;
echo '<ul>';
foreach ($values as $value){
echo '<li>' . $value . '</li>';
}
echo '</ul>';
}
?>
ПЕРЕСМОТР
Недавно я разработал очень легковесное решение, которое работает значительно быстрее других возможных вариантов. На тестовом сайте общее время генерации составляет всего ~0.07 секунд и всего 6 запросов к БД (согласно Query Monitor), тогда как другие методы дают время генерации ~0.35 секунд и 50 дополнительных запросов.
Разберем мой метод:
ШАГ 1
Сначала создаем кастомный запрос с помощью WP_Query
для получения всех опубликованных постов
$args = array(
'posts_per_page' => -1
);
$query = new WP_Query($args);
$q = array();
while ( $query->have_posts() ) {
}
/* Восстанавливаем оригинальные данные поста */
wp_reset_postdata();
ШАГ 2
Используя get_the_category
, получаем список всех категорий поста.
$categories = get_the_category();
foreach ( $categories as $key=>$category ) {
$b = '<a href="' . get_category_link( $category ) . '">' . $category->name . '</a>';
}
ШАГ 3
Присваиваем переменные заголовку поста и его категориям
$a = '<a href="'. get_permalink() .'">' . get_the_title() .'</a>';
и
$b = '<a href="' . get_category_link( $category ) . '">' . $category->name . '</a>';
ШАГ 4
Объединяем эти переменные в многомерный массив
$q[$b][] = $a;
Чтобы увидеть содержимое массива, используйте var_dump
?><pre><?php var_dump($q); ?></pre><?php
ШАГ 5
С помощью циклов foreach
создаем список постов, отсортированный по категориям
foreach ($q as $key=>$values) {
echo $key;
echo '<ul>';
foreach ($values as $value){
echo '<li>' . $value . '</li>';
}
echo '</ul>';
}
ВСЁ ВМЕСТЕ!
Полный код:
<?php
$args = array(
'posts_per_page' => -1
);
$query = new WP_Query($args);
$q = array();
while ( $query->have_posts() ) {
$query->the_post();
$a = '<a href="'. get_permalink() .'">' . get_the_title() .'</a>';
$categories = get_the_category();
foreach ( $categories as $key=>$category ) {
$b = '<a href="' . get_category_link( $category ) . '">' . $category->name . '</a>';
}
$q[$b][] = $a; // Создаем массив с названиями категорий и заголовками постов
}
/* Восстанавливаем оригинальные данные поста */
wp_reset_postdata();
foreach ($q as $key=>$values) {
echo $key;
echo '<ul>';
foreach ($values as $value){
echo '<li>' . $value . '</li>';
}
echo '</ul>';
}
?>

@Pieter-Goosen Отличный код! Вместо использования <ul> я хочу использовать шаблон из файла. Я попробовал подключить файл в цикле `foreach ($values as $value) {include(template.php);}`, но тогда в качестве заголовка страницы устанавливаются названия записей, а не их заголовки. Есть идеи как это исправить?

Я хотел поделиться своим решением, которое возникло из этого вопроса. Оно кэширует запрос для категорий, а также кэширует посты, включая содержимое постов, в каждой категории. При первом заполнении кэша выполняются обычные запросы к базе данных, но после заполнения кэша категории и посты берутся из кэша, что исключает дополнительные запросы к базе данных.
// Transients API для всех категорий и всех постов
$query_categories = get_transient('cached_categories');
if ( false === $query_categories){
$args_cat = array(
// сортировка по имени категории по возрастанию
'orderby' => 'name',
'order' => 'ASC',
// получаем только родительские категории
'parent' => 0
);
// Вместо кэширования WP_Query кэшируем результат 'get_categories()'.
$query_categories = get_categories($args_cat);
// var_dump($query_categories);
set_transient('cached_categories', $query_categories, DAY_IN_SECONDS );
}
// Полный запрос постов
// если есть категории, содержащие посты
if (!empty ($query_categories) && !is_wp_error( $query_categories )) {
foreach ($query_categories as $category) {
// var_dump($category);
$query_category_posts = get_transient('cached_posts_' . $category->slug );
if ( false === $query_category_posts ){
// Запрос всех постов по slug внутри каждой категории
$args_category_posts = array(
'post_type' => 'post',
// slug и имя категории получаем из цикла по всем категориям
'category_name' => $category->slug
);
// Здесь кэшируем WP_Query, это выполняется для всех категорий.
// Для этого используется '$category->slug', чтобы передавать строку, а не объект.
$query_category_posts = new WP_Query($args_category_posts);
set_transient( 'cached_posts_' . $category->slug , $query_category_posts, DAY_IN_SECONDS );
}
if ($query_category_posts->have_posts()) {
while ($query_category_posts->have_posts()) {
$query_category_posts->the_post(); ?>
<article class="<?php echo $category->slug ?>-article">
<h2 class="<?php echo $category->slug ?>-article-title">
<a href="<?php echo get_permalink() ?>"><?php echo get_the_title() ?></a>
</h2>
<p class="<?php echo $category->slug ?>-post-info">
<?php the_time('d. m. Y') ?>
</p>
<div <?php post_class() ?> >
<?php the_content(); ?>
</div>
</article> <?php
}
} // конец цикла
} // конец foreach
wp_reset_postdata() ;
} // конец условия, если есть категории с постами

Вот мое решение для получения категорий и записей внутри этих категорий в виде одного результата.
Мой пример основан на пользовательской таксономии категории document_category
и пользовательском типе записей documents
. Но я уверен, что вы уловите суть.
$result = [];
$categories = get_terms( [
'taxonomy' => 'document_category'
] );
foreach ( $categories as $index => $category ) {
$args = [
'post_type' => 'documents',
'posts_per_page' => - 1,
'public' => true,
'tax_query' => [
[
'taxonomy' => 'document_category',
'field' => 'term_id',
'terms' => $category->term_id,
'include_children' => true
]
]
];
$documents = get_posts( $args );
$result[ $index ]['category'] = $category;
$result[ $index ]['documents'] = $documents;
}
return $result;

Попробуйте этот код сейчас
$cat_ids=array();
foreach (get_categories() as $cat)
{
array_push($cat_ids, $cat->cat_ID);
}
$the_query = new WP_Query(array('post_type'=>'post', array('category__and' => $cat_ids) ) );
if( $the_query->have_posts() ):
while ( $the_query->have_posts() ) : $the_query->the_post();
echo the_title();
endwhile;
endif;
wp_reset_query();

Вы можете использовать этот код... Укажите нужное количество записей...
Я также поместил всё внутрь div, чтобы вы могли настроить структуру и дизайн по своему усмотрению.
<?php
$allcats = get_categories('child_of=0');
foreach ($allcats as $cat) :
$args = array(
'posts_per_page' => 3, // укажите количество записей на категорию здесь
'category__in' => array($cat->term_id)
);
$customInCatQuery = new WP_Query($args);
if ($customInCatQuery->have_posts()) :
echo '<div>';
echo '<h3>'.$cat->name.'</h3>';
echo '<ul>';
while ($customInCatQuery->have_posts()) : $customInCatQuery->the_post(); ?>
<li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php
endwhile;
echo '</ul></div>';
?>
<?php
else :
echo 'Нет записей в категории: '.$cat->name;
endif;
wp_reset_query();
endforeach;
?>
Надеюсь, это поможет.

Спасибо, но возьмем, например, 10 категорий. В этом случае ваш код вызывает 11 SQL-запросов. Мне нужен всего 1 SQL-запрос. Лучшим решением, вероятно, было бы получить все записи, отсортированные по категориям, но я не знаю, как это сделать (я не нашел order_by = cat
в кодексе) !?

Не думаю, что есть хоть какая-то разница между 10 или 20 категориями... (пробовал с 20) если только вы не пытаетесь загрузить огромное количество записей, что в любом случае может замедлить загрузку страницы... Попробуйте - увидите, что всё работает очень быстро - попытки перегруппировать после загрузки по категориям потребуют написания громоздкого и в основном бесполезного кода (по моему скромному мнению :))

Нет, всё проще - нужно лишь немного изменить вывод (т.е. закрыть список для категории n и начать список для категории n+1) каждый раз, когда запись имеет категорию, отличную от категории предыдущей записи... Гораздо проще, чем нагружать вашу бедную базу данных 20+ запросами на каждую загрузку страницы... (по моему скромному мнению :)

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

Разница в количестве запросов к базе данных. Один большой против множества маленьких... ;)

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

Я создал кое-что для себя, чем пользуюсь довольно часто, вот код. Вы можете использовать ярлыки (слаги), ID или объекты терминов в массиве $categories
. Если вам нужно получить все категории, можно использовать get_terms()
и передать массив объектов терминов, но будьте осторожны — этот код не обрабатывает иерархию.
$categories = array( 1, 'slug', 3 );
echo '<ul>';
foreach($categories as $category) {
$term = ( is_numeric($category) || is_object($category) ? get_term( $category, 'category' ) : get_term_by( 'slug', $category, 'category' ) );
$args = array(
'cat' => $term->term_id
// Добавьте другие аргументы по необходимости
);
$q = new WP_Query( $args );
if( $q->have_posts() ) {
echo '<li><a href="' . get_term_link( $term->term_id, 'category' ) . '">' . $term->name . '</a><ul>';
while( $q->have_posts() ) {
$q->the_post();
echo '<li><a href="' . get_permalink() . '">' . get_the_title() . '</a></li>';
}
echo '</ul></li>';
} else {
}
}
echo '</ul>';

Непроверенный, но один из самых простых подходов, который я бы попробовал, выглядит следующим образом:
<?php
$category_ids = get_all_category_ids();
foreach ($category_ids as $values) {
$args = array('category' => $value);
$posts_array = get_posts( $args );
foreach ($posts_array as $post) : setup_postdata($post);
?>
<li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php
endforeach;
wp_reset_query();
}
?>

Хорошо. Невозможно выполнить одним запросом. Загрузка любой страницы WordPress уже включает множество запросов, даже без добавления пользовательского кода. Все встроенные запросы используют класс WPDB. Единственный способ сделать это одним запросом - использовать свой собственный Transact SQL или сделать WPDB родительским классом для другого класса с этой целью и вызывать его таким образом.

Если вы ищете плагин, вам может подойти List Category Posts.
Для запросов ознакомьтесь с функцией get_posts

или вот это: http://codex.wordpress.org/Function_Reference/get_all_category_ids
