Ссылки на предыдущие и следующие страницы для иерархических записей

Ссылки на предыдущие и следующие страницы для иерархических страниц

Задача

Мы имеем иерархический пользовательский тип записей (постов) с названием "wiki", где родительские страницы (с уровнем вложенности 0) действуют как архивы, а внутренние страницы — как документы. В шаблоне необходимо создать кнопки "Предыдущая" и "Следующая", которые будут превращаться в ссылки на соответствующие записи (страницы). Это позволит пользователю легко перемещаться между родительской страницей и вложенными страницами (независимо от уровня вложенности).

Получение предыдущих и следующих страниц

Чтобы получить объекты предыдущей и следующей документации относительно текущей страницы, можно использовать следующий код:


/**

  • Получает объекты предыдущих и следующих страниц документации относительно текущей.
  • @return Wiki_Item[]|null[]
    */
    public function get_adjacent_pages() {
    global $chapter_ids;

    $walker = new class extends Walker_Page {
    public function start_el( &$output, $data_object, $depth = 0, $args = [], $current_object_id = 0 ) {
    global $chapter_ids;
    $chapter_ids[] = (int) $data_object->ID;
    }
    };

    // Добавляем ID страницы индекса в начало списка, так как её не будет в wp_list_pages()
    $index_page_id = $this->get_index_page()->get_id();
    $chapter_ids = [ $index_page_id ];

    wp_list_pages( [
    'post_type' => $this->get_post_type(),
    'child_of' => $index_page_id,
    'echo' => false,
    'walker' => $walker,
    ] );

    $data = [
    'prev' => null,
    'next' => null,
    ];

    foreach ( $chapter_ids as $index => $chapter_id ) {
    if ( $chapter_id === $this->get_id() ) {
    $data['prev'] = isset( $chapter_ids[ $index - 1 ] ) ? new self( $chapter_ids[ $index - 1 ] ) : null;
    $data['next'] = isset( $chapter_ids[ $index + 1 ] ) ? new self( $chapter_ids[ $index + 1 ] ) : null;
    break;
    }
    }

    unset( $chapter_ids );

    return $data;
    }

Использование в шаблоне

Чтобы добавить кнопки навигации, используйте следующий код:

get_adjacent_pages();
$prev_url = $wiki_items['prev'] ? $wiki_items['prev']->get_url() : null;
$next_url = $wiki_items['next'] ? $wiki_items['next']->get_url() : null;
?>





Упрощенная версия кода

Если вам нужно более простое решение, можно использовать следующий вариант кода:


class Adjacent_Pages {

/** Данные страниц в порядке пагинации */
private array $items_data;

public function __construct( string $post_type, int $child_of = 0 ) {
    $this->set_items_data( $post_type, $child_of );
}

/**
 * Возвращает объекты предыдущей и следующей страниц относительно текущей.
 * Учитывает иерархическое вложение.
 *
 * @return array{0:WP_Post|null, 1:WP_Post|null} Предыдущий и следующий пост.
 */
public function get_prev_next( ?WP_Post $current_post = null ): array {

    if( ! $current_post ) {
        $current_post = $GLOBALS['post'];
    }

    foreach ( $this->items_data as $index => $item ) {
        if ( $item->ID !== $current_post->ID ) {
            continue;
        }

        $prev_item = $this->items_data[ $index - 1 ] ?? null;
        $next_item = $this->items_data[ $index + 1 ] ?? null;

        break;
    }

    return [
        isset( $prev_item ) ? get_post( $prev_item->ID ) : null,
        isset( $next_item ) ? get_post( $next_item->ID ) : null,
    ];
}

private function set_items_data( string $post_type, int $child_of ): void {
    $walker = new class extends Walker_Page {
        public array $items_data = [];

        public function start_el( &$output, $data_object, $depth = 0, $args = [], $current_object_id = 0 ) {
            $this->items_data[] = (object) [
                'ID' => (int) $data_object->ID,
                'post_parent' => (int) $data_object->post_parent,
                'post_title' => $data_object->post_title,
            ];
        }
    };

    wp_list_pages( [
        'post_type'   => $post_type,
        'child_of'    => $child_of,
        'echo'        => false,
        'sort_order'  => 'ASC',
        'sort_column' => 'menu_order, post_title',
        'walker'      => $walker,
    ] );

    $this->items_data = $walker->items_data;
}

}

Использование

Чтобы использовать данный класс, выполните следующие шаги:


$chapter_post_id = 654;
[ $prev, $next ] = ( new Adjacent_Pages( 'wiki', $chapter_post_id ) )->get_prev_next();
$prev_url = $prev ? get_permalink( $prev ) : '';
$next_url = $next ? get_permalink( $next ) : '';

Альтернативный способ без использования wp_list_pages()

Если вы хотите не использовать функцию wp_list_pages(), замените метод set_items_data() в классе Adjacent_Pages следующим образом:


class Native_Adjacent_Pages extends Adjacent_Pages {

protected function set_items_data( string $post_type, int $child_of ): void {
    $pages = get_pages( [
        'post_type'    => $post_type,
        'child_of'     => $child_of,
        'hierarchical' => false,
        'sort_order'   => 'ASC',
        'sort_column'  => 'menu_order, post_title',
    ] );

    $this->items_data = ( new Kama_Make_Pages_Tree( $pages ) )->get_tree_flat_data();
}

}

Использование остается прежним

Метод использования остается тем же:


$chapter_post_id = 654;
[ $prev, $next ] = ( new Native_Adjacent_Pages( 'wiki', $chapter_post_id ) )->get_prev_next();
$prev_url = $prev ? get_permalink( $prev ) : '';
$next_url = $next ? get_permalink( $next ) : '';

Класс для работы с иерархией

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


final class Kama_Make_Pages_Tree {

public array $parent_groups;
public array $top_parents;

private array $wp_post_fields = [
    'ID'          => 'int',
    'post_parent' => 'int',
    'post_title'  => 'string',
];

public function __construct( $pages ) {
    $pages = $this->prepare_pages( $pages );
    $this->parent_groups = self::group_by_parent_id( $pages );
    $this->top_parents = $this->parent_groups[0]; // посты нулевого уровня
}

public function get_tree_flat_data(): array {
    return $this->collect_flat_data( $this->get_tree() );
}

public function get_tree(): array {
    $tree = $this->top_parents;
    $this->fill_tree_recursively( $tree );

    return $tree;
}

private static function group_by_parent_id( array $pages ) {
    $groups = [];

    foreach( $pages as $p ) {
        $parent_id = (int) $p->post_parent;
        $groups[ $parent_id ][] = $p;
    }

    return $groups;
}

private function collect_flat_data( $pages_tree ): array {
    $flat_data = [];

    foreach( $pages_tree as $obj ) {
        $flat_data[] = $obj;

        if( ! empty( $obj->children ) ) {
            foreach( $this->collect_flat_data( $obj->children ) as $_obj ) {
                $flat_data[] = $_obj;
            }
        }
    }

    return $flat_data;
}

private function fill_tree_recursively( & $parents ): void {
    foreach( $parents as & $parent ) {
        if( empty( $this->parent_groups[ $parent->ID ] ) ) {
            continue;
        }

        $parent->children = $this->parent_groups[ $parent->ID ];
        $this->fill_tree_recursively( $this->parent_groups[ $parent->ID ] );
    }
    unset( $parent );
}

private function prepare_pages( $pages ): array {
    $unset_fields = array_diff_key( (array) reset( $pages ), $this->wp_post_fields );
    $unset_fields = array_keys( $unset_fields );

    foreach( $pages as & $p ) {
        $p = (object) (array) $p; // привести к стандартному классу

        foreach( $unset_fields as $name ) {
            unset( $p->$name );
        }

        foreach( $this->wp_post_fields as $name => $type ) {
            settype( $p->$name, $type );
        }
    }

    return $pages;
}

}

Заключение

С помощью вышеописанных подходов вы можете реализовать навигацию между страницами в иерархической структуре WordPress. Это позволит пользователям легко перемещаться по вашему контенту и улучшит впечатления от использования вашего сайта.

Leave a Reply

Ваш адрес email не будет опубликован. Обязательные поля помечены *