refactor: flatten src/AKSRT → src (drop theme-identifier dir)
Move all domain folders from src/AKSRT/{Domain} to src/{Domain}; remap
Composer PSR-4 "AKSRT\\": "src/"; update audit script + README + ADR
paths; remove dead tools/build-psr4-map.php (scanned deleted inc/classes).
This commit is contained in:
@@ -0,0 +1,740 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\User;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class Badges {
|
||||
|
||||
public const META_OWNED = 'aksrt_user_badges';
|
||||
public const META_WEAR = 'aksrt_wear_badge';
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
add_action('rest_api_init', [$this, 'register_routes']);
|
||||
add_action('aksrt_badge_granted', [$this, 'on_badge_granted_notify'], 10, 3);
|
||||
// HTML modal for remote-modal (zibll user_medal_info_modal).
|
||||
add_action('wp_ajax_aksrt_user_badge_info_modal', [$this, 'ajax_info_modal']);
|
||||
add_action('wp_ajax_nopriv_aksrt_user_badge_info_modal', [$this, 'ajax_info_modal']);
|
||||
add_action('wp_ajax_aksrt_single_badge_info_modal', [$this, 'ajax_single_info_modal']);
|
||||
add_action('wp_ajax_nopriv_aksrt_single_badge_info_modal', [$this, 'ajax_single_info_modal']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify user when a badge is granted (zibll user_add_medal message).
|
||||
*/
|
||||
public function on_badge_granted_notify(int $user_id, string $slug, int $actor = 0): void {
|
||||
if ($user_id <= 0 || $slug === '') {
|
||||
return;
|
||||
}
|
||||
$map = self::get_dictionary_map();
|
||||
if (!isset($map[$slug])) {
|
||||
return;
|
||||
}
|
||||
$label = (string) ($map[$slug]['label'] ?? $slug);
|
||||
$title = sprintf(
|
||||
/* translators: %s badge name */
|
||||
__('恭喜您!获得新的徽章[%s]', 'aksrt'),
|
||||
$label
|
||||
);
|
||||
$content = sprintf(
|
||||
/* translators: %s badge name */
|
||||
__('恭喜您获得徽章「%s」', 'aksrt'),
|
||||
$label
|
||||
);
|
||||
if (class_exists('\AKSRT\Messages\Message')) {
|
||||
\AKSRT\Messages\Message::get_instance()->send($user_id, 'medal', $content, [
|
||||
'title' => $title,
|
||||
'target_url' => function_exists('aksrt_get_user_center_url')
|
||||
? aksrt_get_user_center_url()
|
||||
: home_url('/'),
|
||||
'slug' => $slug,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: full badge list modal HTML.
|
||||
*/
|
||||
public function ajax_info_modal(): void {
|
||||
$user_id = max(0, (int) ($_REQUEST['id'] ?? $_REQUEST['user_id'] ?? 0)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ($user_id <= 0) {
|
||||
$user_id = get_current_user_id();
|
||||
}
|
||||
if ($user_id <= 0 || !get_userdata($user_id)) {
|
||||
wp_send_json_error(['message' => __('用户不存在', 'aksrt')], 404);
|
||||
}
|
||||
$html = self::render_info_modal_html($user_id);
|
||||
wp_send_json_success(['html' => $html]);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: single badge detail modal.
|
||||
*/
|
||||
public function ajax_single_info_modal(): void {
|
||||
$slug = sanitize_key((string) ($_REQUEST['name'] ?? $_REQUEST['slug'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$user_id = max(0, (int) ($_REQUEST['user_id'] ?? $_REQUEST['id'] ?? 0)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ($slug === '') {
|
||||
wp_send_json_error(['message' => __('徽章不存在', 'aksrt')], 404);
|
||||
}
|
||||
$html = self::render_single_modal_html($slug, $user_id);
|
||||
wp_send_json_success(['html' => $html]);
|
||||
}
|
||||
|
||||
/**
|
||||
* zibll zib_get_user_medal_info_modal lite.
|
||||
*/
|
||||
public static function render_info_modal_html(int $user_id): string {
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user) {
|
||||
return '<div class="aksrt-badge-modal"><p>' . esc_html__('用户不存在', 'aksrt') . '</p></div>';
|
||||
}
|
||||
$current = get_current_user_id();
|
||||
$is_self = $current > 0 && $current === $user_id;
|
||||
$display = $is_self ? __('我', 'aksrt') : $user->display_name;
|
||||
$avatar = function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($user_id, ['size' => 64])
|
||||
: get_avatar_url($user_id, ['size' => 64]);
|
||||
$data = self::list_for_user($user_id, $is_self || self::can_manually_set($current));
|
||||
$items = is_array($data['items'] ?? null) ? $data['items'] : [];
|
||||
$wear = (string) ($data['wear'] ?? '');
|
||||
|
||||
$html = '<div class="aksrt-badge-modal">';
|
||||
$html .= '<div class="aksrt-badge-modal__head">';
|
||||
$html .= '<img class="aksrt-badge-modal__avatar" src="' . esc_url($avatar) . '" alt="" width="40" height="40" />';
|
||||
$html .= '<div class="aksrt-badge-modal__title">' . esc_html(sprintf(__('%s的徽章', 'aksrt'), $display));
|
||||
if ($wear !== '') {
|
||||
$map = self::get_dictionary_map();
|
||||
if (isset($map[$wear])) {
|
||||
$html .= ' <span class="aksrt-badge-modal__wear-tag">' . esc_html(sprintf(__('佩戴:%s', 'aksrt'), (string) $map[$wear]['label'])) . '</span>';
|
||||
}
|
||||
} else {
|
||||
$html .= ' <span class="badg badg-sm c-red">' . esc_html__('暂无徽章', 'aksrt') . '</span>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
$html .= '<button type="button" class="aksrt-remote-modal__close" data-aksrt-remote-modal-close aria-label="' . esc_attr__('关闭', 'aksrt') . '">×</button>';
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '<div class="aksrt-badge-modal__list mini-scrollbar">';
|
||||
if (!$items) {
|
||||
$html .= '<div class="aksrt-badge-modal__empty">' . esc_html__('暂无徽章', 'aksrt') . '</div>';
|
||||
} else {
|
||||
foreach ($items as $item) {
|
||||
$slug = (string) ($item['slug'] ?? '');
|
||||
$label = (string) ($item['label'] ?? $slug);
|
||||
$icon = (string) ($item['icon_url'] ?? '');
|
||||
$is_wear = !empty($item['wear']);
|
||||
$html .= '<div class="aksrt-badge-modal__item' . ($is_wear ? ' is-wear' : '') . '" data-slug="' . esc_attr($slug) . '">';
|
||||
if ($icon !== '') {
|
||||
$html .= '<img class="medal-icon img-icon" src="' . esc_url($icon) . '" alt="" width="40" height="40" loading="lazy" />';
|
||||
} else {
|
||||
$html .= '<span class="aksrt-badge-modal__icon-fa"><i class="fas fa-award" aria-hidden="true"></i></span>';
|
||||
}
|
||||
$html .= '<div class="aksrt-badge-modal__meta"><div class="aksrt-badge-modal__name">' . esc_html($label) . '</div>';
|
||||
if ($is_wear) {
|
||||
$html .= '<div class="em09 c-blue">' . esc_html__('当前佩戴', 'aksrt') . '</div>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
if ($is_self && !$is_wear) {
|
||||
$html .= '<button type="button" class="but jb-blue but-sm" data-aksrt-badge-wear="' . esc_attr($slug) . '">' . esc_html__('佩戴此徽章', 'aksrt') . '</button>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
}
|
||||
}
|
||||
$html .= '</div></div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Single badge card modal.
|
||||
*/
|
||||
public static function render_single_modal_html(string $slug, int $user_id = 0): string {
|
||||
$map = self::get_dictionary_map();
|
||||
$slug = sanitize_key($slug);
|
||||
if ($slug === '' || !isset($map[$slug])) {
|
||||
return '<div class="aksrt-badge-modal"><p>' . esc_html__('徽章不存在', 'aksrt') . '</p></div>';
|
||||
}
|
||||
$item = $map[$slug];
|
||||
$current = get_current_user_id();
|
||||
$is_self = $user_id > 0 && $current === $user_id;
|
||||
$owned = $user_id > 0 ? self::get_owned($user_id) : [];
|
||||
$has = in_array($slug, $owned, true);
|
||||
$wear = $user_id > 0 ? self::get_wear($user_id) : '';
|
||||
$is_wear = $has && $wear === $slug;
|
||||
|
||||
$html = '<div class="aksrt-badge-modal aksrt-badge-modal--single">';
|
||||
$html .= '<button type="button" class="aksrt-remote-modal__close" data-aksrt-remote-modal-close aria-label="' . esc_attr__('关闭', 'aksrt') . '">×</button>';
|
||||
$html .= '<div class="aksrt-badge-modal__card">';
|
||||
if (!empty($item['icon_url'])) {
|
||||
$html .= '<img class="medal-icon img-icon" src="' . esc_url((string) $item['icon_url']) . '" alt="" width="72" height="72" />';
|
||||
} else {
|
||||
$html .= '<span class="aksrt-badge-modal__icon-fa lg"><i class="fas fa-award" aria-hidden="true"></i></span>';
|
||||
}
|
||||
$html .= '<div class="aksrt-badge-modal__name">' . esc_html((string) $item['label']) . '</div>';
|
||||
if ($is_self) {
|
||||
$html .= $has
|
||||
? '<div class="c-blue em09 mt6">' . esc_html__('已获得', 'aksrt') . '</div>'
|
||||
: '<div class="c-yellow em09 mt6">' . esc_html__('暂未获得', 'aksrt') . '</div>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
if ($is_self && $has && !$is_wear) {
|
||||
$html .= '<div class="aksrt-badge-modal__actions"><button type="button" class="but jb-blue btn-block" data-aksrt-badge-wear="' . esc_attr($slug) . '">' . esc_html__('佩戴此徽章', 'aksrt') . '</button></div>';
|
||||
} elseif ($is_self && $is_wear) {
|
||||
$html .= '<div class="aksrt-badge-modal__actions"><span class="badg btn-block c-blue">' . esc_html__('当前佩戴此徽章', 'aksrt') . '</span></div>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote-modal trigger for a user's badge list (zibll zib_get_user_medal_info_link).
|
||||
*/
|
||||
public static function get_info_link(int $user_id, string $class = '', ?string $text = null): string {
|
||||
if ($user_id <= 0 || !self::enabled()) {
|
||||
return '';
|
||||
}
|
||||
if ($text === null) {
|
||||
$text = __('徽章明细', 'aksrt');
|
||||
}
|
||||
if (!function_exists('aksrt_get_remote_modal_link')) {
|
||||
return '';
|
||||
}
|
||||
$url = add_query_arg(
|
||||
[
|
||||
'action' => 'aksrt_user_badge_info_modal',
|
||||
'id' => $user_id,
|
||||
],
|
||||
admin_url('admin-ajax.php')
|
||||
);
|
||||
return aksrt_get_remote_modal_link([
|
||||
'tag' => 'a',
|
||||
'class' => trim('user-medal-info ' . $class),
|
||||
'text' => $text,
|
||||
'url' => $url,
|
||||
'data_class' => 'full-sm',
|
||||
'height' => 240,
|
||||
'mobile_bottom' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
register_rest_route('aksrt/v1', '/users/(?P<id>\d+)/badges', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_list'],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'id' => [
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'absint',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
register_rest_route('aksrt/v1', '/users/me/badges/wear', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_wear'],
|
||||
'permission_callback' => static function () {
|
||||
return is_user_logged_in();
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('aksrt/v1', '/users/(?P<id>\d+)/badges/grant', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_grant'],
|
||||
'permission_callback' => [$this, 'rest_can_manually_set'],
|
||||
'args' => [
|
||||
'id' => [
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'absint',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
register_rest_route('aksrt/v1', '/users/(?P<id>\d+)/badges/revoke', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_revoke'],
|
||||
'permission_callback' => [$this, 'rest_can_manually_set'],
|
||||
'args' => [
|
||||
'id' => [
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'absint',
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function rest_can_manually_set(): bool {
|
||||
return self::can_manually_set(get_current_user_id());
|
||||
}
|
||||
|
||||
public static function enabled(): bool {
|
||||
if (!class_exists('\AKSRT\Settings\Settings')) {
|
||||
return false;
|
||||
}
|
||||
$settings = \AKSRT\Settings\Settings::get_user_identity_settings();
|
||||
return !empty($settings['badge']['enabled']);
|
||||
}
|
||||
|
||||
public static function public_display(): bool {
|
||||
if (!class_exists('\AKSRT\Settings\Settings')) {
|
||||
return false;
|
||||
}
|
||||
$settings = \AKSRT\Settings\Settings::get_user_identity_settings();
|
||||
return !empty($settings['badge']['enabled']) && !empty($settings['badge']['public_display']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{slug:string,label:string,tone:string,icon_url:string}>
|
||||
*/
|
||||
public static function get_dictionary(): array {
|
||||
if (!class_exists('\AKSRT\Settings\Settings')) {
|
||||
return [];
|
||||
}
|
||||
$settings = \AKSRT\Settings\Settings::get_user_identity_settings();
|
||||
$dict = is_array($settings['badge']['dictionary'] ?? null) ? $settings['badge']['dictionary'] : [];
|
||||
$out = [];
|
||||
foreach ($dict as $item) {
|
||||
$slug = sanitize_key((string) ($item['slug'] ?? ''));
|
||||
if ($slug === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'slug' => $slug,
|
||||
'label' => (string) ($item['label'] ?? $slug),
|
||||
'tone' => (string) ($item['tone'] ?? 'neutral'),
|
||||
'icon_url' => (string) ($item['icon_url'] ?? ''),
|
||||
];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,array{slug:string,label:string,tone:string,icon_url:string}>
|
||||
*/
|
||||
public static function get_dictionary_map(): array {
|
||||
$map = [];
|
||||
foreach (self::get_dictionary() as $item) {
|
||||
$map[$item['slug']] = $item;
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function get_owned(int $user_id): array {
|
||||
if ($user_id <= 0) {
|
||||
return [];
|
||||
}
|
||||
$raw = get_user_meta($user_id, self::META_OWNED, true);
|
||||
if (!is_array($raw)) {
|
||||
$raw = preg_split('/[\s,]+/', (string) $raw, -1, PREG_SPLIT_NO_EMPTY);
|
||||
}
|
||||
$map = self::get_dictionary_map();
|
||||
$out = [];
|
||||
foreach ((array) $raw as $slug) {
|
||||
$slug = sanitize_key((string) $slug);
|
||||
if ($slug === '' || !isset($map[$slug])) {
|
||||
continue;
|
||||
}
|
||||
$out[] = $slug;
|
||||
}
|
||||
return array_values(array_unique($out));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $slugs
|
||||
*/
|
||||
public static function set_owned(int $user_id, array $slugs): void {
|
||||
if ($user_id <= 0) {
|
||||
return;
|
||||
}
|
||||
$map = self::get_dictionary_map();
|
||||
$clean = [];
|
||||
foreach ($slugs as $slug) {
|
||||
$slug = sanitize_key((string) $slug);
|
||||
if ($slug !== '' && isset($map[$slug])) {
|
||||
$clean[] = $slug;
|
||||
}
|
||||
}
|
||||
$clean = array_values(array_unique($clean));
|
||||
update_user_meta($user_id, self::META_OWNED, $clean);
|
||||
|
||||
$wear = self::get_wear($user_id);
|
||||
if ($wear !== '' && !in_array($wear, $clean, true)) {
|
||||
delete_user_meta($user_id, self::META_WEAR);
|
||||
}
|
||||
}
|
||||
|
||||
public static function get_wear(int $user_id): string {
|
||||
if ($user_id <= 0) {
|
||||
return '';
|
||||
}
|
||||
$wear = sanitize_key((string) get_user_meta($user_id, self::META_WEAR, true));
|
||||
if ($wear === '') {
|
||||
// zibll alias.
|
||||
$wear = sanitize_key((string) get_user_meta($user_id, 'wear_medal', true));
|
||||
}
|
||||
$owned = self::get_owned($user_id);
|
||||
if ($wear === '') {
|
||||
// Default to newest/first owned badge when unset (zibll behavior).
|
||||
return $owned[0] ?? '';
|
||||
}
|
||||
return in_array($wear, $owned, true) ? $wear : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true|\WP_Error
|
||||
*/
|
||||
public static function wear(int $user_id, string $slug) {
|
||||
if ($user_id <= 0) {
|
||||
return new \WP_Error('aksrt_badge_user', __('无效用户', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
if (!self::enabled()) {
|
||||
return new \WP_Error('aksrt_badge_disabled', __('徽章系统未启用', 'aksrt'), ['status' => 403]);
|
||||
}
|
||||
$slug = sanitize_key($slug);
|
||||
$map = self::get_dictionary_map();
|
||||
if ($slug === '' || !isset($map[$slug])) {
|
||||
return new \WP_Error('aksrt_badge_unknown', __('徽章不存在', 'aksrt'), ['status' => 404]);
|
||||
}
|
||||
$owned = self::get_owned($user_id);
|
||||
if (!in_array($slug, $owned, true)) {
|
||||
return new \WP_Error('aksrt_badge_not_owned', __('尚未获得该徽章', 'aksrt'), ['status' => 403]);
|
||||
}
|
||||
update_user_meta($user_id, self::META_WEAR, $slug);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true|\WP_Error
|
||||
*/
|
||||
public static function grant(int $user_id, string $slug, int $actor = 0) {
|
||||
if ($user_id <= 0) {
|
||||
return new \WP_Error('aksrt_badge_user', __('无效用户', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
if (!self::enabled()) {
|
||||
return new \WP_Error('aksrt_badge_disabled', __('徽章系统未启用', 'aksrt'), ['status' => 403]);
|
||||
}
|
||||
$slug = sanitize_key($slug);
|
||||
$map = self::get_dictionary_map();
|
||||
if ($slug === '' || !isset($map[$slug])) {
|
||||
return new \WP_Error('aksrt_badge_unknown', __('徽章不存在', 'aksrt'), ['status' => 404]);
|
||||
}
|
||||
$owned = self::get_owned($user_id);
|
||||
if (in_array($slug, $owned, true)) {
|
||||
return true;
|
||||
}
|
||||
$owned[] = $slug;
|
||||
self::set_owned($user_id, $owned);
|
||||
/**
|
||||
* Fires after a badge is granted.
|
||||
*
|
||||
* @param int $user_id
|
||||
* @param string $slug
|
||||
* @param int $actor
|
||||
*/
|
||||
do_action('aksrt_badge_granted', $user_id, $slug, $actor);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true|\WP_Error
|
||||
*/
|
||||
public static function revoke(int $user_id, string $slug, int $actor = 0) {
|
||||
if ($user_id <= 0) {
|
||||
return new \WP_Error('aksrt_badge_user', __('无效用户', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
$slug = sanitize_key($slug);
|
||||
$owned = self::get_owned($user_id);
|
||||
$owned = array_values(array_filter($owned, static function ($s) use ($slug) {
|
||||
return $s !== $slug;
|
||||
}));
|
||||
self::set_owned($user_id, $owned);
|
||||
/**
|
||||
* @param int $user_id
|
||||
* @param string $slug
|
||||
* @param int $actor
|
||||
*/
|
||||
do_action('aksrt_badge_revoked', $user_id, $slug, $actor);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function can_manually_set(int $actor = 0): bool {
|
||||
$actor = $actor > 0 ? $actor : get_current_user_id();
|
||||
if ($actor <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (user_can($actor, 'manage_options')) {
|
||||
return true;
|
||||
}
|
||||
if (class_exists('\AKSRT\Settings\Capabilities')) {
|
||||
return \AKSRT\Settings\Capabilities::user_can('medal_manually_set', $actor);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public DTO for REST / modals.
|
||||
*
|
||||
* @return array{enabled:bool,public_display:bool,wear:string,items:list<array>}
|
||||
*/
|
||||
public static function list_for_user(int $user_id, bool $include_private = false): array {
|
||||
$enabled = self::enabled();
|
||||
$public = self::public_display();
|
||||
$viewer = get_current_user_id();
|
||||
$can_see = $include_private
|
||||
|| $public
|
||||
|| ($viewer > 0 && $viewer === $user_id)
|
||||
|| self::can_manually_set($viewer);
|
||||
|
||||
if (!$enabled || !$can_see || $user_id <= 0) {
|
||||
return [
|
||||
'enabled' => $enabled,
|
||||
'public_display' => $public,
|
||||
'wear' => '',
|
||||
'items' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$map = self::get_dictionary_map();
|
||||
$owned = self::get_owned($user_id);
|
||||
$wear = self::get_wear($user_id);
|
||||
// Prefer worn first (Zibll).
|
||||
if ($wear !== '') {
|
||||
$owned = array_values(array_unique(array_merge([$wear], $owned)));
|
||||
}
|
||||
$items = [];
|
||||
foreach ($owned as $slug) {
|
||||
if (!isset($map[$slug])) {
|
||||
continue;
|
||||
}
|
||||
$items[] = [
|
||||
'slug' => $slug,
|
||||
'label' => $map[$slug]['label'],
|
||||
'tone' => $map[$slug]['tone'],
|
||||
'icon_url' => $map[$slug]['icon_url'],
|
||||
'wear' => ($slug === $wear),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'enabled' => true,
|
||||
'public_display' => $public,
|
||||
'wear' => $wear,
|
||||
'items' => $items,
|
||||
];
|
||||
}
|
||||
|
||||
public function rest_list(\WP_REST_Request $request) {
|
||||
$user_id = (int) $request['id'];
|
||||
if ($user_id <= 0 || !get_userdata($user_id)) {
|
||||
return new \WP_Error('aksrt_badge_user', __('用户不存在', 'aksrt'), ['status' => 404]);
|
||||
}
|
||||
$data = self::list_for_user($user_id);
|
||||
return rest_ensure_response(['success' => true, 'data' => $data]);
|
||||
}
|
||||
|
||||
public function rest_wear(\WP_REST_Request $request) {
|
||||
$params = $request->get_json_params();
|
||||
if (!is_array($params)) {
|
||||
$params = $request->get_params();
|
||||
}
|
||||
$slug = sanitize_key((string) ($params['slug'] ?? $params['name'] ?? ''));
|
||||
$result = self::wear(get_current_user_id(), $slug);
|
||||
if (is_wp_error($result)) {
|
||||
return $result;
|
||||
}
|
||||
return rest_ensure_response([
|
||||
'success' => true,
|
||||
'data' => self::list_for_user(get_current_user_id(), true),
|
||||
'message' => __('已佩戴徽章', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function rest_grant(\WP_REST_Request $request) {
|
||||
$user_id = (int) $request['id'];
|
||||
if ($user_id <= 0 || !get_userdata($user_id)) {
|
||||
return new \WP_Error('aksrt_badge_user', __('用户不存在', 'aksrt'), ['status' => 404]);
|
||||
}
|
||||
$params = $request->get_json_params();
|
||||
if (!is_array($params)) {
|
||||
$params = $request->get_params();
|
||||
}
|
||||
$slug = sanitize_key((string) ($params['slug'] ?? $params['name'] ?? ''));
|
||||
$result = self::grant($user_id, $slug, get_current_user_id());
|
||||
if (is_wp_error($result)) {
|
||||
return $result;
|
||||
}
|
||||
return rest_ensure_response([
|
||||
'success' => true,
|
||||
'data' => self::list_for_user($user_id, true),
|
||||
'message' => __('已授予徽章', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function rest_revoke(\WP_REST_Request $request) {
|
||||
$user_id = (int) $request['id'];
|
||||
if ($user_id <= 0 || !get_userdata($user_id)) {
|
||||
return new \WP_Error('aksrt_badge_user', __('用户不存在', 'aksrt'), ['status' => 404]);
|
||||
}
|
||||
$params = $request->get_json_params();
|
||||
if (!is_array($params)) {
|
||||
$params = $request->get_params();
|
||||
}
|
||||
$slug = sanitize_key((string) ($params['slug'] ?? $params['name'] ?? ''));
|
||||
$result = self::revoke($user_id, $slug, get_current_user_id());
|
||||
if (is_wp_error($result)) {
|
||||
return $result;
|
||||
}
|
||||
return rest_ensure_response([
|
||||
'success' => true,
|
||||
'data' => self::list_for_user($user_id, true),
|
||||
'message' => __('已收回徽章', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Auto-grant medals whose get_type metrics meet get_val (skip manually_add).
|
||||
*/
|
||||
public static function auto_evaluate(int $user_id): void {
|
||||
if ($user_id <= 0 || !self::enabled() || !class_exists('\AKSRT\Settings\Settings')) {
|
||||
return;
|
||||
}
|
||||
$settings = \AKSRT\Settings\Settings::get_user_identity_settings();
|
||||
$medal = is_array($settings['medal'] ?? null) ? $settings['medal'] : [];
|
||||
$items = is_array($medal['items_by_slug'] ?? null) ? $medal['items_by_slug'] : [];
|
||||
if (!$items) {
|
||||
// Fall back to dictionary only — no get_type → nothing to auto-grant.
|
||||
return;
|
||||
}
|
||||
$owned = self::get_owned($user_id);
|
||||
$owned_map = array_fill_keys($owned, true);
|
||||
foreach ($items as $slug => $item) {
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
$slug = sanitize_key((string) $slug);
|
||||
if ($slug === '' || !empty($owned_map[$slug])) {
|
||||
continue;
|
||||
}
|
||||
$type = sanitize_key((string) ($item['get_type'] ?? 'manually_add'));
|
||||
if ($type === '' || $type === 'manually_add') {
|
||||
continue;
|
||||
}
|
||||
$need = max(0, (float) ($item['get_val'] ?? 0));
|
||||
$have = (float) self::metric_value($user_id, $type);
|
||||
if ($have >= $need) {
|
||||
self::grant($user_id, $slug, 0);
|
||||
$owned_map[$slug] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Numeric metric for a medal get_type.
|
||||
*/
|
||||
public static function metric_value(int $user_id, string $type): float {
|
||||
switch ($type) {
|
||||
case 'registration_time':
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user) {
|
||||
return 0.0;
|
||||
}
|
||||
$reg = get_date_from_gmt($user->user_registered, 'Y-m-d H:i:s');
|
||||
$now = current_time('Y-m-d H:i:s');
|
||||
return max(0.0, (strtotime($now) - strtotime($reg)) / 86400);
|
||||
|
||||
case 'checkin_all_day':
|
||||
// Prefer dedicated check-in total meta if present.
|
||||
$v = get_user_meta($user_id, 'aksrt_checkin_all_day', true);
|
||||
if ($v === '' || $v === false) {
|
||||
$v = get_user_meta($user_id, 'checkin_all_day', true);
|
||||
}
|
||||
return max(0.0, (float) $v);
|
||||
|
||||
case 'new_post':
|
||||
return (float) count_user_posts($user_id, 'post', true);
|
||||
|
||||
case 'comment':
|
||||
$counts = get_comments(['user_id' => $user_id, 'count' => true, 'status' => 'approve']);
|
||||
return (float) $counts;
|
||||
|
||||
case 'followed':
|
||||
if (class_exists('AKSRT\\User\\Follow')) {
|
||||
return (float) Follow::get_instance()->get_follower_count($user_id);
|
||||
}
|
||||
return 0.0;
|
||||
|
||||
case 'like':
|
||||
return (float) get_user_meta($user_id, 'aksrt_posts_like_sum', true);
|
||||
|
||||
case 'comment_like':
|
||||
return (float) get_user_meta($user_id, 'aksrt_comment_like_sum', true);
|
||||
|
||||
case 'views':
|
||||
$sum = (float) get_user_meta($user_id, 'aksrt_posts_views_sum', true);
|
||||
if ($sum > 0) {
|
||||
return $sum;
|
||||
}
|
||||
// Lazy backfill once from postmeta.
|
||||
global $wpdb;
|
||||
$sum = (float) $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT COALESCE(SUM(CAST(pm.meta_value AS UNSIGNED)),0)
|
||||
FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = 'aksrt_post_views'
|
||||
WHERE p.post_author = %d AND p.post_type = 'post' AND p.post_status = 'publish'",
|
||||
$user_id
|
||||
));
|
||||
if ($sum > 0) {
|
||||
update_user_meta($user_id, 'aksrt_posts_views_sum', $sum);
|
||||
}
|
||||
return $sum;
|
||||
|
||||
case 'pay_income':
|
||||
return (float) get_user_meta($user_id, 'aksrt_pay_income_sum', true);
|
||||
|
||||
case 'pay_rebate':
|
||||
return (float) get_user_meta($user_id, 'aksrt_pay_rebate_sum', true);
|
||||
|
||||
default:
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user_login
|
||||
* @param \WP_User $user
|
||||
*/
|
||||
public function on_login($user_login, $user): void {
|
||||
$user_id = $user instanceof \WP_User ? (int) $user->ID : 0;
|
||||
if ($user_id > 0) {
|
||||
self::auto_evaluate($user_id);
|
||||
}
|
||||
}
|
||||
|
||||
public function on_current_user_probe(): void {
|
||||
$uid = get_current_user_id();
|
||||
if ($uid > 0) {
|
||||
self::auto_evaluate($uid);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user