Как вернуть результаты get_posts() в явно определённом порядке

3 мар. 2011 г., 19:59:57
Просмотры: 10K
Голосов: 6

Я пытаюсь создать цикл с явно заданным порядком постов, например:

<?php $args = array(
    'include'         => '1,3,8,4,12' ); ?>

<?php get_posts( $args ); ?> 

По умолчанию результаты сортируются по дате, и нет опции orderby для возврата постов в том порядке, в котором они были указаны. В Trac было несколько запросов на исправление/добавление этой функции, но пока безрезультатно. Я немного покопался в основных файлах, но ничего не добился.

Может кто-нибудь предложить обходное решение для этого поведения?

С уважением, Далтон

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

@Dalton, исключается ли использование плагина для порядка записей? Тогда можно было бы использовать 'orderby' => 'menu_order', 'order' => 'ASC'

eileencodes eileencodes
3 мар. 2011 г. 20:12:47

В данном случае это не сработает - я фактически возвращаю вложения, каждое с разными родителями, поэтому menu_order становится нерелевантным. Большое спасибо за ответ!

Dalton Rooney Dalton Rooney
3 мар. 2011 г. 20:20:09

@Dalton, возможно, я неправильно понимаю вашу проблему, но в окне загрузки медиафайлов, если перейти в "галерею", можно изменить порядок изображений, назначив им номера, и затем, как предложено выше, использовать 'orderby' => 'menu_order, 'order' => 'ASC'

eileencodes eileencodes
3 мар. 2011 г. 20:43:19

Небольшое замечание: вы можете использовать несколько аргументов для orderby, разделённых пробелом: &order_by=date ID, например.

kaiser kaiser
3 мар. 2011 г. 22:57:22

@eileen.carpenter: Проблема в моём случае в том, что каждое вложение теоретически может иметь разного родителя, что делает menu_order нерелевантным. Каждый из ID вложений в моём запросе потенциально может иметь одинаковый порядок меню. Я передаю включаемые записи через шорткод, поэтому мне нужно вернуть вложения в заданном порядке.

Dalton Rooney Dalton Rooney
4 мар. 2011 г. 01:02:39

@kaiser: Не думаю, что это решит мою проблему, но это очень круто, и я не знал об этом!

Dalton Rooney Dalton Rooney
4 мар. 2011 г. 01:03:08

Вы можете сделать что-то похожее на то, что я сделал здесь, только вместо инструментов использовать массив ID (и без всего остального, что не применимо в вашем случае).

t31os t31os
4 мар. 2011 г. 01:58:40
Показать остальные 2 комментариев
Все ответы на вопрос 5
7
11

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

<?php

$posts_order = array('1,3,8,4,12');
$args = array(
    'post__in' => $posts_order,
    'orderby' => 'post__in'
); 
get_posts( $args ); 

?> 
4 авг. 2013 г. 00:09:24
Комментарии

Пожалуйста, добавьте объяснение к вашему ответу: почему это может решить проблему?

fuxia fuxia
4 авг. 2013 г. 00:53:18

Это нативное решение без хаков

Pablo S G Pacheco Pablo S G Pacheco
5 авг. 2013 г. 18:01:28

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

fuxia fuxia
5 авг. 2013 г. 19:39:47

В принятом ответе @dougal предложил создать две функции: set_include_order() и menu_order_sort(). В этом нет необходимости. Кроме того, он предложил использовать WP_Query вместо get_posts, как хотел автор вопроса.

И post__in работает только если вы также включите параметр 'orderby', как я предложил

Pablo S G Pacheco Pablo S G Pacheco
5 авг. 2013 г. 21:26:42

@PabloKarzin: если посмотреть дату оригинального вопроса, такая сортировка не была возможна в WP_Query до WordPress 3.5. Ваше обновление верно, но это было невозможно в 2011 году.

Dalton Rooney Dalton Rooney
15 авг. 2013 г. 20:36:32

отличный ответ - минус несправедлив - 3.6.1 - просто и легко - Спасибо!

Q Studio Q Studio
17 окт. 2013 г. 20:32:54

лучший ответ на данный момент.

RafaSashi RafaSashi
19 авг. 2016 г. 15:43:32
Показать остальные 2 комментариев
0
10

Вы можете попробовать это:

add_filter('posts_orderby', 'enforce_specific_order');
$posts = get_posts($args);
remove_filter( current_filter(), __FUNCTION__ );

function enforce_specific_order($orderby) {
    global $wpdb;
    return "FIND_IN_SET(".$wpdb->posts.".ID, '1,3,8,4,12') ASC";
}
4 мар. 2011 г. 07:23:46
6

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

// Устанавливаем порядок меню записей на основе нашего списка  
function set_include_order(&$query, $list) {
    // Создаем массив соответствия ID записи и ее порядка в списке:
    $map = array_flip($list);

    // Устанавливаем menu_order в соответствии со списком     
    foreach ($query->posts as &$post) {
      if (isset($map[$post->ID])) {
        $post->menu_order = $map[$post->ID];
      }
    }  
}

// Сортируем записи по $post->menu_order.                                 
function menu_order_sort($a, $b) {
  if ($a->menu_order == $b->menu_order) {
    return 0;
  }
  return ($a->menu_order < $b->menu_order) ? -1 : 1;
}

Эти функции позволяют нам установить свойство menu_order на основе нашего собственного списка, а затем отсортировать записи в объекте запроса на основе этого.

Вот как мы запрашиваем и сортируем записи:

$plist = array(21, 43, 8, 44, 12);
$args = array(
  'post_type' => 'attachment',
  'post_status' => 'any',
  'post__in' => $plist 
);

// Создаем новый запрос  
$myquery = new WP_Query($args);

// Устанавливаем menu_order
set_include_order($myquery, $plist);

// И фактически сортируем записи в нашем запросе
usort($myquery->posts, 'menu_order_sort');

Теперь у нас есть собственный объект запроса, и $myquery->posts отсортирован в соответствии с нашей пользовательской функцией menu_order_sort. Единственная сложность сейчас заключается в том, что мы должны создать наш цикл, используя наш пользовательский объект запроса:

while($myquery->have_posts()) : $myquery->the_post();
  ?>
    <div><a id="post_id_<?php the_ID(); ?>" class="nb" href="<?php the_permalink(); ?>"><?php the_title(); ?></a> ID записи: <?php the_ID(); ?>
    </div>
  <?php

endwhile;
wp_reset_postdata();

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

Я надеялся найти решение, которое не требовало бы использования пользовательского объекта запроса, возможно, с помощью query_posts() и замены свойства posts в глобальном объекте $wp_query, но у меня не получилось заставить это работать правильно. Возможно, с небольшим дополнительным временем на доработку это было бы осуществимо.

В любом случае, посмотрите, подойдет ли это решение для ваших целей?

4 мар. 2011 г. 19:09:41
Комментарии

Выглядит немного избыточно и не работает с пагинацией (поскольку сортировка выполняется после запроса), но +1 за отличную попытку решить проблему без внедрения чего-либо на уровне SQL.

wyrfel wyrfel
4 мар. 2011 г. 20:28:30

Потрясающе! Это определенно работает для записей и отвечает на исходный вопрос. Есть только одна загвоздка — мне нужно возвращать вложения, а не записи. Я не знал, что WP_Query() не работает с вложениями, я использовал get_posts()... Есть ли простой способ заставить это работать с get_posts()? Я продолжу работать над этим и посмотрю, что смогу придумать.

Dalton Rooney Dalton Rooney
4 мар. 2011 г. 20:42:24

Обновил код для получения вложений. :) С вложениями необходимо указывать post_status как 'any', потому что они обычно используют статус inherit вместо publish, чтобы отражать состояние их родителя.

Dougal Campbell Dougal Campbell
4 мар. 2011 г. 21:24:59

И для будущих читателей: обратите внимание на комментарий @wyrfel — это не будет хорошо работать, если вам нужно разбивать результаты на страницы, поскольку сортировка не происходит внутри MySQL.

Dougal Campbell Dougal Campbell
4 мар. 2011 г. 21:41:47

@Dougal: Большое спасибо, ваши изменения сработали. К счастью, мне не нужно разбивать эти результаты на страницы, так что мне всё подходит.

Dalton Rooney Dalton Rooney
4 мар. 2011 г. 22:33:11

Последнее замечание — если вам нужно получить большое количество ID, вы также можете добавить 'posts_per_page' => -1 в массив $args, чтобы избежать ограничений постраничного вывода. На самом деле, возможно, стоит сделать это в любом случае, на случай если на сайте установлено очень малое значение posts_per_page по умолчанию.

Dougal Campbell Dougal Campbell
5 мар. 2011 г. 00:30:31
Показать остальные 1 комментариев
0

Начиная с WordPress 3.5, эта функция теперь встроена в ядро. Вы можете явно задавать порядок записей с помощью параметра "post__in". http://core.trac.wordpress.org/ticket/13729

14 дек. 2012 г. 14:18:03
3

Как насчёт того, чтобы просто очистить параметр orderby с помощью фильтра? Непосредственно перед запросом постов добавьте:

add_filter('posts_orderby', '__return_false');

Затем, после завершения вашего цикла:

remove_filter('posts_orderby', '__return_false');

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

3 мар. 2011 г. 22:48:29
Комментарии

Dougal: На самом деле есть опция orderby=none, но она возвращает записи в естественном порядке, как они расположены в базе данных, по сути — сортируя по ID. Мне же нужно, чтобы записи возвращались в порядке, указанном в запросе. WP_Query это не поддерживает, так что я ищу обходной путь или хак.

Dalton Rooney Dalton Rooney
4 мар. 2011 г. 01:09:23

Что ж, блин. Я думал, что запрос с 'include' вернёт результаты в указанном порядке, если убрать параметр orderby. Но теперь, подумав, я понимаю, что MySQL логично возвращает записи в естественном порядке (он старается быть эффективным).

Dougal Campbell Dougal Campbell
4 мар. 2011 г. 16:18:40

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

Dougal Campbell Dougal Campbell
4 мар. 2011 г. 21:45:13