Как получить все дочерние и внучатые записи иерархического пользовательского типа записей?

15 янв. 2013 г., 14:33:57
Просмотры: 18.8K
Голосов: 9

Мне нужно получить все подзаписи определенного (корневого) родительского ID.

get_posts( array( 'numberposts' => -1, 'post_status' => 'publish', 'post_type' => 'microsite', 'post_parent' => $root_parent_id, 'suppress_filters' => false ) );

WP-Codex: функция get_post() имеет параметр post_parent, но не имеет параметра child_of.

Преимущество функции get_pages() в сочетании с параметром child_of заключается в том, что "... Параметр child_of будет получать не только прямых потомков, но и все 'внучатые' записи для данного ID."*

0
Все ответы на вопрос 5
2
13

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

Например:

function get_posts_children($parent_id){
    $children = array();
    // получаем дочерние записи
    $posts = get_posts( array( 'numberposts' => -1, 'post_status' => 'publish', 'post_type' => 'microsite', 'post_parent' => $parent_id, 'suppress_filters' => false ));
    // теперь получаем "внуков"
    foreach( $posts as $child ){
        // рекурсия!! ура
        $gchildren = get_posts_children($child->ID);
        // объединяем "внуков" в массив детей
        if( !empty($gchildren) ) {
            $children = array_merge($children, $gchildren);
        }
    }
    // добавляем прямых потомков, найденных ранее
    $children = array_merge($children,$posts);
    return $children;
}

// пример использования функции выше, вызовем её и выведем результаты
$descendants = get_posts_children($post->ID);
echo '<pre>';
print_r($descendants);
echo '</pre>';

Да, эта функция вызывает саму себя — это рекурсивная функция. Она будет продолжать вызывать себя, пока не дойдет до записи, у которой нет дочерних элементов, после чего вернет результат без вызова себя, и весь стек вызовов начнет "всплывать" обратно, формируя массив дочерних элементов. Рекомендуется изучить эту тему подробнее.

Обратите внимание, что ваш подход имеет встроенные затраты, независимо от того, используете ли вы рекурсию или нет. Эти затраты зависят от количества уровней вложенности записей. 5 уровней записей будут более ресурсоемкими, чем 2, и масштабирование здесь не линейное. В зависимости от реализации, вы можете использовать transient-ы для кеширования результатов.

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

15 янв. 2013 г. 15:00:37
Комментарии

Проблема с этим кодом в том, что он не обеспечивает правильный порядок; я получаю сначала все элементы верхнего уровня, а затем все элементы второго уровня в одном уплощенном массиве. Есть идеи, как это исправить?

dama_do_bling dama_do_bling
9 апр. 2015 г. 00:23:12

Этот код не предназначен для такой функциональности, у вас другая проблема, которая требует немного другого решения и хорошей дозы контекста

Tom J Nowell Tom J Nowell
9 апр. 2015 г. 03:38:38
1

У меня было похожее требование, но необходимо было сохранить иерархию, поэтому ответ Тома дал мне хороший старт:

function get_post_offspring($post)
{
    // Функция для программного обхода потомков записи

    // Получаем непосредственных потомков текущей записи
    // и добавляем их в новый элемент объекта под названием children
    $post->children = get_children(array("post_parent" => $post->ID, "post_type" => "page", "post_status" => "publish"));
    
    // если у записи нет потомков, просто возвращаем её
    // с пустым массивом children
    if (empty($post->children)) {
        return $post;
    }
    foreach ($post->children as $child) {
        // Для каждого потомка этой записи... получаем его потомков
        $children = get_children(array("post_parent" => $child->ID, "post_type" => "page", "post_status" => "publish"));
        if (!empty($children)) {
            // если у этой записи есть потомки... то вызываем эту функцию снова
            // чтобы назначить элемент children этой записи и продолжить обход вглубь
            // потомков этой записи

            $child = get_post_offspring($child);
        }
    }

    return $post;
}

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

function render_list_items($children)
{
    foreach ($children as $child) {
        $has_children = !empty($child->children);

        echo "<li>";
        echo "<a href='" . get_permalink($child) . "'>" . $child->post_title . "</a";
        if ($has_children) {
            // Если запись имеет потомков, создаём новый <ul> внутри текущего
            // <li> и снова вызываем функцию
            echo "<ul>";
            echo render_list_items($child->children);
            echo "</ul>";
        }

        echo "</li>";
    }

}

Таким образом, всё вместе выглядит примерно так:

global $post;

$post_id = $post->ID;

// Получаем предков текущей записи, где последний элемент массива
// является самым верхним родителем
$ancestors = get_post_ancestors($post->ID);

$root_id = array_pop($ancestors);
// Теперь у нас есть корневая запись этой записи, и мы можем получить всех её потомков
$root_post = get_post($root_id);

$root_children = get_post_offspring($root_post);
23 июл. 2021 г. 16:05:53
Комментарии

echo "<a href='" . get_permalink($child) . "'>" . $child->post_title . "</a";

отсутствует закрывающий > у ahref

Но в остальном блестящий ответ, спасибо!

Lemon Bacon Lemon Bacon
25 сент. 2022 г. 19:29:16
1

Просто используйте get_page_children(). Эта функция работает для любого типа записей (не только для страниц) и по сути является уже реализованной в ядре WordPress версией того, что показал @TomJNowell в другом ответе.

$children = get_page_children( $post->ID, $GLOBALS['wp_query'] );

Приведённый выше пример взят из Codex. Поэтому вы можете просто использовать глобальный объект запроса (или любой другой объект запроса) в качестве основы для поиска.

23 сент. 2013 г. 14:14:09
Комментарии

Но как использовать это для другого типа записи? Не получается заставить работать.

dama_do_bling dama_do_bling
9 апр. 2015 г. 00:18:30
0

Используйте следующий шорткод для отображения всех дочерних и вложенных страниц в иерархическом виде. Использование: [my_children_list] или [my_children_list page_id=123]

function my_children_list_func($atts, $content = null) {
    global $post;

    $a = shortcode_atts( array(
            'page_id' => ''
    ), $atts );

    $args = array( 
            'numberposts' => -1, 
            'post_status' => 'publish', 
            'post_type' => 'microsite', 
            'post_parent' => (isset($a['page_id']) && $a['page_id']) ? $a['page_id'] : $post->ID,
            'suppress_filters' => false 
    );

    $parent = new WP_Query( $args );

    ob_start();

    if ( $parent->have_posts() ) :?>
            <ul>
            <?php while ( $parent->have_posts() ) : $parent->the_post(); ?>
                    <li><a href="<?php the_permalink(); ?>" title="<?php the_title(); ?>"><?php the_title(); ?></a>
                    <?php echo do_shortcode('[tadam_children_list page_id='.get_the_ID().']') ?>
                    </li>
            <?php endwhile;?>
            </ul>
    <?php endif; wp_reset_postdata();

    return ob_get_clean();
}
add_shortcode( 'my_children_list', 'my_children_list_func' );
31 дек. 2018 г. 11:22:10
0

На случай, если кто-то наткнется на это и решит реализовать выбранный ответ, функция get_pages() работает со страницами и иерархическими типами записей. Таким образом, вы можете просто использовать get_pages() с параметром child_of.

8 июн. 2020 г. 14:53:26