Files
wordpress-theme-aksrt/src/Settings/UserRuntime.php
T
kate522andClaude Opus 5 35bb96e82f refactor: rename the theme brand and every identifier from AKSRT to Jio
Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.

Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
  cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
  window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)

Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
  so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
  (dropped 3 stale entries, added the 17 missing ones)

Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
  get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
  when it is empty

Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.

Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-01 20:05:04 +08:00

340 lines
13 KiB
PHP

<?php
/**
* @package Jio
*/
namespace Jio\Settings;
defined('ABSPATH') || exit;
class UserRuntime {
/**
* Canonical avatar, cover, and signature settings.
*
* @return array{avatar_upload_s:bool,avatar_external:string,avatar_default_img:string,user_cover_upload_s:bool,user_cover_img:string,user_desc_std:string}
*/
public static function get_user_feature_settings(): array {
$settings = Settings::read_domain('users', 'features');
$source = sanitize_key((string) ($settings['avatar_external'] ?? ''));
if (!in_array($source, ['', 'gravatar', 'weavatar', 'cravatar', 'geekzu', 'loli', 'sep', 'v2ex'], true)) {
$source = '';
}
$avatar = esc_url_raw((string) ($settings['avatar_default_img'] ?? ''));
$cover = esc_url_raw((string) ($settings['user_cover_img'] ?? ''));
return [
'avatar_upload_s' => !array_key_exists('avatar_upload_s', $settings) || !empty($settings['avatar_upload_s']),
'avatar_external' => $source,
'avatar_default_img' => $avatar !== '' ? $avatar : get_theme_file_uri('assets/img/avatar-default.png'),
'user_cover_upload_s' => !array_key_exists('user_cover_upload_s', $settings) || !empty($settings['user_cover_upload_s']),
'user_cover_img' => $cover !== '' ? $cover : get_theme_file_uri('assets/img/user_t.jpg'),
'user_desc_std' => sanitize_text_field((string) ($settings['user_desc_std'] ?? '这家伙很懒,什么都没有写...')),
];
}
/**
* Public user-home display settings.
*
* @return array
*/
public static function get_user_home_settings(): array {
$settings = Settings::read_domain('page_display', 'user-home');
$tab_options = is_array($settings['author_main_tab_opt'] ?? null)
? $settings['author_main_tab_opt']
: [
'post' => ['pc_s' => true, 'm_s' => true],
'favorite' => ['pc_s' => true, 'm_s' => true],
'comment' => ['pc_s' => true, 'm_s' => true],
'follow' => ['pc_s' => true, 'm_s' => true],
];
$map = ['post' => 'posts', 'favorite' => 'favorites', 'comment' => 'comments', 'follow' => 'follow'];
$ordered = [];
$pc_hide = [];
$mobile_hide = [];
foreach ($tab_options as $key => $option) {
$key = sanitize_key((string) $key);
if (!isset($map[$key]) || !is_array($option)) {
continue;
}
$tab = $map[$key];
$ordered[] = $tab;
if (empty($option['pc_s'])) {
$pc_hide[] = $tab;
}
if (empty($option['m_s'])) {
$mobile_hide[] = $tab;
}
}
return [
'route_identifier' => 'nicename',
'tabs' => $ordered,
'tabs_order' => $ordered,
'tabs_pc_hide' => $pc_hide,
'tabs_mobile_hide' => $mobile_hide,
'sidebar' => false,
'cover' => true,
'follow' => true,
'posts_per_page' => max(1, (int) get_option('posts_per_page', 10)),
'avatar' => true,
'name' => true,
'desc' => true,
'stats' => false,
'social' => true,
'tab_swiper' => !empty($settings['author_main_tab_swiper']),
'message' => true,
'private' => true,
'paging_ajax' => true,
];
}
/**
* Private user-center display settings.
*
* @return array{tabs:string[]}
*/
public static function get_user_center_settings(): array {
return [
'tabs' => ['profile', 'level', 'comments', 'credits', 'revenue', 'favorites', 'auth'],
];
}
/**
* User identity, level, medal, and report governance settings.
*
* @return array{
* verify:array{enabled:bool,public_display:bool,allow_apply:bool,disable_desc:string,desc:string,minimum_level:int},
* level:array{enabled:bool,public_display:bool,source:string,label_prefix:string,thresholds:array<int,int>},
* medal:array{enabled:bool,public_display:bool,limit:int,categories:array<int,array{cat_name:string,items:array<int,array{slug:string,name:string,desc:string,icon:string,get_type:string,get_val:int}>}>,items_by_slug:array<string,array{slug:string,name:string,desc:string,icon:string,get_type:string,get_val:int,cat_name:string}>},
* badge:array{enabled:bool,public_display:bool,limit:int,dictionary:array<int,array{slug:string,label:string,tone:string,desc:string,icon_url?:string}>},
* report:array{enabled:bool,public_entry:bool,categories:array<int,array{slug:string,label:string}>}
* }
*/
public static function get_user_identity_settings(): array {
$settings = Settings::get_all();
$medal = self::parse_medal_catalog($settings);
return [
'verify' => [
'enabled' => !isset($settings['user_auth_s']) || !empty($settings['user_auth_s']),
'public_display' => !isset($settings['user_auth_s']) || !empty($settings['user_auth_s']),
'allow_apply' => !isset($settings['user_auth_apply_s']) || !empty($settings['user_auth_apply_s']),
'disable_desc' => (string) ($settings['user_auth_apply_disable_desc'] ?? ''),
'desc' => (string) ($settings['user_auth_apply_desc'] ?? ''),
'minimum_level' => max(0, min(30, (int) ($settings['user_auth_apply_limit_level'] ?? 0))),
],
'level' => [
'enabled' => !isset($settings['user_level_s']) || !empty($settings['user_level_s']),
'public_display' => !isset($settings['user_level_public_display']) || !empty($settings['user_level_public_display']),
'max' => max(3, min(30, (int) ($settings['user_level_max'] ?? 10))),
],
'medal' => $medal,
// Alias for existing call sites that still read badge.*; dictionary is derived from medal catalog.
'badge' => [
'enabled' => $medal['enabled'],
'public_display' => $medal['public_display'],
'limit' => $medal['limit'],
'dictionary' => array_values(array_map(static function (array $item): array {
return [
'slug' => $item['slug'],
'label' => $item['name'],
'tone' => 'info',
'desc' => (string) ($item['desc'] ?? ''),
'icon_url' => $item['icon'],
];
}, $medal['items_by_slug'])),
],
'report' => [
// 默认开启:对齐后台 schema default true 与参考主题 _pz('user_report_s', true)。
'enabled' => (bool) ($settings['user_report_s'] ?? $settings['user_report_enabled'] ?? true),
'public_entry' => !isset($settings['user_report_public_entry']) || !empty($settings['user_report_public_entry']),
],
];
}
/**
* 禁封/举报分区的运行时读取。
*
* get_all() 是裸 option:用户从未保存过“禁封/举报”分区时,
* ban_preset_reason / user_report_img_s / user_report_desc 等键不存在,
* schema 默认值只活在后台 UI 里。参考主题 _pz($key, $default) 的语义是
* 缺键即用默认值,因此这里改走 SchemaProvider 的带默认读取。
*/
private static function read_report_section(): array {
$settings = class_exists('\Jio\Admin\SchemaProvider')
? Settings::read_domain('users', 'report')
: [];
return is_array($settings) && $settings ? $settings : Settings::get_all();
}
/**
* 禁封设置
*/
public static function ban_settings(): array {
$settings = self::read_report_section();
$reasons_raw = $settings['ban_preset_reason'] ?? [];
$reasons = [];
foreach (is_array($reasons_raw) ? $reasons_raw : [] as $row) {
$text = is_array($row) ? sanitize_text_field((string) ($row['t'] ?? '')) : '';
if ($text !== '') {
$reasons[] = ['name' => $text];
}
}
// 申诉资料:group 数组 [{t:...}] 或旧版逗号分隔文本
$keys_raw = $settings['ban_appeal_keys'] ?? null;
$appeal_keys = [];
if (is_array($keys_raw)) {
foreach ($keys_raw as $row) {
$text = trim((string) ($row['t'] ?? ''));
if ($text !== '') {
$appeal_keys[] = sanitize_text_field($text);
}
}
} else {
$appeal_keys = array_filter(array_map('trim', explode(',', (string) $keys_raw)));
}
return [
'reasons' => $reasons,
'appeal_desc' => wp_kses_post((string) ($settings['ban_appeal_desc'] ?? '')),
'appeal_keys' => array_values($appeal_keys),
];
}
/**
* 举报设置
*/
public static function report_settings(): array {
$settings = self::read_report_section();
return [
// 默认开启:对齐后台 schema default true 与参考主题 _pz('user_report_s', true)。
'enabled' => (bool) ($settings['user_report_s'] ?? $settings['user_report_enabled'] ?? true),
'public_entry' => !isset($settings['user_report_public_entry']) || !empty($settings['user_report_public_entry']),
'img_enabled' => !empty($settings['user_report_img_s']),
'desc' => wp_kses_post((string) ($settings['user_report_desc'] ?? '')),
];
}
/**
* Parse structured user_medal_args catalog (rewrite; no badge dictionary dual-read).
*
* @param array<string,mixed> $settings
* @return array{enabled:bool,public_display:bool,limit:int,categories:array,items_by_slug:array}
*/
public static function parse_medal_catalog(array $settings): array {
$enabled = !empty($settings['user_medal_s']);
$public = !isset($settings['user_medal_public_display']) || !empty($settings['user_medal_public_display']);
$limit = max(1, min(8, (int) ($settings['user_medal_limit'] ?? 3)));
$allowed_types = [
'registration_time',
'checkin_all_day',
'new_post',
'comment',
'followed',
'like',
'comment_like',
'views',
'creator_income',
'affiliate_commission',
'manually_add',
];
$raw = $settings['user_medal_args'] ?? null;
if (is_string($raw) && $raw !== '') {
$decoded = json_decode($raw, true);
$raw = is_array($decoded) ? $decoded : [];
}
if (!is_array($raw)) {
$raw = [];
}
$categories = [];
$items_by_slug = [];
foreach ($raw as $cat) {
if (!is_array($cat)) {
continue;
}
$cat_name = sanitize_text_field((string) ($cat['cat_name'] ?? ''));
if ($cat_name === '') {
$cat_name = __('未命名', 'jio');
}
$items_out = [];
$items = is_array($cat['items'] ?? null) ? $cat['items'] : [];
foreach ($items as $item) {
if (!is_array($item)) {
continue;
}
$slug = sanitize_key((string) ($item['slug'] ?? ''));
$name = sanitize_text_field((string) ($item['name'] ?? ''));
if ($slug === '' || $name === '') {
continue;
}
$get_type = sanitize_key((string) ($item['get_type'] ?? 'manually_add'));
if (!in_array($get_type, $allowed_types, true)) {
$get_type = 'manually_add';
}
$icon = esc_url_raw((string) ($item['icon'] ?? ''));
$row = [
'slug' => $slug,
'name' => $name,
'desc' => sanitize_text_field((string) ($item['desc'] ?? '')),
'icon' => $icon,
'get_type' => $get_type,
'get_val' => max(0, (int) ($item['get_val'] ?? 0)),
'cat_name' => $cat_name,
];
$items_out[] = $row;
$items_by_slug[$slug] = $row;
}
$categories[] = [
'cat_name' => $cat_name,
'items' => $items_out,
];
}
return [
'enabled' => $enabled,
'public_display' => $public,
'limit' => $limit,
'categories' => $categories,
'items_by_slug' => $items_by_slug,
];
}
/**
* @return array<int,int>
*/
public static function parse_level_thresholds(string $raw): array {
$lines = preg_split('/[\r\n]+/', $raw, -1, PREG_SPLIT_NO_EMPTY);
$thresholds = [];
foreach ((array) $lines as $line) {
$parts = preg_split('/[|:,]/', trim($line), 2);
if (!is_array($parts) || count($parts) < 2) {
continue;
}
$level = (int) trim((string) $parts[0]);
$value = (int) trim((string) $parts[1]);
if ($level <= 0 || $value < 0) {
continue;
}
$thresholds[$level] = $value;
}
if (empty($thresholds)) {
$thresholds = [1 => 0, 2 => 30, 3 => 100, 4 => 300];
}
ksort($thresholds, SORT_NUMERIC);
return $thresholds;
}
}