navWalker = new NavWalker(); } /** * Build the complete context for the current request. */ public function build(): array { $context = [ 'site' => $this->getSiteData(), 'menu' => $this->getMenuData('primary'), 'footer_menu' => $this->getMenuData('footer'), 'dark_mode' => true, 'layout' => 'default', 'header_variant' => $this->getHeaderVariant(), 'footer_variant' => $this->getFooterVariant(), 'user' => $this->getUserData(), ]; if (is_singular()) { $context['post'] = $this->getPostData(); if (is_singular('post')) { $context['comments'] = $this->getCommentsData(); $context['post_navigation'] = $this->getPostNavigation(); $context['more_posts'] = $this->getMorePosts(); } } if (is_home() || is_archive() || is_search()) { $context['posts'] = $this->getPostsLoop(); $context['pagination'] = $this->getPagination(); } if (is_archive()) { $context['archive'] = $this->getArchiveData(); } if (is_search()) { $context['search_query'] = get_search_query(); } // Sidebar layout detection. if (is_home()) { $pageId = (int) get_option('page_for_posts'); if ($pageId) { $templateSlug = get_page_template_slug($pageId); if ($templateSlug === 'home-sidebar') { $context['layout'] = 'sidebar'; } } $context['sidebar'] = $this->getSidebarData(); } // Sidebar data for pages/posts using the "Page with Sidebar" template. if (is_page() || is_singular('post')) { $slug = get_page_template_slug(); if ($slug === 'page-sidebar') { $context['sidebar'] = $this->getSidebarData(); } } return $context; } /** * Get global site information. */ private function getSiteData(): array { return [ 'name' => get_bloginfo('name'), 'description' => get_bloginfo('description'), 'url' => home_url('/'), 'charset' => get_bloginfo('charset'), ]; } /** * Get current user data for header/navigation. */ private function getUserData(): array { if (! is_user_logged_in()) { return ['logged_in' => false]; } $user = wp_get_current_user(); $account_url = function_exists('wc_get_page_permalink') ? wc_get_page_permalink('myaccount') : admin_url('profile.php'); return [ 'logged_in' => true, 'display_name' => $user->display_name, 'avatar' => get_avatar($user->ID, 32, '', '', ['class' => 'rounded-circle']), 'account_url' => $account_url, ]; } /** * Get navigation menu items for a location. */ private function getMenuData(string $location): array { $locations = get_nav_menu_locations(); if (isset($locations[$location])) { $menu = wp_get_nav_menu_object($locations[$location]); if ($menu) { $items = wp_get_nav_menu_items($menu->term_id); if ($items) { return $this->navWalker->buildTree($items); } } } // Fallback for primary: list top-level pages. if ($location === 'primary') { return $this->getPagesFallback(); } return []; } /** * Fallback menu from published pages. */ private function getPagesFallback(): array { $pages = get_pages([ 'sort_column' => 'menu_order,post_title', 'parent' => 0, ]); $items = []; foreach ($pages as $page) { $items[] = [ 'id' => $page->ID, 'title' => $page->post_title, 'url' => get_permalink($page->ID), 'target' => '', 'classes' => '', 'active' => is_page($page->ID), 'children' => [], ]; } return $items; } /** * Get single post/page data. */ private function getPostData(): array { global $post; return [ 'id' => $post->ID, 'title' => wp_specialchars_decode( get_the_title() ), 'url' => get_permalink(), 'content' => apply_filters('the_content', get_the_content()), 'excerpt' => get_the_excerpt(), 'date' => get_the_date(), 'date_iso' => get_the_date('c'), 'modified' => get_the_modified_date(), 'author' => [ 'name' => get_the_author(), 'url' => get_author_posts_url(get_the_author_meta('ID')), ], 'thumbnail' => get_the_post_thumbnail_url($post->ID, 'large') ?: '', 'categories' => $this->getTermsList('category'), 'tags' => $this->getTermsList('post_tag'), 'type' => get_post_type(), ]; } /** * Get posts for the main query loop. */ private function getPostsLoop(): array { global $wp_query; $posts = []; if ($wp_query->have_posts()) { while ($wp_query->have_posts()) { $wp_query->the_post(); $posts[] = [ 'id' => get_the_ID(), 'title' => wp_specialchars_decode( get_the_title() ), 'url' => get_permalink(), 'excerpt' => get_the_excerpt(), 'date' => get_the_date(), 'date_iso' => get_the_date('c'), 'author' => [ 'name' => get_the_author(), 'url' => get_author_posts_url(get_the_author_meta('ID')), ], 'thumbnail' => get_the_post_thumbnail_url(null, 'medium_large') ?: '', 'categories' => $this->getTermsList('category'), 'tags' => $this->getTermsList('post_tag'), 'read_more' => __('Read more', 'wp-bootstrap'), ]; } wp_reset_postdata(); } return $posts; } /** * Build pagination data. */ private function getPagination(): array { global $wp_query; $totalPages = (int) $wp_query->max_num_pages; $currentPage = max(1, get_query_var('paged')); if ($totalPages <= 1) { return []; } $pages = []; for ($i = 1; $i <= $totalPages; $i++) { $pages[] = [ 'number' => $i, 'url' => get_pagenum_link($i), 'is_current' => ($i === $currentPage), ]; } return [ 'pages' => $pages, 'current' => $currentPage, 'total' => $totalPages, 'prev_url' => ($currentPage > 1) ? get_pagenum_link($currentPage - 1) : null, 'next_url' => ($currentPage < $totalPages) ? get_pagenum_link($currentPage + 1) : null, 'prev_text' => __('Previous', 'wp-bootstrap'), 'next_text' => __('Next', 'wp-bootstrap'), ]; } /** * Get archive page data. */ private function getArchiveData(): array { return [ // wp_kses_post() allows safe HTML (headings, links, spans) while stripping // script/event-handler attributes that could be injected via term descriptions. 'title' => wp_kses_post(get_the_archive_title()), 'description' => wp_kses_post(get_the_archive_description()), ]; } /** * Get comments for the current post. */ private function getCommentsData(): array { $postId = get_the_ID(); $count = (int) get_comments_number($postId); $comments = get_comments([ 'post_id' => $postId, 'status' => 'approve', 'orderby' => 'comment_date_gmt', 'order' => 'ASC', ]); return [ 'list' => $this->buildCommentTree($comments), 'count' => $count, 'title' => sprintf( _n('%s Comment', '%s Comments', $count, 'wp-bootstrap'), number_format_i18n($count) ), 'form' => $this->getCommentFormHtml(), 'is_open' => comments_open($postId), ]; } /** * Build a nested comment tree from flat comments. */ private function buildCommentTree(array $comments, int $parentId = 0): array { $tree = []; foreach ($comments as $comment) { if ((int) $comment->comment_parent !== $parentId) { continue; } $tree[] = [ 'id' => (int) $comment->comment_ID, // Escape at source — comment_author is user-supplied, store as safe text. 'author' => esc_html($comment->comment_author), // esc_url() strips dangerous schemes (javascript:, data:) and encodes for HTML. 'author_url' => esc_url($comment->comment_author_url), 'avatar_url' => get_avatar_url($comment, ['size' => 40]), 'date' => get_comment_date('', $comment), 'date_iso' => get_comment_date('c', $comment), 'content' => apply_filters('comment_text', $comment->comment_content, $comment), 'edit_url' => current_user_can('edit_comment', $comment->comment_ID) ? get_edit_comment_link($comment) : '', 'reply_url' => get_comment_reply_link([ 'depth' => 1, 'max_depth' => get_option('thread_comments_depth', 5), ], $comment), 'children' => $this->buildCommentTree($comments, (int) $comment->comment_ID), ]; } return $tree; } /** * Capture the WordPress comment form HTML. */ private function getCommentFormHtml(): string { if (! comments_open()) { return ''; } ob_start(); comment_form([ 'title_reply' => __('Leave a Comment', 'wp-bootstrap'), 'title_reply_before' => '