Как вернуть результаты get_posts() в явно определённом порядке
Я пытаюсь создать цикл с явно заданным порядком постов, например:
<?php $args = array(
'include' => '1,3,8,4,12' ); ?>
<?php get_posts( $args ); ?>
По умолчанию результаты сортируются по дате, и нет опции orderby для возврата постов в том порядке, в котором они были указаны. В Trac было несколько запросов на исправление/добавление этой функции, но пока безрезультатно. Я немного покопался в основных файлах, но ничего не добился.
Может кто-нибудь предложить обходное решение для этого поведения?
С уважением, Далтон

Я считаю, что это самый быстрый способ вернуть результаты get_posts в заданном порядке. Кроме того, это нативное решение без хаков.
<?php
$posts_order = array('1,3,8,4,12');
$args = array(
'post__in' => $posts_order,
'orderby' => 'post__in'
);
get_posts( $args );
?>

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

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

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

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

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

Вы можете попробовать это:
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";
}

Хорошо, я был настроен найти способ сделать это, и думаю, у меня получилось. Я надеялся найти более простое решение и избежать необходимости использования нового объекта 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
, но у меня не получилось заставить это работать правильно. Возможно, с небольшим дополнительным временем на доработку это было бы осуществимо.
В любом случае, посмотрите, подойдет ли это решение для ваших целей?

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

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

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

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

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

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

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

Как насчёт того, чтобы просто очистить параметр orderby
с помощью фильтра? Непосредственно перед запросом постов добавьте:
add_filter('posts_orderby', '__return_false');
Затем, после завершения вашего цикла:
remove_filter('posts_orderby', '__return_false');
Причина удаления фильтра заключается в том, что на странице могут быть другие циклы (например, из виджетов), которым потребуется обычное явное упорядочивание.

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

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