Произвольный тип записи, WP_Query и сортировка 'orderby'
У меня есть пользовательский тип записи со следующей настройкой:
$supports = array(
'title'
, 'editor'
, 'thumbnail'
, 'revisions'
, 'page-attributes'
);
$args = array(
'hierarchical' => true
, 'supports' => $supports
[...]
);
register_post_type('myType', $args);
Я хочу отобразить все записи и отсортировать их как в админ-панели WordPress (отступы для наглядности):
1,
2,
3,
1, (родитель 3)
2, (родитель 3)
4
Для этого я попробовал следующий запрос с типом сортировки 'menu_order':
$loop = new WP_Query( array(
'post_type' => 'myType'
, 'posts_per_page' => 50
, 'orderby' => 'menu_order'
, 'order' => 'ASC'
));
К сожалению, все записи сортируются только по menu_order
, полностью игнорируя родительские отношения (атрибут post_parent
). В результате я получаю что-то вроде:
1,
1, (родитель 3)
2,
2, (родитель 3)
3,
4
Изменение запроса на 'orderby' => 'parent menu_order'
приводит к следующему:
1,
2,
3,
4
1, (родитель 3)
2, (родитель 3)
Похоже, что все работает как задумано, и значение orderby напрямую преобразуется в соответствующий SQL 'Order By'.
Вопрос
Какой самый простой способ получить желаемый порядок сортировки?
SQL запрос
Предполагаю, что это основной SQL-запрос, который создает WordPress:
SELECT SQL_CALC_FOUND_ROWS wp_2_posts.ID
FROM wp_2_posts
WHERE 1=1 AND wp_2_posts.post_type = 'inhalt' AND (wp_2_posts.post_status = 'publish' OR wp_2_posts.post_status = 'private')
ORDER BY wp_2_posts.post_parent, wp_2_posts.menu_order ASC LIMIT 0, 50
За которым следует:
SELECT wp_2_posts.*
FROM wp_2_posts
WHERE ID IN (40,42,44,46,48,50,52,54,56,58,60,76,62,65,69,71,74)
SELECT post_id, meta_key, meta_value
FROM wp_2_postmeta
WHERE post_id IN (40,42,44,46,48,50,52,54,56,58,60,62,65,74,69,71,76)
Обходное решение
Известное, но не идеальное решение - задавать записям более высокие и "разнесенные" значения порядка, например:
100,
200,
300,
310,
320,
400

Ознакомьтесь с кодексом для других вариантов, но в данном случае, похоже, вам нужно использовать 'parent' в качестве основного критерия сортировки.
$loop = new WP_Query( array(
'post_type' => 'myType'
, 'posts_per_page' => 50
, 'orderby' => 'parent menu_order'
, 'order' => 'ASC'
));
Это обеспечит сортировку в первую очередь по родительскому элементу, с дополнительной сортировкой по порядку меню. Такой подход должен дать желаемый результат.

Спасибо, Эрик, но, к сожалению, это приводит к 1, 2, 3, 4, 1(родитель 3), 2 (родитель 3).

Хм, странно. Попробуй поменять их местами? Или использовать только родителя? Я это не тестировал, просто руководствуюсь кодексом.

+1 Наконец-то кто-то понял, что пробел позволяет использовать несколько аргументов.

@SunnyRed Не могли бы вы опубликовать точную строку запроса, которая получается в результате? (Подсказка: плагин Debug Bar + расширения).

Спасибо, Kaiser. Ваша подсказка про Debug Bar уже сделала мой вопрос стоящим. Я добавил SQL - надеюсь, что правильный, так как на первый взгляд там довольно много всего.

Судя по SQL-запросу, порядок сортировки должен быть изменен на:
'orderby' => 'parent menu_order'
, так как SQL-запрос уже меняет их местами.

Привет, Эрик. Извини, это произошло потому что я пробовал разные подходы. Порядок в WP и SQL синхронизирован.

Насколько мне известно, обходного решения на уровне базы данных для этой проблемы не существует. Это довольно распространенная ситуация, когда необходимо преобразовать список со структурными ссылками в упорядоченный массив, где дочерние элементы следуют сразу после своих родительских элементов. Это можно реализовать в PHP, и хотя представленное решение довольно компактное, оно не является тривиальным.
Следующее решение добавляет фильтр к хуку the_posts
, который структурирует, а затем "разворачивает" результирующий набор с помощью рекурсивной функции.
// Добавляем дочерние записи каждого уровня в результирующий список по порядку
function recursively_flatten_list( $list, &$result ) {
foreach( $list as $node ) {
$result[] = $node['post'];
if( isset( $node['children'] ) )
recursively_flatten_list( $node['children'], $result );
}
}
function my_sort_posts( $posts, $query ) {
// Не выполняем в админке. Работаем только с основным запросом. Только для запросов страниц.
if( is_admin() || !$query->is_main_query() || $query->get( 'post_type' ) != 'page' )
return;
$refs = $list = array();
// Создаем иерархическую структуру за один проход.
// Благодарность Nate Weiner:
// http://blog.ideashower.com/post/15147134343/create-a-parent-child-array-structure-in-one-pass
foreach( $posts as $post ) {
$thisref = &$refs[$post->ID];
$thisref['post'] = $post;
if( $post->post_parent == 0)
$list[$post->ID] = &$thisref;
else
$refs[$post->post_parent]['children'][$post->ID] = &$thisref;
}
// Создаем единый отсортированный список
$result = array();
recursively_flatten_list( $list, $result );
return $result;
}
add_filter( 'the_posts', 'my_sort_posts', 10, 2 );
Я протестировал это решение, и оно достаточно универсально для работы с произвольными иерархиями страниц.
Этот код предполагает, что записи уже упорядочены по menu_order
. Если вы используете это решение, убедитесь, что параметр orderby
установлен в значение "menu_order"
при вызове new WP_Query
.
