Отображение записей по месяцам

27 янв. 2015 г., 11:52:21
Просмотры: 27K
Голосов: 3

Я хочу достичь чего-то подобного, не знаю, возможно ли это и каким был бы лучший способ сделать это:

Пример группировки записей по месяцам

Сейчас я делаю запрос записей таким образом:

<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>

Кто-нибудь может дать мне совет, как или каким лучшим способом это сделать?

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

Для реализации такой структуры вам понадобится несколько запросов. Сначала необходимо определить месяцы для отображения, а затем пройтись по результатам и выбрать соответствующие записи. Я попробую дать ответ в ближайшее время...

David Gard David Gard
27 янв. 2015 г. 13:47:13

Этого можно добиться одним запросом без использования query_posts

Pieter Goosen Pieter Goosen
27 янв. 2015 г. 15:18:55
Все ответы на вопрос 3
5
11

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

ВАЖНЫЕ ЗАМЕЧАНИЯ

Прежде чем начать, несколько важных замечаний:

  • Никогда не используйте 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();

    }

}
27 янв. 2015 г. 16:44:06
Комментарии

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

David Gard David Gard
27 янв. 2015 г. 17:37:37

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

David Gard David Gard
27 янв. 2015 г. 17:39:25

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

Pieter Goosen Pieter Goosen
27 янв. 2015 г. 17:50:13

@wpuser пожалуйста. Наслаждайтесь :-)

Pieter Goosen Pieter Goosen
27 янв. 2015 г. 17:50:51

@PieterGoosen - Я понимаю, просто подумал, что стоит упомянуть для других, кто может наткнуться на этот ответ в будущем. Я знаю, что легко путаюсь, поэтому предполагаю, что все остальные тоже!

David Gard David Gard
27 янв. 2015 г. 17:56:38
7

Обновленный ответ

После размышлений над комментарием от @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;
27 янв. 2015 г. 14:29:12
Комментарии

Это можно сделать одним запросом :-)

Pieter Goosen Pieter Goosen
27 янв. 2015 г. 14:33:14

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

David Gard David Gard
27 янв. 2015 г. 14:36:49

Попробую выложить решение позже. Сейчас немного занят :-)

Pieter Goosen Pieter Goosen
27 янв. 2015 г. 15:18:14

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

David Gard David Gard
27 янв. 2015 г. 15:29:51

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

David Gard David Gard
27 янв. 2015 г. 16:39:16

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

Pieter Goosen Pieter Goosen
27 янв. 2015 г. 18:15:08

Спасибо :) Я обычно очень подробно отвечаю, всегда предполагаю, что пользователь начинает с нуля (если только не предоставлен какой-то код).

David Gard David Gard
27 янв. 2015 г. 18:36:53
Показать остальные 2 комментариев
4

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

// вы можете изменить имя функции, если оно конфликтует с другим плагином
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 (массив объектов записей)

Удачного кодинга... :)

15 авг. 2017 г. 11:59:30
Комментарии

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

birgire birgire
15 авг. 2017 г. 16:45:52

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

Luca Reghellin Luca Reghellin
16 авг. 2017 г. 16:05:42

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

birgire birgire
16 авг. 2017 г. 17:52:27

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

Luca Reghellin Luca Reghellin
16 авг. 2017 г. 19:09:10