Получение родительской записи с дочерними элементами используя WP_Query

23 мар. 2016 г., 12:57:14
Просмотры: 26.7K
Голосов: 8

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

Визуализация того, чего я хочу достичь. Представьте следующие страницы, иерархически отсортированные следующим образом:

  • страница A
  • страница B
    • Дочерняя страница A
    • Дочерняя страница B
    • Дочерняя страница C
  • страница C

Выделенные жирным элементы списка - это записи/страницы, которые я хочу получить.

Мои первые мысли направлены на использование этих двух параметров для WP_Query:

$args = array(
   'post_id' => $parent->ID,
   'post_parent' => $parent->ID,
);

К сожалению, здесь будет использоваться только один параметр. С приведенными выше $args (поправьте меня, если я ошибаюсь) будут выведены все дочерние записи родительской записи, но не сама родительская запись.

Эту проблему можно решить, собрав все необходимые записи и поместив их в параметр post__in следующим образом:

$args = array(
   'post__in' => $children_and_parent_ids,
);

Однако существует wp_list_pages(), позволяющий вам include запись(и) и указать запись, чьи дочерние элементы вы хотите включить (child_of). Почему это невозможно с WP_Query?

Пример того, чего я пытаюсь достичь, используя wp_list_pages():

wp_list_pages(array(
    'include' => $parent->ID,
    'child_of' => $parent->ID,
));

Посмотрите документацию по WP_Query.

0
Все ответы на вопрос 6
0

Если вам нужны только результаты для типа записи "page", то следуйте совету @birgire.

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

$parent = 2;      // измените по необходимости
$type   = 'page'; // измените по необходимости

$child_args = array( 
    'post_type'   => $type, 
    'post_parent' => $parent 
);

$ids = array( $parent );
$ids = array_merge( $ids, array_keys( get_children( $child_args ) ));

$query = new WP_Query( 
    array( 
        'post_type'      => 'page', 
        'post_status'    => 'publish', 
        'post__in'       => $ids, 
        'posts_per_page' => -1 
    ) 
);

Приведённый выше код по сути делает то же самое, что и подключение к фильтру posts_where с последующим разбором SQL-условия, однако достигает этого другим способом.

23 мар. 2016 г. 14:10:26
7

Мы можем отфильтровать условие posts_where в генерируемом SQL, чтобы возвращать не только дочерние записи, но и родительскую запись/страницу. Для этого мы установим собственный аргумент wpse_include_parent, который при значении true будет соответствующим образом изменять SQL-запрос.

Внутри нашего фильтра posts_where нам нужно проверить, установлен ли наш пользовательский аргумент и указан ли аргумент post_parent. Затем мы получаем это значение и передаем его в фильтр для расширения SQL-запроса. Здесь удобно то, что post_parent принимает только целочисленное значение, поэтому нам нужно лишь проверить, что значение является целым числом.

ЗАПРОС

$args = [
    'wpse_include_parent' => true,
    'post_parent'         => 256,
    'post_type'           => 'page'
    // Дополнительные аргументы
];
$q = new WP_Query( $args );

Как видно, мы установили 'wpse_include_parent' => true, чтобы "активировать" наш фильтр.

ФИЛЬТР

add_filter( 'posts_where', function ( $where, \WP_Query $q ) use ( &$wpdb )
{
    if ( true !== $q->get( 'wpse_include_parent' ) )
        return $where;

    /**
     * Получаем значение post_parent и проверяем его
     * post_parent принимает только целочисленное значение, поэтому
     * просто проверяем, что значение является целым числом
     */
    $post_parent = filter_var( $q->get( 'post_parent' ), FILTER_VALIDATE_INT );
    if ( !$post_parent )
        return $where;

    /** 
     * Включаем родительскую запись в наш запрос
     *
     * Поскольку мы уже проверили значение $post_parent,
     * нам не нужно использовать метод prepare() здесь
     */
    $where .= " OR $wpdb->posts.ID = $post_parent";

    return $where;
}, 10, 2 );

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

23 мар. 2016 г. 20:18:49
Комментарии

Код работает, но, к сожалению, возвращает одну и ту же родительскую запись дважды. Это происходит при выполнении WP_Query с пользовательским атрибутом.

luukvhoudt luukvhoudt
30 мар. 2016 г. 12:22:31

Я перепроверил свой код и не могу воспроизвести вашу проблему

Pieter Goosen Pieter Goosen
30 мар. 2016 г. 14:58:26

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

luukvhoudt luukvhoudt
30 мар. 2016 г. 15:13:25

Я посмотрел на var_dump(). В $q->posts возвращаются две записи с ID 2126 и 2116, что выглядит корректно — дублирующихся записей нет. Думаю, вы видите запись 2116 также в $q->post, что тоже правильно. Это значение глобальной переменной $post для данного конкретного запроса, и по умолчанию это всегда будет первая запись из $q->posts.

Pieter Goosen Pieter Goosen
30 мар. 2016 г. 15:54:17

Хорошо, но код всегда возвращает две родительские записи, даже при просмотре дочерней записи этого родителя (т.е. global $post содержит объект дочерней записи). Вопрос в том, почему происходит три итерации, когда есть только две записи для перебора?

luukvhoudt luukvhoudt
30 мар. 2016 г. 16:14:33

почему происходит три итерации, когда есть только две записи для перебора Это уже не связано с самим запросом. В объекте запроса действительно две записи. Если появляется третья запись, она добавляется в запрос через какой-то пользовательский фильтр. Всё в вашем запросе выглядит нормально, хотя SQL-запрос кажется странным из-за работы других фильтров. Вам стоит отладить это: убедитесь, что $post соответствует ожиданиям, правильно ли организован цикл, корректно ли используются другие фильтры в данном контексте. На чистой установке у меня всё работает как задумано.

Pieter Goosen Pieter Goosen
30 мар. 2016 г. 16:42:38

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

Pieter Goosen Pieter Goosen
30 мар. 2016 г. 16:44:07
Показать остальные 2 комментариев
2
    $args = array(
        'post_type' => 'tribe_events', // Тип записи - события
        'posts_per_page' => '-1', // Получить все записи
        'orderby' => 'ID', // Сортировка по ID
        'order' => 'ASC', // По возрастанию
        'post_parent' => $postID, // Родительская запись
    );

    $children = new WP_Query($args); // Запрос для дочерних записей
    $parent[] = get_post($postID); // Получаем родительскую запись
    $family = array_merge($parent, $children->get_posts()); // Объединяем в один массив

Похоже, работает. Какие будут комментарии?

22 окт. 2017 г. 19:31:35
Комментарии

Верно, но это не будет так эффективно, как принятый ответ. Подход из принятого ответа требует выполнения только одного запроса, в то время как ваш подход приведет к выполнению двух запросов (если они еще не закешированы).

luukvhoudt luukvhoudt
23 окт. 2017 г. 14:21:23

Я считаю, ваш ответ намного лучше!!! Гораздо проще и читаемее!!! И ради чего... ради сохранения одного запроса? из сотен в контексте WordPress... и он уже закеширован раз 30, прежде чем дойдет до вашего кода...

К тому же, сохраненный запрос — это просто WHERE ID=123.... 0.0001с

Antony Gibbs Antony Gibbs
28 апр. 2022 г. 09:49:02
1

Использование global $wpdb в сочетании с [get_results()][1] также является вариантом. С точки зрения производительности, я считаю это лучшим решением, так как выполняется только один запрос.

Вот мой окончательный код.

<ul class="tabs"><?php

    global $wpdb, $post;

    $parent = count(get_post_ancestors($post->ID))-1 > 0 ? $post->post_parent : $post->ID;

    $sql = "SELECT ID FROM `{$wpdb->prefix}posts`";
    $sql.= " WHERE ID='{$parent}' OR post_parent='{$parent}' AND post_type='page'";
    $sql.= " ORDER BY `menu_order` ASC";

    $tabs = $wpdb->get_results($sql);

    $output = '';
    foreach ($tabs as $tab) {
        $current = $post->ID == $tab->ID ? ' class="active"' : '';

        $output .= '<li'.$current.'>';
        $output .= empty($current) ? '<a href="'.get_permalink($tab->ID).'">' : '';
        $output .=   get_the_post_thumbnail($tab->ID, 'menu-24x24');
        $output .=   '<span>'.get_the_title($tab->ID).'</span>';
        $output .= empty($current) ? '</a>' : '';
        $output .= '</li>';
    }
    print $output;

?></ul>
23 мар. 2016 г. 14:45:45
Комментарии

Можно было бы подумать, что это так, однако WP_Query внутренне кэширует результаты запросов, поэтому, например, вызовы get_children() или get_post_ancestors() в дополнение к WP_Query не должны создавать дополнительной нагрузки на производительность.

Adam Adam
23 мар. 2016 г. 14:56:22
1

Если я правильно вас понял, вы хотите получить идентификаторы как родительских, так и всех дочерних страниц. В WordPress есть функции для получения дочерних страниц, например:

https://codex.wordpress.org/Function_Reference/get_page_children

Насколько я понимаю, поскольку вы используете WP_Query, вы уже получаете идентификаторы родительских страниц, поэтому вам остается только передать соответствующий ID в указанную выше функцию, чтобы получить нужный результат.

Примечание: стоит отметить, что эта функция не выполняет запрос к базе данных, поэтому с точки зрения производительности она более эффективна, так как вы делаете только один запрос к БД.

23 мар. 2016 г. 21:11:04
Комментарии

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

luukvhoudt luukvhoudt
23 мар. 2016 г. 21:37:12
5
-2

Вы можете использовать функцию get_pages() с параметром child_of следующим образом:

<?php
   get_pages( array(
     'child_of' => $parent_page_id;
   ) );
?>
23 мар. 2016 г. 13:45:41
Комментарии

Это не то, чего я хочу достичь, я не хочу использовать wp_list_pages, потому что вывод не соответствует моим потребностям.

luukvhoudt luukvhoudt
23 мар. 2016 г. 13:48:30

Посмотрите мой обновленный ответ. Надеюсь, это вам поможет.

Lalji Nakum Lalji Nakum
23 мар. 2016 г. 13:54:49

Спасибо за обновление, к сожалению, здесь нет объяснения, как включить родительскую запись.

luukvhoudt luukvhoudt
23 мар. 2016 г. 14:00:39

Подробное объяснение можно найти здесь: https://codex.wordpress.org/Function_Reference/get_pages

Lalji Nakum Lalji Nakum
23 мар. 2016 г. 14:06:38

Согласно документации: Параметр child_of не применяется к SQL-запросу для страниц. Он применяется к результатам запроса. А также он не возвращает родительскую страницу.

luukvhoudt luukvhoudt
23 окт. 2017 г. 14:17:34