Ссылки на предыдущие и следующие страницы для иерархических страниц
Задача
Мы имеем иерархический пользовательский тип записей (постов) с названием "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;
}
Использование в шаблоне
Чтобы добавить кнопки навигации, используйте следующий код:
Упрощенная версия кода
Если вам нужно более простое решение, можно использовать следующий вариант кода:
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. Это позволит пользователям легко перемещаться по вашему контенту и улучшит впечатления от использования вашего сайта.