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

Если вам нужны только результаты для типа записи "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-условия, однако достигает этого другим способом.

Мы можем отфильтровать условие 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
, а также ее дочерние записи.

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

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

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

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

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

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

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

$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()); // Объединяем в один массив
Похоже, работает. Какие будут комментарии?

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

Я считаю, ваш ответ намного лучше!!! Гораздо проще и читаемее!!! И ради чего... ради сохранения одного запроса? из сотен в контексте WordPress... и он уже закеширован раз 30, прежде чем дойдет до вашего кода...
К тому же, сохраненный запрос — это просто WHERE ID=123
.... 0.0001с

Использование 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>

Если я правильно вас понял, вы хотите получить идентификаторы как родительских, так и всех дочерних страниц. В WordPress есть функции для получения дочерних страниц, например:
https://codex.wordpress.org/Function_Reference/get_page_children
Насколько я понимаю, поскольку вы используете WP_Query, вы уже получаете идентификаторы родительских страниц, поэтому вам остается только передать соответствующий ID в указанную выше функцию, чтобы получить нужный результат.
Примечание: стоит отметить, что эта функция не выполняет запрос к базе данных, поэтому с точки зрения производительности она более эффективна, так как вы делаете только один запрос к БД.

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

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

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

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