Wire pay_box_position to box_top/top/bottom/box_bottom, render download or paywall modules at those slots, and resolve featured_slide/attachment IDs plus video episode chrome for single covers.
936 lines
36 KiB
PHP
936 lines
36 KiB
PHP
<?php
|
|
/**
|
|
* Unified single post composition (Zibll-style closed surface).
|
|
*
|
|
* single.php stays a thin shell. All article assembly lives here.
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
if (!function_exists('aksrt_single_build_context')) :
|
|
/**
|
|
* Resolve the single-page view model once.
|
|
*
|
|
* @param int|null $post_id Post ID.
|
|
* @return array<string,mixed>
|
|
*/
|
|
function aksrt_single_build_context($post_id = null): array {
|
|
$post_id = $post_id ? (int) $post_id : (int) get_the_ID();
|
|
$settings = function_exists('aksrt_resolve_single_post_display_settings')
|
|
? aksrt_resolve_single_post_display_settings($post_id)
|
|
: [];
|
|
|
|
$views = function_exists('aksrt_get_post_views') ? (int) aksrt_get_post_views($post_id) : 0;
|
|
$comment_count = (int) get_comments_number($post_id);
|
|
$like_count = function_exists('aksrt_get_post_likes') ? (int) aksrt_get_post_likes($post_id) : 0;
|
|
$favorite_count = function_exists('aksrt_get_post_favorites') ? (int) aksrt_get_post_favorites($post_id) : 0;
|
|
$word_count = function_exists('aksrt_post_word_count') && !empty($settings['word_count_enabled'])
|
|
? aksrt_post_word_count($post_id)
|
|
: 0;
|
|
$reading_minutes = function_exists('aksrt_reading_minutes') && !empty($settings['reading_time_enabled'])
|
|
? aksrt_reading_minutes($post_id)
|
|
: 0;
|
|
|
|
$meta_items = [];
|
|
if (empty($settings['hide_time'])) {
|
|
$meta_items[] = [
|
|
'icon' => 'far fa-calendar-alt',
|
|
'text' => get_the_date('', $post_id),
|
|
];
|
|
}
|
|
$meta_items[] = [
|
|
'icon' => 'far fa-eye',
|
|
'text' => sprintf(
|
|
__('浏览 %s', 'aksrt'),
|
|
function_exists('aksrt_format_number') ? aksrt_format_number($views) : number_format_i18n($views)
|
|
),
|
|
];
|
|
$meta_items[] = [
|
|
'icon' => 'far fa-comment-dots',
|
|
'text' => sprintf(__('评论 %s', 'aksrt'), number_format_i18n($comment_count)),
|
|
];
|
|
if ($word_count > 0) {
|
|
$meta_items[] = [
|
|
'icon' => 'far fa-file-word',
|
|
'text' => sprintf(__('约 %s 字', 'aksrt'), number_format_i18n($word_count)),
|
|
];
|
|
}
|
|
if ($reading_minutes > 0) {
|
|
$meta_items[] = [
|
|
'icon' => 'far fa-hourglass',
|
|
'text' => sprintf(_n('约 %d 分钟', '约 %d 分钟', $reading_minutes, 'aksrt'), $reading_minutes),
|
|
];
|
|
}
|
|
|
|
$copyright = trim((string) ($settings['copyright_text'] ?? ''));
|
|
if ($copyright === '') {
|
|
$copyright = sprintf(
|
|
__('本文由 %s 原创发布,转载请注明出处。', 'aksrt'),
|
|
get_the_author_meta('display_name', (int) get_post_field('post_author', $post_id))
|
|
);
|
|
}
|
|
|
|
$paid_settings = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_paid_content_settings() : [];
|
|
$paid_display = is_array($paid_settings['display'] ?? null) ? $paid_settings['display'] : [];
|
|
$paid_box_position = sanitize_key((string) ($paid_display['box_position'] ?? 'top'));
|
|
// Zibll positions: box_top | top | bottom | box_bottom (do not collapse).
|
|
if (!in_array($paid_box_position, ['box_top', 'top', 'bottom', 'box_bottom'], true)) {
|
|
$paid_box_position = 'top';
|
|
}
|
|
|
|
$show_cover = !empty($settings['show_cover']) && (string) ($settings['cover_url'] ?? '') !== '';
|
|
$show_toc = !empty($settings['toc_enabled']);
|
|
$has_download = function_exists('aksrt_is_downloadable_content') && aksrt_is_downloadable_content($post_id);
|
|
$pay_meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($post_id) : ['pay_type' => 'no'];
|
|
$pay_type = (string) ($pay_meta['pay_type'] ?? 'no');
|
|
$is_paid_post = function_exists('aksrt_is_paid_post') && aksrt_is_paid_post($post_id);
|
|
// Pay module: download box OR non-download paid types (reading/gallery/video/vip).
|
|
$show_pay_box = $has_download || ($is_paid_post && $pay_type !== 'no' && $pay_type !== 'download');
|
|
|
|
$breadcrumbs_html = '';
|
|
if (!empty($settings['breadcrumbs']) && function_exists('aksrt_breadcrumbs')) {
|
|
ob_start();
|
|
aksrt_breadcrumbs($settings['breadcrumbs_home'] ?? '');
|
|
$breadcrumbs_html = trim((string) ob_get_clean());
|
|
}
|
|
|
|
$status_badge = '';
|
|
$post_status = get_post_status($post_id);
|
|
if ($post_status && $post_status !== 'publish') {
|
|
$status_map = [
|
|
'draft' => ['label' => __('草稿', 'aksrt'), 'class' => 'jb-yellow'],
|
|
'pending' => ['label' => __('待审核', 'aksrt'), 'class' => 'jb-red'],
|
|
'private' => ['label' => __('私密', 'aksrt'), 'class' => 'b-black opacity8'],
|
|
'future' => ['label' => __('定时发布', 'aksrt'), 'class' => 'c-blue'],
|
|
];
|
|
if (isset($status_map[$post_status])) {
|
|
$status_badge = '<badge class="img-badge right ' . esc_attr($status_map[$post_status]['class']) . '">'
|
|
. esc_html($status_map[$post_status]['label']) . '</badge>';
|
|
}
|
|
}
|
|
|
|
$aside_widgets = is_active_sidebar('sidebar-article')
|
|
|| is_active_sidebar('all_sidebar_top')
|
|
|| is_active_sidebar('all_sidebar_bottom');
|
|
|
|
return [
|
|
'post_id' => $post_id,
|
|
'settings' => $settings,
|
|
'show_cover' => $show_cover,
|
|
'show_toc' => $show_toc,
|
|
'cover_url' => (string) ($settings['cover_url'] ?? ''),
|
|
'cover_type' => (string) ($settings['cover_type'] ?? 'none'),
|
|
'img_scale' => (int) ($settings['article_image_cover_scale'] ?? 35),
|
|
'video_scale' => (int) ($settings['article_video_cover_scale'] ?? 56),
|
|
'slide_images' => array_values(array_filter((array) ($settings['slide_images'] ?? []))),
|
|
'episode_count' => max(0, (int) ($settings['episode_count'] ?? 0)),
|
|
'video_poster' => (string) ($settings['video_poster'] ?? ''),
|
|
'meta_items' => $meta_items,
|
|
'views' => $views,
|
|
'comment_count' => $comment_count,
|
|
'like_count' => $like_count,
|
|
'favorite_count' => $favorite_count,
|
|
'is_liked' => function_exists('aksrt_is_post_liked') ? aksrt_is_post_liked($post_id) : false,
|
|
'is_favorited' => function_exists('aksrt_is_post_favorited') ? aksrt_is_post_favorited($post_id) : false,
|
|
'copyright_text' => $copyright,
|
|
'front_content' => (string) ($settings['front_content'] ?? ''),
|
|
'after_content' => (string) ($settings['after_content'] ?? ''),
|
|
'content_maxheight' => !empty($settings['maxheight_enabled']) ? max(0, (int) ($settings['maxheight'] ?? 0)) : 0,
|
|
'has_download' => $has_download,
|
|
'show_pay_box' => $show_pay_box,
|
|
'pay_type' => $pay_type,
|
|
'is_paid_post' => $is_paid_post,
|
|
'paid_box_position' => $paid_box_position,
|
|
'breadcrumbs_html' => $breadcrumbs_html,
|
|
'status_badge' => $status_badge,
|
|
'show_aside' => $aside_widgets || $show_toc || $has_download || $show_pay_box,
|
|
'toc_title' => __('文章目录', 'aksrt'),
|
|
];
|
|
}
|
|
endif;
|
|
|
|
if (!function_exists('aksrt_single_is_docs_page')) :
|
|
/**
|
|
* Docs fast-path: true when the current post should leave the unified single surface.
|
|
*/
|
|
function aksrt_single_is_docs_page(int $post_id): bool {
|
|
if (!class_exists('AKSRT_Docs_Presenter')) {
|
|
return false;
|
|
}
|
|
return (bool) AKSRT_Docs_Presenter::get_instance()->is_docs_page($post_id);
|
|
}
|
|
endif;
|
|
|
|
if (!function_exists('aksrt_single_render_author_bar')) :
|
|
/**
|
|
* Render author bar from a real args array (no ambient query vars).
|
|
*
|
|
* @param array<string,mixed> $args Partial args.
|
|
*/
|
|
function aksrt_single_render_author_bar(array $args = []): void {
|
|
$args = wp_parse_args($args, [
|
|
'extra_class' => '',
|
|
'show_actions' => false,
|
|
'show_bio' => false,
|
|
'meta_items' => [],
|
|
]);
|
|
get_template_part('templates/parts/single-author-bar', null, $args);
|
|
}
|
|
endif;
|
|
|
|
if (!function_exists('aksrt_single_render_cover')) :
|
|
/**
|
|
* Cover / slide / video header (Zibll single-head-cover).
|
|
*
|
|
* @param array<string,mixed> $ctx Single context.
|
|
*/
|
|
function aksrt_single_render_cover(array $ctx): void {
|
|
if (empty($ctx['show_cover']) || $ctx['cover_url'] === '') {
|
|
return;
|
|
}
|
|
|
|
$type = (string) $ctx['cover_type'];
|
|
$badge = (string) $ctx['status_badge'];
|
|
$crumbs = (string) $ctx['breadcrumbs_html'];
|
|
$meta_items = is_array($ctx['meta_items'] ?? null) ? $ctx['meta_items'] : [];
|
|
$img_scale = max(10, min(100, (int) $ctx['img_scale']));
|
|
$video_scale = max(20, min(100, (int) $ctx['video_scale']));
|
|
$episode = (int) $ctx['episode_count'];
|
|
$slides = is_array($ctx['slide_images'] ?? null) ? $ctx['slide_images'] : [];
|
|
$poster = (string) ($ctx['video_poster'] ?? '');
|
|
$title = get_the_title();
|
|
|
|
if ($type === 'video') :
|
|
?>
|
|
<div class="single-head-cover">
|
|
<div class="single-video single-cover single-cover--video graphic imgbox-container" style="padding-bottom:<?php echo esc_attr((string) $video_scale); ?>%;">
|
|
<video src="<?php echo esc_url($ctx['cover_url']); ?>"<?php echo $poster !== '' ? ' poster="' . esc_url($poster) . '"' : ''; ?> autoplay muted loop playsinline controls class="single-cover__video fit-cover"></video>
|
|
<div class="single-cover__overlay"></div>
|
|
</div>
|
|
<div class="single-video-footer dplayer-featured">
|
|
<div class="flex jsb at">
|
|
<?php if ($crumbs !== '') : ?>
|
|
<div class="post-breadcrumbs"><?php echo $crumbs; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
|
|
<?php endif; ?>
|
|
<div class="cover-meta single-metabox">
|
|
<?php foreach ($meta_items as $item) : ?>
|
|
<item class="meta-view"><?php echo esc_html((string) ($item['text'] ?? '')); ?></item>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
<h1 class="article-title">
|
|
<?php if ($episode > 1) : ?>
|
|
<span class="badg badg-sm b-theme mr6"><i class="fa fa-play-circle mr3" aria-hidden="true"></i><?php printf(esc_html__('共%d集', 'aksrt'), $episode); ?></span>
|
|
<?php else : ?>
|
|
<i class="fa fa-play-circle-o mr6 opacity8" aria-hidden="true"></i>
|
|
<?php endif; ?>
|
|
<?php echo esc_html($title); ?>
|
|
</h1>
|
|
<?php echo $badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
|
<?php if ($episode > 0) : ?>
|
|
<div class="featured-video-episode mt10">
|
|
<span class="switch-video text-ellipsis active"><span class="mr6 badg badg-sm">1</span><i class="episode-active-icon"></i><?php printf(esc_html__('第%d集', 'aksrt'), 1); ?></span>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
return;
|
|
endif;
|
|
|
|
if ($type === 'slide' && !empty($slides)) :
|
|
?>
|
|
<div class="single-head-cover">
|
|
<div class="single-cover single-cover--slide graphic imgbox-container mb20 single-cover-slide" style="padding-bottom:<?php echo esc_attr((string) $img_scale); ?>%;">
|
|
<div class="single-cover__slides">
|
|
<?php foreach ($slides as $idx => $slide_url) : ?>
|
|
<div class="single-cover__slide<?php echo $idx === 0 ? ' is-active' : ''; ?>">
|
|
<img src="<?php echo esc_url($slide_url); ?>" alt="<?php echo esc_attr($title); ?>" class="fit-cover" loading="<?php echo $idx === 0 ? 'eager' : 'lazy'; ?>">
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<div class="single-cover__overlay"></div>
|
|
<div class="abs-center left-bottom single-cover-con single-cover__content">
|
|
<?php if ($crumbs !== '') : ?>
|
|
<div class="post-breadcrumbs"><?php echo $crumbs; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
|
|
<?php endif; ?>
|
|
<h1 class="article-title title-h-left"><?php echo esc_html($title); ?></h1>
|
|
<?php echo $badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
|
</div>
|
|
<div class="cover-meta abs-right">
|
|
<?php foreach ($meta_items as $item) : ?>
|
|
<item class="meta-view"><?php echo esc_html((string) ($item['text'] ?? '')); ?></item>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<div class="single-cover__dots" aria-hidden="true">
|
|
<?php foreach ($slides as $idx => $_slide) : ?>
|
|
<span class="single-cover__dot<?php echo $idx === 0 ? ' is-active' : ''; ?>"></span>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
return;
|
|
endif;
|
|
?>
|
|
<div class="single-head-cover">
|
|
<div class="graphic mb20 single-cover imgbox-container" style="padding-bottom:<?php echo esc_attr((string) $img_scale); ?>%;">
|
|
<img src="<?php echo esc_url($ctx['cover_url']); ?>" alt="<?php echo esc_attr($title); ?>" class="fit-cover" loading="eager">
|
|
<div class="single-cover__overlay"></div>
|
|
<div class="abs-center left-bottom single-cover-con single-cover__content">
|
|
<?php if ($crumbs !== '') : ?>
|
|
<div class="post-breadcrumbs"><?php echo $crumbs; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
|
|
<?php endif; ?>
|
|
<h1 class="article-title title-h-left"><?php echo esc_html($title); ?></h1>
|
|
<?php echo $badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
|
</div>
|
|
<div class="cover-meta abs-right">
|
|
<?php foreach ($meta_items as $item) : ?>
|
|
<item class="meta-view"><?php echo esc_html((string) ($item['text'] ?? '')); ?></item>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
endif;
|
|
|
|
if (!function_exists('aksrt_single_render_box_header')) :
|
|
/**
|
|
* Title + author bar (when no cover).
|
|
*
|
|
* @param array<string,mixed> $ctx Single context.
|
|
*/
|
|
function aksrt_single_render_box_header(array $ctx): void {
|
|
$settings = is_array($ctx['settings'] ?? null) ? $ctx['settings'] : [];
|
|
?>
|
|
<div class="article-header theme-box clearfix relative">
|
|
<?php if (empty($ctx['show_cover'])) : ?>
|
|
<?php echo (string) $ctx['status_badge']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
|
<h1 class="article-title"><?php the_title(); ?></h1>
|
|
<?php endif; ?>
|
|
|
|
<?php
|
|
$show_author_bar = array_key_exists('authordesc', $settings)
|
|
? !empty($settings['authordesc'])
|
|
: !empty($settings['authordesc_s']);
|
|
if ($show_author_bar) :
|
|
?>
|
|
<?php
|
|
aksrt_single_render_author_bar([
|
|
'show_actions' => true,
|
|
'meta_items' => empty($ctx['show_cover']) ? (array) $ctx['meta_items'] : [],
|
|
]);
|
|
?>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php
|
|
}
|
|
endif;
|
|
|
|
if (!function_exists('aksrt_single_render_actions')) :
|
|
/**
|
|
* Like / reward / share / favorite row.
|
|
*
|
|
* @param array<string,mixed> $ctx Single context.
|
|
*/
|
|
function aksrt_single_render_actions(array $ctx): void {
|
|
$settings = is_array($ctx['settings'] ?? null) ? $ctx['settings'] : [];
|
|
$post_id = (int) $ctx['post_id'];
|
|
$like_count = (int) $ctx['like_count'];
|
|
$favorite_count = (int) $ctx['favorite_count'];
|
|
$is_liked = !empty($ctx['is_liked']);
|
|
$is_favorited = !empty($ctx['is_favorited']);
|
|
?>
|
|
<div class="text-center muted-3-color box-body em09"><?php echo esc_html($settings['button_toptext'] ?? __('喜欢就支持一下吧', 'aksrt')); ?></div>
|
|
<div class="text-center post-actions">
|
|
<?php if (!empty($settings['like_enabled'])) : ?>
|
|
<button type="button" class="action action-like<?php echo $is_liked ? ' is-active' : ''; ?>" id="action-like" data-post-id="<?php echo esc_attr((string) $post_id); ?>" data-initial-active="<?php echo $is_liked ? '1' : '0'; ?>">
|
|
<i class="<?php echo $is_liked ? 'fas' : 'far'; ?> fa-heart" aria-hidden="true"></i>
|
|
<span class="action-btn__count" data-stat-count="likes" data-base-count="<?php echo esc_attr((string) $like_count); ?>"><?php echo esc_html((string) $like_count); ?></span>
|
|
</button>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($settings['rewards_enabled']) && !empty($settings['rewards_image'])) : ?>
|
|
<button type="button" class="action action-rewards" id="action-reward">
|
|
<i class="fas fa-gift" aria-hidden="true"></i>
|
|
<span class="action-btn__label"><?php echo esc_html($settings['rewards_btn_text'] ?? __('赞赏', 'aksrt')); ?></span>
|
|
</button>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($settings['share_enabled'])) : ?>
|
|
<button type="button" class="action action-share" id="action-share-trigger" aria-haspopup="true" aria-expanded="false">
|
|
<i class="fas fa-share-alt" aria-hidden="true"></i>
|
|
<span class="action-btn__label"><?php esc_html_e('分享', 'aksrt'); ?></span>
|
|
</button>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($settings['favorite_enabled'])) : ?>
|
|
<button type="button" class="action action-favorite<?php echo $is_favorited ? ' is-active' : ''; ?>" id="action-favorite" data-post-id="<?php echo esc_attr((string) $post_id); ?>" data-initial-active="<?php echo $is_favorited ? '1' : '0'; ?>">
|
|
<i class="<?php echo $is_favorited ? 'fas' : 'far'; ?> fa-star" aria-hidden="true"></i>
|
|
<span class="action-btn__count" data-stat-count="favorites" data-base-count="<?php echo esc_attr((string) $favorite_count); ?>"><?php echo esc_html((string) $favorite_count); ?></span>
|
|
</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<?php if (!empty($settings['share_enabled'])) : ?>
|
|
<div class="share-dropdown" id="share-dropdown" aria-hidden="true">
|
|
<div class="share-dropdown__grid">
|
|
<button type="button" class="share-item" data-platform="qq"><i class="fab fa-qq"></i><span>QQ</span></button>
|
|
<button type="button" class="share-item" data-platform="weibo"><i class="fab fa-weibo"></i><span>微博</span></button>
|
|
<button type="button" class="share-item" data-platform="twitter"><i class="fab fa-x-twitter"></i><span>X</span></button>
|
|
<button type="button" class="share-item" data-platform="wechat"><i class="fab fa-weixin"></i><span>微信</span></button>
|
|
<button type="button" class="share-item" data-platform="copy"><i class="fas fa-link"></i><span><?php esc_html_e('复制链接', 'aksrt'); ?></span></button>
|
|
</div>
|
|
<div id="share-qrcode-box" aria-hidden="true"><img id="share-qrcode-image" src="" alt="QR Code"></div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($settings['rewards_enabled']) && !empty($settings['rewards_image'])) : ?>
|
|
<div class="reward-modal" id="reward-modal" aria-hidden="true">
|
|
<div class="reward-modal__overlay"></div>
|
|
<div class="reward-modal__dialog">
|
|
<button type="button" class="reward-modal__close" aria-label="<?php esc_attr_e('关闭', 'aksrt'); ?>"><i class="fas fa-times" aria-hidden="true"></i></button>
|
|
<div class="reward-modal__content">
|
|
<h3><?php echo esc_html((string) ($settings['rewards_text'] ?? '')); ?></h3>
|
|
<div class="reward-modal__qrcode">
|
|
<img src="<?php echo esc_url((string) $settings['rewards_image']); ?>" alt="<?php esc_attr_e('打赏二维码', 'aksrt'); ?>">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php
|
|
}
|
|
endif;
|
|
|
|
if (!function_exists('aksrt_single_render_content_footer')) :
|
|
/**
|
|
* Copyright + THE END + tags.
|
|
*
|
|
* @param array<string,mixed> $ctx Single context.
|
|
*/
|
|
function aksrt_single_render_content_footer(array $ctx): void {
|
|
$settings = is_array($ctx['settings'] ?? null) ? $ctx['settings'] : [];
|
|
$post_id = (int) $ctx['post_id'];
|
|
?>
|
|
<?php if (!empty($settings['yiyan_footer'])) : ?>
|
|
<div class="article-yiyan yiyan-box aksrt-yiyan yiyan--footer" data-aksrt-yiyan data-type="hitokoto"></div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($settings['copyright_enabled'])) : ?>
|
|
<div class="em09 muted-3-color">
|
|
<div><span>©</span> <?php esc_html_e('版权声明', 'aksrt'); ?></div>
|
|
<div class="posts-copyright"><?php echo esc_html((string) $ctx['copyright_text']); ?></div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="text-center theme-box muted-3-color box-body separator em09">THE END</div>
|
|
<div class="theme-box article-tags">
|
|
<?php foreach (get_the_category() ?: [] as $cat) : ?>
|
|
<a href="<?php echo esc_url(get_category_link($cat->term_id)); ?>" class="but"><i class="far fa-folder-open" aria-hidden="true"></i> <?php echo esc_html($cat->name); ?></a>
|
|
<?php endforeach; ?>
|
|
<?php
|
|
$topics = taxonomy_exists('topics') ? wp_get_post_terms($post_id, 'topics') : [];
|
|
if (!is_wp_error($topics) && !empty($topics)) :
|
|
foreach (array_slice($topics, 0, 2) as $topic) :
|
|
?>
|
|
<a href="<?php echo esc_url(get_term_link($topic)); ?>" class="but"><i class="fas fa-cube" aria-hidden="true"></i> <?php echo esc_html($topic->name); ?></a>
|
|
<?php
|
|
endforeach;
|
|
endif;
|
|
?>
|
|
<br>
|
|
<?php foreach (get_the_tags() ?: [] as $tag) : ?>
|
|
<a href="<?php echo esc_url(get_tag_link($tag->term_id)); ?>" class="but">#<?php echo esc_html($tag->name); ?></a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php
|
|
}
|
|
endif;
|
|
|
|
if (!function_exists('aksrt_single_render_author_card')) :
|
|
/**
|
|
* Compact author card used in the after-box stack.
|
|
*
|
|
* @param array<string,mixed> $ctx Single context.
|
|
*/
|
|
function aksrt_single_render_author_card(array $ctx): void {
|
|
if (!function_exists('aksrt_get_user_dashboard_view')) {
|
|
return;
|
|
}
|
|
|
|
$author_id = (int) get_the_author_meta('ID');
|
|
$dashboard = aksrt_get_user_dashboard_view($author_id, ['viewer_id' => get_current_user_id()]);
|
|
$recent = is_array($dashboard['recent_posts'] ?? null) ? $dashboard['recent_posts'] : [];
|
|
$stats = [
|
|
['label' => __('文章', 'aksrt'), 'value' => (int) ($dashboard['post_count'] ?? 0)],
|
|
['label' => __('评论', 'aksrt'), 'value' => (int) ($dashboard['comment_count'] ?? 0)],
|
|
['label' => __('收藏', 'aksrt'), 'value' => (int) ($dashboard['favorite_count'] ?? 0)],
|
|
];
|
|
$bio = trim((string) get_the_author_meta('description'));
|
|
?>
|
|
<div class="author author-box">
|
|
<div class="author-box__header">
|
|
<a href="<?php echo esc_url(get_author_posts_url($author_id)); ?>" class="author-box__avatar">
|
|
<?php echo get_avatar($author_id, 56, '', esc_attr(get_the_author())); ?>
|
|
</a>
|
|
<div class="author-box__info">
|
|
<a href="<?php echo esc_url(get_author_posts_url($author_id)); ?>" class="author-box__name"><?php echo esc_html(get_the_author()); ?></a>
|
|
<?php if ($bio !== '') : ?>
|
|
<p class="author-box__bio"><?php echo esc_html($bio); ?></p>
|
|
<?php endif; ?>
|
|
<div class="author-box__stats">
|
|
<?php foreach ($stats as $stat) : ?>
|
|
<span><strong><?php echo esc_html((string) $stat['value']); ?></strong> <?php echo esc_html($stat['label']); ?></span>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php if (!empty($recent)) : ?>
|
|
<div class="author-box__posts">
|
|
<h3 class="author-box__posts-title"><?php esc_html_e('最新文章', 'aksrt'); ?></h3>
|
|
<ul>
|
|
<?php foreach ($recent as $recent_post) : ?>
|
|
<?php if ($recent_post instanceof WP_Post) : ?>
|
|
<li><a href="<?php echo esc_url(get_permalink($recent_post)); ?>"><?php echo esc_html(get_the_title($recent_post)); ?></a></li>
|
|
<?php endif; ?>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php
|
|
}
|
|
endif;
|
|
|
|
if (!function_exists('aksrt_single_render_prevnext')) :
|
|
/**
|
|
* Previous / next navigation.
|
|
*
|
|
* @param array<string,mixed> $ctx Single context.
|
|
*/
|
|
function aksrt_single_render_prevnext(array $ctx = []): void {
|
|
$settings = is_array($ctx['settings'] ?? null) ? $ctx['settings'] : [];
|
|
// Accept both canonical `prevnext` (Settings DTO) and legacy `prevnext_s`.
|
|
$enabled = array_key_exists('prevnext', $settings)
|
|
? !empty($settings['prevnext'])
|
|
: (!array_key_exists('prevnext_s', $settings) || !empty($settings['prevnext_s']));
|
|
if (!$enabled) {
|
|
return;
|
|
}
|
|
|
|
$prev = get_previous_post();
|
|
$next = get_next_post();
|
|
if (!$prev && !$next) {
|
|
return;
|
|
}
|
|
?>
|
|
<nav class="post-nav-box theme-box box-body radius8 main-shadow">
|
|
<?php if ($prev) : ?>
|
|
<a class="post-nav-box__item post-nav-box__item--prev" href="<?php echo esc_url(get_permalink($prev)); ?>">
|
|
<span class="post-nav-box__label"><?php esc_html_e('上一篇', 'aksrt'); ?></span>
|
|
<span class="post-nav-box__title"><?php echo esc_html(get_the_title($prev)); ?></span>
|
|
</a>
|
|
<?php else : ?>
|
|
<span class="post-nav-box__item post-nav-box__item--prev is-empty" aria-hidden="true"></span>
|
|
<?php endif; ?>
|
|
<?php if ($next) : ?>
|
|
<a class="post-nav-box__item post-nav-box__item--next" href="<?php echo esc_url(get_permalink($next)); ?>">
|
|
<span class="post-nav-box__label"><?php esc_html_e('下一篇', 'aksrt'); ?></span>
|
|
<span class="post-nav-box__title"><?php echo esc_html(get_the_title($next)); ?></span>
|
|
</a>
|
|
<?php else : ?>
|
|
<span class="post-nav-box__item post-nav-box__item--next is-empty" aria-hidden="true"></span>
|
|
<?php endif; ?>
|
|
</nav>
|
|
<?php
|
|
}
|
|
endif;
|
|
|
|
if (!function_exists('aksrt_single_related_query_args')) :
|
|
/**
|
|
* Build related-posts WP_Query args (Zibll-like multi-tax OR + orderby).
|
|
*
|
|
* @param int $post_id Post ID.
|
|
* @param array<string,mixed> $settings Single display settings.
|
|
* @return array<string,mixed>
|
|
*/
|
|
function aksrt_single_related_query_args(int $post_id, array $settings): array {
|
|
$count = max(1, min(12, (int) ($settings['related_count'] ?? 6)));
|
|
$type = sanitize_key((string) ($settings['related_type'] ?? 'category'));
|
|
if (!in_array($type, ['category', 'tag', 'random'], true)) {
|
|
$type = 'category';
|
|
}
|
|
$orderby = sanitize_key((string) ($settings['related_orderby'] ?? 'views'));
|
|
if (!in_array($orderby, ['rand', 'relevance', 'date', 'views', 'modified'], true)) {
|
|
$orderby = 'views';
|
|
}
|
|
|
|
$args = [
|
|
'post_type' => get_post_type($post_id) ?: 'post',
|
|
'post_status' => 'publish',
|
|
'post__not_in' => [$post_id],
|
|
'posts_per_page' => $count,
|
|
'ignore_sticky_posts' => true,
|
|
'no_found_rows' => true,
|
|
'order' => 'DESC',
|
|
];
|
|
|
|
if ($type === 'random') {
|
|
$args['orderby'] = 'rand';
|
|
return $args;
|
|
}
|
|
|
|
$tax_query = ['relation' => 'OR'];
|
|
if ($type === 'tag') {
|
|
$tag_ids = wp_get_post_terms($post_id, 'post_tag', ['fields' => 'ids']);
|
|
if (!empty($tag_ids) && !is_wp_error($tag_ids)) {
|
|
$tax_query[] = [
|
|
'taxonomy' => 'post_tag',
|
|
'field' => 'term_id',
|
|
'terms' => array_map('intval', $tag_ids),
|
|
];
|
|
}
|
|
} else {
|
|
// category (default): OR across category + tags + topics like Zibll.
|
|
$cat_ids = wp_get_post_terms($post_id, 'category', ['fields' => 'ids']);
|
|
if (!empty($cat_ids) && !is_wp_error($cat_ids)) {
|
|
$tax_query[] = [
|
|
'taxonomy' => 'category',
|
|
'field' => 'term_id',
|
|
'terms' => array_map('intval', $cat_ids),
|
|
];
|
|
}
|
|
$tag_ids = wp_get_post_terms($post_id, 'post_tag', ['fields' => 'ids']);
|
|
if (!empty($tag_ids) && !is_wp_error($tag_ids)) {
|
|
$tax_query[] = [
|
|
'taxonomy' => 'post_tag',
|
|
'field' => 'term_id',
|
|
'terms' => array_map('intval', $tag_ids),
|
|
];
|
|
}
|
|
if (taxonomy_exists('topics')) {
|
|
$topic_ids = wp_get_post_terms($post_id, 'topics', ['fields' => 'ids']);
|
|
if (!empty($topic_ids) && !is_wp_error($topic_ids)) {
|
|
$tax_query[] = [
|
|
'taxonomy' => 'topics',
|
|
'field' => 'term_id',
|
|
'terms' => array_map('intval', $topic_ids),
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count($tax_query) > 1) {
|
|
$args['tax_query'] = $tax_query;
|
|
}
|
|
|
|
switch ($orderby) {
|
|
case 'rand':
|
|
$args['orderby'] = 'rand';
|
|
break;
|
|
case 'date':
|
|
$args['orderby'] = 'date';
|
|
break;
|
|
case 'modified':
|
|
$args['orderby'] = 'modified';
|
|
break;
|
|
case 'views':
|
|
$args['meta_key'] = 'aksrt_post_views';
|
|
$args['orderby'] = 'meta_value_num';
|
|
break;
|
|
case 'relevance':
|
|
default:
|
|
// Prefer date when no search relevance context.
|
|
$args['orderby'] = 'date';
|
|
break;
|
|
}
|
|
|
|
return $args;
|
|
}
|
|
endif;
|
|
|
|
if (!function_exists('aksrt_single_render_related')) :
|
|
/**
|
|
* Related posts — Zibll-like list + optional shared mini renderer.
|
|
*
|
|
* @param array<string,mixed> $ctx Single context.
|
|
*/
|
|
function aksrt_single_render_related(array $ctx): void {
|
|
$settings = is_array($ctx['settings'] ?? null) ? $ctx['settings'] : [];
|
|
if (empty($settings['related_enabled'])) {
|
|
return;
|
|
}
|
|
|
|
$post_id = (int) $ctx['post_id'];
|
|
$title = (string) ($settings['related_title'] ?? '');
|
|
if ($title === '') {
|
|
$title = __('相关文章', 'aksrt');
|
|
}
|
|
|
|
$args = function_exists('aksrt_single_related_query_args')
|
|
? aksrt_single_related_query_args($post_id, $settings)
|
|
: [
|
|
'post_type' => get_post_type($post_id),
|
|
'post_status' => 'publish',
|
|
'post__not_in' => [$post_id],
|
|
'posts_per_page' => max(1, min(12, (int) ($settings['related_count'] ?? 6))),
|
|
'ignore_sticky_posts' => true,
|
|
];
|
|
|
|
$query = new WP_Query($args);
|
|
if (!$query->have_posts()) {
|
|
wp_reset_postdata();
|
|
return;
|
|
}
|
|
?>
|
|
<div class="theme-box relates related-box">
|
|
<div class="box-body notop">
|
|
<div class="title-theme related-box__title"><?php echo esc_html($title); ?></div>
|
|
</div>
|
|
<div class="zib-widget related-box__list">
|
|
<?php if (function_exists('aksrt_render_post_list')) : ?>
|
|
<?php aksrt_render_post_list($query, ['context' => 'related', 'mode' => 'mini', 'show_thumb' => true, 'show_meta' => true]); ?>
|
|
<?php else : ?>
|
|
<ul class="no-thumb related-list">
|
|
<?php
|
|
while ($query->have_posts()) :
|
|
$query->the_post();
|
|
?>
|
|
<li>
|
|
<a class="icon-circle" href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
|
|
</li>
|
|
<?php endwhile; ?>
|
|
</ul>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
wp_reset_postdata();
|
|
}
|
|
endif;
|
|
|
|
|
|
if (!function_exists('aksrt_single_render_pay_box')) :
|
|
/**
|
|
* Unified single pay module (Zibll zibpay_posts_pay_content equivalent).
|
|
*
|
|
* @param array<string,mixed> $ctx Single context.
|
|
*/
|
|
function aksrt_single_render_pay_box(array $ctx = []): void {
|
|
if ($ctx === []) {
|
|
$ctx = function_exists('aksrt_single_build_context') ? aksrt_single_build_context() : [];
|
|
}
|
|
if (empty($ctx['show_pay_box'])) {
|
|
return;
|
|
}
|
|
|
|
$post_id = (int) ($ctx['post_id'] ?? get_the_ID());
|
|
$post = get_post($post_id);
|
|
if (!($post instanceof WP_Post)) {
|
|
return;
|
|
}
|
|
|
|
$pay_type = (string) ($ctx['pay_type'] ?? 'no');
|
|
if (!empty($ctx['has_download']) || $pay_type === 'download') {
|
|
echo '<div class="aksrt-single-pay-box aksrt-single-pay-box--download" id="posts-pay">';
|
|
get_template_part('templates/parts/resource-download-box');
|
|
echo '</div>';
|
|
return;
|
|
}
|
|
|
|
if (!function_exists('aksrt_get_pay_meta')) {
|
|
return;
|
|
}
|
|
|
|
$has_access = function_exists('aksrt_has_paid_access') ? aksrt_has_paid_access($post) : false;
|
|
echo '<div class="aksrt-single-pay-box aksrt-single-pay-box--' . esc_attr($pay_type) . '" id="posts-pay">';
|
|
if ($has_access) {
|
|
if ($pay_type === 'gallery' && function_exists('aksrt_render_paid_gallery')) {
|
|
echo aksrt_render_paid_gallery($post); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
} elseif ($pay_type === 'video' && function_exists('aksrt_render_paid_video')) {
|
|
echo aksrt_render_paid_video($post); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
} else {
|
|
echo '<div class="zib-widget pay-box paid-box"><div class="box-body"><span class="badg c-green hollow badg-sm mr6">' . esc_html__('已解锁', 'aksrt') . '</span><b>' . esc_html(get_the_title($post)) . '</b></div></div>';
|
|
}
|
|
} elseif (function_exists('aksrt_get_paywall_html')) {
|
|
echo aksrt_get_paywall_html($post); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
}
|
|
echo '</div>';
|
|
}
|
|
endif;
|
|
|
|
if (!function_exists('aksrt_single_pay_box_matches')) :
|
|
/**
|
|
* @param array<string,mixed> $ctx
|
|
*/
|
|
function aksrt_single_pay_box_matches(array $ctx, string $slot): bool {
|
|
if (empty($ctx['show_pay_box'])) {
|
|
return false;
|
|
}
|
|
return (string) ($ctx['paid_box_position'] ?? 'top') === $slot;
|
|
}
|
|
endif;
|
|
|
|
if (!function_exists('aksrt_single_render_article')) :
|
|
/**
|
|
* Main article box.
|
|
*
|
|
* @param array<string,mixed> $ctx Single context.
|
|
*/
|
|
function aksrt_single_render_article(array $ctx): void {
|
|
$settings = is_array($ctx['settings'] ?? null) ? $ctx['settings'] : [];
|
|
$maxheight = (int) $ctx['content_maxheight'];
|
|
?>
|
|
<article <?php post_class('article article-shell main-bg theme-box box-body radius8 main-shadow'); ?>>
|
|
<?php aksrt_single_render_box_header($ctx); ?>
|
|
|
|
<?php if (!empty($settings['yiyan_header'])) : ?>
|
|
<div class="article-yiyan yiyan-box aksrt-yiyan yiyan--header" data-aksrt-yiyan data-type="hitokoto"></div>
|
|
<?php endif; ?>
|
|
|
|
<?php do_action('aksrt_single_content_before', get_post()); ?>
|
|
|
|
<?php if (function_exists('aksrt_single_pay_box_matches') && aksrt_single_pay_box_matches($ctx, 'top')) : ?>
|
|
<?php aksrt_single_render_pay_box($ctx); ?>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($ctx['front_content'] !== '') : ?>
|
|
<div class="post_front_content theme-box box-body"><?php echo wp_kses_post((string) $ctx['front_content']); ?></div>
|
|
<?php endif; ?>
|
|
|
|
<div class="theme-box wp-posts-content entry-content<?php echo $maxheight > 0 ? ' limit-height' : ''; ?>"
|
|
<?php echo $maxheight > 0 ? ' style="max-height:' . esc_attr((string) $maxheight) . 'px;"' : ''; ?>
|
|
data-nav="posts"
|
|
data-maxheight="<?php echo esc_attr((string) $maxheight); ?>"
|
|
data-aksrt-toc="<?php echo !empty($ctx['show_toc']) ? '1' : '0'; ?>"
|
|
data-aksrt-toc-depth="<?php echo esc_attr((string) ((int) ($settings['toc_depth'] ?? 3))); ?>"
|
|
<?php echo !empty($settings['lightbox']) ? ' data-aksrt-lightbox="1"' : ''; ?>>
|
|
<?php the_content(); ?>
|
|
<?php
|
|
wp_link_pages([
|
|
'before' => '<p class="text-center post-nav-links radius8 padding-6">',
|
|
'after' => '</p>',
|
|
]);
|
|
?>
|
|
</div>
|
|
|
|
<?php if ($ctx['after_content'] !== '') : ?>
|
|
<div class="post_after_content theme-box box-body"><?php echo wp_kses_post((string) $ctx['after_content']); ?></div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (function_exists('aksrt_single_pay_box_matches') && aksrt_single_pay_box_matches($ctx, 'bottom')) : ?>
|
|
<?php aksrt_single_render_pay_box($ctx); ?>
|
|
<?php endif; ?>
|
|
|
|
<?php aksrt_single_render_actions($ctx); ?>
|
|
<?php aksrt_single_render_content_footer($ctx); ?>
|
|
</article>
|
|
<?php
|
|
}
|
|
endif;
|
|
|
|
if (!function_exists('aksrt_single_render_after_box')) :
|
|
/**
|
|
* After-article stack: yiyan box / author card / prevnext / related.
|
|
*
|
|
* @param array<string,mixed> $ctx Single context.
|
|
*/
|
|
function aksrt_single_render_after_box(array $ctx): void {
|
|
$settings = is_array($ctx['settings'] ?? null) ? $ctx['settings'] : [];
|
|
|
|
if (!empty($settings['yiyan_box'])) {
|
|
?>
|
|
<div class="yiyan-box main-bg theme-box text-center box-body radius8 main-shadow aksrt-yiyan" data-aksrt-yiyan data-type="hitokoto"></div>
|
|
<?php
|
|
}
|
|
|
|
// Accept both `authordesc` (Settings DTO) and legacy `authordesc_s`.
|
|
$show_author = array_key_exists('authordesc', $settings)
|
|
? !empty($settings['authordesc'])
|
|
: !empty($settings['authordesc_s']);
|
|
if ($show_author) {
|
|
aksrt_single_render_author_card($ctx);
|
|
}
|
|
|
|
aksrt_single_render_prevnext($ctx);
|
|
aksrt_single_render_related($ctx);
|
|
}
|
|
endif;
|
|
|
|
if (!function_exists('aksrt_single_render_aside')) :
|
|
/**
|
|
* Page-owned single aside (not a generic sidebar prepend hack).
|
|
*
|
|
* @param array<string,mixed> $ctx Single context.
|
|
*/
|
|
function aksrt_single_render_aside(array $ctx): void {
|
|
if (empty($ctx['show_aside']) || wp_is_mobile()) {
|
|
return;
|
|
}
|
|
?>
|
|
<div class="sidebar" role="complementary" aria-label="<?php esc_attr_e('侧栏', 'aksrt'); ?>">
|
|
<?php if (!empty($ctx['has_download'])) : ?>
|
|
<?php get_template_part('templates/parts/resource-meta-box'); ?>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($ctx['show_toc'])) : ?>
|
|
<div class="zib-widget toc-widget posts-nav-box" data-title="<?php echo esc_attr((string) $ctx['toc_title']); ?>">
|
|
<h3><?php echo esc_html((string) $ctx['toc_title']); ?></h3>
|
|
<ol class="toc-list"></ol>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (is_active_sidebar('all_sidebar_top')) : ?>
|
|
<?php dynamic_sidebar('all_sidebar_top'); ?>
|
|
<?php endif; ?>
|
|
|
|
<?php if (is_active_sidebar('sidebar-article')) : ?>
|
|
<?php dynamic_sidebar('sidebar-article'); ?>
|
|
<?php endif; ?>
|
|
|
|
<?php if (is_active_sidebar('all_sidebar_bottom')) : ?>
|
|
<?php dynamic_sidebar('all_sidebar_bottom'); ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php
|
|
}
|
|
endif;
|
|
|
|
if (!function_exists('aksrt_single')) :
|
|
/**
|
|
* Compose header + article + after-box (Zibll zib_single equivalent).
|
|
*
|
|
* @param array<string,mixed>|null $ctx Optional prebuilt context.
|
|
*/
|
|
function aksrt_single(?array $ctx = null): void {
|
|
$ctx = is_array($ctx) ? $ctx : aksrt_single_build_context();
|
|
|
|
if (empty($ctx['show_cover'])) {
|
|
if ($ctx['breadcrumbs_html'] !== '') {
|
|
echo $ctx['breadcrumbs_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
} elseif (function_exists('aksrt_breadcrumbs')) {
|
|
$settings = is_array($ctx['settings'] ?? null) ? $ctx['settings'] : [];
|
|
aksrt_breadcrumbs($settings['breadcrumbs_home'] ?? '');
|
|
}
|
|
} else {
|
|
aksrt_single_render_cover($ctx);
|
|
}
|
|
|
|
do_action('aksrt_single_before', get_post());
|
|
if (function_exists('aksrt_single_pay_box_matches') && aksrt_single_pay_box_matches($ctx, 'box_top')) {
|
|
aksrt_single_render_pay_box($ctx);
|
|
}
|
|
aksrt_single_render_article($ctx);
|
|
do_action('aksrt_single_article_after', get_post());
|
|
aksrt_single_render_after_box($ctx);
|
|
if (function_exists('aksrt_single_pay_box_matches') && aksrt_single_pay_box_matches($ctx, 'box_bottom')) {
|
|
aksrt_single_render_pay_box($ctx);
|
|
}
|
|
do_action('aksrt_single_after', get_post());
|
|
}
|
|
endif;
|