Files
wordpress-theme-aksrt/inc/classes/class-aksrt-docs-presenter.php
T

233 lines
6.8 KiB
PHP

<?php
/**
* AKSRT Docs Presenter
*
* Builds document tree structures, breadcrumb chains,
* and chapter navigation for the docs mode.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
class AKSRT_Docs_Presenter {
private static $instance = null;
private function __construct() {}
public static function get_instance(): self {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Get the document root page ID for a given page.
*
* @param int $page_id Current page ID.
* @return int Root page ID (self if top-level, parent otherwise).
*/
public function get_root_id(int $page_id): int {
$parent_id = wp_get_post_parent_id($page_id);
return $parent_id ?: $page_id;
}
/**
* Build a flat ordered list of all pages under a document root.
*
* @param int $root_id Root page ID.
* @param int $max_depth Maximum depth (default 4).
* @return array Array of page objects with added '_depth' property.
*/
public function get_flat_tree(int $root_id, int $max_depth = 4): array {
$result = [];
$this->collect_children($root_id, $result, 0, $max_depth);
return $result;
}
/**
* Recursive helper to collect pages in order.
*/
private function collect_children(int $parent_id, array &$result, int $depth, int $max_depth): void {
if ($depth >= $max_depth) {
return;
}
$children = get_pages([
'parent' => $parent_id,
'sort_column' => 'menu_order, post_title',
'sort_order' => 'ASC',
'post_status' => 'publish',
]);
foreach ($children as $child) {
$child->_depth = $depth;
$result[] = $child;
$this->collect_children($child->ID, $result, $depth + 1, $max_depth);
}
}
/**
* Get sibling pages for chapter navigation (prev/next).
*
* @param int $page_id Current page ID.
* @return array{prev: object|null, next: object|null}
*/
public function get_chapter_nav(int $page_id): array {
$parent_id = wp_get_post_parent_id($page_id);
$siblings = get_pages([
'parent' => $parent_id ?: 0,
'sort_column' => 'menu_order, post_title',
'sort_order' => 'ASC',
'post_status' => 'publish',
]);
$current_index = -1;
foreach ($siblings as $i => $sibling) {
if ((int) $sibling->ID === $page_id) {
$current_index = $i;
break;
}
}
$prev = $current_index > 0 ? $siblings[$current_index - 1] : null;
$next = ($current_index >= 0 && $current_index < count($siblings) - 1)
? $siblings[$current_index + 1]
: null;
return ['prev' => $prev, 'next' => $next];
}
/**
* Get root-level document pages for the document navigation index.
*
* @param string $order Sort order: 'menu_order', 'date', 'title'.
* @return array
*/
public function get_root_pages(string $order = 'menu_order'): array {
$sort_column = 'menu_order, post_title';
if ($order === 'title') {
$sort_column = 'post_title';
} elseif ($order === 'date') {
$sort_column = 'post_date';
}
return get_pages([
'sort_column' => $sort_column,
'sort_order' => 'ASC',
'parent' => 0,
'hierarchical' => 0,
'post_status' => 'publish',
]);
}
/**
* Get recently modified pages for the sidebar.
*
* @param int $count Number of pages to return.
* @return array
*/
public function get_recent_pages(int $count = 8): array {
return get_pages([
'sort_column' => 'post_modified',
'sort_order' => 'DESC',
'number' => $count,
'post_status' => 'publish',
]);
}
/**
* Build breadcrumb trail for a document page.
*
* @param int $page_id Current page ID.
* @return array Array of {id, title, url} objects.
*/
public function get_breadcrumbs(int $page_id): array {
$trail = [];
$current = get_post($page_id);
while ($current) {
array_unshift($trail, [
'id' => $current->ID,
'title' => $current->post_title,
'url' => get_permalink($current->ID),
]);
$parent_id = wp_get_post_parent_id($current->ID);
$current = $parent_id ? get_post($parent_id) : null;
}
return $trail;
}
/**
* Search document pages by keyword.
*
* @param string $keyword Search keyword.
* @param int $root_id Optional root page ID to scope search.
* @param int $limit Max results.
* @return array Array of page objects with 'url' and 'excerpt' added.
*/
public function search_pages(string $keyword, int $root_id = 0, int $limit = 20): array {
$args = [
'post_type' => 'page',
'post_status' => 'publish',
's' => $keyword,
'posts_per_page' => $limit,
'orderby' => 'relevance',
];
if ($root_id > 0) {
$tree_ids = [$root_id];
$this->collect_children($root_id, $tree_ids, 0, 4);
$ids = array_map(static function ($item) {
return is_object($item) ? $item->ID : (int) $item;
}, $tree_ids);
$ids[] = $root_id;
$args['post__in'] = array_unique($ids);
}
$query = new WP_Query($args);
$results = [];
foreach ($query->posts as $post) {
$results[] = [
'id' => $post->ID,
'title' => $post->post_title,
'url' => get_permalink($post->ID),
'excerpt' => wp_trim_words(wp_strip_all_tags($post->post_content), 30),
'modified' => $post->post_modified,
'parent' => (int) $post->post_parent,
];
}
wp_reset_postdata();
return $results;
}
/**
* Check whether a given page belongs to the docs mode.
*
* @param int $page_id Page ID.
* @return bool
*/
public function is_docs_page(int $page_id): bool {
if (!class_exists('AKSRT_Settings')) {
return false;
}
$settings = AKSRT_Settings::get_docs_mode_settings();
if (empty($settings['enabled'])) {
return false;
}
$template = get_page_template_slug($page_id);
return in_array($template, [
'templates/pages/page-documentnav.php',
'templates/pages/page-special.php',
], true);
}
}