Произвольный тип записи, WP_Query и сортировка 'orderby'

30 авг. 2012 г., 13:31:32
Просмотры: 18.9K
Голосов: 4

У меня есть пользовательский тип записи со следующей настройкой:

$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
0
Все ответы на вопрос 2
8

Ознакомьтесь с кодексом для других вариантов, но в данном случае, похоже, вам нужно использовать 'parent' в качестве основного критерия сортировки.

$loop = new WP_Query( array(
       'post_type' => 'myType'
     , 'posts_per_page' => 50
     , 'orderby' => 'parent menu_order'
     , 'order' => 'ASC'
));

Это обеспечит сортировку в первую очередь по родительскому элементу, с дополнительной сортировкой по порядку меню. Такой подход должен дать желаемый результат.

30 авг. 2012 г. 16:27:25
Комментарии

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

SunnyRed SunnyRed
30 авг. 2012 г. 16:42:11

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

Eric Holmes Eric Holmes
30 авг. 2012 г. 17:39:07

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

kaiser kaiser
30 авг. 2012 г. 19:57:10

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

kaiser kaiser
30 авг. 2012 г. 19:57:38

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

SunnyRed SunnyRed
30 авг. 2012 г. 20:54:46

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

Eric Holmes Eric Holmes
30 авг. 2012 г. 21:24:49

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

SunnyRed SunnyRed
31 авг. 2012 г. 12:27:33

Причина такого поведения в том, что значение 'parent' для категорий верхнего уровня будет 0, поэтому порядок корректен, так как все записи с родителем '0' будут выводиться первыми.

Bendoh Bendoh
5 сент. 2012 г. 04:38:28
Показать остальные 3 комментариев
1

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

5 сент. 2012 г. 06:37:32
Комментарии

Круто! Мне пришлось изменить это на $loop->posts = my_sort_posts($loop->posts); без добавления фильтра, но теперь всё работает отлично. Спасибо!

SunnyRed SunnyRed
7 сент. 2012 г. 15:18:39