Files
wordpress-theme-aksrt/src/WidgetSystem/Renderers/UserRenderer.php
T
kate522 c472045ce4 fix(auth): align the social-login surfaces with the reference theme
The "other login methods" block drifted from zibll on three axes. Gitee was
mapped to fa-code-branch because FA5 Free ships no Gitee brand glyph, so the
button showed a generic branch icon; Google showed the monochrome FA glyph
instead of the reference's multi-colour mark. The modal also printed an
implicit agreement line under the social buttons, which the reference guest
card does not have. And the top-nav user popover built its own
.social-login-divider / .oauth-row / .oauth-btn markup and then wrapped that
in a second separator and .social_loginbar, so the block rendered nested and
unlike the reference.

aksrt_brand_icon() now routes zibsvg-/customsvg- values through the shared
icon renderer, and the brand map resolves gitee to zibsvg-gitee and google to
zibsvg-google-color. Both sprite keys already exist in the runtime symbol
sheet, which the loader pulls in on every frontend page. Because
get_auth_providers_data() and binding_row() both go through
aksrt_brand_icon(), one map change fixes the icon on the modal, the user card,
the top-nav popover, the user-center binding list and the profile social
links.

render_social_buttons() is rewritten to the reference shape: an optional
.social-separator, a .social_loginbar, and one .social-login-item <type>
toggle-radius anchor per enabled provider with rel="nofollow" and a
"<label>登录" title. oauth_button_lg switches to the button-lg variant with a
label, and the WeChat entry carries qrcode-signin so auth.js opens the QR
panel instead of navigating, matching the reference. The navbar and the link
apply modal now append the block verbatim instead of re-wrapping it, and the
user-card loop adopts the same item attributes. The retired
.social-login-divider / .oauth-row / .oauth-btn rules are dropped from
base-1.css, and the binding-list icon rule gains an svg selector so the
sprite icons keep the 22px the FA glyphs had.

Verification: node --test across widget-system, navigation, admin,
asset-ledger, dev, front-submit and visual (613 tests), plus php -l on every
changed PHP file. Extended the auth source contract with the agreement-line,
brand-icon and popover-structure cases, and pinned the user-card guest
markup.
2026-10-17 14:42:42 +08:00

312 lines
13 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
namespace AKSRT\WidgetSystem\Renderers;
use AKSRT\WidgetSystem\RendererHelpers;
defined('ABSPATH') || exit;
/** Render login, author, ranking and user-list widgets. */
final class UserRenderer {
public static function render(string $slug, array $instance, array $context = []): string {
unset($context);
switch ($slug) {
case 'user_card': return self::user_card($instance);
case 'author_card': return self::author_card($instance);
case 'user_ranking': return self::ranking($instance);
case 'user_list': return self::user_list($instance);
}
return '';
}
private static function user_card(array $i): string {
if (class_exists('AKSRT\\Settings\\Settings') && \AKSRT\Settings\Settings::close_sign()) {
return '';
}
$user_id = get_current_user_id();
if ($user_id <= 0) {
return self::guest_card($i);
}
$user = get_userdata($user_id);
if (!$user instanceof \WP_User) {
return '';
}
return '<div class="mb20">' . self::card_box($user_id, [
'show_posts' => false,
'show_checkin' => true,
'show_img_bg' => !isset($i['show_img_bg']) || !empty($i['show_img_bg']),
] + self::card_button_args($i)) . '</div>';
}
private static function author_card(array $i): string {
$post = get_post();
if (!$post instanceof \WP_Post) {
return '';
}
$user = get_userdata((int) $post->post_author);
if (!$user instanceof \WP_User) {
return '';
}
$post_style = sanitize_key((string) ($i['post_style'] ?? 'mini'));
return '<div class="mb20">' . self::card_box((int) $user->ID, [
'show_img_bg' => !isset($i['show_img_bg']) || !empty($i['show_img_bg']),
'show_posts' => !isset($i['show_posts']) || !empty($i['show_posts']),
'limit' => max(0, min(12, (int) ($i['limit'] ?? 6))),
'orderby' => sanitize_key((string) ($i['orderby'] ?? 'date')),
'post_type' => 'post',
'post_style' => $post_style === 'card' ? 'card' : 'mini',
] + self::card_button_args($i)) . '</div>';
}
private static function card_button_args(array $i): array {
return [
'show_button' => !isset($i['show_button']) || !empty($i['show_button']),
'button_1' => (string) ($i['button_1'] ?? 'post'),
'button_1_class' => (string) ($i['button_1_class'] ?? 'jb-pink'),
'button_1_text' => (string) ($i['button_1_text'] ?? ''),
'button_2' => (string) ($i['button_2'] ?? 'center'),
'button_2_class' => (string) ($i['button_2_class'] ?? 'jb-blue'),
'button_2_text' => (string) ($i['button_2_text'] ?? ''),
];
}
/**
* 未登录卡片(legacy zib_get_user_singin_page_box parity)。
*/
private static function guest_card(array $i): string {
$title = trim((string) ($i['loged_title'] ?? ''));
if ($title === '') {
$title = __('Hi!请登录', 'aksrt');
}
$cover = !isset($i['show_img_bg']) || !empty($i['show_img_bg'])
? self::cover(0, 'lazy_other')
: '';
$avatar = '<span class="avatar-img avatar-lg"><img alt="' . esc_attr__('默认头像', 'aksrt') . '" class="fit-cover avatar" src="' . esc_url(aksrt_get_default_avatar_url()) . '"></span>';
$buttons = '<a href="javascript:;" class="signin-loader but jb-blue padding-lg"><i class="fa fas fa-fw fa-sign-in-alt" aria-hidden="true"></i>' . esc_html__('登录', 'aksrt') . '</a>';
$registration_enabled = class_exists('AKSRT\\Settings\\Settings')
? \AKSRT\Settings\Settings::registration_enabled()
: (bool) get_option('users_can_register');
if ($registration_enabled) {
$buttons .= '<a href="javascript:;" class="signup-loader ml10 but jb-yellow padding-lg">' . aksrt_renderer_svg_icon('signup') . esc_html__('注册', 'aksrt') . '</a>';
}
return '<div class="mb20"><div class="user-card zib-widget widget">' . $cover
. '<div class="card-content mt10">'
. '<div class="user-content">'
. '<div class="user-avatar">' . $avatar . '</div>'
. '<div class="user-info mt10">'
. '<div class="text-center">'
. '<p class="muted-color box-body em12">' . esc_html($title) . '</p>'
. '<p>' . $buttons . '</p>'
. self::social_login()
. '</div></div></div></div></div></div>';
}
private static function social_login(): string {
if (!class_exists('AKSRT\\Auth\\OAuth')) {
return '';
}
$items = \AKSRT\Auth\OAuth::get_instance()->get_auth_providers_data();
if ($items === []) {
return '';
}
$html = '<div class="social-separator separator muted-3-color em09 mt20 mb20">' . esc_html__('社交账号登录', 'aksrt') . '</div><div class="social_loginbar">';
foreach ($items as $item) {
$name = sanitize_html_class((string) ($item['name'] ?? ''));
$style_name = str_starts_with($name, 'clogin_') ? substr($name, 7) : $name;
$label = trim((string) ($item['label'] ?? ''));
$url = (string) ($item['url'] ?? '');
if ($name === '' || $style_name === '' || $label === '' || $url === '') {
continue;
}
$html .= '<a rel="nofollow" href="' . esc_url($url) . '" class="social-login-item ' . esc_attr($style_name) . ' toggle-radius" data-provider="' . esc_attr($name) . '" aria-label="' . esc_attr(sprintf(__('%s登录', 'aksrt'), $label)) . '" title="' . esc_attr(sprintf(__('%s登录', 'aksrt'), $label)) . '">' . (string) ($item['icon'] ?? '') . '</a>';
}
return $html . '</div>';
}
private static function cover(int $user_id, string $lazy_type): string {
$cover_url = aksrt_get_user_cover_url($user_id);
if ($cover_url === '') {
return '';
}
return '<div class="user-cover graphic" style="padding-bottom: 50%;"><img' . aksrt_get_lazy_attr($lazy_type, $cover_url, 'fit-cover', aksrt_get_lazy_thumb()) . ' alt="' . esc_attr__('用户封面', 'aksrt') . '"></div>';
}
/**
* 登录卡片(legacy zib_get_user_card_box parity)。
*
* @param int $user_id 卡片用户。
* @param array $args show_posts/show_checkin/show_img_bg/show_button/button_1,2 与文章模块参数。
*/
private static function card_box(int $user_id, array $args = []): string {
$args = wp_parse_args($args, [
'show_posts' => true,
'show_checkin' => false,
'show_img_bg' => false,
'show_button' => true,
'button_1' => 'post',
'button_1_class' => 'jb-pink',
'button_1_text' => __('发布文章', 'aksrt'),
'button_2' => 'center',
'button_2_class' => 'jb-blue',
'button_2_text' => __('用户中心', 'aksrt'),
'limit' => 6,
'orderby' => 'views',
'post_type' => 'post',
'post_style' => 'card',
]);
// 会员/禁封徽章不再挂头像角标,改由名字行的会员位出图(aksrt_get_user_name)。
$avatar = aksrt_get_avatar_box($user_id, 'avatar-img avatar-lg', true, false);
$name = aksrt_get_user_name([
'id' => $user_id,
'class' => 'flex1 flex ac',
'vip' => true,
'medal' => true,
'auth' => true,
'level' => true,
'follow' => true,
]);
$tag_metas = aksrt_get_user_badges($user_id);
$desc = aksrt_get_user_description($user_id);
$btns = '';
$posts = '';
$checkin = '';
$cuid = get_current_user_id();
if (!$cuid || $cuid !== $user_id) {
$args['show_button'] = false;
}
if ($args['show_posts'] && (int) $args['limit'] > 0) {
$posts = aksrt_render_user_card_posts([
'user_id' => $user_id,
'limit' => (int) $args['limit'],
'orderby' => (string) $args['orderby'],
'post_type' => (string) $args['post_type'],
'post_style' => (string) $args['post_style'],
]);
}
if ($args['show_checkin'] && $cuid === $user_id) {
$checkin = aksrt_header_user_checkin_btn($user_id, 'img-badge jb-yellow');
}
if ($args['show_button']) {
$btns .= self::new_add_btn((string) $args['button_1'], 'but pw-1em mr6 ' . (string) $args['button_1_class'], (string) $args['button_1_text']);
if ('home' === $args['button_2']) {
$btns .= self::card_link(aksrt_get_user_home_url($user_id), (string) $args['button_2_class'], (string) $args['button_2_text']);
} else {
$btns .= self::card_link(aksrt_get_user_center_url(), (string) $args['button_2_class'], (string) $args['button_2_text'], true);
}
$btns = $btns !== '' ? '<div class="user-btns mt20">' . $btns . '</div>' : '';
}
$cover = !empty($args['show_img_bg']) ? self::cover($user_id, 'lazy_cover') : '';
return '<div class="user-card zib-widget widget">' . $cover
. '<div class="card-content mt10 relative">'
. '<div class="user-content">' . $checkin
. '<div class="user-avatar">' . $avatar . '</div>'
. '<div class="user-info mt20 mb10">'
. '<div class="user-name flex jc">' . $name . '</div>'
. '<div class="author-tag mt10 mini-scrollbar">' . $tag_metas . '</div>'
. '<div class="user-desc mt10 muted-2-color em09">' . esc_html($desc) . '</div>'
. $btns
. '</div></div>'
. $posts
. '</div></div>';
}
/**
* 卡片按钮1(legacy zib_get_new_add_btns 的单按钮 parity:正则剥掉 icon/text,输出纯文字药丸)。
*/
private static function new_add_btn(string $type, string $class, string $text): string {
if ($type !== 'post') {
return '';
}
$url = aksrt_get_new_post_url();
if ($url === '') {
return '';
}
return '<a rel="nofollow" class="newadd-btns ' . esc_attr($class) . '" href="' . esc_url($url) . '">' . esc_html($text) . '</a>';
}
private static function card_link(string $url, string $class, string $text, bool $nofollow = false): string {
if ($url === '') {
return '';
}
return '<a' . ($nofollow ? ' rel="nofollow"' : '') . ' href="' . esc_url($url) . '" class="but pw-1em ml6 ' . esc_attr($class) . '">' . esc_html($text) . '</a>';
}
private static function ranking(array $i): string {
$users = get_users(['number' => 200, 'orderby' => 'registered', 'order' => 'DESC']);
$metric = (string) $i['orderby'];
usort($users, static fn(\WP_User $a, \WP_User $b): int => self::metric($b, $metric) <=> self::metric($a, $metric));
$users = array_slice($users, 0, (int) $i['number']);
if (!$users) {
return RendererHelpers::empty_state(__('暂无用户数据', 'aksrt'));
}
$html = '<ol class="aksrt-user-ranking">';
foreach ($users as $index => $user) {
$html .= '<li><span class="aksrt-user-ranking__index' . (!empty($i['show_badge']) && $index < 3 ? ' is-top' : '') . '">' . ($index + 1) . '</span>' . self::user_tile($user, self::metric($user, $metric), self::metric_label($metric)) . '</li>';
}
return $html . '</ol>';
}
private static function user_list(array $i): string {
$args = ['number' => 200];
if (!empty($i['include'])) {
$args['include'] = RendererHelpers::ids((string) $i['include']);
}
if (!empty($i['exclude'])) {
$args['exclude'] = RendererHelpers::ids((string) $i['exclude']);
}
$users = get_users($args);
$orderby = (string) $i['orderby'];
usort($users, static function (\WP_User $a, \WP_User $b) use ($orderby): int {
if ($orderby === 'display_name') {
return strcasecmp($a->display_name, $b->display_name);
}
if ($orderby === 'registered') {
return strcmp($b->user_registered, $a->user_registered);
}
return self::metric($b, $orderby) <=> self::metric($a, $orderby);
});
$html = '<div class="aksrt-widget-grid aksrt-user-list">';
foreach (array_slice($users, 0, (int) $i['number']) as $user) {
$html .= '<article>' . self::user_tile($user, self::metric($user, $orderby), self::metric_label($orderby)) . '</article>';
}
return $html . '</div>';
}
private static function user_tile(\WP_User $user, int $value, string $label): string {
$url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($user) : get_author_posts_url((int) $user->ID);
return '<a class="aksrt-user-tile" href="' . esc_url($url) . '">' . (function_exists('aksrt_get_avatar_html') ? aksrt_get_avatar_html($user->ID, 64) : get_avatar($user->ID, 64)) . '<span><strong>' . esc_html($user->display_name) . '</strong><small>' . esc_html($label) . ' ' . number_format_i18n($value) . '</small></span></a>';
}
private static function metric(\WP_User $user, string $metric): int {
$id = (int) $user->ID;
if ($metric === 'points') {
return aksrt_user_points($id);
}
if ($metric === 'followers') {
return class_exists('AKSRT\\User\\Follow') ? (int) \AKSRT\User\Follow::get_instance()->get_follower_count($id) : 0;
}
if ($metric === 'post_count') {
return (int) count_user_posts($id, 'post', true);
}
$key = $metric === 'checkin_continuous_day' ? 'aksrt_checkin_continuous_day' : 'aksrt_checkin_all_day';
return (int) get_user_meta($id, $key, true);
}
private static function metric_label(string $metric): string {
$labels = ['points' => __('积分', 'aksrt'), 'followers' => __('粉丝', 'aksrt'), 'post_count' => __('文章', 'aksrt'), 'registered' => __('新加入', 'aksrt'), 'display_name' => __('用户', 'aksrt'), 'checkin_all_day' => __('累计签到', 'aksrt'), 'checkin_continuous_day' => __('连续签到', 'aksrt')];
return $labels[$metric] ?? $labels['points'];
}
}