feat(ticket): redesign native ticket admin page UI/UX

- Fix empty status/department selects in the detail panel (printf leak)
- Replace dead 代客提交 button with a working collapsible create form
- Error-aware notices; redirect keeps filters and the open ticket
- Status views with counts, incl. 未关闭 default and 已关闭 view
- Two-column detail: conversation + attachments + prereplies | info,
  status/department/assignee select, internal notes; closed = read-only
- Split detail panel into UsersMenusTicketsDetail trait; page-scoped
  tickets.css / admin-tickets.js replace inline styles
- AdminListPagination accepts parent file so users.php keeps menu highlight
- Drop native loading="lazy" per lazyload contract

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Fable 5 committed 2026-10-27 14:04:48 +08:00
1 parent b927b9ec7c
commit b74381cfa2
9 files changed
+885 -278

No files matched your search

+199 -203
View File
@@ -1,6 +1,8 @@
<?php
/**
* 工单管理后台页 — 工单列表 Tab 渲染。
* 工单管理后台页 — 代客提交卡片、状态视图、筛选与工单列表。
*
* 详情面板见 UsersMenusTicketsDetail;写操作见 UsersMenusTicketsCenter。
*
* @package AKSRT
*/
@@ -11,74 +13,78 @@ defined('ABSPATH') || exit;
trait UsersMenusTicketsList {
/** 代客提交:原生 <details> 折叠,失败回跳时自动展开。 */
private function render_ticket_create_form(): void {
$types = \AKSRT\Ticket\TicketTypeService::all();
$failed = isset($_GET['aksrt_ticket_msg']) && sanitize_key((string) wp_unslash($_GET['aksrt_ticket_msg'])) === 'create_failed';
echo '<details class="aksrt-tickets-admin__create"' . ($failed ? ' open' : '') . '>';
echo '<summary><span class="dashicons dashicons-plus-alt2" aria-hidden="true"></span>' . esc_html__('代客提交工单', 'aksrt') . '</summary>';
if ($types === []) {
printf(
'<p class="aksrt-tickets-admin__create-empty">%s <a href="%s">%s</a></p></details>',
esc_html__('尚未配置工单部门,无法创建工单。', 'aksrt'),
esc_url(admin_url('admin.php?page=aksrt-settings#/users/ticket')),
esc_html__('前往配置', 'aksrt')
);
return;
}
echo $this->ticket_form_open('create', 0, 'aksrt-tickets-admin__create-form'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
wp_nonce_field('aksrt_ticket_create_0');
echo '<div class="aksrt-tickets-admin__create-grid">';
printf(
'<label><span>%s</span><input type="number" name="user_id" min="1" required class="regular-text" placeholder="%s" /></label>',
esc_html__('用户 ID', 'aksrt'),
esc_attr__('工单归属用户', 'aksrt')
);
echo '<label><span>' . esc_html__('部门', 'aksrt') . '</span><select name="ticket_type_id" required>';
foreach ($types as $row) {
printf('<option value="%d">%s</option>', (int) $row['id'], esc_html((string) $row['name']));
}
echo '</select></label>';
printf(
'<label class="is-wide"><span>%s</span><input type="text" name="title" maxlength="200" required class="large-text" /></label>',
esc_html__('标题', 'aksrt')
);
if (\AKSRT\Ticket\TicketSettings::order_link_enabled()) {
printf(
'<label class="is-wide"><span>%s</span><input type="text" name="order_number" class="regular-text" placeholder="%s" /></label>',
esc_html__('关联订单', 'aksrt'),
esc_attr__('可选', 'aksrt')
);
}
printf(
'<label class="is-wide"><span>%s</span><textarea name="content" rows="4" maxlength="%d" required class="large-text"></textarea></label>',
esc_html__('问题描述', 'aksrt'),
(int) self::TICKET_CONTENT_MAX
);
echo '</div>';
printf(
'<p class="aksrt-tickets-admin__create-foot"><span class="description">%s</span><button type="submit" class="button button-primary">%s</button></p>',
esc_html__('创建后状态为「已回复」并由你跟进,用户会收到通知。', 'aksrt'),
esc_html__('创建工单', 'aksrt')
);
echo '</form></details>';
}
private function render_ticket_list_tab(): void {
$status = isset($_GET['status']) ? sanitize_key((string) wp_unslash($_GET['status'])) : '';
$type_id = isset($_GET['ticket_type_id']) ? absint($_GET['ticket_type_id']) : 0;
$s = isset($_GET['s']) ? sanitize_text_field((string) wp_unslash($_GET['s'])) : '';
$paged = isset($_GET['paged']) ? max(1, (int) $_GET['paged']) : 1;
$open_id = isset($_GET['id']) ? absint($_GET['id']) : 0;
$statuses = \AKSRT\Ticket\TicketStatusService::all();
$types = \AKSRT\Ticket\TicketTypeService::all();
$filters = array_filter([
'status' => $status,
'ticket_type_id' => $type_id,
's' => $s,
], static fn($value): bool => $value !== '' && $value !== 0);
$view_url = $this->ticket_page_url(array_merge($filters, $paged > 1 ? ['paged' => $paged] : []));
$base_url = remove_query_arg(['status', 'ticket_type_id', 's', 'paged', 'id'], $this->ticket_page_url());
// 状态 subsubsub(完结态单独放最后,默认视图不显示已关闭工单)
echo '<ul class="subsubsub">';
$links = ['' => __('全部', 'aksrt')];
foreach ($statuses as $row) {
if (!empty($row['is_closed'])) {
continue;
}
$links[(string) $row['status_key']] = (string) $row['name'];
}
$i = 0;
foreach ($links as $key => $label) {
$url = $key === '' ? $base_url : add_query_arg('status', $key, $base_url);
$sep = ++$i < count($links) ? ' |' : '';
printf(
'<li><a class="%s" href="%s">%s</a>%s</li> ',
$status === $key ? 'current' : '',
esc_url($url),
esc_html($label),
esc_html($sep)
);
}
echo '</ul>';
// 筛选 + 搜索
echo '<form method="get" class="search-form" style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin:10px 0;">';
echo '<input type="hidden" name="page" value="' . esc_attr(self::TICKET_PAGE_SLUG) . '" />';
echo '<select name="status">';
echo '<option value="">' . esc_html__('全部状态', 'aksrt') . '</option>';
foreach ($statuses as $row) {
printf(
'<option value="%s"%s>%s</option>',
esc_attr((string) $row['status_key']),
selected($status, (string) $row['status_key'], false),
esc_html((string) $row['name'])
);
}
echo '</select> ';
echo '<select name="ticket_type_id">';
echo '<option value="">' . esc_html__('全部部门', 'aksrt') . '</option>';
foreach ($types as $row) {
printf(
'<option value="%d"%s>%s</option>',
(int) $row['id'],
selected($type_id, (int) $row['id'], false),
esc_html((string) $row['name'])
);
}
echo '</select> ';
echo '<input type="search" name="s" value="' . esc_attr($s) . '" placeholder="' . esc_attr__('工单号 / 标题', 'aksrt') . '" /> ';
submit_button(__('筛选', 'aksrt'), 'primary', '', false);
echo '</form>';
// 新建工单入口
printf(
'<p><button type="button" class="button" data-aksrt-ticket-new>%s</button></p>',
esc_html__('代客提交工单', 'aksrt')
);
$this->render_ticket_status_views($status);
$this->render_ticket_filter_form($status, $type_id, $s);
$repo = \AKSRT\Ticket\TicketRepository::get_instance();
$result = $repo->paginate([
@@ -90,45 +96,31 @@ trait UsersMenusTicketsList {
], true);
$items = $result['items'];
$total = $result['total'];
$open_id = isset($_GET['id']) ? absint($_GET['id']) : 0;
echo '<div class="table-box" style="overflow-y:auto;width:100%;">';
echo '<table class="widefat fixed striped posts" style="min-width:960px;">';
echo '<div class="aksrt-tickets-admin__table-wrap">';
echo '<table class="widefat striped aksrt-tickets-admin__table">';
echo '<thead><tr>';
echo '<th width="6%">' . esc_html__('状态', 'aksrt') . '</th>';
echo '<th width="10%">' . esc_html__('工单号', 'aksrt') . '</th>';
echo '<th width="22%">' . esc_html__('标题', 'aksrt') . '</th>';
echo '<th width="10%">' . esc_html__('部门', 'aksrt') . '</th>';
echo '<th width="10%">' . esc_html__('用户', 'aksrt') . '</th>';
echo '<th width="10%">' . esc_html__('跟进人', 'aksrt') . '</th>';
echo '<th width="12%">' . esc_html__('提交时间', 'aksrt') . '</th>';
echo '<th width="12%">' . esc_html__('最后回复', 'aksrt') . '</th>';
echo '<th class="column-title">' . esc_html__('工单', 'aksrt') . '</th>';
echo '<th class="column-status">' . esc_html__('状态', 'aksrt') . '</th>';
echo '<th class="column-type">' . esc_html__('部门', 'aksrt') . '</th>';
echo '<th class="column-user">' . esc_html__('用户', 'aksrt') . '</th>';
echo '<th class="column-admin">' . esc_html__('跟进人', 'aksrt') . '</th>';
echo '<th class="column-time">' . esc_html__('最后活动', 'aksrt') . '</th>';
echo '</tr></thead><tbody>';
if ($items === []) {
echo '<tr><td colspan="8" align="center"><strong>' . esc_html__('暂无工单', 'aksrt') . '</strong></td></tr>';
$hint = $filters === []
? __('暂无进行中的工单,已关闭工单可在「已关闭」视图查看。', 'aksrt')
: __('当前筛选条件下没有工单。', 'aksrt');
printf(
'<tr class="no-items"><td colspan="6"><div class="aksrt-tickets-admin__empty"><strong>%s</strong><span>%s</span></div></td></tr>',
esc_html__('暂无工单', 'aksrt'),
esc_html($hint)
);
}
foreach ($items as $ticket) {
$is_open = $open_id === (int) $ticket['id'];
printf(
'<tr><td><span class="aksrt-ticket-badge" style="--aksrt-ticket-color:%s">%s</span></td>'
. '<td>%s</td><td><a href="%s"><strong>%s</strong></a></td><td>%s</td><td>%s</td><td>%s</td>'
. '<td>%s</td><td>%s</td></tr>',
esc_attr((string) $ticket['status_color']),
esc_html((string) $ticket['status_label']),
esc_html((string) $ticket['ticket_num']),
esc_url(add_query_arg('id', (int) $ticket['id'], $base_url)),
esc_html((string) $ticket['title']),
esc_html((string) $ticket['ticket_type_name']),
esc_html((string) $ticket['user_name']),
esc_html((string) $ticket['admin_name'] !== '' ? (string) $ticket['admin_name'] : '—'),
esc_html((string) $ticket['post_time']),
esc_html((string) $ticket['last_reply_time'] !== '' ? (string) $ticket['last_reply_time'] : '—')
);
if ($is_open) {
echo '<tr><td colspan="8">' . $this->render_ticket_detail_panel($ticket) . '</td></tr>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$this->render_ticket_center_row($ticket, $view_url, $open_id === (int) $ticket['id']);
}
echo '</tbody></table></div>';
@@ -137,132 +129,136 @@ trait UsersMenusTicketsList {
$total,
$paged,
self::TICKET_PER_PAGE,
array_filter([
'status' => $status,
'ticket_type_id' => $type_id,
's' => $s,
], static fn($value): bool => $value !== '' && $value !== 0)
$filters,
'users.php'
);
}
/**
* 行内展开的工单详情:沟通记录 + 回复框 + 状态迁移 + 后台备注。
*
* @param array<string,mixed> $ticket
*/
private function render_ticket_detail_panel(array $ticket): string {
$id = (int) $ticket['id'];
/** 状态视图:未关闭(默认)+ 各进行中状态 + 完结态,均带计数。 */
private function render_ticket_status_views(string $current): void {
$repo = \AKSRT\Ticket\TicketRepository::get_instance();
$replies = $repo->replies($id);
$notes = \AKSRT\Ticket\TicketNoteService::for_ticket($id);
$html = '<div class="aksrt-ticket-detail" data-aksrt-ticket-detail="' . esc_attr((string) $id) . '">';
// 工单正文
$html .= '<div class="aksrt-ticket-detail__meta">';
$html .= '<p><strong>' . esc_html__('工单号', 'aksrt') . ':</strong>' . esc_html((string) $ticket['ticket_num']) . '</p>';
$html .= '<p><strong>' . esc_html__('提交用户', 'aksrt') . ':</strong>' . esc_html((string) $ticket['user_name']) . ' (#' . esc_html((string) $ticket['user_id']) . ')</p>';
if ((string) $ticket['order_number'] !== '') {
$html .= '<p><strong>' . esc_html__('关联订单', 'aksrt') . ':</strong>' . esc_html((string) $ticket['order_number']) . '</p>';
}
$html .= '<p><strong>' . esc_html__('问题描述', 'aksrt') . ':</strong></p>';
$html .= '<div class="aksrt-ticket-detail__content">' . nl2br(esc_html((string) $ticket['content'])) . '</div>';
$html .= '</div>';
// 沟通记录
$html .= '<h4>' . esc_html__('沟通记录', 'aksrt') . '</h4>';
if ($replies === []) {
$html .= '<p class="description">' . esc_html__('暂无沟通记录', 'aksrt') . '</p>';
}
$html .= '<ol class="aksrt-ticket-detail__replies">';
foreach ($replies as $reply) {
$who = (string) $reply['author_type'] === 'admin'
? sprintf(
/* translators: %s admin name */
__('管理员 %s', 'aksrt'),
(string) $reply['author_name']
)
: (string) $reply['author_name'];
$html .= '<li class="is-' . esc_attr((string) $reply['author_type']) . '">';
$html .= '<div class="aksrt-ticket-detail__reply-head"><strong>' . esc_html($who) . '</strong>'
. ' <time>' . esc_html((string) $reply['created_at']) . '</time></div>';
if (is_array($reply['quote'] ?? null)) {
$quote = $reply['quote'];
$html .= '<blockquote class="aksrt-ticket-detail__quote">'
. esc_html(!empty($quote['is_deleted']) ? __('原回复已删除', 'aksrt') : (string) $quote['content'])
. '</blockquote>';
}
$html .= '<div class="aksrt-ticket-detail__reply-body">' . nl2br(esc_html((string) $reply['content'])) . '</div>';
$html .= '</li>';
}
$html .= '</ol>';
// 管理员回复:已完结工单不再给回复框,只提示已关闭。
if (!empty($ticket['is_closed'])) {
$html .= '<p class="aksrt-ticket-detail__closed">' . esc_html__('工单已关闭,无法继续回复。', 'aksrt') . '</p>';
} else {
$html .= '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-ticket-detail__reply-form">';
$html .= '<input type="hidden" name="action" value="aksrt_ticket_update" />';
$html .= '<input type="hidden" name="ticket_action" value="reply" />';
$html .= '<input type="hidden" name="ticket_id" value="' . esc_attr((string) $id) . '" />';
$html .= wp_nonce_field('aksrt_ticket_reply_' . $id, '_wpnonce', true, false);
$html .= '<p><textarea name="content" rows="3" class="large-text" placeholder="' . esc_attr__('填写回复内容', 'aksrt') . '"></textarea></p>';
$html .= '<p><button type="submit" class="button button-primary">' . esc_html__('提交回复', 'aksrt') . '</button></p>';
$html .= '</form>';
}
// 状态迁移 / 部门 / 跟进人
$html .= '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-ticket-detail__status-form">';
$html .= '<input type="hidden" name="action" value="aksrt_ticket_update" />';
$html .= '<input type="hidden" name="ticket_action" value="status" />';
$html .= '<input type="hidden" name="ticket_id" value="' . esc_attr((string) $id) . '" />';
$html .= wp_nonce_field('aksrt_ticket_status_' . $id, '_wpnonce', true, false);
$html .= '<p><label>' . esc_html__('状态', 'aksrt') . ' <select name="status">';
// 默认口径(不带状态)已排除已关闭;完结态单独计数。
$counts = $repo->statistic([], true);
$open_total = 0;
$views = [];
foreach (\AKSRT\Ticket\TicketStatusService::all() as $row) {
$key = (string) $row['status_key'];
$count = !empty($row['is_closed']) ? $repo->count_by_status($key) : (int) ($counts[$key] ?? 0);
if (empty($row['is_closed'])) {
$open_total += $count;
}
$views[] = [$key, (string) $row['name'], $count, (string) $row['color']];
}
array_unshift($views, ['', __('未关闭', 'aksrt'), $open_total, '']);
echo '<ul class="subsubsub aksrt-tickets-admin__views">';
$last = count($views) - 1;
foreach ($views as $i => [$key, $label, $count, $color]) {
printf(
'<option value="%s"%s>%s</option>',
esc_attr((string) $row['status_key']),
selected((string) $ticket['status'], (string) $row['status_key'], false),
esc_html((string) $row['name'])
'<li><a href="%s"%s>%s%s <span class="count">(%d)</span></a>%s</li>',
esc_url($this->ticket_page_url($key === '' ? [] : ['status' => $key])),
$current === $key ? ' class="current" aria-current="page"' : '',
$color !== '' ? '<i class="aksrt-ticket-dot" style="--aksrt-ticket-color:' . esc_attr($color) . '"></i>' : '',
esc_html($label),
$count,
$i < $last ? ' |' : ''
);
}
$html .= '</select></label> ';
$html .= '<label>' . esc_html__('部门', 'aksrt') . ' <select name="ticket_type_id">';
echo '</ul>';
}
/** 部门 + 关键词筛选(状态由视图链接决定,以隐藏域保留)。 */
private function render_ticket_filter_form(string $status, int $type_id, string $s): void {
echo '<form method="get" action="' . esc_url(admin_url('users.php')) . '" class="aksrt-tickets-admin__filters">';
echo '<input type="hidden" name="page" value="' . esc_attr(self::TICKET_PAGE_SLUG) . '" />';
if ($status !== '') {
echo '<input type="hidden" name="status" value="' . esc_attr($status) . '" />';
}
echo '<label class="screen-reader-text" for="aksrt-ticket-filter-type">' . esc_html__('按部门筛选', 'aksrt') . '</label>';
echo '<select name="ticket_type_id" id="aksrt-ticket-filter-type">';
echo '<option value="">' . esc_html__('全部部门', 'aksrt') . '</option>';
foreach (\AKSRT\Ticket\TicketTypeService::all() as $row) {
printf(
'<option value="%d"%s>%s</option>',
(int) $row['id'],
selected((int) $ticket['ticket_type_id'], (int) $row['id'], false),
selected($type_id, (int) $row['id'], false),
esc_html((string) $row['name'])
);
}
$html .= '</select></label> ';
$html .= '<label>' . esc_html__('跟进人 ID', 'aksrt') . ' <input type="number" name="admin_id" value="' . esc_attr((string) $ticket['admin_id']) . '" min="0" style="width:80px;" /></label> ';
$html .= '<button type="submit" class="button">' . esc_html__('保存', 'aksrt') . '</button></p>';
$html .= '</form>';
// 后台备注
$html .= '<h4>' . esc_html__('后台备注', 'aksrt') . '</h4>';
if ($notes === []) {
$html .= '<p class="description">' . esc_html__('暂无备注', 'aksrt') . '</p>';
echo '</select>';
echo '<label class="screen-reader-text" for="aksrt-ticket-filter-s">' . esc_html__('搜索工单', 'aksrt') . '</label>';
printf(
'<input type="search" id="aksrt-ticket-filter-s" name="s" value="%s" placeholder="%s" />',
esc_attr($s),
esc_attr__('工单号 / 标题', 'aksrt')
);
submit_button(__('筛选', 'aksrt'), '', '', false);
if ($type_id > 0 || $s !== '') {
printf(
'<a class="button-link" href="%s">%s</a>',
esc_url($this->ticket_page_url($status === '' ? [] : ['status' => $status])),
esc_html__('清除筛选', 'aksrt')
);
}
$html .= '<ul class="aksrt-ticket-detail__notes">';
foreach ($notes as $note) {
$html .= '<li><strong>' . esc_html((string) $note['admin_name']) . '</strong>'
. ' <time>' . esc_html((string) $note['created_at']) . '</time>'
. '<div>' . nl2br(esc_html((string) $note['content'])) . '</div></li>';
}
$html .= '</ul>';
$html .= '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
$html .= '<input type="hidden" name="action" value="aksrt_ticket_update" />';
$html .= '<input type="hidden" name="ticket_action" value="note" />';
$html .= '<input type="hidden" name="ticket_id" value="' . esc_attr((string) $id) . '" />';
$html .= wp_nonce_field('aksrt_ticket_note_' . $id, '_wpnonce', true, false);
$html .= '<p><input type="text" name="content" class="large-text" placeholder="' . esc_attr__('添加内部备注(用户不可见)', 'aksrt') . '" /> '
. '<button type="submit" class="button">' . esc_html__('添加备注', 'aksrt') . '</button></p>';
$html .= '</form>';
echo '</form>';
}
$html .= '</div>';
return $html;
/** @param array<string,mixed> $ticket */
private function render_ticket_center_row(array $ticket, string $view_url, bool $is_open): void {
$id = (int) $ticket['id'];
$toggle_url = $is_open
? remove_query_arg('id', $view_url)
: add_query_arg('id', $id, $view_url) . '#aksrt-ticket-' . $id;
$last = (string) $ticket['last_reply_time'] !== '' ? (string) $ticket['last_reply_time'] : (string) $ticket['post_time'];
$awaiting = in_array((string) $ticket['status'], [
\AKSRT\Ticket\TicketStatusService::KEY_PENDING,
\AKSRT\Ticket\TicketStatusService::KEY_USER_REPLIED,
], true);
printf(
'<tr id="aksrt-ticket-%1$d" class="aksrt-tickets-admin__row%2$s">'
. '<td class="column-title"><a class="row-title" href="%3$s" aria-expanded="%4$s">%5$s</a>'
. '<div class="aksrt-tickets-admin__sub"><code>%6$s</code>%7$s</div></td>'
. '<td class="column-status"><span class="aksrt-ticket-badge" style="--aksrt-ticket-color:%8$s">%9$s</span></td>'
. '<td class="column-type">%10$s</td>'
. '<td class="column-user">%11$s</td>'
. '<td class="column-admin">%12$s</td>'
. '<td class="column-time"><span>%13$s</span><div class="aksrt-tickets-admin__sub">%14$s</div></td></tr>',
$id,
($is_open ? ' is-open' : '') . ($awaiting ? ' is-awaiting' : ''),
esc_url($toggle_url),
$is_open ? 'true' : 'false',
esc_html((string) $ticket['title']),
esc_html((string) $ticket['ticket_num']),
(string) $ticket['order_number'] !== ''
? ' · ' . esc_html__('订单', 'aksrt') . ' ' . esc_html((string) $ticket['order_number'])
: '',
esc_attr((string) $ticket['status_color']),
esc_html((string) $ticket['status_label']),
esc_html((string) $ticket['ticket_type_name'] !== '' ? (string) $ticket['ticket_type_name'] : '—'),
$this->ticket_user_chip((int) $ticket['user_id'], (string) $ticket['user_name']), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
(int) $ticket['admin_id'] > 0
? esc_html((string) $ticket['admin_name'])
: '<span class="aksrt-tickets-admin__muted">' . esc_html__('未指派', 'aksrt') . '</span>',
esc_html($last),
esc_html(sprintf(
/* translators: %s submit time */
__('提交于 %s', 'aksrt'),
(string) $ticket['post_time']
))
);
if ($is_open) {
echo '<tr class="aksrt-tickets-admin__detail-row"><td colspan="6">' . $this->render_ticket_detail_panel($ticket) . '</td></tr>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
/** 头像 + 名称(链接到用户编辑页)。后台不走前台懒加载,直接输出 src。 */
private function ticket_user_chip(int $user_id, string $name): string {
$avatar = function_exists('aksrt_get_avatar_url') ? aksrt_get_avatar_url($user_id, 48) : '';
$label = $name !== '' ? $name : '#' . $user_id;
return '<a class="aksrt-ticket-user" href="' . esc_url(get_edit_user_link($user_id)) . '">'
. ($avatar !== '' ? '<img src="' . esc_url($avatar) . '" alt="" width="24" height="24" />' : '')
. '<span>' . esc_html($label) . '</span></a>';
}
}