Files
wordpress-theme-aksrt/inc/helpers/comments/item.php
T
kate522 0afead968a fix(icons): emit FA5 only and upgrade legacy FA4 icon values
The theme is FA5-only, but aksrt_icon_markup() prefixed bare glyphs with
the FA4 style token, so stored icon-picker values rendered FA4 markup,
and three runtime strings still carried the FA4 double-fa form (report
modal header and submit button, comment report menu item). That left the
navigation icon contract failing at 11/12.

- aksrt_icon_markup() normalizes bare glyphs, legacy FA4 values and
  style-only values to "fa fas fa-<glyph>", keeping the FA5 brand family
  (fa fab) and trailing classes intact.
- aksrt_normalize_icon_key() strips the legacy FA4 prefix so stored
  values resolve against the brand-icon catalog again.
- report.js and the comment report item emit FA5 markup.
- An icon-picker contract test locks the upgrade behaviour.

Verification: 338 widget-system, 12 navigation, 28 admin and 13
front-submit contract tests pass; normalization also exercised against
legacy, FA5 and brand inputs.
2026-09-20 18:48:22 +08:00

365 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 fas 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>';
}
// 末项(参考主题 comments_action_lists 钩子顺序):管理员 → 禁封用户;
// 否则 → 举报(登录且非本人且举报开启时,既有评论举报契约)。
$lists .= aksrt_get_comment_ban_or_report_item($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;