Files
wp-bootstrap/inc/Template/ContextBuilder.php

472 lines
14 KiB
PHP
Raw Permalink Normal View History

<?php
/**
* Twig Template Context Builder.
*
* Gathers WordPress data into structured arrays for Twig templates.
*
* @package WPBootstrap
* @since 0.1.1
*/
namespace WPBootstrap\Template;
class ContextBuilder
{
private NavWalker $navWalker;
public function __construct()
{
$this->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',
];
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();
}
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 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' => 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' => 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 [
'title' => get_the_archive_title(),
'description' => 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,
'author' => $comment->comment_author,
'author_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' => '<h3 id="reply-title" class="comment-reply-title h5 mb-3">',
'title_reply_after' => '</h3>',
'class_form' => 'needs-validation',
'class_submit' => 'btn btn-primary',
'submit_button' => '<input name="%1$s" type="submit" id="%2$s" class="%3$s" value="%4$s" />',
'comment_field' => '<div class="mb-3"><label for="comment" class="form-label">' . __('Comment', 'wp-bootstrap') . '</label><textarea id="comment" name="comment" class="form-control" rows="5" required></textarea></div>',
]);
return ob_get_clean();
}
/**
* Get previous/next post navigation.
*/
private function getPostNavigation(): array
{
$prev = get_previous_post();
$next = get_next_post();
$navigation = [];
if ($prev) {
$navigation['previous'] = [
'title' => get_the_title($prev),
'url' => get_permalink($prev),
];
}
if ($next) {
$navigation['next'] = [
'title' => get_the_title($next),
'url' => get_permalink($next),
];
}
return $navigation;
}
/**
* Get recent posts for the "More posts" section.
*/
private function getMorePosts(int $count = 3): array
{
$currentId = get_the_ID();
$query = new \WP_Query([
'posts_per_page' => $count,
'post__not_in' => [$currentId],
'orderby' => 'date',
'order' => 'DESC',
]);
$posts = [];
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
$posts[] = [
'id' => get_the_ID(),
'title' => get_the_title(),
'url' => get_permalink(),
'date' => get_the_date(),
'date_iso' => get_the_date('c'),
'thumbnail' => get_the_post_thumbnail_url(null, 'medium_large') ?: '',
];
}
wp_reset_postdata();
}
return $posts;
}
/**
* Get sidebar widget data.
*/
private function getSidebarData(): array
{
return [
'recent_posts' => $this->getSidebarRecentPosts(),
'tags' => $this->getSidebarTags(),
];
}
/**
* Get recent posts for sidebar.
*/
private function getSidebarRecentPosts(int $count = 4): array
{
$query = new \WP_Query([
'posts_per_page' => $count,
'orderby' => 'date',
'order' => 'DESC',
]);
$posts = [];
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
$posts[] = [
'title' => get_the_title(),
'url' => get_permalink(),
'date' => get_the_date(),
];
}
wp_reset_postdata();
}
return $posts;
}
/**
* Get tags for sidebar tag cloud.
*/
private function getSidebarTags(int $count = 15): array
{
$tags = get_tags([
'number' => $count,
'orderby' => 'count',
'order' => 'DESC',
]);
if (! $tags || is_wp_error($tags)) {
return [];
}
$items = [];
foreach ($tags as $tag) {
$items[] = [
'name' => $tag->name,
'url' => get_tag_link($tag->term_id),
'count' => $tag->count,
];
}
return $items;
}
/**
* Get terms list for a taxonomy.
*/
private function getTermsList(string $taxonomy): array
{
$terms = get_the_terms(get_the_ID(), $taxonomy);
if (! $terms || is_wp_error($terms)) {
return [];
}
$list = [];
foreach ($terms as $term) {
$list[] = [
'name' => $term->name,
'url' => get_term_link($term),
];
}
return $list;
}
}