WordPress как на ладони
rgbcode is looking for WordPress developers.

Ссылки Назад и Вперёд для древовидных страниц

Задача:

Имеется иерархическая ctp=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;
}

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

<div class="documents__nav-buttons">
	<div class="documents-nav">
		<?php
		$wiki_item  = \Wiki_Item( get_the_ID() );
		$wiki_items = $wiki_item->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;
		?>

		<a class="documents-nav__item documents-nav__item--prev <?= $prev_url ? '' : 'documents-nav__item--disabled' ?>" href="<?= $prev_url ?>">
			<svg class="documents-nav__icon" xmlns="http://www.w3.org/2000/svg" width="16" height="12" viewBox="0 0 16 12"
				 fill="none">
				<path d="M6.88462 1L1.5 6M1.5 6L6.88462 11M1.5 6H15.5"/>
			</svg>
		</a>

		<a class="documents-nav__item documents-nav__item--next <?= $next_url ? '' : 'documents-nav__item--disabled' ?>" href="<?= $next_url ?>">
			<svg class="documents-nav__icon" xmlns="http://www.w3.org/2000/svg" width="16" height="12" viewBox="0 0 16 12"
				 fill="none">
				<path d="M9.11538 1L14.5 6M14.5 6L9.11538 11M14.5 6H0.499999"/>
			</svg>
		</a>
	</div>
</div>

Выглядит так:

Стрелки видны в правом верхнем углу скриншота

Тот же код, но немного унифицированный

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;
		unset( $walker ); // just in case
	}

}

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

$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()

Для этого нужно заменить метод 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 ) : '';

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

/**
 * Gets a flat array of hierarchical posts {@see get_pages()}, then
 * creates a nested tree from them with `children` elements, then
 * creates flat array in the order of that tree elements.
 */
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',
	];

	/**
	 * @param \WP_Post[] $pages An array of pages objects obtained with {@see get_pages()}.
	 */
	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]; // zero level posts
	}

	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;
	}

	/**
	 * Fill each passed item's `children` field based on existing $parent_groups.
	 *
	 * @param object[] $parents
	 */
	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 ); // just in case
	}

	/**
	 * @param \WP_Post[] $pages
	 */
	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; // cast to stdClass

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

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

		return $pages;
	}

}
campusboy 4856youtube.com/c/wpplus
Создатель YouTube канала wp-plus, на котором делюсь своим опытом. Активный пользователь wp-kama.ru. WordPress-разработчик. Разработка сайтов и лендингов. Доработка существующих проектов. Сопровождение ресурсов.
Редакторы: Kama 9752
3 комментария
    Войти