Отображение записей по месяцам
Я хочу достичь чего-то подобного, не знаю, возможно ли это и каким был бы лучший способ сделать это:
Сейчас я делаю запрос записей таким образом:
<div class="post">
<?php global $wp_query;
query_posts( array('post_type' => array( 'lecturas-post' ),'showposts' => 15, 'paged'=>$paged, 'order' => 'DESC' ));?>
<?php while ( $wp_query->have_posts() ) : $wp_query->the_post(); ?>
<div><?php the_title() ?></a>
<?php endwhile; // конец цикла. ?>
</div>
Кто-нибудь может дать мне совет, как или каким лучшим способом это сделать?

Как упоминалось в комментарии, это можно сделать одним запросом. Принцип заключается в том, чтобы отображать заголовок с датой только в том случае, если месяц даты публикации текущего поста не совпадает с месяцем предыдущего поста.
ВАЖНЫЕ ЗАМЕЧАНИЯ
Прежде чем начать, несколько важных замечаний:
Никогда не используйте
query_posts
, за исключением случаев, когда вам действительно нужно сломать всё на странице. Эта функция не только перезапускает основной запрос и ломает его, но также нарушает пагинацию и глобальные переменные, а также влияет на функции queried object. Если вам действительно нужно выполнить пользовательский запрос, используйтеWP_Query
илиget_posts
.Параметр
showposts
был удалён много лет назад в пользуposts_per_page
.Нет необходимости использовать глобальную переменную
$wp_query
.query_posts
всё равно её испортит.
ПЛАН ДЕЙСТВИЙ
Посты выводятся в хронологическом порядке, от самых новых к самым старым, поэтому они уже в правильном порядке. Остаётся только правильно отображать дату в нужном месте.
Для этого вам нужно получить текущий месяц и год из даты публикации текущего поста и сравнить его с месяцем предыдущего поста, а затем либо отобразить дату, если месяцы не совпадают, либо не отображать, если они совпадают.
В качестве объяснения я буду использовать основной цикл с основным запросом.
Для реализации этого вам потребуется:
Получить месяц из даты публикации текущего поста. Для этого используйте
get_the_date( 'F' )
.Получить предыдущий пост в цикле с помощью
$wp_query->posts['это будет текущий пост -1 ']->post
.Сравнить месяцы между двумя постами.
Отображать или не отображать дату в зависимости от результата сравнения.
КОД
Этот код следует разместить внутри вашего цикла, сразу после оператора while()
.
$current_month = get_the_date('F');
if( $wp_query->current_post === 0 ) {
the_date( 'F Y' );
}else{
$f = $wp_query->current_post - 1;
$old_date = mysql2date( 'F', $wp_query->posts[$f]->post_date );
if($current_month != $old_date) {
the_date( 'F Y' );;
}
}
ПОЛЬЗОВАТЕЛЬСКИЙ ЗАПРОС
Если вам нужно выполнить пользовательский запрос, попробуйте следующий вариант:
$q = new WP_Query( array('post_type' => array( 'lecturas-post' ),'posts_per_page' => 15, 'paged'=>$paged, 'order' => 'DESC' ));
if( $q->have_posts() ) {
while( $q->have_posts() ) {
$q->the_post();
$current_month = get_the_date('F');
if( $q->current_post === 0 ) {
the_date( 'F Y' );
}else{
$f = $q->current_post - 1;
$old_date = mysql2date( 'F', $q->posts[$f]->post_date );
if($current_month != $old_date) {
the_date( 'F Y' );;
}
}
the_title();
}
}

Хороший ответ, и похожая концепция для вашего раздела пользовательского запроса, как в моем обновленном ответе, просто другой способ проверки, был ли выведен заголовок. Думаю, это зависит от автора вопроса, хочет ли он включать date_query
или нет :)

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

@DavidGard спасибо. Дата будет отображаться на каждой странице для первого поста в любом случае. Если это не нужно, добавьте условие is_paged()
.

Обновленный ответ
После размышлений над комментарием от @PieterGoosen ниже я добавил метод достижения вашей цели с использованием одного запроса.
Я провел тестирование методов, и одиночный запрос оказался быстрее, при этом метод с множественными запросами примерно на 15% медленнее. Не огромная разница, но каждая мелочь помогает, и, честно говоря, метод, вероятно, можно еще улучшить.
Я оставил метод с множественными запросами в ответе для истории, но рекомендую использовать метод с одиночным запросом.
Метод с одним запросом
$time_start = microtime(true);
/** Создаем объект интервала дат для 6 месяцев назад (можно изменить по необходимости) */
$interval = new DateInterval('P6M');
$interval->invert = 1;
/** Получаем дату, которая была 6 месяцев назад */
$date = new DateTime(date('Y-m-d'));
$date->add($interval);
/** Запрашиваем базу данных на все записи новее заданного интервала дат */
$args = array(
'nopaging' => true,
'posts_per_page' => -1,
'post_type' => 'post',
'post_status' => 'publish',
'order_by' => 'date',
'date_query' => array(
'after' => $date->format('Y-m-d')
)
);
$month_query = new WP_Query($args);
/** Проверяем, есть ли статьи за этот месяц... */
if($month_query->have_posts()) :
$month_titles = array();
$close_ul = false;
/** Устанавливаем атрибуты для отображения заголовка в качестве атрибута */
$title_attribute_args = array(
'before' => 'Посетить статью \'',
'after' => '\' ',
'echo' => false
);
/** Перебираем каждую запись за этот месяц... */
while($month_query->have_posts()) : $month_query->the_post();
/** Проверяем месяц/год текущей записи */
$month_title = date('F Y', strtotime(get_the_date('Y-m-d H:i:s')));
/** Выводим удобочитаемую дату, если она еще не была выведена */
if(!in_array($month_title, $month_titles)) :
if($close_ul) echo '</ul>'; // Проверяем, нужно ли закрывать список (не нужно для первого '$month_title')
echo '<h1 style="padding-left: 250px;" id="monthly-title">' . $month_title . '</h1>'; // Выводим '$month_title'
echo '<ul style="padding-left: 250px;" id="monthly-posts">'; // Открываем список для предстоящих записей
$month_titles[] = $month_title; // Добавляем этот `$month_title' в массив, чтобы не повторялся
$close_ul = true; // Указываем, что список нужно закрыть при следующей возможности
endif;
/** Выводим каждую статью за этот месяц */
printf(
'<li id="monthly-post-%1$s">%2$s <a href="%3$s" title="%4$s">%3$s</a></li>',
get_the_ID(), /** %1$s - ID записи */
get_the_title(), /** %2$s - Заголовок статьи */
get_permalink(get_the_ID()), /** %3$s - Ссылка на статью */
the_title_attribute($title_attribute_args) /** %4$s - Заголовок для атрибута */
);
endwhile;
if($close_ul) echo '</ul>'; // Закрываем последний список записей (если они были показаны)
endif;
/** Сбрасываем запрос, чтобы WP не вел себя странно */
wp_reset_query();
Исходный ответ
Попробуйте этот вариант. Я настроил его так, чтобы выбирались только последние 6 месяцев и только последние 5 записей из каждого месяца, но вы можете изменить это по своему усмотрению.
По сути, код сначала проверяет, в каких месяцах есть записи, а затем выводит последние пять записей за этот месяц вместе со ссылкой.
Метод с множественными запросами
global $wpdb, $wp_locale;
/** Запрашиваем отдельные месяцы для отображения (я выбрал последние 6 месяцев) */
$query = $wpdb->prepare('
SELECT DISTINCT YEAR(`%1$s`.`post_date`) AS year, MONTH(`%1$s`.`post_date`) AS month
FROM `%1$s`
WHERE `%1$s`.`post_type` = "post"
ORDER BY `%1$s`.`post_date` DESC
LIMIT 6',
$wpdb->posts
);
$months = $wpdb->get_results($query);
/** Считаем количество месяцев */
$month_count = count($months);
/** Убеждаемся, что есть месяцы для отображения... */
if($month_count || ($month_count === 1 && $months[0]->month > 0)) :
/** Перебираем каждый месяц... */
foreach($months as $month) :
if($month->year === 0) :
continue;
endif;
/** Получаем отдельный месяц и год, создаем удобочитаемую дату (для заголовка) */
$m = zeroise($month->month, 2);
$y = $month->year;
$human_date = sprintf(__('%1$s %2$d'), $wp_locale->get_month($m), $y);
/** Получаем записи за этот месяц (я выбрал только последние 5 записей) */
$args = array(
'nopaging' => true,
'posts_per_page' => 5,
'post_type' => 'post',
'post_status' => 'publish',
'order_by' => 'date',
'year' => $y,
'monthnum' => $m
);
$month_query = new WP_Query($args);
/** Проверяем, есть ли статьи за этот месяц... */
if($month_query->have_posts()) :
/** Выводим удобочитаемую дату */
echo '<h1 id="monthly-title">' . $human_date . '</h1>';
echo '<ul id="monthly-posts">';
/** Устанавливаем атрибуты для отображения заголовка в качестве атрибута */
$title_attribute_args = array(
'before' => 'Посетить статью \'',
'after' => '\' ',
'echo' => false
);
/** Перебираем каждую запись за этот месяц... */
while($month_query->have_posts()) : $month_query->the_post();
/** Выводим каждую статью за этот месяц */
printf(
'<li id="monthly-post-%1$s">%2$s <a href="%3$s" title="%4$s">%3$s</a></li>',
get_the_ID(), /** %1$s - ID записи */
get_the_title(), /** %2$s - Заголовок статьи */
get_permalink(get_the_ID()), /** %3$s - Ссылка на статью */
the_title_attribute($title_attribute_args) /** %4$s - Заголовок для атрибута */
);
endwhile;
echo '</ul>';
endif;
/** Сбрасываем запрос, чтобы WP не вел себя странно */
wp_reset_query();
endforeach;
endif;

Правда? Что ты имеешь в виду? Я думал про GROUP BY
, но автор хочет заголовки с месяцем/годом, поэтому мой маленький мозг не видит решения без нескольких запросов :-/

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

@PieterGoosen Я попробовал реализовать метод с одним запросом, но буду благодарен за любые (конструктивные) замечания, которые у вас могут быть!

Ваша обновленная версия выглядит хорошо. Определенно еще один рабочий метод, хотя, возможно, он немного перегружен. Вы получите мой голос за обновленную версию :-)

Это функция, которую я создал для общих нужд, чтобы получать данные записей или пользовательских типов записей до или после определенного года/месяца, или текущего года, в любом порядке, который вам нужен:
// вы можете изменить имя функции, если оно конфликтует с другим плагином
function get_posts_by_date($user_options = array()){
$options = array(
'year_limit' => '1980'
,'month_limit' => '01'
,'operator' => '>=' // оператор сравнения дат
,'current_year' => true // ограничить данные текущим годом
,'post_type' => 'post'
,'year_order' => 'DESC'
,'month_order' => 'DESC'
,'post_ids_order' => 'DESC'
,'raw_output' => false
);
extract(array_merge($options, $user_options));
global $wpdb;
if($operator == '>=' || $operator == '=='){
$day = "01";
} elseif($mode == '<='){
$day = "31";
}
if($current_year){ // будет после 31/12 [прошлого года]
$year_limit = date('Y', strtotime('-1 year'));
$month_limit = '12';
$day = "31";
$operator == '>=';
}
// предупреждение: если ваши параметры поступают из пользовательского ввода/форм,
// передавайте их с помощью $wpdb::prepare()
// https://developer.wordpress.org/reference/classes/wpdb/prepare/
$results = $wpdb->get_results("
SELECT tbl.y year, group_concat(month_posts ORDER BY tbl.m " . $month_order . " SEPARATOR '-') months
FROM (
SELECT YEAR(p.post_date) y, MONTH(p.post_date) m, concat(MONTH(p.post_date), ':', group_concat(p.id ORDER BY p.post_date " . $post_ids_order . " )) month_posts
FROM $wpdb->posts p
WHERE (p.post_status = 'publish' OR p.post_status = 'future')
AND p.post_type = '" . $post_type . "'
AND p.post_date " . $operator . " DATE('" . $year_limit . "-" . $month_limit . "-" . $day . " 00:00:00')
GROUP BY y, m
) tbl
GROUP BY tbl.y
ORDER BY tbl.y " . $year_order
);
if($raw_output) return $results;
global $wp_locale;
foreach ($results as $data){
$months_data = explode('-',$data->months);
$months = array();
$data->count = 0; // счетчик постов за год
foreach ($months_data as $month_data){
$month_obj = new stdClass;
$splitted = explode(':',$month_data);
$raw_month = $splitted[0];
$month_obj->number = $raw_month;
$month_obj->name = $wp_locale->get_month($raw_month);
$month_obj->posts = array();
$post_ids = explode(',',$splitted[1]);
$data->count += count($post_ids);
foreach($post_ids as $post_id){
$month_obj->posts[] = get_post($post_id);
$months[$raw_month] = $month_obj;
}// foreach
}// foreach
$data->months = $months;
}// foreach
return $results;
}// get_posts_by_date
Пример использования:
$posts_by_date = get_posts_by_date(array(
'year_limit' => '2016'
,'operator' => '<='
,'current_year' => false
,'post_type' => 'product'
,'month_order' => 'ASC'
,'raw_output' => true
));
Если параметр raw_output
равен true, стандартный вывод будет выглядеть примерно так:
array(2) {
[0]=>
object(stdClass)#6645 (2) {
["year"]=>
string(4) "2017"
["months"]=>
string(65) "8:386,401-7:406,373,380,377,408,399,362-6:1,391,404-5:367,397,394"
}
[1]=>
object(stdClass)#6644 (2) {
["year"]=>
string(4) "2016"
["months"]=>
string(5) "6:429"
}
}
Строка "months" содержит значения в формате:
месяц:[ID записей]-месяц:[ID записей]-и т.д.
Если параметр raw_output
равен false, вы получите список записей в таком формате:
array (массив объектов)
object
-> year (например, '2017')
-> count (общее количество записей за год)
-> months (массив объектов)
month
-> number (номер месяца)
-> name (локализованное название)
-> posts (массив объектов записей)
Удачного кодинга... :)

Будьте осторожны с возможными SQL-инъекциями и избегайте использования extract
(это усложняет отладку). Также не используйте общие названия функций, чтобы избежать возможных конфликтов имен (или используйте пространства имен).

@birgire: эх... Я знаю всё это, но разработчик сам должен оценить название функции в контексте своего окружения и при необходимости санировать ввод от пользователя/форм. Этот код просто для обмена полезными решениями, и я искренне верю, что данная функция многим поможет. И, конечно, я надеюсь, что любой разработчик — (если понадобится) — сможет легко изменить название функции, санировать пользовательский ввод или добавить подготовленные выражения вместо прямого SQL-запроса...

Надеюсь, они так и сделают, но я боюсь, что многие просто копируют/вставляют код с этого сайта и забывают о нём, если он сработал с первого раза ;-)
