feat(comments): full 1:1 zibll comment kernel for posts, forum, shop
Extract shared comment helpers (content/item/form/geo/actions), port GIF [g=] smilies + FixedInput/moveForm UX, wire close_comments/city/quick settings, REST comment get, and align product review chrome with zibll.
This commit is contained in:
@@ -16,6 +16,12 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-icons.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-domain-model.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-template-tags.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-frontend-tags.php';
|
||||
// Comment kernel (shared by post / forum / shop surfaces).
|
||||
require_once AKSRT_DIR . '/inc/helpers/comments/content.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/comments/actions.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/comments/geo.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/comments/form-parts.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/comments/item.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-flow.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-homepage.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-single-components.php';
|
||||
|
||||
@@ -20,6 +20,12 @@ defined('ABSPATH') || exit;
|
||||
// ============================================================
|
||||
|
||||
add_filter('comments_open', function ($open, $post_id) {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$display = \AKSRT\Settings\Settings::get_comment_display_settings();
|
||||
if (!empty($display['close_comments'])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!get_option('aksrt_comment_enabled', true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+175
-128
@@ -94,31 +94,36 @@ if (!function_exists('aksrt_comments_build_context')) :
|
||||
? $settings['toolbar']
|
||||
: ['smilie' => true, 'code' => true, 'image' => true];
|
||||
|
||||
return [
|
||||
'settings' => $settings,
|
||||
'comment_count' => (int) get_comments_number(),
|
||||
'is_logged_in' => is_user_logged_in(),
|
||||
'current_user' => wp_get_current_user(),
|
||||
'require_login' => !empty($settings['require_login']),
|
||||
'order' => $order,
|
||||
'reverse_comments' => $order === 'newest',
|
||||
'is_hottest' => $order === 'hottest',
|
||||
'author_only' => isset($_GET['aksrt_author_only']) && (int) $_GET['aksrt_author_only'] === 1,
|
||||
'post_author_id' => (int) get_the_author_meta('ID'),
|
||||
'avatar_size' => $avatar_size,
|
||||
'max_depth' => max(1, (int) ($settings['nested_depth'] ?? 3)),
|
||||
'allow_reply' => !empty($settings['allow_reply']),
|
||||
'empty_text' => (string) ($settings['empty_text'] ?? __('暂无评论,来做第一个评论的人吧~', 'aksrt')),
|
||||
'toolbar' => $toolbar,
|
||||
'upload_enabled' => !empty($toolbar['upload_img']),
|
||||
'box_placeholder' => (string) ($settings['box_placeholder'] ?? __('说点什么吧...', 'aksrt')),
|
||||
'submit_text' => (string) ($settings['submit_text'] ?? __('提交评论', 'aksrt')),
|
||||
'title_text' => (string) ($settings['title'] ?? __('评论', 'aksrt')),
|
||||
'corderby_enabled' => !empty($settings['corderby']),
|
||||
'oauth_providers' => class_exists('\AKSRT\Auth\OAuth')
|
||||
? \AKSRT\Auth\OAuth::get_instance()->get_auth_providers_data()
|
||||
: [],
|
||||
];
|
||||
$phrases = is_array($settings['quick_phrases'] ?? null) ? $settings['quick_phrases'] : [];
|
||||
|
||||
return [
|
||||
'settings' => $settings,
|
||||
'comment_count' => (int) get_comments_number(),
|
||||
'is_logged_in' => is_user_logged_in(),
|
||||
'current_user' => wp_get_current_user(),
|
||||
'require_login' => !empty($settings['require_login']),
|
||||
'order' => $order,
|
||||
'reverse_comments' => $order === 'newest',
|
||||
'is_hottest' => $order === 'hottest',
|
||||
'author_only' => isset($_GET['aksrt_author_only']) && (int) $_GET['aksrt_author_only'] === 1,
|
||||
'post_author_id' => (int) get_the_author_meta('ID'),
|
||||
'avatar_size' => $avatar_size,
|
||||
'max_depth' => max(1, (int) ($settings['nested_depth'] ?? 3)),
|
||||
'allow_reply' => !empty($settings['allow_reply']),
|
||||
'empty_text' => (string) ($settings['empty_text'] ?? __('暂无评论,来做第一个评论的人吧~', 'aksrt')),
|
||||
'toolbar' => $toolbar,
|
||||
'upload_enabled' => !empty($toolbar['upload_img']),
|
||||
'box_placeholder' => (string) ($settings['box_placeholder'] ?? __('说点什么吧...', 'aksrt')),
|
||||
'submit_text' => (string) ($settings['submit_text'] ?? __('提交评论', 'aksrt')),
|
||||
'title_text' => (string) ($settings['title'] ?? __('评论', 'aksrt')),
|
||||
'corderby_enabled' => !empty($settings['corderby']),
|
||||
'close_comments' => !empty($settings['close_comments']),
|
||||
'quick_s' => !isset($settings['quick_s']) || !empty($settings['quick_s']),
|
||||
'quick_phrases' => $phrases,
|
||||
'oauth_providers' => class_exists('\AKSRT\Auth\OAuth')
|
||||
? \AKSRT\Auth\OAuth::get_instance()->get_auth_providers_data()
|
||||
: [],
|
||||
];
|
||||
}
|
||||
endif;
|
||||
|
||||
@@ -440,73 +445,105 @@ if (!function_exists('aksrt_comments_render_form')) :
|
||||
* @param array<string,mixed> $ctx Comments context.
|
||||
*/
|
||||
function aksrt_comments_render_form(array $ctx): void {
|
||||
$is_logged_in = !empty($ctx['is_logged_in']);
|
||||
$current_user = $ctx['current_user'] ?? null;
|
||||
$avatar_size = (int) $ctx['avatar_size'];
|
||||
$toolbar = is_array($ctx['toolbar'] ?? null) ? $ctx['toolbar'] : [];
|
||||
$upload_enabled = !empty($ctx['upload_enabled']);
|
||||
?>
|
||||
<div id="respond" class="comment-respond mobile-fixed">
|
||||
<form id="commentform" class="comment-form" method="post" action="<?php echo esc_url(site_url('/wp-comments-post.php')); ?>">
|
||||
<div class="fixed-body">
|
||||
<div class="comment-form__inner">
|
||||
<div class="comment-form__avatar">
|
||||
$is_logged_in = !empty($ctx['is_logged_in']);
|
||||
$current_user = $ctx['current_user'] ?? null;
|
||||
$avatar_size = max(40, (int) $ctx['avatar_size']);
|
||||
$require_name_email = (bool) get_option('require_name_email');
|
||||
$comment_author = isset($_COOKIE['comment_author_' . COOKIEHASH])
|
||||
? sanitize_text_field(wp_unslash((string) $_COOKIE['comment_author_' . COOKIEHASH]))
|
||||
: '';
|
||||
$comment_email = isset($_COOKIE['comment_author_email_' . COOKIEHASH])
|
||||
? sanitize_text_field(wp_unslash((string) $_COOKIE['comment_author_email_' . COOKIEHASH]))
|
||||
: '';
|
||||
$placeholder = (string) $ctx['box_placeholder'];
|
||||
|
||||
if (function_exists('aksrt_get_comment_virtual_input_html')) {
|
||||
echo aksrt_get_comment_virtual_input_html($placeholder, '#respond'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
?>
|
||||
<div id="respond" class="comment-respond mobile-fixed">
|
||||
<div class="fixed-body"></div>
|
||||
<form id="commentform" class="comment-form" method="post" action="<?php echo esc_url(site_url('/wp-comments-post.php')); ?>">
|
||||
<div class="flex ac">
|
||||
<div class="comt-title text-center flex0 mr10">
|
||||
<?php if ($is_logged_in && $current_user instanceof WP_User) : ?>
|
||||
<img src="<?php echo esc_url(get_avatar_url($current_user->ID, ['size' => $avatar_size])); ?>" alt="" width="<?php echo esc_attr((string) $avatar_size); ?>" height="<?php echo esc_attr((string) $avatar_size); ?>" class="avatar-img comt-avatar">
|
||||
<div class="comt-avatar mb10">
|
||||
<?php echo get_avatar($current_user->ID, $avatar_size, '', '', ['class' => 'avatar-img comt-avatar']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</div>
|
||||
<p class="text-ellipsis muted-2-color"><?php echo esc_html($current_user->display_name); ?></p>
|
||||
<?php else : ?>
|
||||
<div class="comment-form__avatar-placeholder"><i class="fas fa-user"></i></div>
|
||||
<div class="comt-avatar mb10">
|
||||
<?php echo get_avatar(0, $avatar_size, '', '', ['class' => 'avatar-img comt-avatar']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</div>
|
||||
<p data-toggle-class="open" data-target="#comment-user-info" title="<?php esc_attr_e('填写用户信息', 'aksrt'); ?>">
|
||||
<?php echo $comment_author !== '' ? esc_html($comment_author) : esc_html__('昵称', 'aksrt'); ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="grow1 comt-box">
|
||||
<div class="comt-tips" id="comment-reply-info" style="display:none;">
|
||||
<span class="comt-tips-left comment-form__reply-text"></span>
|
||||
<span class="comt-tips-right"><a href="javascript:;" id="cancel-comment-reply-link" class="comment-form__cancel-reply"><i class="fas fa-times"></i> <?php esc_html_e('取消', 'aksrt'); ?></a></span>
|
||||
</div>
|
||||
<textarea id="comment" name="comment" class="form-control grin comt-textarea" rows="2" placeholder="<?php echo esc_attr((string) $ctx['box_placeholder']); ?>" required></textarea>
|
||||
<div class="comt-ctrl">
|
||||
<div class="comment-toolbar" role="toolbar" aria-label="<?php esc_attr_e('评论工具栏', 'aksrt'); ?>">
|
||||
<button type="button" class="comment-toolbar__btn"<?php echo empty($toolbar['smilie']) ? ' hidden' : ''; ?> title="<?php esc_attr_e('表情', 'aksrt'); ?>" data-toolbar-action="emoji"><i class="far fa-smile"></i></button>
|
||||
<button type="button" class="comment-toolbar__btn"<?php echo empty($toolbar['code']) ? ' hidden' : ''; ?> title="<?php esc_attr_e('插入代码', 'aksrt'); ?>" data-toolbar-action="code"><i class="fas fa-code"></i></button>
|
||||
<button type="button" class="comment-toolbar__btn"<?php echo empty($toolbar['image']) ? ' hidden' : ''; ?> title="<?php esc_attr_e('图片', 'aksrt'); ?>" data-toolbar-action="image"><i class="fas fa-image"></i></button>
|
||||
<button type="button" class="comment-toolbar__btn"<?php echo $upload_enabled ? '' : ' hidden'; ?> title="<?php esc_attr_e('上传图片', 'aksrt'); ?>" data-toolbar-action="upload-image"><i class="fas fa-cloud-upload-alt"></i></button>
|
||||
<button type="button" class="comment-toolbar__btn" title="<?php esc_attr_e('快捷回复', 'aksrt'); ?>" data-toolbar-action="quick-reply"><i class="fas fa-bolt"></i></button>
|
||||
</div>
|
||||
<div class="comment-form__submit-area">
|
||||
<?php if (!$is_logged_in) : ?>
|
||||
<div class="comment-form__guest">
|
||||
<button type="button" class="comment-form__guest-toggle muted-color em09" data-aksrt-guest-toggle aria-expanded="false">
|
||||
<i class="fa fa-user-edit em09" aria-hidden="true"></i> <?php esc_html_e('填写昵称邮箱', 'aksrt'); ?>
|
||||
</button>
|
||||
<div class="comment-form__guest-fields" data-aksrt-guest-fields hidden>
|
||||
<input type="text" name="author" class="form-control form-control-sm comment-form__guest-field" placeholder="<?php esc_attr_e('昵称', 'aksrt'); ?>" required>
|
||||
<input type="email" name="email" class="form-control form-control-sm comment-form__guest-field" placeholder="<?php esc_attr_e('邮箱', 'aksrt'); ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<button type="submit" class="btn btn-primary btn-sm comment-form__submit" id="comment-submit">
|
||||
<i class="fas fa-paper-plane" aria-hidden="true"></i>
|
||||
<?php echo esc_html((string) $ctx['submit_text']); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comt-box grow1">
|
||||
<div class="action-text mb10 em09 muted-2-color" id="comment-reply-info" style="display:none;"></div>
|
||||
<textarea placeholder="<?php echo esc_attr($placeholder); ?>" autoheight="true" maxheight="188" class="form-control grin comt-textarea" name="comment" id="comment" cols="100%" rows="4" tabindex="1" required></textarea>
|
||||
<?php if (function_exists('aksrt_is_captcha_enabled_for') && function_exists('aksrt_render_captcha') && aksrt_is_captcha_enabled_for('comment')) : ?>
|
||||
<div class="comment-form__captcha">
|
||||
<?php aksrt_render_captcha('comment'); ?>
|
||||
</div>
|
||||
<div class="comment-form__captcha" style="width:230px;margin-top:8px;"><?php aksrt_render_captcha('comment'); ?></div>
|
||||
<?php endif; ?>
|
||||
<div class="comt-ctrl relative">
|
||||
<div class="comt-tips">
|
||||
<?php comment_id_fields(); ?>
|
||||
<?php wp_nonce_field('aksrt_comment_nonce', '_aksrt_comment_nonce'); ?>
|
||||
<input type="hidden" name="comment_post_ID" value="<?php echo esc_attr((string) get_the_ID()); ?>">
|
||||
<?php do_action('comment_form', get_the_ID()); ?>
|
||||
</div>
|
||||
<div class="comt-tips-right pull-right">
|
||||
<a class="but c-red" id="cancel-comment-reply-link" href="javascript:;" style="display:none;"><?php esc_html_e('取消', 'aksrt'); ?></a>
|
||||
<button class="but c-blue pw-1em input-expand-submit comment-send comment-form__submit" name="submit" id="submit" type="submit" tabindex="5"><?php echo esc_html((string) $ctx['submit_text']); ?></button>
|
||||
</div>
|
||||
<div class="comt-tips-left">
|
||||
<?php if (!$is_logged_in) :
|
||||
$req_mark = $require_name_email ? '(' . __('必填', 'aksrt') . ')' : '';
|
||||
?>
|
||||
<span class="dropup relative" id="comment-user-info"<?php echo $require_name_email ? ' require_name_email="true"' : ''; ?>>
|
||||
<a class="but mr6" data-toggle-class="open" data-target="#comment-user-info" href="javascript:;"><i class="fa fa-fw fa-user" aria-hidden="true"></i><span class="hide-sm"><?php esc_html_e('昵称', 'aksrt'); ?></span></a>
|
||||
<div class="dropdown-menu box-body" style="width:250px;">
|
||||
<div class="mb20">
|
||||
<p><?php esc_html_e('请填写用户信息:', 'aksrt'); ?></p>
|
||||
<ul>
|
||||
<li class="line-form mb10">
|
||||
<input type="text" name="author" class="line-form-input" tabindex="1" value="<?php echo esc_attr($comment_author); ?>" placeholder="" <?php echo $require_name_email ? 'required' : ''; ?> />
|
||||
<div class="scale-placeholder"><?php echo esc_html__('昵称', 'aksrt') . esc_html($req_mark); ?></div>
|
||||
</li>
|
||||
<li class="line-form">
|
||||
<input type="email" name="email" class="line-form-input" tabindex="2" value="<?php echo esc_attr($comment_email); ?>" placeholder="" <?php echo $require_name_email ? 'required' : ''; ?> />
|
||||
<div class="scale-placeholder"><?php echo esc_html__('邮箱', 'aksrt') . esc_html($req_mark); ?></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<?php
|
||||
$oauth = is_array($ctx['oauth_providers'] ?? null) ? $ctx['oauth_providers'] : [];
|
||||
if (!empty($oauth)) :
|
||||
?>
|
||||
<div class="comment-signarea__oauth flex ac" style="flex-wrap:wrap;gap:6px;">
|
||||
<?php foreach ($oauth as $provider) :
|
||||
$label = (string) ($provider['label'] ?? $provider['name'] ?? '');
|
||||
$url = (string) ($provider['url'] ?? '#');
|
||||
?>
|
||||
<a href="<?php echo esc_url($url); ?>" class="but hollow social-login-item"><?php echo esc_html($label); ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<?php if (function_exists('aksrt_render_comment_expand_toolbar')) { aksrt_render_comment_expand_toolbar($ctx); } ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
endif;
|
||||
|
||||
<?php comment_id_fields(); ?>
|
||||
<?php wp_nonce_field('aksrt_comment_nonce', '_aksrt_comment_nonce'); ?>
|
||||
<input type="hidden" name="comment_post_ID" value="<?php echo esc_attr((string) get_the_ID()); ?>">
|
||||
</form>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_comments_render_filter')) :
|
||||
/**
|
||||
@@ -612,55 +649,65 @@ if (!function_exists('aksrt_comments_render')) :
|
||||
* Compose the full comments surface.
|
||||
*/
|
||||
function aksrt_comments_render(): void {
|
||||
if (post_password_required()) {
|
||||
return;
|
||||
}
|
||||
if (post_password_required()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ctx = aksrt_comments_build_context();
|
||||
if (!empty($ctx['is_hottest'])) {
|
||||
aksrt_comments_apply_hottest_order();
|
||||
}
|
||||
$ctx = aksrt_comments_build_context();
|
||||
if (!empty($ctx['close_comments'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep callback/pagination contracts that still read these query vars.
|
||||
set_query_var('aksrt_comment_order', (string) $ctx['order']);
|
||||
set_query_var('aksrt_comment_settings', $ctx['settings']);
|
||||
if (!empty($ctx['is_hottest'])) {
|
||||
aksrt_comments_apply_hottest_order();
|
||||
}
|
||||
|
||||
$avatar_size = (int) $ctx['avatar_size'];
|
||||
?>
|
||||
<div class="theme-box" id="comments" style="<?php echo esc_attr('--aksrt-comment-avatar:' . $avatar_size . 'px;--aksrt-comment-avatar-form:' . max(40, $avatar_size) . 'px;--aksrt-comment-indent:' . $avatar_size . 'px;'); ?>">
|
||||
<div class="box-body notop">
|
||||
<div class="title-theme">
|
||||
<?php echo esc_html((string) $ctx['title_text']); ?>
|
||||
<small class="comments-count"><?php echo esc_html(sprintf(__('共 %d 条', 'aksrt'), (int) $ctx['comment_count'])); ?></small>
|
||||
set_query_var('aksrt_comment_order', (string) $ctx['order']);
|
||||
set_query_var('aksrt_comment_settings', $ctx['settings']);
|
||||
|
||||
$avatar_size = max(45, (int) $ctx['avatar_size']);
|
||||
$count = (int) $ctx['comment_count'];
|
||||
$count_label = $count > 0
|
||||
? sprintf(__('共%s条', 'aksrt'), (string) $count)
|
||||
: __('抢沙发', 'aksrt');
|
||||
?>
|
||||
<div class="theme-box" id="comments" style="<?php echo esc_attr('--aksrt-comment-avatar:' . $avatar_size . 'px;--aksrt-comment-avatar-form:' . $avatar_size . 'px;--aksrt-comment-indent:86px;'); ?>">
|
||||
<div class="box-body notop">
|
||||
<div class="title-theme">
|
||||
<?php echo esc_html((string) $ctx['title_text']); ?>
|
||||
<small class="comments-count"><?php echo esc_html($count_label); ?></small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="no_webshot main-bg theme-box box-body radius8 main-shadow">
|
||||
<?php if (comments_open()) : ?>
|
||||
<?php if (!empty($ctx['require_login']) && empty($ctx['is_logged_in'])) : ?>
|
||||
<?php aksrt_comments_render_signarea($ctx); ?>
|
||||
<?php else : ?>
|
||||
<?php aksrt_comments_render_form($ctx); ?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$show_filter = !empty($ctx['corderby_enabled']) && ($count > 0 || !empty($ctx['author_only']) || have_comments());
|
||||
if ($show_filter) {
|
||||
aksrt_comments_render_filter($ctx);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if (have_comments()) : ?>
|
||||
<?php aksrt_comments_render_list($ctx); ?>
|
||||
<?php elseif (!post_password_required()) : ?>
|
||||
<?php aksrt_comments_render_empty($ctx); ?>
|
||||
<div class="pagenav hide"><div class="next-page ajax-next"><a href="#"></a></div></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!comments_open() && $count && post_type_supports(get_post_type(), 'comments')) : ?>
|
||||
<p class="comments-closed separator text-muted em12"><?php esc_html_e('评论已关闭。', 'aksrt'); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (comments_open()) : ?>
|
||||
<?php if (!empty($ctx['require_login']) && empty($ctx['is_logged_in'])) : ?>
|
||||
<?php aksrt_comments_render_signarea($ctx); ?>
|
||||
<?php else : ?>
|
||||
<?php aksrt_comments_render_form($ctx); ?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
// Keep filter visible under author-only empty results.
|
||||
$show_filter = !empty($ctx['corderby_enabled']) && ((int) $ctx['comment_count'] > 0 || !empty($ctx['author_only']) || have_comments());
|
||||
if ($show_filter) {
|
||||
aksrt_comments_render_filter($ctx);
|
||||
}
|
||||
?>
|
||||
}
|
||||
endif;
|
||||
|
||||
<?php if (have_comments()) : ?>
|
||||
<?php aksrt_comments_render_list($ctx); ?>
|
||||
<?php elseif (!post_password_required()) : ?>
|
||||
<?php aksrt_comments_render_empty($ctx); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!comments_open() && get_comments_number() && post_type_supports(get_post_type(), 'comments')) : ?>
|
||||
<p class="comments-closed"><?php esc_html_e('评论已关闭。', 'aksrt'); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
endif;
|
||||
|
||||
@@ -5,101 +5,6 @@
|
||||
* @package AKSRT
|
||||
*/
|
||||
defined('ABSPATH') || exit;
|
||||
if (!function_exists('aksrt_comment_smilie_map')) :
|
||||
/**
|
||||
* 评论 grin / smilie 映射表。
|
||||
*
|
||||
* @return array<string,string>
|
||||
*/
|
||||
function aksrt_comment_smilie_map(): array {
|
||||
return [
|
||||
'grinning' => '😀',
|
||||
'joy' => '😂',
|
||||
'rofl' => '🤣',
|
||||
'blush' => '😊',
|
||||
'heart-eyes' => '😍',
|
||||
'smiling-hearts' => '🥰',
|
||||
'sunglasses' => '😎',
|
||||
'thinking' => '🤔',
|
||||
'cry' => '😢',
|
||||
'sob' => '😭',
|
||||
'angry' => '😡',
|
||||
'pleading' => '🥺',
|
||||
'scream' => '😱',
|
||||
'rolling-eyes' => '🙄',
|
||||
'sleeping' => '😴',
|
||||
'nauseated' => '🤮',
|
||||
'thumbs-up' => '👍',
|
||||
'thumbs-down' => '👎',
|
||||
'clap' => '👏',
|
||||
'pray' => '🙏',
|
||||
'muscle' => '💪',
|
||||
'heart' => '❤️',
|
||||
'fire' => '🔥',
|
||||
'star' => '⭐',
|
||||
'tada' => '🎉',
|
||||
'confetti' => '🎊',
|
||||
'check' => '✅',
|
||||
'cross' => '❌',
|
||||
'warning' => '⚠️',
|
||||
'hundred' => '💯',
|
||||
'ok' => '🆗',
|
||||
'new' => '🆕',
|
||||
];
|
||||
}
|
||||
endif;
|
||||
if (!function_exists('aksrt_render_comment_smilie')) :
|
||||
/**
|
||||
* 渲染单个 grin 表情为 smilie-icon 图片。
|
||||
*
|
||||
* @param string $key grin key.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_render_comment_smilie(string $key): string {
|
||||
$map = aksrt_comment_smilie_map();
|
||||
if (!isset($map[$key])) {
|
||||
return '[g=' . $key . ']';
|
||||
}
|
||||
$emoji = $map[$key];
|
||||
$svg = '<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30"><text x="50%" y="50%" dominant-baseline="central" text-anchor="middle" font-size="24">' . esc_html($emoji) . '</text></svg>';
|
||||
$src = 'data:image/svg+xml;charset=UTF-8,' . rawurlencode($svg);
|
||||
return sprintf(
|
||||
'<img class="smilie-icon" src="%1$s" alt="%2$s" title="%2$s" width="30" height="30" loading="lazy" decoding="async" data-smilie-key="%3$s" />',
|
||||
esc_url($src),
|
||||
esc_attr($emoji),
|
||||
esc_attr($key)
|
||||
);
|
||||
}
|
||||
endif;
|
||||
if (!function_exists('aksrt_render_comment_content')) :
|
||||
/**
|
||||
* 将评论中的 grin token 渲染为 smilie-icon,并按 post 白名单输出。
|
||||
*
|
||||
* @param string $content Comment HTML/text.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_render_comment_content(string $content): string {
|
||||
$rendered = preg_replace_callback('/\[g=([a-z0-9-]+)\]/i', static function (array $matches): string {
|
||||
return aksrt_render_comment_smilie(sanitize_key($matches[1]));
|
||||
}, $content);
|
||||
$allowed = wp_kses_allowed_html('post');
|
||||
if (!isset($allowed['img'])) {
|
||||
$allowed['img'] = [];
|
||||
}
|
||||
$allowed['img'] = array_merge($allowed['img'], [
|
||||
'class' => true,
|
||||
'src' => true,
|
||||
'alt' => true,
|
||||
'title' => true,
|
||||
'width' => true,
|
||||
'height' => true,
|
||||
'loading' => true,
|
||||
'decoding' => true,
|
||||
'data-smilie-key' => true,
|
||||
]);
|
||||
return wp_kses((string) $rendered, $allowed);
|
||||
}
|
||||
endif;
|
||||
if (!function_exists('aksrt_reading_minutes')) :
|
||||
/**
|
||||
* 估算文章阅读分钟数
|
||||
@@ -822,329 +727,3 @@ add_action('wp_ajax_aksrt_toggle_like', 'aksrt_toggle_like_ajax');
|
||||
add_action('wp_ajax_nopriv_aksrt_toggle_like', 'aksrt_toggle_like_ajax');
|
||||
add_action('wp_ajax_aksrt_toggle_favorite', 'aksrt_toggle_favorite_ajax');
|
||||
add_action('wp_ajax_nopriv_aksrt_toggle_favorite', 'aksrt_toggle_favorite_ajax');
|
||||
// Float moderator-pinned top-level comments to the front of the first-page list.
|
||||
// Runs during comments_template() before comments.php is loaded.
|
||||
add_filter('comments_array', 'aksrt_reorder_pinned_comments', 20, 2);
|
||||
if (!function_exists('aksrt_comment_action_permissions')) :
|
||||
/**
|
||||
* Single source of truth for per-comment action permissions.
|
||||
*
|
||||
* Both the renderer (button visibility) and the REST endpoints
|
||||
* (server-side enforcement) must read from this contract so the UI never
|
||||
* advertises an action the backend would reject.
|
||||
*
|
||||
* @param WP_Comment|int $comment Comment object or ID.
|
||||
* @return array{can_edit:bool,can_delete:bool,can_moderate:bool,can_pin:bool}
|
||||
*/
|
||||
function aksrt_comment_action_permissions($comment): array {
|
||||
$comment = $comment instanceof WP_Comment ? $comment : get_comment($comment);
|
||||
$none = ['can_edit' => false, 'can_delete' => false, 'can_moderate' => false, 'can_pin' => false];
|
||||
if (!($comment instanceof WP_Comment) || !is_user_logged_in()) {
|
||||
return $none;
|
||||
}
|
||||
$uid = get_current_user_id();
|
||||
$is_wp_mod = current_user_can('moderate_comments') || current_user_can('manage_options');
|
||||
$is_owner = (int) $comment->user_id > 0 && (int) $comment->user_id === $uid;
|
||||
$post_id = (int) $comment->comment_post_ID;
|
||||
$post = $post_id > 0 ? get_post($post_id) : null;
|
||||
$is_post_author = $post instanceof WP_Post && (int) $post->post_author === $uid;
|
||||
$ctx = $post_id > 0 ? $post_id : null;
|
||||
|
||||
$can = static function (string $cap) use ($uid, $ctx): bool {
|
||||
if (function_exists('aksrt_user_can')) {
|
||||
return aksrt_user_can($cap, $uid, $ctx);
|
||||
}
|
||||
if (class_exists('\AKSRT\Settings\Capabilities')) {
|
||||
return \AKSRT\Settings\Capabilities::user_can($cap, $uid, $ctx);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
$edit_window = 10;
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$display = \AKSRT\Settings\Settings::get_comment_display_settings();
|
||||
$edit_window = (int) ($display['edit_window'] ?? 10);
|
||||
}
|
||||
$within_window = $edit_window > 0
|
||||
&& (time() - strtotime((string) $comment->comment_date_gmt)) < ($edit_window * MINUTE_IN_SECONDS);
|
||||
|
||||
$can_edit_own = $is_owner && $within_window && $can('comment_edit');
|
||||
$can_edit_others = (!$is_owner) && (
|
||||
($is_post_author && $can('comment_edit_my_post'))
|
||||
|| $can('comment_edit_other')
|
||||
);
|
||||
$can_delete_own = $is_owner && $can('comment_delete');
|
||||
$can_delete_others = (!$is_owner) && (
|
||||
($is_post_author && $can('comment_delete_my_post'))
|
||||
|| $can('comment_delete_other')
|
||||
);
|
||||
$can_pin = (int) $comment->comment_parent === 0 && (
|
||||
($is_post_author && $can('comment_set_topping_my_post'))
|
||||
|| $can('comment_set_topping_other')
|
||||
);
|
||||
|
||||
return [
|
||||
'can_edit' => $is_wp_mod || $can_edit_own || $can_edit_others,
|
||||
'can_delete' => $is_wp_mod || $can_delete_own || $can_delete_others,
|
||||
'can_moderate' => $is_wp_mod || $can('comment_audit_other') || ($is_post_author && $can('comment_audit_my_post')),
|
||||
'can_pin' => $is_wp_mod || $can_pin,
|
||||
];
|
||||
}
|
||||
endif;
|
||||
if (!function_exists('aksrt_is_comment_pinned')) :
|
||||
/**
|
||||
* Whether a comment is pinned ("置顶") by a moderator.
|
||||
*
|
||||
* @param int|WP_Comment $comment Comment object or ID.
|
||||
* @return bool
|
||||
*/
|
||||
function aksrt_is_comment_pinned($comment): bool {
|
||||
$comment_id = $comment instanceof WP_Comment ? (int) $comment->comment_ID : (int) $comment;
|
||||
if ($comment_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
return '1' === (string) get_comment_meta($comment_id, '_aksrt_comment_pinned', true);
|
||||
}
|
||||
endif;
|
||||
if (!function_exists('aksrt_update_comment_counter')) :
|
||||
/**
|
||||
* Atomically bump a numeric comment-meta counter, floored at zero.
|
||||
*
|
||||
* @param int $comment_id Comment ID.
|
||||
* @param string $meta_key Meta key.
|
||||
* @param int $delta Signed delta.
|
||||
* @return int Latest value (>= 0).
|
||||
*/
|
||||
function aksrt_update_comment_counter($comment_id, $meta_key, $delta) {
|
||||
global $wpdb;
|
||||
$comment_id = (int) $comment_id;
|
||||
$delta = (int) $delta;
|
||||
$exists = (int) $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT 1 FROM {$wpdb->commentmeta} WHERE comment_id = %d AND meta_key = %s LIMIT 1",
|
||||
$comment_id,
|
||||
$meta_key
|
||||
));
|
||||
if ($exists) {
|
||||
$wpdb->query($wpdb->prepare(
|
||||
"UPDATE {$wpdb->commentmeta}
|
||||
SET meta_value = GREATEST(0, CAST(meta_value AS SIGNED) + %d)
|
||||
WHERE comment_id = %d AND meta_key = %s",
|
||||
$delta,
|
||||
$comment_id,
|
||||
$meta_key
|
||||
));
|
||||
} else {
|
||||
add_comment_meta($comment_id, $meta_key, max(0, $delta), true);
|
||||
}
|
||||
wp_cache_delete($comment_id, 'comment_meta');
|
||||
return (int) get_comment_meta($comment_id, $meta_key, true);
|
||||
}
|
||||
endif;
|
||||
if (!function_exists('aksrt_get_comment_likes')) :
|
||||
/**
|
||||
* Comment like count (shared by renderer, "hottest" sort and REST).
|
||||
*
|
||||
* @param int|WP_Comment $comment Comment object or ID.
|
||||
* @return int
|
||||
*/
|
||||
function aksrt_get_comment_likes($comment): int {
|
||||
$comment_id = $comment instanceof WP_Comment ? (int) $comment->comment_ID : (int) $comment;
|
||||
if ($comment_id <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return max(0, (int) get_comment_meta($comment_id, 'aksrt_comment_likes', true));
|
||||
}
|
||||
endif;
|
||||
if (!function_exists('aksrt_is_comment_liked')) :
|
||||
/**
|
||||
* Whether the current visitor already liked a comment.
|
||||
*
|
||||
* @param int $comment_id Comment ID.
|
||||
* @return bool
|
||||
*/
|
||||
function aksrt_is_comment_liked($comment_id): bool {
|
||||
$comment_id = (int) $comment_id;
|
||||
if ($comment_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (is_user_logged_in()) {
|
||||
$liked = get_user_meta(get_current_user_id(), '_aksrt_liked_comments', true);
|
||||
$liked = is_array($liked) ? array_map('intval', $liked) : [];
|
||||
return in_array($comment_id, $liked, true);
|
||||
}
|
||||
$cookie = 'aksrt_clike_' . $comment_id;
|
||||
return !empty($_COOKIE[$cookie]) && '1' === $_COOKIE[$cookie];
|
||||
}
|
||||
endif;
|
||||
if (!function_exists('aksrt_set_comment_like_state')) :
|
||||
/**
|
||||
* Persist a visitor's like state for a comment.
|
||||
*
|
||||
* @param int $comment_id Comment ID.
|
||||
* @param bool $active Target state.
|
||||
*/
|
||||
function aksrt_set_comment_like_state($comment_id, $active): void {
|
||||
$comment_id = (int) $comment_id;
|
||||
if ($comment_id <= 0) {
|
||||
return;
|
||||
}
|
||||
if (is_user_logged_in()) {
|
||||
$user_id = get_current_user_id();
|
||||
$liked = get_user_meta($user_id, '_aksrt_liked_comments', true);
|
||||
$liked = is_array($liked) ? array_map('intval', $liked) : [];
|
||||
if ($active) {
|
||||
$liked[] = $comment_id;
|
||||
} else {
|
||||
$liked = array_filter($liked, static function ($id) use ($comment_id) {
|
||||
return (int) $id !== $comment_id;
|
||||
});
|
||||
}
|
||||
update_user_meta($user_id, '_aksrt_liked_comments', array_values(array_unique($liked)));
|
||||
return;
|
||||
}
|
||||
$cookie = 'aksrt_clike_' . $comment_id;
|
||||
$path = defined('COOKIEPATH') && COOKIEPATH ? COOKIEPATH : '/';
|
||||
$domain = defined('COOKIE_DOMAIN') ? COOKIE_DOMAIN : '';
|
||||
setcookie($cookie, $active ? '1' : '', $active ? time() + YEAR_IN_SECONDS : time() - HOUR_IN_SECONDS, $path, $domain, is_ssl(), true);
|
||||
if ($active) {
|
||||
$_COOKIE[$cookie] = '1';
|
||||
} else {
|
||||
unset($_COOKIE[$cookie]);
|
||||
}
|
||||
}
|
||||
endif;
|
||||
if (!function_exists('aksrt_reorder_pinned_comments')) :
|
||||
/**
|
||||
* Float pinned top-level comments to the front of the queried list.
|
||||
*
|
||||
* Attached to `comments_array` on singular views so the first-page render
|
||||
* and the canonical renderer agree on pin ordering. Nested replies keep
|
||||
* their parent ordering; only top-level pins are reordered.
|
||||
*
|
||||
* @param array $comments Comment objects.
|
||||
* @param int $post_id Post ID.
|
||||
* @return array
|
||||
*/
|
||||
function aksrt_reorder_pinned_comments($comments, $post_id) {
|
||||
if (!is_array($comments) || count($comments) < 2) {
|
||||
return $comments;
|
||||
}
|
||||
$pinned = [];
|
||||
$normal = [];
|
||||
foreach ($comments as $comment) {
|
||||
if ($comment instanceof WP_Comment && (int) $comment->comment_parent === 0 && aksrt_is_comment_pinned($comment)) {
|
||||
$pinned[] = $comment;
|
||||
} else {
|
||||
$normal[] = $comment;
|
||||
}
|
||||
}
|
||||
if (empty($pinned)) {
|
||||
return $comments;
|
||||
}
|
||||
return array_merge($pinned, $normal);
|
||||
}
|
||||
endif;
|
||||
if (!function_exists('aksrt_comment_callback')) :
|
||||
/**
|
||||
* Canonical comment list item renderer.
|
||||
*
|
||||
* This is the single contract used by first-page rendering (comments.php),
|
||||
* AJAX pagination (\AKSRT\Admin\Admin::ajax_load_more_comments) and the post-submit
|
||||
* list reload (main.js reloadCommentList(), which refetches the rendered
|
||||
* page). Emits a stable `data-comment-id` plus reply, like, management
|
||||
* (edit/delete/moderate) and pin controls gated through
|
||||
* aksrt_comment_action_permissions().
|
||||
*
|
||||
* @param WP_Comment $comment 评论对象
|
||||
* @param array $args 参数
|
||||
* @param int $depth 嵌套深度
|
||||
*/
|
||||
function aksrt_comment_callback($comment, $args, $depth) {
|
||||
$GLOBALS['comment'] = $comment;
|
||||
$settings = get_query_var('aksrt_comment_settings', []);
|
||||
$settings = is_array($settings) ? $settings : [];
|
||||
$comment_id = (int) $comment->comment_ID;
|
||||
$depth_class = 'aksrt-depth-' . min((int) ($settings['nested_depth'] ?? 3), (int) $depth);
|
||||
$author_name = get_comment_author($comment);
|
||||
$comment_date = get_comment_date('c', $comment);
|
||||
$comment_label = get_comment_date('Y-m-d', $comment);
|
||||
$avatar_size = max(24, (int) ($settings['avatar_size'] ?? 48));
|
||||
$allow_reply = !empty($settings['allow_reply']);
|
||||
$show_author_tag = !isset($settings['author_tag']) || !empty($settings['author_tag']);
|
||||
$like_enabled = !isset($settings['like_enabled']) || !empty($settings['like_enabled']);
|
||||
$is_post_author = (int) $comment->user_id > 0 && (int) $comment->user_id === (int) get_post_field('post_author', $comment->comment_post_ID);
|
||||
$is_pinned = aksrt_is_comment_pinned($comment);
|
||||
$perms = aksrt_comment_action_permissions($comment);
|
||||
$is_pending = '0' === $comment->comment_approved;
|
||||
$is_spam = 'spam' === $comment->comment_approved;
|
||||
$like_count = aksrt_get_comment_likes($comment);
|
||||
$is_liked = aksrt_is_comment_liked($comment_id);
|
||||
$li_classes = trim($depth_class . ($is_pinned ? ' aksrt-comment--pinned' : ''));
|
||||
?>
|
||||
<li <?php comment_class('aksrt-comment ' . $li_classes); ?> id="comment-<?php echo esc_attr((string) $comment_id); ?>" data-comment-id="<?php echo esc_attr((string) $comment_id); ?>" data-comment-status="<?php echo esc_attr((string) $comment->comment_approved); ?>">
|
||||
<ul class="list-inline">
|
||||
<li class="comt-main" id="div-comment-<?php echo esc_attr((string) $comment_id); ?>">
|
||||
<div class="comment-header aksrt-comment__header">
|
||||
<div class="author-box">
|
||||
<?php echo get_avatar($comment, $avatar_size, '', '', ['class' => 'avatar-img comt-avatar']); ?>
|
||||
<span class="aksrt-comment__author"><?php comment_author_link(); ?></span>
|
||||
<?php
|
||||
$comment_user_id = (int) $comment->user_id;
|
||||
if ($comment_user_id > 0 && function_exists('aksrt_render_user_identity_chips')) {
|
||||
echo aksrt_render_user_identity_chips($comment_user_id, 'comment'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
?>
|
||||
<?php if ($show_author_tag && $is_post_author) : ?>
|
||||
<span class="aksrt-comment__badge"><?php esc_html_e('作者', 'aksrt'); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if (function_exists('aksrt_is_blog_comment_hot') && aksrt_is_blog_comment_hot($comment)) : ?>
|
||||
<span class="aksrt-comment__badge aksrt-comment__badge--hot"><i class="fas fa-fire" aria-hidden="true"></i> <?php esc_html_e('神评', 'aksrt'); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ($is_pinned) : ?>
|
||||
<span class="aksrt-comment__badge aksrt-comment__badge--pinned"><i class="fas fa-thumbtack" aria-hidden="true"></i> <?php esc_html_e('置顶', 'aksrt'); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<time class="post-time aksrt-comment__time" datetime="<?php echo esc_attr($comment_date); ?>"><?php echo esc_html($comment_label); ?></time>
|
||||
</div>
|
||||
<div class="comment-content aksrt-comment__text" id="comment-content-<?php echo esc_attr((string) $comment_id); ?>">
|
||||
<?php if ($is_pending) : ?>
|
||||
<p class="awaiting-moderation"><?php esc_html_e('您的评论正在等待审核。', 'aksrt'); ?></p>
|
||||
<?php else : ?>
|
||||
<?php echo aksrt_render_comment_content((string) get_comment_text($comment)); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="comment-footer comt-meta aksrt-comment__actions">
|
||||
<?php if ($like_enabled) : ?>
|
||||
<button type="button" class="aksrt-comment__like<?php echo $is_liked ? ' is-active' : ''; ?>" data-comment-action="like" data-active="<?php echo $is_liked ? '1' : '0'; ?>" aria-pressed="<?php echo $is_liked ? 'true' : 'false'; ?>">
|
||||
<i class="<?php echo $is_liked ? 'fas' : 'far'; ?> fa-thumbs-up" aria-hidden="true"></i>
|
||||
<span class="aksrt-comment__like-count"<?php echo $like_count > 0 ? '' : ' hidden'; ?>><?php echo esc_html((string) $like_count); ?></span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if ($allow_reply && !$is_pending) : ?>
|
||||
<?php comment_reply_link(array_merge($args, ['depth' => $depth, 'max_depth' => $args['max_depth'], 'before' => '', 'after' => '', 'reply_text' => '<i class="fas fa-reply" aria-hidden="true"></i> <span>' . esc_html__('回复', 'aksrt') . '</span><span class="screen-reader-text"> ' . esc_html($author_name) . '</span>']), $comment); ?>
|
||||
<?php endif; ?>
|
||||
<?php if ($perms['can_edit']) : ?>
|
||||
<button type="button" class="aksrt-comment__action" data-comment-action="edit"><i class="fas fa-pen" aria-hidden="true"></i> <?php esc_html_e('编辑', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if ($perms['can_pin']) : ?>
|
||||
<button type="button" class="aksrt-comment__action" data-comment-action="pin" data-pinned="<?php echo $is_pinned ? '1' : '0'; ?>"><i class="fas fa-thumbtack" aria-hidden="true"></i> <?php echo $is_pinned ? esc_html__('取消置顶', 'aksrt') : esc_html__('置顶', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if ($perms['can_moderate'] && $is_pending) : ?>
|
||||
<button type="button" class="aksrt-comment__action aksrt-comment__action--success" data-comment-action="approve"><i class="fas fa-check" aria-hidden="true"></i> <?php esc_html_e('通过', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if ($perms['can_moderate'] && !$is_pending && !$is_spam) : ?>
|
||||
<button type="button" class="aksrt-comment__action" data-comment-action="hold"><i class="fas fa-eye-slash" aria-hidden="true"></i> <?php esc_html_e('待审', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if ($perms['can_moderate']) : ?>
|
||||
<button type="button" class="aksrt-comment__action" data-comment-action="spam"><i class="fas fa-ban" aria-hidden="true"></i> <?php esc_html_e('垃圾', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if ($perms['can_delete']) : ?>
|
||||
<button type="button" class="aksrt-comment__action aksrt-comment__action--danger" data-comment-action="delete"><i class="fas fa-trash-alt" aria-hidden="true"></i> <?php esc_html_e('删除', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
endif;
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
/**
|
||||
* Comment action permissions + like/pin primitives.
|
||||
*
|
||||
* Extracted from aksrt-frontend-tags.php for size budget.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_comment_action_permissions')) :
|
||||
/**
|
||||
* Single source of truth for per-comment action permissions.
|
||||
*
|
||||
* @param WP_Comment|int $comment Comment object or ID.
|
||||
* @return array{can_edit:bool,can_delete:bool,can_moderate:bool,can_pin:bool}
|
||||
*/
|
||||
function aksrt_comment_action_permissions($comment): array {
|
||||
$comment = $comment instanceof WP_Comment ? $comment : get_comment($comment);
|
||||
$none = ['can_edit' => false, 'can_delete' => false, 'can_moderate' => false, 'can_pin' => false];
|
||||
if (!($comment instanceof WP_Comment) || !is_user_logged_in()) {
|
||||
return $none;
|
||||
}
|
||||
$uid = get_current_user_id();
|
||||
$is_wp_mod = current_user_can('moderate_comments') || current_user_can('manage_options');
|
||||
$is_owner = (int) $comment->user_id > 0 && (int) $comment->user_id === $uid;
|
||||
$post_id = (int) $comment->comment_post_ID;
|
||||
$post = $post_id > 0 ? get_post($post_id) : null;
|
||||
$is_post_author = $post instanceof WP_Post && (int) $post->post_author === $uid;
|
||||
$ctx = $post_id > 0 ? $post_id : null;
|
||||
|
||||
$can = static function (string $cap) use ($uid, $ctx): bool {
|
||||
if (function_exists('aksrt_user_can')) {
|
||||
return aksrt_user_can($cap, $uid, $ctx);
|
||||
}
|
||||
if (class_exists('\AKSRT\Settings\Capabilities')) {
|
||||
return \AKSRT\Settings\Capabilities::user_can($cap, $uid, $ctx);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
$edit_window = 10;
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$display = \AKSRT\Settings\Settings::get_comment_display_settings();
|
||||
$edit_window = (int) ($display['edit_window'] ?? 10);
|
||||
}
|
||||
$within_window = $edit_window > 0
|
||||
&& (time() - strtotime((string) $comment->comment_date_gmt)) < ($edit_window * MINUTE_IN_SECONDS);
|
||||
|
||||
$can_edit_own = $is_owner && $within_window && $can('comment_edit');
|
||||
$can_edit_others = (!$is_owner) && (
|
||||
($is_post_author && $can('comment_edit_my_post'))
|
||||
|| $can('comment_edit_other')
|
||||
);
|
||||
$can_delete_own = $is_owner && $can('comment_delete');
|
||||
$can_delete_others = (!$is_owner) && (
|
||||
($is_post_author && $can('comment_delete_my_post'))
|
||||
|| $can('comment_delete_other')
|
||||
);
|
||||
$can_pin = (int) $comment->comment_parent === 0 && (
|
||||
($is_post_author && $can('comment_set_topping_my_post'))
|
||||
|| $can('comment_set_topping_other')
|
||||
);
|
||||
|
||||
return [
|
||||
'can_edit' => $is_wp_mod || $can_edit_own || $can_edit_others,
|
||||
'can_delete' => $is_wp_mod || $can_delete_own || $can_delete_others,
|
||||
'can_moderate' => $is_wp_mod || $can('comment_audit_other') || ($is_post_author && $can('comment_audit_my_post')),
|
||||
'can_pin' => $is_wp_mod || $can_pin,
|
||||
];
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_is_comment_pinned')) :
|
||||
/**
|
||||
* @param int|WP_Comment $comment Comment.
|
||||
*/
|
||||
function aksrt_is_comment_pinned($comment): bool {
|
||||
$comment_id = $comment instanceof WP_Comment ? (int) $comment->comment_ID : (int) $comment;
|
||||
if ($comment_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
return '1' === (string) get_comment_meta($comment_id, '_aksrt_comment_pinned', true);
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_update_comment_counter')) :
|
||||
/**
|
||||
* Atomically bump a numeric comment-meta counter, floored at zero.
|
||||
*
|
||||
* @param int $comment_id Comment ID.
|
||||
* @param string $meta_key Meta key.
|
||||
* @param int $delta Signed delta.
|
||||
* @return int Latest value (>= 0).
|
||||
*/
|
||||
function aksrt_update_comment_counter($comment_id, $meta_key, $delta) {
|
||||
global $wpdb;
|
||||
$comment_id = (int) $comment_id;
|
||||
$delta = (int) $delta;
|
||||
$exists = (int) $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT 1 FROM {$wpdb->commentmeta} WHERE comment_id = %d AND meta_key = %s LIMIT 1",
|
||||
$comment_id,
|
||||
$meta_key
|
||||
));
|
||||
if ($exists) {
|
||||
$wpdb->query($wpdb->prepare(
|
||||
"UPDATE {$wpdb->commentmeta}
|
||||
SET meta_value = GREATEST(0, CAST(meta_value AS SIGNED) + %d)
|
||||
WHERE comment_id = %d AND meta_key = %s",
|
||||
$delta,
|
||||
$comment_id,
|
||||
$meta_key
|
||||
));
|
||||
} else {
|
||||
add_comment_meta($comment_id, $meta_key, max(0, $delta), true);
|
||||
}
|
||||
wp_cache_delete($comment_id, 'comment_meta');
|
||||
return (int) get_comment_meta($comment_id, $meta_key, true);
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_comment_likes')) :
|
||||
/**
|
||||
* @param int|WP_Comment $comment Comment.
|
||||
*/
|
||||
function aksrt_get_comment_likes($comment): int {
|
||||
$comment_id = $comment instanceof WP_Comment ? (int) $comment->comment_ID : (int) $comment;
|
||||
if ($comment_id <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return max(0, (int) get_comment_meta($comment_id, 'aksrt_comment_likes', true));
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_is_comment_liked')) :
|
||||
/**
|
||||
* @param int $comment_id Comment ID.
|
||||
*/
|
||||
function aksrt_is_comment_liked($comment_id): bool {
|
||||
$comment_id = (int) $comment_id;
|
||||
if ($comment_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (is_user_logged_in()) {
|
||||
$liked = get_user_meta(get_current_user_id(), '_aksrt_liked_comments', true);
|
||||
$liked = is_array($liked) ? array_map('intval', $liked) : [];
|
||||
return in_array($comment_id, $liked, true);
|
||||
}
|
||||
$cookie = 'aksrt_clike_' . $comment_id;
|
||||
return !empty($_COOKIE[$cookie]) && '1' === $_COOKIE[$cookie];
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_set_comment_like_state')) :
|
||||
/**
|
||||
* @param int $comment_id Comment ID.
|
||||
* @param bool $active Target state.
|
||||
*/
|
||||
function aksrt_set_comment_like_state($comment_id, $active): void {
|
||||
$comment_id = (int) $comment_id;
|
||||
if ($comment_id <= 0) {
|
||||
return;
|
||||
}
|
||||
if (is_user_logged_in()) {
|
||||
$user_id = get_current_user_id();
|
||||
$liked = get_user_meta($user_id, '_aksrt_liked_comments', true);
|
||||
$liked = is_array($liked) ? array_map('intval', $liked) : [];
|
||||
if ($active) {
|
||||
$liked[] = $comment_id;
|
||||
} else {
|
||||
$liked = array_filter($liked, static function ($id) use ($comment_id) {
|
||||
return (int) $id !== $comment_id;
|
||||
});
|
||||
}
|
||||
update_user_meta($user_id, '_aksrt_liked_comments', array_values(array_unique($liked)));
|
||||
return;
|
||||
}
|
||||
$cookie = 'aksrt_clike_' . $comment_id;
|
||||
$path = defined('COOKIEPATH') && COOKIEPATH ? COOKIEPATH : '/';
|
||||
$domain = defined('COOKIE_DOMAIN') ? COOKIE_DOMAIN : '';
|
||||
setcookie($cookie, $active ? '1' : '', $active ? time() + YEAR_IN_SECONDS : time() - HOUR_IN_SECONDS, $path, $domain, is_ssl(), true);
|
||||
if ($active) {
|
||||
$_COOKIE[$cookie] = '1';
|
||||
} else {
|
||||
unset($_COOKIE[$cookie]);
|
||||
}
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_reorder_pinned_comments')) :
|
||||
/**
|
||||
* Float pinned top-level comments to the front of the queried list.
|
||||
*
|
||||
* @param array $comments Comment objects.
|
||||
* @param int $post_id Post ID.
|
||||
* @return array
|
||||
*/
|
||||
function aksrt_reorder_pinned_comments($comments, $post_id) {
|
||||
if (!is_array($comments) || count($comments) < 2) {
|
||||
return $comments;
|
||||
}
|
||||
$pinned = [];
|
||||
$normal = [];
|
||||
foreach ($comments as $comment) {
|
||||
if ($comment instanceof WP_Comment && (int) $comment->comment_parent === 0 && aksrt_is_comment_pinned($comment)) {
|
||||
$pinned[] = $comment;
|
||||
} else {
|
||||
$normal[] = $comment;
|
||||
}
|
||||
}
|
||||
if (empty($pinned)) {
|
||||
return $comments;
|
||||
}
|
||||
return array_merge($pinned, $normal);
|
||||
}
|
||||
endif;
|
||||
|
||||
add_filter('comments_array', 'aksrt_reorder_pinned_comments', 20, 2);
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/**
|
||||
* Comment content filters — zibll-compatible shortcodes.
|
||||
*
|
||||
* Supports [g=key] GIF smilies, [img=url], [code]...[/code].
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_comment_smilie_keys')) :
|
||||
/**
|
||||
* Canonical zibll smilie keys (filename without .gif).
|
||||
*
|
||||
* @return array<int,string>
|
||||
*/
|
||||
function aksrt_comment_smilie_keys(): array {
|
||||
return [
|
||||
'aoman', 'baiyan', 'bishi', 'bizui', 'cahan', 'ciya', 'dabing', 'daku', 'deyi', 'doge',
|
||||
'fadai', 'fanu', 'fendou', 'ganga', 'guzhang', 'haixiu', 'hanxiao', 'zuohengheng', 'zhuakuang',
|
||||
'zhouma', 'zhemo', 'zhayanjian', 'zaijian', 'yun', 'youhengheng', 'yiwen', 'yinxian', 'xu',
|
||||
'xieyanxiao', 'xiaoku', 'xiaojiujie', 'xia', 'wunai', 'wozuimei', 'weixiao', 'weiqu', 'tuosai',
|
||||
'tu', 'touxiao', 'tiaopi', 'shui', 'se', 'saorao', 'qiudale', 'qinqin', 'qiaoda', 'piezui',
|
||||
'penxue', 'nanguo', 'liulei', 'liuhan', 'lenghan', 'leiben', 'kun', 'kuaikule', 'ku', 'koubi',
|
||||
'kelian', 'keai', 'jingya', 'jingxi', 'jingkong', 'jie', 'huaixiao', 'haqian', 'aini', 'OK',
|
||||
'qiang', 'quantou', 'shengli', 'woshou', 'gouyin', 'baoquan', 'aixin', 'bangbangtang',
|
||||
'xiaoyanger', 'xigua', 'hexie', 'pijiu', 'lanqiu', 'juhua', 'hecai', 'haobang', 'caidao',
|
||||
'baojin', 'chi', 'dan', 'kulou', 'shuai', 'shouqiang', 'yangtuo', 'youling',
|
||||
];
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_comment_smilie_map')) :
|
||||
/**
|
||||
* Smilie key => URL map (GIF under assets/img/smilies).
|
||||
*
|
||||
* @return array<string,string>
|
||||
*/
|
||||
function aksrt_comment_smilie_map(): array {
|
||||
static $map = null;
|
||||
if (is_array($map)) {
|
||||
return $map;
|
||||
}
|
||||
$base = trailingslashit(get_template_directory_uri()) . 'assets/img/smilies/';
|
||||
$map = [];
|
||||
foreach (aksrt_comment_smilie_keys() as $key) {
|
||||
$map[$key] = $base . rawurlencode($key) . '.gif';
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_comment_smilies_base_uri')) :
|
||||
/**
|
||||
* Base URI for smilie GIFs (localized to JS).
|
||||
*/
|
||||
function aksrt_comment_smilies_base_uri(): string {
|
||||
return trailingslashit(get_template_directory_uri()) . 'assets/img/smilies/';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_render_comment_smilie')) :
|
||||
/**
|
||||
* Render a single [g=key] smilie as <img class="smilie-icon">.
|
||||
*/
|
||||
function aksrt_render_comment_smilie(string $key): string {
|
||||
// Preserve case for OK.gif; sanitize other keys.
|
||||
$raw = trim($key);
|
||||
if ($raw === '') {
|
||||
return '';
|
||||
}
|
||||
$lookup = $raw === 'OK' ? 'OK' : sanitize_key($raw);
|
||||
// Allow original casing match for files like OK.gif.
|
||||
$map = aksrt_comment_smilie_map();
|
||||
if (!isset($map[$lookup]) && isset($map[$raw])) {
|
||||
$lookup = $raw;
|
||||
}
|
||||
if (!isset($map[$lookup])) {
|
||||
// Fallback: try case-insensitive.
|
||||
foreach ($map as $k => $url) {
|
||||
if (strcasecmp((string) $k, $raw) === 0) {
|
||||
$lookup = (string) $k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isset($map[$lookup])) {
|
||||
return '[g=' . esc_html($raw) . ']';
|
||||
}
|
||||
return sprintf(
|
||||
'<img class="smilie-icon" src="%1$s" alt="%2$s" title="%2$s" width="30" height="30" loading="lazy" decoding="async" data-smilie="%3$s" />',
|
||||
esc_url($map[$lookup]),
|
||||
esc_attr('[' . $lookup . ']'),
|
||||
esc_attr($lookup)
|
||||
);
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_render_comment_content')) :
|
||||
/**
|
||||
* Filter comment body: smilies, images, code blocks (zibll parity).
|
||||
*/
|
||||
function aksrt_render_comment_content(string $content): string {
|
||||
// [img=url]
|
||||
$rendered = preg_replace(
|
||||
'/\[img=(https?:\/\/[^\s\[\]]+)\]/i',
|
||||
'<img class="box-img comment-inline-img" src="$1" alt="' . esc_attr__('评论图片', 'aksrt') . '" loading="lazy" decoding="async" />',
|
||||
$content
|
||||
);
|
||||
// [code]...[/code]
|
||||
$rendered = str_replace(
|
||||
['[code]', '[/code]'],
|
||||
['<pre class="comment-code"><code>', '</code></pre>'],
|
||||
(string) $rendered
|
||||
);
|
||||
// [g=key]
|
||||
$rendered = preg_replace_callback(
|
||||
'/\[g=([^\]]+)\]/i',
|
||||
static function (array $matches): string {
|
||||
return aksrt_render_comment_smilie(trim((string) $matches[1]));
|
||||
},
|
||||
(string) $rendered
|
||||
);
|
||||
|
||||
$allowed = wp_kses_allowed_html('post');
|
||||
if (!isset($allowed['img'])) {
|
||||
$allowed['img'] = [];
|
||||
}
|
||||
$allowed['img'] = array_merge($allowed['img'], [
|
||||
'class' => true,
|
||||
'src' => true,
|
||||
'alt' => true,
|
||||
'title' => true,
|
||||
'width' => true,
|
||||
'height' => true,
|
||||
'loading' => true,
|
||||
'decoding' => true,
|
||||
'data-smilie' => true,
|
||||
'data-src' => true,
|
||||
]);
|
||||
$allowed['pre'] = array_merge($allowed['pre'] ?? [], ['class' => true]);
|
||||
$allowed['code'] = $allowed['code'] ?? [];
|
||||
|
||||
return wp_kses((string) $rendered, $allowed);
|
||||
}
|
||||
endif;
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* Comment form partials — virtual-input, expand toolbar, respond shell helpers.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_get_comment_virtual_input_html')) :
|
||||
/**
|
||||
* Mobile virtual input bar that opens FixedInput #respond.
|
||||
*
|
||||
* @param string $placeholder Placeholder text.
|
||||
* @param string $target CSS selector for FixedInput target.
|
||||
*/
|
||||
function aksrt_get_comment_virtual_input_html(string $placeholder = '', string $target = '#respond'): string {
|
||||
$user_id = get_current_user_id();
|
||||
$avatar = get_avatar($user_id, 36, '', '', ['class' => 'avatar-img']);
|
||||
if ($placeholder === '') {
|
||||
$placeholder = __('说点什么吧...', 'aksrt');
|
||||
}
|
||||
$html = '<div class="flex ac jsb virtual-input hide-pc" fixed-input="' . esc_attr($target) . '">';
|
||||
$html .= '<div class="flex flex1 ac">';
|
||||
$html .= $avatar;
|
||||
$html .= '<div class="text-ellipsis simulation mr10">' . esc_html($placeholder) . '</div>';
|
||||
$html .= '</div>';
|
||||
$html .= '<span class="but c-blue">' . esc_html__('提交', 'aksrt') . '</span>';
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_input_expand_but')) :
|
||||
/**
|
||||
* zibll-style expand toolbar button + dropdown.
|
||||
*
|
||||
* @param string $type smilie|code|image|quick|upload.
|
||||
* @param mixed $arg1 Type-specific.
|
||||
* @param array<string,mixed> $ctx Optional context (upload enabled, phrases).
|
||||
*/
|
||||
function aksrt_get_input_expand_but(string $type = 'smilie', $arg1 = null, array $ctx = []): string {
|
||||
switch ($type) {
|
||||
case 'smilie':
|
||||
$map = function_exists('aksrt_comment_smilie_map') ? aksrt_comment_smilie_map() : [];
|
||||
$icons = '';
|
||||
foreach ($map as $key => $url) {
|
||||
$icons .= '<a class="smilie-icon" href="javascript:;" data-smilie="' . esc_attr((string) $key) . '"><img src="' . esc_url($url) . '" alt="[' . esc_attr((string) $key) . ']" width="28" height="28" loading="lazy" /></a>';
|
||||
}
|
||||
$but = '<a class="but btn-input-expand input-smilie mr6" href="javascript:;" data-toggle-class="open" data-target=".dropdown-smilie-wrap" title="' . esc_attr__('表情', 'aksrt') . '"><i class="fa fa-fw fa-smile-o" aria-hidden="true"></i><span class="hide-sm">' . esc_html__('表情', 'aksrt') . '</span></a>';
|
||||
$drop = '<div class="dropdown-smilie-wrap relative dropup"><div class="dropdown-smilie scroll-y mini-scrollbar dropdown-menu">' . $icons . '</div></div>';
|
||||
return '<span class="dropup relative input-expand-wrap input-expand-smilie">' . $but . $drop . '</span>';
|
||||
|
||||
case 'code':
|
||||
$but = '<a class="but btn-input-expand input-code mr6" href="javascript:;" data-toggle-class="open" data-target=".dropdown-code" title="' . esc_attr__('代码', 'aksrt') . '"><i class="fa fa-fw fa-code" aria-hidden="true"></i><span class="hide-sm">' . esc_html__('代码', 'aksrt') . '</span></a>';
|
||||
$drop = '<div class="dropdown-code dropdown-menu box-body" style="width:280px;">'
|
||||
. '<p class="muted-2-color em09 mb6">' . esc_html__('插入代码块', 'aksrt') . '</p>'
|
||||
. '<textarea class="form-control mb6" rows="4" placeholder="' . esc_attr__('粘贴代码…', 'aksrt') . '"></textarea>'
|
||||
. '<button type="button" class="but c-blue pw-1em input-code-submit">' . esc_html__('插入', 'aksrt') . '</button>'
|
||||
. '</div>';
|
||||
return '<span class="dropup relative input-expand-wrap input-expand-code">' . $but . $drop . '</span>';
|
||||
|
||||
case 'image':
|
||||
$upload = !empty($arg1) || !empty($ctx['upload']);
|
||||
$but = '<a class="but btn-input-expand input-image mr6" href="javascript:;" data-toggle-class="open" data-target=".dropdown-image" title="' . esc_attr__('图片', 'aksrt') . '"><i class="fa fa-fw fa-image" aria-hidden="true"></i><span class="hide-sm">' . esc_html__('图片', 'aksrt') . '</span></a>';
|
||||
$upload_btn = $upload
|
||||
? '<button type="button" class="but hollow mr6 input-image-upload" data-toolbar-action="upload-image">' . esc_html__('上传', 'aksrt') . '</button>'
|
||||
: '';
|
||||
$drop = '<div class="dropdown-image dropdown-menu box-body" style="width:280px;">'
|
||||
. '<p class="muted-2-color em09 mb6">' . esc_html__('插入图片地址', 'aksrt') . '</p>'
|
||||
. '<input type="url" class="form-control mb6 input-image-url" placeholder="https://" />'
|
||||
. $upload_btn
|
||||
. '<button type="button" class="but c-blue pw-1em input-image-submit">' . esc_html__('插入', 'aksrt') . '</button>'
|
||||
. '</div>';
|
||||
return '<span class="dropup relative input-expand-wrap input-expand-image">' . $but . $drop . '</span>';
|
||||
|
||||
case 'quick':
|
||||
$phrases = [];
|
||||
if (is_string($arg1) && $arg1 !== '') {
|
||||
$phrases = array_values(array_filter(array_map('trim', preg_split('/\r\n|\r|\n/', $arg1) ?: [])));
|
||||
} elseif (!empty($ctx['quick_phrases']) && is_array($ctx['quick_phrases'])) {
|
||||
$phrases = $ctx['quick_phrases'];
|
||||
}
|
||||
if (empty($phrases)) {
|
||||
$phrases = [
|
||||
__('不错,学习了', 'aksrt'),
|
||||
__('感谢分享', 'aksrt'),
|
||||
__('期待更新', 'aksrt'),
|
||||
];
|
||||
}
|
||||
$items = '';
|
||||
foreach ($phrases as $p) {
|
||||
$items .= '<a class="quick-reply-item" href="javascript:;" data-text="' . esc_attr((string) $p) . '">' . esc_html((string) $p) . '</a>';
|
||||
}
|
||||
$but = '<a class="but btn-input-expand input-quick mr6" href="javascript:;" data-toggle-class="open" data-target=".dropdown-quick" title="' . esc_attr__('快捷', 'aksrt') . '"><i class="fa fa-fw fa-bolt" aria-hidden="true"></i><span class="hide-sm">' . esc_html__('快捷', 'aksrt') . '</span></a>';
|
||||
$drop = '<div class="dropdown-quick dropdown-menu box-body scroll-y mini-scrollbar" style="max-height:220px;min-width:160px;">' . $items . '</div>';
|
||||
return '<span class="dropup relative input-expand-wrap input-expand-quick">' . $but . $drop . '</span>';
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_render_comment_expand_toolbar')) :
|
||||
/**
|
||||
* Render left-side expand tools based on settings.
|
||||
*
|
||||
* @param array<string,mixed> $ctx Comments context.
|
||||
*/
|
||||
function aksrt_render_comment_expand_toolbar(array $ctx): void {
|
||||
$toolbar = is_array($ctx['toolbar'] ?? null) ? $ctx['toolbar'] : [];
|
||||
$upload = !empty($ctx['upload_enabled']);
|
||||
$phrases = is_array($ctx['quick_phrases'] ?? null) ? $ctx['quick_phrases'] : [];
|
||||
$quick_s = !isset($ctx['quick_s']) || !empty($ctx['quick_s']);
|
||||
|
||||
if (!empty($toolbar['smilie'])) {
|
||||
echo aksrt_get_input_expand_but('smilie'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
if (!empty($toolbar['code'])) {
|
||||
echo aksrt_get_input_expand_but('code'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
if (!empty($toolbar['image'])) {
|
||||
echo aksrt_get_input_expand_but('image', $upload, ['upload' => $upload]); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
if ($quick_s) {
|
||||
echo aksrt_get_input_expand_but('quick', null, ['quick_phrases' => $phrases]); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
}
|
||||
endif;
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
/**
|
||||
* Comment city / IP geo helpers.
|
||||
*
|
||||
* Meta key: aksrt_comment_addr (array|string from IPGeo).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_comment_city_enabled')) :
|
||||
/**
|
||||
* Whether comment city badges are enabled.
|
||||
*/
|
||||
function aksrt_comment_city_enabled(): bool {
|
||||
if (!class_exists('\AKSRT\Settings\Settings')) {
|
||||
return false;
|
||||
}
|
||||
$s = \AKSRT\Settings\Settings::get_comment_display_settings();
|
||||
return !empty($s['city_s']);
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_comment_city_type')) :
|
||||
/**
|
||||
* Display format key for city badge.
|
||||
*/
|
||||
function aksrt_comment_city_type(): string {
|
||||
if (!class_exists('\AKSRT\Settings\Settings')) {
|
||||
return 'province_city';
|
||||
}
|
||||
$s = \AKSRT\Settings\Settings::get_comment_display_settings();
|
||||
$type = sanitize_key((string) ($s['city_type'] ?? 'province_city'));
|
||||
return in_array($type, ['province_city', 'province', 'country_province'], true)
|
||||
? $type
|
||||
: 'province_city';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_store_comment_addr')) :
|
||||
/**
|
||||
* Resolve IP geo and persist on comment.
|
||||
*
|
||||
* @param int $comment_id Comment ID.
|
||||
* @param string|null $ip Optional IP override.
|
||||
*/
|
||||
function aksrt_store_comment_addr(int $comment_id, ?string $ip = null): void {
|
||||
if ($comment_id <= 0 || !aksrt_comment_city_enabled()) {
|
||||
return;
|
||||
}
|
||||
if ($ip === null || $ip === '') {
|
||||
$comment = get_comment($comment_id);
|
||||
$ip = $comment instanceof WP_Comment ? (string) $comment->comment_author_IP : '';
|
||||
}
|
||||
$ip = trim((string) $ip);
|
||||
if ($ip === '' || !class_exists('\AKSRT\Extensions\IPGeo')) {
|
||||
return;
|
||||
}
|
||||
$geo = \AKSRT\Extensions\IPGeo::get_instance()->lookup($ip);
|
||||
if (!is_array($geo)) {
|
||||
return;
|
||||
}
|
||||
// Store compact array.
|
||||
$addr = [
|
||||
'country' => (string) ($geo['country'] ?? ''),
|
||||
'province' => (string) ($geo['province'] ?? ''),
|
||||
'city' => (string) ($geo['city'] ?? ''),
|
||||
];
|
||||
if ($addr['country'] === '' && $addr['province'] === '' && $addr['city'] === '') {
|
||||
return;
|
||||
}
|
||||
update_comment_meta($comment_id, 'aksrt_comment_addr', $addr);
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_comment_addr')) :
|
||||
/**
|
||||
* @param int|WP_Comment $comment Comment.
|
||||
* @return array{country?:string,province?:string,city?:string}
|
||||
*/
|
||||
function aksrt_get_comment_addr($comment): array {
|
||||
$comment_id = $comment instanceof WP_Comment ? (int) $comment->comment_ID : (int) $comment;
|
||||
if ($comment_id <= 0) {
|
||||
return [];
|
||||
}
|
||||
$raw = get_comment_meta($comment_id, 'aksrt_comment_addr', true);
|
||||
return is_array($raw) ? $raw : [];
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_format_comment_city_label')) :
|
||||
/**
|
||||
* Format addr array into display string.
|
||||
*
|
||||
* @param array<string,string> $addr Addr.
|
||||
* @param string $type Format.
|
||||
*/
|
||||
function aksrt_format_comment_city_label(array $addr, string $type = 'province_city'): string {
|
||||
$country = trim((string) ($addr['country'] ?? ''));
|
||||
$province = trim((string) ($addr['province'] ?? ''));
|
||||
$city = trim((string) ($addr['city'] ?? ''));
|
||||
switch ($type) {
|
||||
case 'province':
|
||||
return $province !== '' ? $province : $city;
|
||||
case 'country_province':
|
||||
$parts = array_filter([$country, $province]);
|
||||
return implode(' ', $parts);
|
||||
case 'province_city':
|
||||
default:
|
||||
$parts = array_filter([$province, $city]);
|
||||
if (empty($parts) && $country !== '') {
|
||||
return $country;
|
||||
}
|
||||
return implode(' ', $parts);
|
||||
}
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_comment_city_badge_html')) :
|
||||
/**
|
||||
* HTML badge for comment city (empty string if disabled/missing).
|
||||
*/
|
||||
function aksrt_get_comment_city_badge_html($comment): string {
|
||||
if (!aksrt_comment_city_enabled()) {
|
||||
return '';
|
||||
}
|
||||
$addr = aksrt_get_comment_addr($comment);
|
||||
$label = aksrt_format_comment_city_label($addr, aksrt_comment_city_type());
|
||||
if ($label === '') {
|
||||
return '';
|
||||
}
|
||||
return '<span class="badg badg-sm comment-city-badge" title="' . esc_attr__('IP 属地', 'aksrt') . '">' . esc_html($label) . '</span>';
|
||||
}
|
||||
endif;
|
||||
|
||||
// Persist geo after any successful insert (covers REST + core).
|
||||
add_action('comment_post', static function ($comment_id): void {
|
||||
if (function_exists('aksrt_store_comment_addr')) {
|
||||
aksrt_store_comment_addr((int) $comment_id);
|
||||
}
|
||||
}, 20, 1);
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
/**
|
||||
* Comment list item renderer — zibll DOM contract.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_get_comment_like_html')) :
|
||||
/**
|
||||
* Header like control (zibll places like in comment-header).
|
||||
*/
|
||||
function aksrt_get_comment_like_html($comment, bool $enabled = true): string {
|
||||
if (!$enabled) {
|
||||
return '';
|
||||
}
|
||||
$comment_id = $comment instanceof WP_Comment ? (int) $comment->comment_ID : (int) $comment;
|
||||
if ($comment_id <= 0) {
|
||||
return '';
|
||||
}
|
||||
$like_count = function_exists('aksrt_get_comment_likes') ? aksrt_get_comment_likes($comment_id) : 0;
|
||||
$is_liked = function_exists('aksrt_is_comment_liked') && aksrt_is_comment_liked($comment_id);
|
||||
$classes = 'action action-comment-like muted-2-color flex0 ml10 aksrt-comment__like' . ($is_liked ? ' is-active' : '');
|
||||
$count_attr = $like_count > 0 ? (string) $like_count : '';
|
||||
return sprintf(
|
||||
'<a href="javascript:;" class="%1$s" data-comment-action="like" data-active="%2$s" data-pid="%3$d" aria-pressed="%4$s"><i class="%5$s fa-thumbs-up" aria-hidden="true"></i><count class="aksrt-comment__like-count"%6$s>%7$s</count></a>',
|
||||
esc_attr($classes),
|
||||
$is_liked ? '1' : '0',
|
||||
$comment_id,
|
||||
$is_liked ? 'true' : 'false',
|
||||
$is_liked ? 'fas' : 'far',
|
||||
$like_count > 0 ? '' : ' hidden',
|
||||
esc_html($count_attr)
|
||||
);
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_comments_user_name_html')) :
|
||||
/**
|
||||
* Name row: topping + author link + identity + author/hot badges.
|
||||
*
|
||||
* @param WP_Comment $comment Comment.
|
||||
* @param array<string,mixed> $settings Display settings.
|
||||
*/
|
||||
function aksrt_get_comments_user_name_html(WP_Comment $comment, array $settings = []): string {
|
||||
$user_id = (int) $comment->user_id;
|
||||
$show_author_tag = !isset($settings['author_tag']) || !empty($settings['author_tag']);
|
||||
$is_pinned = function_exists('aksrt_is_comment_pinned') && aksrt_is_comment_pinned($comment);
|
||||
$is_post_author = $user_id > 0 && $user_id === (int) get_post_field('post_author', $comment->comment_post_ID);
|
||||
|
||||
$topping = '';
|
||||
if ($is_pinned) {
|
||||
$topping = '<badge class="badge-topping badge-topping jb-blue mr6 flex0 aksrt-comment__badge aksrt-comment__badge--pinned" title="' . esc_attr__('置顶评论', 'aksrt') . '"><i class="fas fa-thumbtack" aria-hidden="true"></i></badge>';
|
||||
}
|
||||
|
||||
if ($user_id > 0) {
|
||||
$user = get_userdata($user_id);
|
||||
$display = $user instanceof WP_User ? $user->display_name : get_comment_author($comment);
|
||||
$url = get_author_posts_url($user_id);
|
||||
$name = '<a class="text-ellipsis font-bold aksrt-comment__author" href="' . esc_url($url) . '">' . esc_html($display) . '</a>';
|
||||
if (function_exists('aksrt_render_user_identity_chips')) {
|
||||
$name .= aksrt_render_user_identity_chips($user_id, 'comment');
|
||||
}
|
||||
} else {
|
||||
$name = '<b class="mr6 aksrt-comment__author">' . esc_html(get_comment_author($comment)) . '</b>';
|
||||
}
|
||||
|
||||
$badges = '';
|
||||
if ($show_author_tag && $is_post_author) {
|
||||
$badges .= '<span class="badg c-green badg-sm flex0 ml3 aksrt-comment__badge">' . esc_html__('作者', 'aksrt') . '</span>';
|
||||
}
|
||||
if (function_exists('aksrt_is_blog_comment_hot') && aksrt_is_blog_comment_hot($comment)) {
|
||||
$badges .= '<span class="badg c-yellow badg-sm flex0 ml3 aksrt-comment__badge aksrt-comment__badge--hot"><i class="fas fa-fire" aria-hidden="true"></i> ' . esc_html__('神评', 'aksrt') . '</span>';
|
||||
}
|
||||
|
||||
return '<name class="flex ac flex1">' . $topping . $name . $badges . '</name>';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_comment_header_html')) :
|
||||
/**
|
||||
* @param WP_Comment $comment Comment.
|
||||
* @param array<string,mixed> $settings Settings.
|
||||
*/
|
||||
function aksrt_get_comment_header_html(WP_Comment $comment, array $settings = []): string {
|
||||
$avatar_size = max(24, (int) ($settings['avatar_size'] ?? 45));
|
||||
$like_enabled = !isset($settings['like_enabled']) || !empty($settings['like_enabled']);
|
||||
$avatar = get_avatar($comment, $avatar_size, '', '', ['class' => 'avatar-img comt-avatar']);
|
||||
$name = aksrt_get_comments_user_name_html($comment, $settings);
|
||||
$like = aksrt_get_comment_like_html($comment, $like_enabled);
|
||||
$inner = '<div class="author-box flex ac">' . $avatar . $name . $like . '</div>';
|
||||
return '<div class="comment-header mb10 aksrt-comment__header">' . $inner . '</div>';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_comments_action_dropdown_html')) :
|
||||
/**
|
||||
* More menu: pin / edit / approve / delete (REST-driven via data-comment-action).
|
||||
*
|
||||
* @param WP_Comment $comment Comment.
|
||||
* @param int $depth Depth.
|
||||
*/
|
||||
function aksrt_get_comments_action_dropdown_html(WP_Comment $comment, int $depth = 0): string {
|
||||
$perms = function_exists('aksrt_comment_action_permissions')
|
||||
? aksrt_comment_action_permissions($comment)
|
||||
: ['can_edit' => false, 'can_delete' => false, 'can_moderate' => false, 'can_pin' => false];
|
||||
$is_pinned = function_exists('aksrt_is_comment_pinned') && aksrt_is_comment_pinned($comment);
|
||||
$is_pending = '0' === (string) $comment->comment_approved;
|
||||
$is_spam = 'spam' === (string) $comment->comment_approved;
|
||||
$lists = '';
|
||||
|
||||
if (!empty($perms['can_pin']) && (int) $comment->comment_parent === 0) {
|
||||
$label = $is_pinned ? __('取消置顶', 'aksrt') : __('设置置顶', 'aksrt');
|
||||
$lists .= '<li><a href="javascript:;" class="comment-topping-link" data-comment-action="pin" data-pinned="' . ($is_pinned ? '1' : '0') . '"><i class="fas fa-thumbtack fa-fw mr10" aria-hidden="true"></i>' . esc_html($label) . '</a></li>';
|
||||
}
|
||||
if (!empty($perms['can_edit'])) {
|
||||
$lists .= '<li><a href="javascript:;" class="comment-edit-link" data-comment-action="edit" data-commentid="' . (int) $comment->comment_ID . '" data-postid="' . (int) $comment->comment_post_ID . '"><i class="fa fa-edit fa-fw mr10" aria-hidden="true"></i>' . esc_html__('编辑', 'aksrt') . '</a></li>';
|
||||
}
|
||||
if (!empty($perms['can_moderate'])) {
|
||||
if ($is_pending) {
|
||||
$lists .= '<li><a href="javascript:;" class="comment-approve-link approve" data-comment-action="approve"><i class="fas fa-check fa-fw mr10" aria-hidden="true"></i><text>' . esc_html__('批准', 'aksrt') . '</text></a></li>';
|
||||
} elseif (!$is_spam) {
|
||||
$lists .= '<li><a href="javascript:;" class="comment-approve-link unapprove" data-comment-action="hold"><i class="fas fa-eye-slash fa-fw mr10" aria-hidden="true"></i><text>' . esc_html__('驳回', 'aksrt') . '</text></a></li>';
|
||||
}
|
||||
$lists .= '<li><a href="javascript:;" class="comment-spam-link" data-comment-action="spam"><i class="fas fa-ban fa-fw mr10" aria-hidden="true"></i>' . esc_html__('垃圾', 'aksrt') . '</a></li>';
|
||||
}
|
||||
if (!empty($perms['can_delete'])) {
|
||||
$lists .= '<li><a href="javascript:;" class="comment-trash-link c-red" data-comment-action="delete" data-commentid="' . (int) $comment->comment_ID . '"><i class="fa fa-trash-o fa-fw mr10" aria-hidden="true"></i>' . esc_html__('删除', 'aksrt') . '</a></li>';
|
||||
}
|
||||
|
||||
if ($lists === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<span class="dropdown drop-fixed-sm padding-6 comment-action-dropdown">'
|
||||
. '<a href="javascript:;" class="muted-color padding-6" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="' . esc_attr__('更多操作', 'aksrt') . '"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></a>'
|
||||
. '<ul class="dropdown-menu">' . $lists . '</ul>'
|
||||
. '</span>';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_comment_footer_html')) :
|
||||
/**
|
||||
* comt-meta: time-ago · city · @parent · approve · reply · dropdown.
|
||||
*
|
||||
* @param WP_Comment $comment Comment.
|
||||
* @param array $args wp_list_comments args.
|
||||
* @param int $depth Depth.
|
||||
* @param array<string,mixed> $settings Settings.
|
||||
*/
|
||||
function aksrt_get_comment_footer_html(WP_Comment $comment, array $args, int $depth, array $settings = []): string {
|
||||
$is_pending = '0' === (string) $comment->comment_approved;
|
||||
$allow_reply = !empty($settings['allow_reply']);
|
||||
|
||||
// Time ago.
|
||||
$absolute = date_i18n('Y-m-d H:i:s', strtotime((string) $comment->comment_date));
|
||||
$ago = function_exists('aksrt_time_ago')
|
||||
? aksrt_time_ago((string) $comment->comment_date)
|
||||
: human_time_diff(strtotime((string) $comment->comment_date), current_time('timestamp')) . __('前', 'aksrt');
|
||||
$time_html = '<span class="comt-author" title="' . esc_attr($absolute) . '">' . esc_html($ago) . '</span>';
|
||||
|
||||
// City.
|
||||
$city_html = function_exists('aksrt_get_comment_city_badge_html')
|
||||
? aksrt_get_comment_city_badge_html($comment)
|
||||
: '';
|
||||
|
||||
// @ parent.
|
||||
$parent_html = '';
|
||||
if ((int) $comment->comment_parent > 0) {
|
||||
$parent_author = get_comment_author((int) $comment->comment_parent);
|
||||
$parent_html = '<span>@<a rel="nofollow" class="url" href="javascript:;" data-scroll-to="#comment-' . (int) $comment->comment_parent . '">' . esc_html($parent_author) . '</a></span>';
|
||||
}
|
||||
|
||||
// Pending badge.
|
||||
$approve_html = '<span class="badge-approve">';
|
||||
if ($is_pending) {
|
||||
$approve_html .= '<span class="badg c-red badg-sm">' . esc_html__('待审核', 'aksrt') . '</span>';
|
||||
}
|
||||
$approve_html .= '</span>';
|
||||
|
||||
// Reply.
|
||||
$reply_html = '';
|
||||
if ($allow_reply && !$is_pending && $depth > 0) {
|
||||
$max_depth = (int) ($args['max_depth'] ?? get_option('thread_comments_depth', 5));
|
||||
$link = get_comment_reply_link(
|
||||
array_merge(
|
||||
$args,
|
||||
[
|
||||
'add_below' => 'div-comment',
|
||||
'reply_text' => __('回复', 'aksrt'),
|
||||
'depth' => $depth,
|
||||
'max_depth' => $max_depth,
|
||||
]
|
||||
),
|
||||
$comment
|
||||
);
|
||||
if ($link) {
|
||||
// Ensure data-commentid for moveForm path.
|
||||
if (strpos($link, 'data-commentid') === false) {
|
||||
$link = str_replace(
|
||||
'comment-reply-link',
|
||||
'comment-reply-link" data-commentid="' . (int) $comment->comment_ID . '" data-postid="' . (int) $comment->comment_post_ID,
|
||||
$link
|
||||
);
|
||||
}
|
||||
$reply_html = '<span class="reply-link">' . $link . '</span>';
|
||||
}
|
||||
}
|
||||
|
||||
$actions = aksrt_get_comments_action_dropdown_html($comment, $depth);
|
||||
$info = $time_html . $city_html . $parent_html . $approve_html . $reply_html;
|
||||
|
||||
return '<div class="comt-meta muted-2-color aksrt-comment__actions">' . $info . $actions . '</div>';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_comment_callback')) :
|
||||
/**
|
||||
* Canonical comment list item renderer (zibll structure).
|
||||
*
|
||||
* @param WP_Comment $comment Comment.
|
||||
* @param array $args Args.
|
||||
* @param int $depth Depth.
|
||||
*/
|
||||
function aksrt_comment_callback($comment, $args, $depth) {
|
||||
$GLOBALS['comment'] = $comment;
|
||||
$settings = get_query_var('aksrt_comment_settings', []);
|
||||
$settings = is_array($settings) ? $settings : [];
|
||||
if (empty($settings) && class_exists('\AKSRT\Settings\Settings')) {
|
||||
$settings = \AKSRT\Settings\Settings::get_comment_display_settings();
|
||||
}
|
||||
|
||||
$comment_id = (int) $comment->comment_ID;
|
||||
$depth_class = 'aksrt-depth-' . min((int) ($settings['nested_depth'] ?? 3), (int) $depth);
|
||||
$is_pinned = function_exists('aksrt_is_comment_pinned') && aksrt_is_comment_pinned($comment);
|
||||
$is_pending = '0' === (string) $comment->comment_approved;
|
||||
$li_classes = trim($depth_class . ($is_pinned ? ' aksrt-comment--pinned' : ''));
|
||||
|
||||
$header = aksrt_get_comment_header_html($comment, $settings);
|
||||
$footer = aksrt_get_comment_footer_html($comment, $args, (int) $depth, $settings);
|
||||
|
||||
if ($is_pending) {
|
||||
$body = '<p class="awaiting-moderation">' . esc_html__('您的评论正在等待审核。', 'aksrt') . '</p>';
|
||||
} else {
|
||||
$body = function_exists('aksrt_render_comment_content')
|
||||
? aksrt_render_comment_content((string) get_comment_text($comment))
|
||||
: wp_kses_post((string) get_comment_text($comment));
|
||||
}
|
||||
|
||||
echo '<li ';
|
||||
comment_class('aksrt-comment ' . $li_classes);
|
||||
echo ' id="comment-' . esc_attr((string) $comment_id) . '" data-comment-id="' . esc_attr((string) $comment_id) . '" data-comment-status="' . esc_attr((string) $comment->comment_approved) . '">';
|
||||
echo '<ul class="list-inline">';
|
||||
echo '<li class="comt-main" id="div-comment-' . esc_attr((string) $comment_id) . '">';
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- composed helpers escape.
|
||||
echo $header;
|
||||
echo '<div class="comment-footer">';
|
||||
echo '<div class="mb10 comment-content aksrt-comment__text" id="comment-content-' . esc_attr((string) $comment_id) . '">' . $body . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $footer;
|
||||
echo '</div>';
|
||||
echo '</li>';
|
||||
echo '</ul>';
|
||||
// Closing </li> is emitted by Walker_Comment after children.
|
||||
}
|
||||
endif;
|
||||
Reference in New Issue
Block a user