\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\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\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')) { return false; } $settings = AKSRT_Settings::get_user_identity_settings(); return !empty($settings['badge']['enabled']); } public static function public_display(): bool { if (!class_exists('AKSRT_Settings')) { return false; } $settings = AKSRT_Settings::get_user_identity_settings(); return !empty($settings['badge']['enabled']) && !empty($settings['badge']['public_display']); } /** * @return list */ public static function get_dictionary(): array { if (!class_exists('AKSRT_Settings')) { return []; } $settings = AKSRT_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 */ public static function get_dictionary_map(): array { $map = []; foreach (self::get_dictionary() as $item) { $map[$item['slug']] = $item; } return $map; } /** * @return list */ 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 $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 === '') { return ''; } $owned = self::get_owned($user_id); 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)) { $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} */ 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'), ]); } }