Files
wordpress-theme-aksrt/inc/helpers/comments/item.php
T
kate522 913958820c fix(comments): open the action dropdown with theme-owned delegation
The comment three-dot menu relied on the legacy data-toggle=dropdown
delegation living in comment-move-form.js; when that bundle chain does
not evaluate (stale cache or an earlier top-level error), the menu
appears dead. Move the comment action dropdown to its own top-level
document delegation in comments.js (data-aksrt-comment-drop), keep the
.open-driven CSS contract including the mobile drop-fixed-sm sheet,
sync aria-expanded, close on outside click / Escape / menu item click,
and stay idempotent for AJAX-injected comments.
2026-09-20 13:55:03 +08:00

363 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Comment list item renderer — legacy DOM contract (1:1).
*
* Mirrors legacy / legacy / legacy
* from the legacy comments-list module.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
if (!function_exists('aksrt_get_comment_like_html')) :
/**
* Header like control (legacy: legacy).
*
* @param WP_Comment $comment Comment.
* @param bool $enabled Like enabled.
* @return string
*/
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') ? (int) 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' . ($is_liked ? ' actived' : '');
$icon = function_exists('aksrt_get_comment_icon') ? aksrt_get_comment_icon('like', 'icon mr3') : '';
return sprintf(
'<a href="javascript:;" data-comment-action="like" data-action="comment_like" class="%1$s" data-pid="%2$d" data-active="%3$s" aria-pressed="%4$s">%5$s<text></text><count>%6$s</count></a>',
esc_attr($classes),
$comment_id,
$is_liked ? '1' : '0',
$is_liked ? 'true' : 'false',
$icon, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- fixed sprite markup.
esc_html((string) $like_count)
);
}
endif;
if (!function_exists('aksrt_get_comment_avatar_box')) :
/**
* Comment avatar box (legacy: legacy).
*
* @param WP_Comment $comment Comment.
* @param string $class Classes for the avatar wrapper.
* @param bool $link Wrap in a link when a target URL exists.
* @return string
*/
function aksrt_get_comment_avatar_box(WP_Comment $comment, string $class = 'avatar-img comt-avatar', bool $link = true): string {
$user_id = (int) $comment->user_id;
$size = 45;
$avatar = $user_id > 0
? get_avatar($user_id, $size, '', '', ['class' => 'avatar'])
: get_avatar($comment->comment_author_email, $size, '', '', ['class' => 'avatar']);
$html = '<span class="' . esc_attr($class) . '">' . $avatar . '</span>';
if (!$link) {
return $html;
}
if ($user_id > 0) {
$url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($user_id) : get_author_posts_url($user_id);
return '<a href="' . esc_url($url) . '">' . $html . '</a>';
}
if (!empty($comment->comment_author_url)) {
return '<a rel="nofollow" href="' . esc_url($comment->comment_author_url) . '">' . $html . '</a>';
}
return $html;
}
endif;
if (!function_exists('aksrt_get_comments_user_name_html')) :
/**
* Name row (legacy: legacy) — topping + author + badges.
*
* @param WP_Comment $comment Comment.
* @param array<string,mixed> $settings Display settings.
* @return string
*/
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_icon = function_exists('aksrt_get_comment_icon') ? aksrt_get_comment_icon('topping') : '';
$topping = '<badge class="badge-topping jb-blue mr6 flex0" title="' . esc_attr__('置顶评论', 'aksrt') . '" data-toggle="tooltip">' . $topping_icon . '</badge>';
}
$user_name = '';
if ($user_id > 0) {
$user = get_userdata($user_id);
if ($user instanceof WP_User) {
$url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($user_id) : get_author_posts_url($user_id);
$user_name = '<a class="text-ellipsis font-bold" href="' . esc_url($url) . '">' . esc_html($user->display_name) . '</a>';
if (function_exists('aksrt_render_user_identity_chips')) {
$user_name .= aksrt_render_user_identity_chips($user_id, 'comment');
}
}
} elseif (!empty($comment->comment_author_url)) {
$user_name = '<a class="text-ellipsis font-bold" rel="nofollow" href="' . esc_url($comment->comment_author_url) . '">' . esc_html($comment->comment_author) . '</a>';
}
$user_name = $user_name !== '' ? $user_name : '<b class="mr6 text-ellipsis">' . esc_html($comment->comment_author) . '</b>';
$badge = '';
if ($show_author_tag && $is_post_author) {
$badge .= '<span class="badg c-green badg-sm flex0 ml3">' . esc_html__('作者', 'aksrt') . '</span>';
}
return '<name class="flex ac flex1">' . $topping . $user_name . $badge . '</name>';
}
endif;
if (!function_exists('aksrt_get_comment_header_html')) :
/**
* Comment header (legacy: legacy).
*
* @param WP_Comment $comment Comment.
* @param array<string,mixed> $settings Settings.
* @return string
*/
function aksrt_get_comment_header_html(WP_Comment $comment, array $settings = []): string {
$like_enabled = !isset($settings['like_enabled']) || !empty($settings['like_enabled']);
$avatar = aksrt_get_comment_avatar_box($comment, 'avatar-img comt-avatar');
$name = aksrt_get_comments_user_name_html($comment, $settings);
$like = aksrt_get_comment_like_html($comment, $like_enabled);
$html = '<div class="author-box flex ac">';
$html .= $avatar;
$html .= $name;
$html .= $like;
$html .= '</div>';
return '<div class="comment-header mb10">' . $html . '</div>';
}
endif;
if (!function_exists('aksrt_get_comment_report_html')) :
/**
* 评论举报入口 — 登录用户可举报他人评论(前台走 report.js 的 data-aksrt-report 契约)。
*
* 举报位于“回复”右侧三点下拉菜单内(参考主题同位置)。
*
* @param WP_Comment $comment Comment.
* @return string
*/
function aksrt_get_comment_report_html(WP_Comment $comment): string {
if (!is_user_logged_in() || !class_exists('\AKSRT\Settings\Settings')) {
return '';
}
$author_id = (int) $comment->user_id;
$reporter_id = get_current_user_id();
if ($author_id > 0 && $author_id === $reporter_id) {
return '';
}
$settings = \AKSRT\Settings\Settings::get_user_identity_settings();
if (empty($settings['report']['enabled'])) {
return '';
}
if (function_exists('aksrt_user_can') && !aksrt_user_can('user_report', $reporter_id, $author_id)) {
return '';
}
$report_payload = wp_json_encode([
'target_type' => 'comment',
'target_id' => (int) $comment->comment_ID,
'target_name' => get_comment_author($comment),
'target_avatar' => get_avatar_url((int) $comment->user_id > 0 ? (int) $comment->user_id : 0, ['size' => 40]) ?: '',
]);
return '<li><a class="comment-report-link" href="javascript:;" data-aksrt-report="' . esc_attr((string) $report_payload) . '"><i class="fa fa-exclamation-triangle mr10 fa-fw c-red" aria-hidden="true"></i>' . esc_html__('举报', 'aksrt') . '</a></li>';
}
endif;
if (!function_exists('aksrt_get_comments_action_dropdown_html')) :
/**
* More-actions menu (legacy: legacy) — pin / edit / approve / delete.
*
* @param WP_Comment $comment Comment.
* @param int $depth Depth.
* @return string
*/
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;
$lists = '';
if (!empty($perms['can_pin']) && (int) $comment->comment_parent === 0) {
$label = $is_pinned ? __('取消置顶', 'aksrt') : __('设置置顶', 'aksrt');
// 不带 legacy 的 wp-ajax-submit 类:vendor main.min.js 会全局代理该类并向
// admin-ajax 发空 action 请求(400 "0"),置顶走 data-comment-action REST。
$lists .= '<li><a class="comment-topping" data-comment-action="pin" data-pinned="' . ($is_pinned ? '1' : '0') . '" href="javascript:;"><i class="fa fas fa-thumbtack mr10 fa-fw" aria-hidden="true"></i>' . esc_html($label) . '</a></li>';
}
if (!empty($perms['can_edit'])) {
$lists .= '<li><a class="comment-edit-link" data-commentid="' . (int) $comment->comment_ID . '" data-postid="' . (int) $comment->comment_post_ID . '" href="javascript:;"><i class="fa fas fa-edit mr10 fa-fw" aria-hidden="true"></i>' . esc_html__('编辑', 'aksrt') . '</a></li>';
}
if (!empty($perms['can_moderate'])) {
$text = $is_pending ? __('批准', 'aksrt') : __('驳回', 'aksrt');
$class = $is_pending ? 'approve' : 'unapprove';
$action = $is_pending ? 'approve' : 'hold';
$approve_icon = function_exists('aksrt_get_comment_icon') ? aksrt_get_comment_icon('approve', 'mr10 icon fa-fw') : '';
$lists .= '<li><a class="comment-approve-link ' . $class . '" data-comment-action="' . $action . '" data-commentid="' . (int) $comment->comment_ID . '" data-postid="' . (int) $comment->comment_post_ID . '" href="javascript:;">' . $approve_icon . '<text>' . esc_html($text) . '</text></a></li>';
}
if (!empty($perms['can_delete'])) {
$lists .= '<li><a class="comment-trash-link c-red" data-comment-action="delete" data-commentid="' . (int) $comment->comment_ID . '" data-postid="' . (int) $comment->comment_post_ID . '" href="javascript:;"><i class="fa far fa-trash-alt mr10 fa-fw" aria-hidden="true"></i>' . esc_html__('删除', 'aksrt') . '</a></li>';
}
$lists .= aksrt_get_comment_report_html($comment);
if ($lists === '') {
return '';
}
$menu_icon = function_exists('aksrt_get_comment_icon') ? aksrt_get_comment_icon('menu_2') : '';
// 交互由主题自有 comments.js 委托处理(data-aksrt-comment-drop),
// 不用 vendor 的 data-toggle="dropdown" 遗留委托:该链路在部分环境
// 未注册/被早退,导致三点菜单点不开。open 类驱动的 CSS 契约不变。
$icon_a = '<a href="javascript:;" class="muted-color padding-6" data-aksrt-comment-drop aria-haspopup="true" aria-expanded="false" aria-label="' . esc_attr__('更多操作', 'aksrt') . '">' . $menu_icon . '</a>';
return '<span class="dropdown drop-fixed-sm padding-6">' . $icon_a . '<ul class="dropdown-menu">' . $lists . '</ul></span>';
}
endif;
if (!function_exists('aksrt_get_comment_footer_html')) :
/**
* comt-meta row (legacy: legacy).
*
* @param WP_Comment $comment Comment.
* @param array $args wp_list_comments args.
* @param int $depth Depth.
* @param array<string,mixed> $settings Settings.
* @return string
*/
function aksrt_get_comment_footer_html(WP_Comment $comment, array $args, int $depth, array $settings = []): string {
$is_pending = '0' === (string) $comment->comment_approved;
$is_mobile = wp_is_mobile();
$badg_approve = '';
if ($is_pending) {
$badg_approve = '<span class="badg c-red badg-sm">' . esc_html__('待审核', 'aksrt') . '</span>';
}
$badg_approve = '<span class="badge-approve">' . $badg_approve . '</span>';
$absolute = date_i18n(__('Y年m月d日 H:i:s', 'aksrt'), 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"' . ($is_mobile ? ' data-toggle="tooltip"' : '') . ' title="' . esc_attr($absolute) . '">' . esc_html($ago) . '</span>';
$city_html = function_exists('aksrt_get_comment_city_badge_html')
? aksrt_get_comment_city_badge_html($comment)
: '';
$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>';
}
$reply_html = '';
if (!$is_pending && $depth > 0) {
$max_depth = (int) ($args['max_depth'] ?? get_option('thread_comments_depth', 5));
$link = get_comment_reply_link(
[
'add_below' => 'div-comment',
'reply_text' => __('回复', 'aksrt'),
'login_text' => __('回复', 'aksrt'),
'depth' => $depth,
'max_depth' => $max_depth,
],
$comment
);
if ($link) {
// 去掉 WP 内联 onclick(addComment.moveForm),回复由 comment-move-form.js 事件委托处理。
$link = preg_replace('/ onclick="[^"]*"/i', '', $link);
// legacy 契约:aria-label 换成 tooltip 触发属性。
$link = preg_replace('# aria-label=#', $is_mobile ? ' title=' : ' data-toggle="tooltip" title=', $link);
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 . $badg_approve . $reply_html;
return '<div class="comt-meta muted-2-color">' . $info . $actions . '</div>';
}
endif;
if (!function_exists('aksrt_get_comments_list_html')) :
/**
* Single comment item HTML (legacy: legacy).
*
* @param WP_Comment $comment Comment.
* @param array $args Args.
* @param int $depth Depth.
* @param array $settings Settings.
* @return string
*/
function aksrt_get_comments_list_html(WP_Comment $comment, array $args = [], int $depth = 0, array $settings = []): string {
$comment_id = (int) $comment->comment_ID;
$con = function_exists('aksrt_render_comment_content')
? aksrt_render_comment_content((string) get_comment_text($comment))
: wp_kses_post((string) get_comment_text($comment));
$header = aksrt_get_comment_header_html($comment, $settings);
$footer = aksrt_get_comment_footer_html($comment, $args, $depth, $settings);
$html = '<li ' . comment_class('', $comment, null, false) . ' id="comment-' . esc_attr((string) $comment_id) . '">';
$html .= '<ul class="list-inline">';
$html .= '<li class="comt-main" id="div-comment-' . esc_attr((string) $comment_id) . '">';
$html .= $header;
$html .= '<div class="comment-footer">';
$html .= '<div class="mb10 comment-content" id="comment-content-' . esc_attr((string) $comment_id) . '">' . $con . '</div>';
$html .= $footer;
$html .= '</div>';
$html .= '</li>';
$html .= '</ul>';
return $html;
}
endif;
if (!function_exists('aksrt_comment_callback')) :
/**
* Canonical wp_list_comments callback (legacy: legacy).
*
* The closing </li> is emitted by the walker after children.
*
* @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();
}
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- composed helpers escape.
echo aksrt_get_comments_list_html($comment, is_array($args) ? $args : [], (int) $depth, $settings);
}
endif;