300 lines
18 KiB
PHP
300 lines
18 KiB
PHP
<?php
|
|
|
|
namespace Jio\Forum;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
final class View {
|
|
public static function open(string $title, string $context): void {
|
|
get_header();
|
|
echo '<main id="forum" class="jio-forum jio-forum--' . esc_attr($context) . '"><div class="jio-forum__wrap">';
|
|
echo '<nav class="jio-forum__breadcrumbs" aria-label="路径"><a href="' . esc_url(home_url('/')) . '">首页</a><span>/</span><a href="' . esc_url(Core::home_url()) . '">论坛</a>';
|
|
if ($context !== 'home') {
|
|
echo '<span>/</span><span>' . esc_html($title) . '</span>';
|
|
}
|
|
echo '</nav>';
|
|
}
|
|
|
|
public static function close(): void {
|
|
echo '</div></main>';
|
|
get_footer();
|
|
}
|
|
|
|
public static function sidebar(string $context): void {
|
|
echo '<aside class="jio-forum__aside">';
|
|
if (is_user_logged_in()) {
|
|
echo '<a class="jio-forum__new" href="' . esc_url(Core::edit_url()) . '">发布帖子</a>';
|
|
} else {
|
|
echo '<a class="jio-forum__new" href="' . esc_url(jio_get_login_url(Core::edit_url())) . '">登录后发帖</a>';
|
|
}
|
|
\Jio\WidgetSystem\WidgetAreas::render('forum_' . $context . '_sidebar');
|
|
$plates = get_posts(['post_type' => Core::PLATE, 'post_status' => 'publish', 'posts_per_page' => 8, 'orderby' => 'date']);
|
|
if ($plates) {
|
|
echo '<section class="jio-forum__aside-section"><h2>热门版块</h2>';
|
|
foreach ($plates as $plate) {
|
|
echo '<a href="' . esc_url(get_permalink($plate)) . '">' . esc_html($plate->post_title) . '</a>';
|
|
}
|
|
echo '</section>';
|
|
}
|
|
echo '</aside>';
|
|
}
|
|
|
|
public static function home(): void {
|
|
HomeView::render();
|
|
}
|
|
|
|
public static function listing(): void {
|
|
$obj = get_queried_object();
|
|
$plate = $obj instanceof \WP_Post && $obj->post_type === Core::PLATE ? $obj : null;
|
|
$term = $obj instanceof \WP_Term ? $obj : null;
|
|
$title = $plate ? $plate->post_title : ($term ? $term->name : '论坛');
|
|
$context = $plate ? 'plate' : 'term';
|
|
self::open($title, $context);
|
|
\Jio\WidgetSystem\WidgetAreas::render('forum_' . $context . '_top_fluid');
|
|
echo '<div class="jio-forum__layout"><div class="jio-forum__main">';
|
|
echo '<header class="jio-forum__page-head"><h1>' . esc_html($title) . '</h1>';
|
|
if ($plate) {
|
|
echo '<div class="jio-forum__description">' . wp_kses_post(wpautop($plate->post_excerpt ?: $plate->post_content)) . '</div>';
|
|
echo '<p class="jio-forum__stats">' . (int) get_post_meta($plate->ID, '_jio_forum_follow_count', true) . ' 人关注</p>';
|
|
echo Cards::follow_button($plate->ID, 'but c-blue'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
if (Access::is_moderator(get_current_user_id(), $plate->ID)) {
|
|
self::plate_editor($plate);
|
|
}
|
|
if (is_user_logged_in() && !Access::is_moderator(get_current_user_id(), $plate->ID) && jio_get_setting('jio_forum_moderator_apply', true)) {
|
|
self::action('apply', $plate->ID, '申请版主');
|
|
}
|
|
if (Access::is_admin(get_current_user_id()) || (int) $plate->post_author === get_current_user_id()) {
|
|
self::applications($plate->ID);
|
|
}
|
|
} elseif ($term) {
|
|
echo '<p>' . esc_html($term->description) . '</p>';
|
|
}
|
|
echo '</header>';
|
|
self::tabs();
|
|
if ($term && $term->taxonomy === Core::CATEGORY) {
|
|
self::plates(['tax_query' => [['taxonomy' => Core::CATEGORY, 'terms' => [$term->term_id]]]]);
|
|
} else {
|
|
$args = ['post_type' => Core::POST, 'post_status' => 'publish'];
|
|
if ($plate) {
|
|
$args['meta_query'] = [['key' => '_jio_forum_plate_id', 'value' => $plate->ID]];
|
|
} elseif ($term) {
|
|
$args['tax_query'] = [['taxonomy' => $term->taxonomy, 'terms' => [$term->term_id]]];
|
|
}
|
|
self::posts($args, $context);
|
|
if ($plate && Access::is_moderator(get_current_user_id(), $plate->ID)) {
|
|
echo '<h2 class="jio-forum__section-title">待审核帖子</h2>';
|
|
$args['post_status'] = 'pending';
|
|
self::posts($args, $context);
|
|
}
|
|
}
|
|
echo '</div>';
|
|
self::sidebar($context);
|
|
echo '</div>';
|
|
\Jio\WidgetSystem\WidgetAreas::render('forum_' . $context . '_bottom_fluid');
|
|
self::close();
|
|
}
|
|
|
|
/** Listing pages (plate / taxonomy) use a fixed, always-available tab set. */
|
|
public static function tabs(): void {
|
|
$tabs = ['latest' => '最新', 'popular' => '热门', 'essence' => '精华', 'question' => '问答', 'vote' => '投票'];
|
|
$base_url = is_singular(Core::PLATE) ? get_permalink(get_queried_object_id()) : (is_tax([Core::CATEGORY, Core::TOPIC, Core::TAG]) ? get_term_link(get_queried_object()) : Core::home_url());
|
|
echo '<nav class="jio-forum__tabs" aria-label="论坛栏目">';
|
|
foreach ($tabs as $key => $label) {
|
|
$url = add_query_arg('forum_view', $key, $base_url);
|
|
$selected = (string) ($_GET['forum_view'] ?? 'latest') === $key;
|
|
echo '<a class="' . ($selected ? 'is-active' : '') . '" href="' . esc_url($url) . '">' . esc_html($label) . '</a>';
|
|
}
|
|
echo '</nav>';
|
|
}
|
|
|
|
public static function posts(array $args, string $context = 'home'): void {
|
|
$view = sanitize_key((string) ($_GET['forum_view'] ?? 'latest'));
|
|
if (!in_array($view, ['latest', 'popular', 'essence', 'question', 'vote'], true)) {
|
|
$view = 'latest';
|
|
}
|
|
if (in_array($view, ['question', 'vote'], true)) {
|
|
$args['meta_query'][] = ['key' => '_jio_forum_type', 'value' => $view];
|
|
}
|
|
if ($view === 'essence') {
|
|
$args['meta_query'][] = ['key' => '_jio_forum_essence', 'value' => 1];
|
|
}
|
|
$args['posts_per_page'] = max(1, min(50, (int) jio_get_setting('jio_forum_page_size', 20)));
|
|
$args['paged'] = max(1, (int) get_query_var('paged', 1));
|
|
$args['orderby'] = 'date';
|
|
$args['order'] = 'DESC';
|
|
$args['jio_forum_list'] = $view;
|
|
$query = new \WP_Query($args);
|
|
\Jio\WidgetSystem\WidgetAreas::render('forum_' . $context . '_top_content');
|
|
if (!$query->have_posts()) {
|
|
echo '<div class="jio-forum__list">' . jio_get_null('暂无帖子') . '</div>';
|
|
} else {
|
|
Cards::prime($query->posts);
|
|
echo '<div class="jio-forum__list">';
|
|
foreach ($query->posts as $post) {
|
|
echo Cards::post_detail($post, 'ajax-item'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
}
|
|
echo '</div>';
|
|
}
|
|
self::pagination($query);
|
|
\Jio\WidgetSystem\WidgetAreas::render('forum_' . $context . '_bottom_content');
|
|
}
|
|
|
|
public static function plates(array $args): void {
|
|
$query = new \WP_Query(array_merge(['post_type' => Core::PLATE, 'post_status' => 'publish', 'posts_per_page' => 20], $args));
|
|
if (!$query->have_posts()) {
|
|
echo jio_get_null('暂无版块'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
return;
|
|
}
|
|
PlateStats::prime($query->posts ? wp_list_pluck($query->posts, 'ID') : []);
|
|
echo '<div class="plate-lists">';
|
|
foreach ($query->posts as $plate) {
|
|
echo Cards::plate_item($plate); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
}
|
|
echo '</div>';
|
|
}
|
|
|
|
public static function plate_card(\WP_Post $plate): void {
|
|
echo Cards::plate_item($plate); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
}
|
|
|
|
public static function post_card(\WP_Post $post): void {
|
|
echo Cards::post_detail($post); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
}
|
|
|
|
public static function replies(int $post_id): void {
|
|
$page = max(1, (int) ($_GET['cpage'] ?? 1));
|
|
$limit = max(1, min(100, (int) jio_get_setting('jio_forum_reply_page_size', 20)));
|
|
$comments = get_comments(['post_id' => $post_id, 'status' => 'approve', 'number' => $limit, 'offset' => ($page - 1) * $limit, 'orderby' => 'comment_date_gmt', 'order' => 'ASC']);
|
|
$total = (int) get_comments(['post_id' => $post_id, 'status' => 'approve', 'count' => true]);
|
|
echo '<section class="jio-forum__replies" id="comments"><h2>回复 <small>' . $total . '</small></h2>';
|
|
if (!$comments) echo '<p class="jio-forum__empty">暂无回复</p>';
|
|
foreach ($comments as $comment) {
|
|
$adopted = (bool) get_comment_meta($comment->comment_ID, '_jio_forum_adopted', true);
|
|
$hot = (bool) get_comment_meta($comment->comment_ID, '_jio_forum_hot', true);
|
|
echo '<article class="jio-forum__reply" id="comment-' . (int) $comment->comment_ID . '"><div class="jio-forum__reply-avatar">' . get_avatar((int) $comment->user_id, 40) . '</div><div class="jio-forum__reply-main"><div class="jio-forum__reply-head"><b>' . esc_html(get_comment_author($comment)) . '</b><time>' . esc_html(get_comment_date('', $comment)) . '</time>' . ($adopted ? '<strong>已采纳</strong>' : '') . '</div>';
|
|
if ($hot) echo '<span class="jio-forum__badge">热门回复</span>';
|
|
if ((int) $comment->comment_parent > 0) {
|
|
$parent = get_comment((int) $comment->comment_parent);
|
|
if ($parent instanceof \WP_Comment) echo '<p class="jio-forum__reply-parent">回复 @' . esc_html(get_comment_author($parent)) . '</p>';
|
|
}
|
|
echo '<div class="jio-forum__reply-text">' . wp_kses_post(wpautop($comment->comment_content)) . '</div><div class="jio-forum__reply-actions">';
|
|
if (is_user_logged_in()) echo '<button type="button" data-forum-action="reply-like" data-id="' . (int) $comment->comment_ID . '">赞 ' . (int) get_comment_meta($comment->comment_ID, 'jio_comment_likes', true) . '</button>';
|
|
if (is_user_logged_in() && ((int) $comment->user_id === get_current_user_id() || Access::is_moderator(get_current_user_id(), (int) get_post_meta($post_id, '_jio_forum_plate_id', true)))) echo '<button type="button" data-forum-action="reply-delete" data-id="' . (int) $comment->comment_ID . '">删除</button>';
|
|
if (Access::is_moderator(get_current_user_id(), (int) get_post_meta($post_id, '_jio_forum_plate_id', true))) echo '<button type="button" data-forum-action="reply-hot" data-id="' . (int) $comment->comment_ID . '">' . ($hot ? '取消热门' : '设为热门') . '</button>';
|
|
if (is_user_logged_in()) echo '<button type="button" data-forum-reply-to="' . (int) $comment->comment_ID . '">回复</button>';
|
|
if (get_post_meta($post_id, '_jio_forum_type', true) === 'question' && !get_post_meta($post_id, '_jio_forum_adopted_comment', true) && Access::can_manage_post(get_current_user_id(), $post_id)) {
|
|
echo '<button type="button" data-forum-action="adopt" data-id="' . $post_id . '" data-comment-id="' . (int) $comment->comment_ID . '">采纳</button>';
|
|
}
|
|
echo '</div></div></article>';
|
|
}
|
|
if ($total > $limit) {
|
|
$links = paginate_links(['base' => add_query_arg('cpage', '%#%', get_permalink($post_id)), 'current' => $page, 'total' => (int) ceil($total / $limit), 'type' => 'list']);
|
|
echo '<nav class="jio-forum__pagination" aria-label="回复分页">' . wp_kses_post((string) $links) . '</nav>';
|
|
}
|
|
if (get_post_field('comment_status', $post_id) === 'open') {
|
|
if (is_user_logged_in()) {
|
|
echo '<form class="jio-forum__reply-form" data-forum-form="reply" data-id="' . $post_id . '"><h3>发表回复</h3><input type="hidden" name="parent_id" value="0"><textarea name="content" required rows="5" placeholder="写下你的回复"></textarea><p class="jio-forum__message" role="status"></p><button type="submit" class="jio-forum__primary">提交回复</button></form>';
|
|
} else {
|
|
echo '<p><a href="' . esc_url(jio_get_login_url(get_permalink($post_id))) . '">登录后回复</a></p>';
|
|
}
|
|
} else {
|
|
echo '<p class="jio-forum__empty">回复已关闭</p>';
|
|
}
|
|
echo '</section>';
|
|
}
|
|
|
|
private static function plate_editor(\WP_Post $plate): void {
|
|
$id = $plate->ID;
|
|
echo '<button type="button" class="jio-forum__action" data-forum-open="plate-edit-' . $id . '">编辑版块</button>';
|
|
echo '<button type="button" class="jio-forum__action" data-forum-action="plate-delete" data-id="' . $id . '">删除版块</button>';
|
|
echo '<dialog class="jio-forum__dialog" data-forum-dialog="plate-edit-' . $id . '"><form data-forum-form="plate" data-id="' . $id . '"><h2>编辑版块</h2>';
|
|
echo '<label>名称<input name="title" required value="' . esc_attr($plate->post_title) . '"></label>';
|
|
echo '<label>介绍<textarea name="content" rows="5">' . esc_textarea($plate->post_content) . '</textarea></label>';
|
|
echo '<label>发帖权限<select name="publish_limit"><option value="all">所有用户</option><option value="follow"' . selected(get_post_meta($id, '_jio_forum_publish_limit', true), 'follow', false) . '>已关注用户</option></select></label>';
|
|
echo '<label>关注方式<select name="follow_mode"><option value="all">免费</option><option value="points"' . selected(get_post_meta($id, '_jio_forum_follow_mode', true), 'points', false) . '>积分</option><option value="pay"' . selected(get_post_meta($id, '_jio_forum_follow_mode', true), 'pay', false) . '>付费</option></select></label>';
|
|
echo '<label>关注价格<input name="follow_price" type="number" min="0" step="0.01" value="' . esc_attr((string) get_post_meta($id, '_jio_forum_follow_price', true)) . '"></label>';
|
|
echo '<label>会员一级价格<input name="follow_price_vip1" type="number" min="0" step="0.01" value="' . esc_attr((string) get_post_meta($id, '_jio_forum_follow_price_vip1', true)) . '"></label>';
|
|
echo '<label>会员二级价格<input name="follow_price_vip2" type="number" min="0" step="0.01" value="' . esc_attr((string) get_post_meta($id, '_jio_forum_follow_price_vip2', true)) . '"></label>';
|
|
echo '<label>阅读权限<select name="view_limit">';
|
|
foreach (['all' => '所有人', 'login' => '登录后', 'follow' => '关注后', 'points' => '积分解锁', 'pay' => '付费解锁'] as $key => $label) {
|
|
echo '<option value="' . esc_attr($key) . '"' . selected(get_post_meta($id, '_jio_forum_view_limit', true), $key, false) . '>' . esc_html($label) . '</option>';
|
|
}
|
|
echo '</select></label><p class="jio-forum__message" role="status"></p><div class="jio-forum__dialog-actions"><button type="button" data-forum-close>取消</button><button type="submit" class="jio-forum__primary">保存</button></div></form></dialog>';
|
|
}
|
|
|
|
private static function applications(int $plate_id): void {
|
|
$users = get_users(['meta_key' => '_jio_forum_application_' . $plate_id, 'meta_value' => 'pending', 'number' => 20]);
|
|
echo '<section class="jio-forum__applications"><h2>版主管理</h2>';
|
|
$moderators = array_map('absint', (array) get_post_meta($plate_id, '_jio_forum_moderators', true));
|
|
foreach ($moderators as $user_id) {
|
|
$user = get_userdata($user_id);
|
|
if ($user) echo '<div class="jio-forum__application"><span>' . esc_html($user->display_name) . '</span><button type="button" data-forum-action="moderator-remove" data-id="' . $plate_id . '" data-user-id="' . $user_id . '">移除</button></div>';
|
|
}
|
|
echo '<form data-forum-form="moderator" data-id="' . $plate_id . '"><label>用户 ID<input name="user_id" type="number" min="1" required></label><p class="jio-forum__message" role="status"></p><button type="submit" class="jio-forum__action">添加版主</button></form>';
|
|
if ($users) echo '<h3>待处理申请</h3>';
|
|
foreach ($users as $user) {
|
|
echo '<div class="jio-forum__application"><span>' . esc_html($user->display_name) . '</span><button type="button" data-forum-action="application" data-id="' . $plate_id . '" data-user-id="' . (int) $user->ID . '" data-status="approved">通过</button><button type="button" data-forum-action="application" data-id="' . $plate_id . '" data-user-id="' . (int) $user->ID . '" data-status="rejected">拒绝</button></div>';
|
|
}
|
|
echo '</section>';
|
|
}
|
|
|
|
public static function pagination(\WP_Query $query): void {
|
|
if ($query->max_num_pages <= 1) {
|
|
return;
|
|
}
|
|
echo '<nav class="jio-forum__pagination" aria-label="分页">' . wp_kses_post(paginate_links(['total' => $query->max_num_pages, 'current' => max(1, (int) get_query_var('paged', 1)), 'type' => 'list'])) . '</nav>';
|
|
}
|
|
|
|
public static function action(string $action, int $id, string $label): void {
|
|
echo '<button type="button" class="jio-forum__action" data-forum-action="' . esc_attr($action) . '" data-id="' . esc_attr((string) $id) . '">' . esc_html($label) . '</button>';
|
|
}
|
|
|
|
public static function user_marked_ids(int $user_id, string $prefix): array {
|
|
if ($user_id <= 0) return [];
|
|
$ids = [];
|
|
foreach (get_user_meta($user_id) as $key => $values) {
|
|
if (str_starts_with((string) $key, $prefix) && !empty($values[0])) {
|
|
$id = absint(substr((string) $key, strlen($prefix)));
|
|
if ($id) $ids[] = $id;
|
|
}
|
|
}
|
|
return array_values(array_unique($ids));
|
|
}
|
|
|
|
public static function user_panel(int $user_id, bool $owner): void {
|
|
$tab = sanitize_key((string) ($_GET['forum_tab'] ?? 'posts'));
|
|
$tabs = ['posts' => '帖子', 'plates' => '版块'];
|
|
if ($owner) $tabs += ['favorites' => '收藏', 'follow' => '关注'];
|
|
if (!isset($tabs[$tab])) $tab = 'posts';
|
|
echo '<div id="forum" class="jio-forum jio-forum__user-panel"><nav class="jio-forum__tabs" aria-label="论坛内容">';
|
|
foreach ($tabs as $key => $label) {
|
|
echo '<a class="' . ($tab === $key ? 'is-active' : '') . '" href="' . esc_url(add_query_arg('forum_tab', $key)) . '">' . esc_html($label) . '</a>';
|
|
}
|
|
echo '</nav>';
|
|
if ($owner) echo '<p><a class="jio-forum__primary" href="' . esc_url(Core::edit_url()) . '">发布帖子</a></p>';
|
|
$args = ['post_status' => $owner ? ['publish', 'pending'] : 'publish', 'posts_per_page' => 20];
|
|
if ($tab === 'posts' || $tab === 'plates') {
|
|
$args['post_type'] = $tab === 'posts' ? Core::POST : Core::PLATE;
|
|
$args['author'] = $user_id;
|
|
} else {
|
|
$args['post_type'] = $tab === 'favorites' ? Core::POST : Core::PLATE;
|
|
$ids = self::user_marked_ids($user_id, $tab === 'favorites' ? '_jio_forum_favorite_' : '_jio_forum_follow_');
|
|
$args['post__in'] = $ids ?: [0];
|
|
}
|
|
$query = new \WP_Query($args);
|
|
echo '<div class="jio-forum__list">';
|
|
if (!$query->have_posts()) echo '<p class="jio-forum__empty">暂无内容</p>';
|
|
Cards::prime($query->posts);
|
|
foreach ($query->posts as $post) {
|
|
if ($post->post_type === Core::POST) {
|
|
echo Cards::post_detail($post); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
continue;
|
|
}
|
|
$url = $post->post_status === 'publish' ? get_permalink($post) : '';
|
|
echo '<div class="jio-forum__user-row">' . ($url ? '<a href="' . esc_url($url) . '">' : '<span>') . esc_html($post->post_title) . ($url ? '</a>' : '</span>') . ($url ? '' : '<small>待审核</small>') . '</div>';
|
|
if ($owner && !$url) self::plate_editor($post);
|
|
}
|
|
echo '</div></div>';
|
|
}
|
|
}
|