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>
This commit is contained in:
kate522andClaude Opus 5 committed 2026-11-01 20:05:04 +08:00
1 parent 5b16bc6e15
commit 35bb96e82f
997 files changed
+28216 -28188

No files matched your search

+26 -26
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -52,8 +52,8 @@ class AIController extends ControllerBase {
if (current_user_can('edit_posts')) {
return true;
}
if (class_exists('\AKSRT\Core\FrontSubmit')) {
return (bool) \AKSRT\Core\FrontSubmit::get_instance()->can_submit();
if (class_exists('\Jio\Core\FrontSubmit')) {
return (bool) \Jio\Core\FrontSubmit::get_instance()->can_submit();
}
return false;
}
@@ -62,8 +62,8 @@ class AIController extends ControllerBase {
* POST /ai/seo-generate — 从 post/term 实体生成 SEO 三字段。
*/
public function seo_generate(\WP_REST_Request $request): \WP_REST_Response {
if (!class_exists('\AKSRT\Ai\AISEO')) {
return $this->error(__('AI SEO 模块未加载', 'aksrt'), 500);
if (!class_exists('\Jio\Ai\AISEO')) {
return $this->error(__('AI SEO 模块未加载', 'jio'), 500);
}
$params = $this->get_json_params($request);
@@ -71,30 +71,30 @@ class AIController extends ControllerBase {
$id = absint($params['id'] ?? 0);
if (!in_array($type, ['post', 'term'], true) || $id <= 0) {
return $this->error(__('参数错误', 'aksrt'), 422);
return $this->error(__('参数错误', 'jio'), 422);
}
if ($type === 'post') {
if (!current_user_can('edit_post', $id)) {
return $this->error(__('权限不足,无法编辑此文章', 'aksrt'), 403);
return $this->error(__('权限不足,无法编辑此文章', 'jio'), 403);
}
} else {
if (!current_user_can('edit_term', $id)) {
return $this->error(__('权限不足,无法编辑此分类/标签', 'aksrt'), 403);
return $this->error(__('权限不足,无法编辑此分类/标签', 'jio'), 403);
}
}
$result = \AKSRT\Ai\AISEO::get_instance()->generate_fields($type, $id);
$result = \Jio\Ai\AISEO::get_instance()->generate_fields($type, $id);
if (empty($result['success'])) {
return $this->error((string) ($result['message'] ?? __('生成失败', 'aksrt')));
return $this->error((string) ($result['message'] ?? __('生成失败', 'jio')));
}
return $this->success(['data' => $result['data']]);
}
public function summary_suggest(\WP_REST_Request $request): \WP_REST_Response {
if (!class_exists('\AKSRT\Ai\AI') || !class_exists('\AKSRT\Ai\AIPrompt')) {
return $this->error(__('AI 模块未加载', 'aksrt'), 500);
if (!class_exists('\Jio\Ai\AI') || !class_exists('\Jio\Ai\AIPrompt')) {
return $this->error(__('AI 模块未加载', 'jio'), 500);
}
$params = $this->get_json_params($request);
@@ -114,19 +114,19 @@ class AIController extends ControllerBase {
$max_len = max(50, min(500, (int) ($params['max_length'] ?? 200)));
if ($content === '') {
return $this->error(__('内容不能为空,请先在编辑器填写正文后再生成摘要', 'aksrt'), 422);
return $this->error(__('内容不能为空,请先在编辑器填写正文后再生成摘要', 'jio'), 422);
}
$ai = \AKSRT\Ai\AI::get_instance();
$ai = \Jio\Ai\AI::get_instance();
if (!$ai->is_available()) {
return $this->error(
__('AI 服务不可用,请配置 WordPress AI Connectors。', 'aksrt'),
__('AI 服务不可用,请配置 WordPress AI Connectors。', 'jio'),
503
);
}
// Prefer excerpt-oriented prompt when title is present.
$prompt_builder = \AKSRT\Ai\AIPrompt::get_instance();
$prompt_builder = \Jio\Ai\AIPrompt::get_instance();
if ($title !== '' && method_exists($prompt_builder, 'build_excerpt_prompt')) {
$prompt = $prompt_builder->build_excerpt_prompt($title, $content);
} else {
@@ -136,13 +136,13 @@ class AIController extends ControllerBase {
$result = $ai->generate_text($prompt, [
'max_tokens' => min(600, max(120, $max_len * 2)),
'temperature' => 0.5,
'system_instruction' => __('你是中文内容编辑,只输出摘要正文,不要标题、前缀或 Markdown。', 'aksrt'),
'system_instruction' => __('你是中文内容编辑,只输出摘要正文,不要标题、前缀或 Markdown。', 'jio'),
]);
if (!empty($result['success'])) {
$summary = trim((string) ($result['content'] ?? ''));
if ($summary === '') {
return $this->error(__('AI 返回内容为空,请稍后重试或检查模型配置', 'aksrt'), 502);
return $this->error(__('AI 返回内容为空,请稍后重试或检查模型配置', 'jio'), 502);
}
// Soft trim to requested length (characters).
if (function_exists('mb_substr') && mb_strlen($summary) > $max_len + 20) {
@@ -158,7 +158,7 @@ class AIController extends ControllerBase {
$message = trim((string) ($result['message'] ?? ''));
if ($message === '') {
$message = __('生成失败', 'aksrt');
$message = __('生成失败', 'jio');
}
// Upstream/provider failures are not client validation errors.
@@ -173,8 +173,8 @@ class AIController extends ControllerBase {
}
public function tags_suggest(\WP_REST_Request $request): \WP_REST_Response {
if (!class_exists('\AKSRT\Ai\AI') || !class_exists('\AKSRT\Ai\AIPrompt')) {
return $this->error(__('AI 模块未加载', 'aksrt'), 500);
if (!class_exists('\Jio\Ai\AI') || !class_exists('\Jio\Ai\AIPrompt')) {
return $this->error(__('AI 模块未加载', 'jio'), 500);
}
$params = $this->get_json_params($request);
@@ -182,11 +182,11 @@ class AIController extends ControllerBase {
$content = wp_kses_post((string) ($params['content'] ?? ''));
if ($title === '' && trim(wp_strip_all_tags($content)) === '') {
return $this->error(__('标题或内容不能为空', 'aksrt'));
return $this->error(__('标题或内容不能为空', 'jio'));
}
$prompt = \AKSRT\Ai\AIPrompt::get_instance()->build_tags_prompt($title, $content);
$result = \AKSRT\Ai\AI::get_instance()->generate_text($prompt, [
$prompt = \Jio\Ai\AIPrompt::get_instance()->build_tags_prompt($title, $content);
$result = \Jio\Ai\AI::get_instance()->generate_text($prompt, [
'max_tokens' => 300,
'temperature' => 0.5,
]);
@@ -200,6 +200,6 @@ class AIController extends ControllerBase {
return $this->success(['data' => ['tags' => $tags]]);
}
return $this->error((string) ($result['message'] ?? __('生成失败', 'aksrt')));
return $this->error((string) ($result['message'] ?? __('生成失败', 'jio')));
}
}
+38 -38
View File
@@ -1,17 +1,17 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
class Admin {
private static $instance = null;
private const PAGE_SLUG = 'aksrt-settings';
private const API_NAMESPACE = 'aksrt/v1';
private const PAGE_SLUG = 'jio-settings';
private const API_NAMESPACE = 'jio/v1';
public static function get_instance(): self {
if (null === self::$instance) {
@@ -29,16 +29,16 @@ class Admin {
add_filter('script_loader_tag', [$this, 'add_module_type_attribute'], 10, 2);
add_action('rest_api_init', [$this, 'register_rest_routes']);
add_action('wp_ajax_aksrt_load_more_posts', [$this, 'ajax_load_more_posts']);
add_action('wp_ajax_nopriv_aksrt_load_more_posts', [$this, 'ajax_load_more_posts']);
add_action('wp_ajax_aksrt_load_more_comments', [$this, 'ajax_load_more_comments']);
add_action('wp_ajax_nopriv_aksrt_load_more_comments', [$this, 'ajax_load_more_comments']);
add_action('wp_ajax_jio_load_more_posts', [$this, 'ajax_load_more_posts']);
add_action('wp_ajax_nopriv_jio_load_more_posts', [$this, 'ajax_load_more_posts']);
add_action('wp_ajax_jio_load_more_comments', [$this, 'ajax_load_more_comments']);
add_action('wp_ajax_nopriv_jio_load_more_comments', [$this, 'ajax_load_more_comments']);
}
public function add_admin_menu(): void {
add_menu_page(
'AKSRT 主题设置',
'AKSRT',
'Jio 主题设置',
'Jio',
'manage_options',
self::PAGE_SLUG,
[$this, 'render_admin_page'],
@@ -48,8 +48,8 @@ class Admin {
}
public function render_admin_page(): void {
echo '<div class="wrap aksrt-admin-wrap">';
echo '<div id="aksrt-admin-app"></div>';
echo '<div class="wrap jio-admin-wrap">';
echo '<div id="jio-admin-app"></div>';
echo '</div>';
}
@@ -59,8 +59,8 @@ class Admin {
return;
}
$build_dir = trailingslashit(AKSRT_DIR) . 'assets/admin/build/';
$build_uri = trailingslashit(AKSRT_URI) . 'assets/admin/build/';
$build_dir = trailingslashit(JIO_DIR) . 'assets/admin/build/';
$build_uri = trailingslashit(JIO_URI) . 'assets/admin/build/';
$entry = $build_dir . 'index.js';
if (!is_file($entry)) {
@@ -69,7 +69,7 @@ class Admin {
return;
}
echo '<div class="notice notice-error"><p>';
echo esc_html__('AKSRT 管理后台构建缺失:请在 assets/admin 执行 npm run build,并完整部署 build/ 目录。', 'aksrt');
echo esc_html__('Jio 管理后台构建缺失:请在 assets/admin 执行 npm run build,并完整部署 build/ 目录。', 'jio');
echo '</p></div>';
});
return;
@@ -82,7 +82,7 @@ class Admin {
return;
}
echo '<div class="notice notice-error"><p>';
echo esc_html__('AKSRT 管理后台构建不完整(动态模块缺失),请重新完整部署 assets/admin/build/,不要只覆盖 index.js。缺失:', 'aksrt');
echo esc_html__('Jio 管理后台构建不完整(动态模块缺失),请重新完整部署 assets/admin/build/,不要只覆盖 index.js。缺失:', 'jio');
echo ' ' . esc_html(implode(', ', array_slice($missing_chunks, 0, 8)));
if (count($missing_chunks) > 8) {
echo '…';
@@ -102,7 +102,7 @@ class Admin {
$path = $build_dir . $css_file;
if (file_exists($path)) {
wp_enqueue_style(
'aksrt-admin-' . sanitize_key(str_replace(['.', '/'], '-', $css_file)),
'jio-admin-' . sanitize_key(str_replace(['.', '/'], '-', $css_file)),
$build_uri . $css_file,
[],
(string) filemtime($path)
@@ -111,12 +111,12 @@ class Admin {
}
// Use the same local FA5 pack as the public stack.
aksrt_enqueue_fontawesome();
jio_enqueue_fontawesome();
// Frontend SVG sprite (defines window.aksrt_svgs and injects the symbol
// Frontend SVG sprite (defines window.jio_svgs and injects the symbol
// sheet) so schema icon fields can preview built-in zibsvg values.
wp_enqueue_script(
'aksrt-svg-sprite',
'jio-svg-sprite',
get_template_directory_uri() . '/assets/js/runtime/svg-icon.js',
['jquery'],
(string) filemtime(get_template_directory() . '/assets/js/runtime/svg-icon.js'),
@@ -124,20 +124,20 @@ class Admin {
);
wp_enqueue_script(
'aksrt-admin-app',
'jio-admin-app',
$build_uri . 'index.js',
[],
(string) filemtime($entry),
true
);
wp_localize_script('aksrt-admin-app', 'aksrtAdminData', [
wp_localize_script('jio-admin-app', 'jioAdminData', [
'apiUrl' => esc_url_raw(rest_url(self::API_NAMESPACE)),
'nonce' => wp_create_nonce('wp_rest'),
'siteUrl' => home_url('/'),
'adminUrl' => admin_url('/'),
'themeVersion' => wp_get_theme()->get('Version') ?: '3.4.0-dev',
'themeUri' => defined('AKSRT_URI') ? AKSRT_URI : get_template_directory_uri(),
'themeUri' => defined('JIO_URI') ? JIO_URI : get_template_directory_uri(),
'buildOk' => $missing_chunks === [],
]);
@@ -211,14 +211,14 @@ class Admin {
}
public function add_module_type_attribute(string $tag, string $handle): string {
if ('aksrt-admin-app' === $handle) {
if ('jio-admin-app' === $handle) {
return str_replace('<script ', '<script type="module" ', $tag);
}
return $tag;
}
public function register_rest_routes(): void {
// FQCN required after PSR-4 move into AKSRT\Admin (unqualified class_exists skips all routes).
// FQCN required after PSR-4 move into Jio\Admin (unqualified class_exists skips all routes).
$controllers = [
GlobalController::class,
PageDisplayController::class,
@@ -233,7 +233,7 @@ class Admin {
CommentController::class,
MessageController::class,
AIController::class,
\AKSRT\Icons\IconController::class,
\Jio\Icons\IconController::class,
];
foreach ($controllers as $class_name) {
@@ -325,8 +325,8 @@ class Admin {
public function ajax_load_more_posts(): void {
$nonce = isset($_POST['nonce']) ? (string) wp_unslash($_POST['nonce']) : '';
if ($nonce === '' || !wp_verify_nonce($nonce, 'aksrt_public_nonce')) {
wp_send_json_error(['message' => __('安全验证失败', 'aksrt')], 403);
if ($nonce === '' || !wp_verify_nonce($nonce, 'jio_public_nonce')) {
wp_send_json_error(['message' => __('安全验证失败', 'jio')], 403);
}
$page = max(1, (int) ($_POST['page'] ?? 1));
@@ -338,7 +338,7 @@ class Admin {
if ($context === '') {
$context = 'archive';
}
$resolved = aksrt_resolve_post_list_render_config([
$resolved = jio_resolve_post_list_render_config([
'context' => $context,
'overrides' => $render_config,
]);
@@ -353,7 +353,7 @@ class Admin {
$query = new \WP_Query($query_vars);
ob_start();
aksrt_render_post_list($render_config, $query, true);
jio_render_post_list($render_config, $query, true);
$html = (string) ob_get_clean();
wp_send_json_success([
@@ -365,30 +365,30 @@ class Admin {
public function ajax_load_more_comments(): void {
$nonce = isset($_POST['nonce']) ? (string) wp_unslash($_POST['nonce']) : '';
if ($nonce === '' || !wp_verify_nonce($nonce, 'aksrt_public_nonce')) {
wp_send_json_error(['message' => __('安全验证失败', 'aksrt')], 403);
if ($nonce === '' || !wp_verify_nonce($nonce, 'jio_public_nonce')) {
wp_send_json_error(['message' => __('安全验证失败', 'jio')], 403);
}
$post_id = absint($_POST['post_id'] ?? 0);
$page = max(1, (int) ($_POST['page'] ?? 1));
if ($post_id <= 0) {
wp_send_json_error(['message' => __('无效的文章 ID', 'aksrt')], 400);
wp_send_json_error(['message' => __('无效的文章 ID', 'jio')], 400);
}
$order = sanitize_key((string) ($_POST['comment_order'] ?? $_POST['order'] ?? 'newest'));
$author_only = !empty($_POST['author_only']) || (isset($_POST['aksrt_author_only']) && (int) $_POST['aksrt_author_only'] === 1);
$author_only = !empty($_POST['author_only']) || (isset($_POST['jio_author_only']) && (int) $_POST['jio_author_only'] === 1);
if (!function_exists('aksrt_comments_query_for_post') || !function_exists('aksrt_comments_render_items_html')) {
wp_send_json_error(['message' => __('评论加载不可用', 'aksrt')]);
if (!function_exists('jio_comments_query_for_post') || !function_exists('jio_comments_render_items_html')) {
wp_send_json_error(['message' => __('评论加载不可用', 'jio')]);
}
$result = aksrt_comments_query_for_post($post_id, [
$result = jio_comments_query_for_post($post_id, [
'page' => $page,
'order' => $order,
'author_only' => $author_only,
]);
$html = aksrt_comments_render_items_html($result['comments'], $result['settings'], $result['order']);
$html = jio_comments_render_items_html($result['comments'], $result['settings'], $result['order']);
wp_send_json_success([
'html' => $html,
+2 -2
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
+7 -7
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -12,7 +12,7 @@ abstract class ControllerBase {
/**
* REST API namespace (shared with Admin).
*/
const API_NAMESPACE = 'aksrt/v1';
const API_NAMESPACE = 'jio/v1';
/**
* Register REST routes for this domain.
@@ -28,12 +28,12 @@ abstract class ControllerBase {
if ($request === null) {
return false;
}
return new \WP_Error('aksrt_admin_forbidden', __('无权执行此操作', 'aksrt'), ['status' => 403]);
return new \WP_Error('jio_admin_forbidden', __('无权执行此操作', 'jio'), ['status' => 403]);
}
if ($request !== null) {
$nonce = (string) $request->get_header('X-WP-Nonce');
if ($nonce === '' || !wp_verify_nonce($nonce, 'wp_rest')) {
return new \WP_Error('aksrt_rest_nonce', __('安全验证失败,请刷新页面后重试', 'aksrt'), ['status' => 403]);
return new \WP_Error('jio_rest_nonce', __('安全验证失败,请刷新页面后重试', 'jio'), ['status' => 403]);
}
}
return true;
@@ -109,10 +109,10 @@ abstract class ControllerBase {
$out[$key] = array_values(array_unique($ids));
break;
case 'color':
$out[$key] = \AKSRT\Core\Color::sanitize($val);
$out[$key] = \Jio\Core\Color::sanitize($val);
break;
case 'color_alpha':
$out[$key] = \AKSRT\Core\Color::sanitize($val, true);
$out[$key] = \Jio\Core\Color::sanitize($val, true);
break;
default:
$out[$key] = sanitize_text_field($val);
+54 -54
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -70,11 +70,11 @@ class ExtensionController extends ControllerBase {
// Build message
if ($installed === 0) {
$message = sprintf(__('已注册 %d 项扩展,均未启用。', 'aksrt'), $available);
$message = sprintf(__('已注册 %d 项扩展,均未启用。', 'jio'), $available);
} elseif ($unconfigured > 0) {
$message = sprintf(__('已启用 %d/%d 项扩展,%d 项待补全配置。', 'aksrt'), $installed, $available, $unconfigured);
$message = sprintf(__('已启用 %d/%d 项扩展,%d 项待补全配置。', 'jio'), $installed, $available, $unconfigured);
} else {
$message = sprintf(__('已启用 %d/%d 项扩展,全部就绪。', 'aksrt'), $installed, $available);
$message = sprintf(__('已启用 %d/%d 项扩展,全部就绪。', 'jio'), $installed, $available);
}
return $this->success([
@@ -140,14 +140,14 @@ class ExtensionController extends ControllerBase {
$ip = trim((string) ($params['ip'] ?? ''));
if ($ip === '' || !filter_var($ip, FILTER_VALIDATE_IP)) {
return $this->error(__('请输入有效的 IP 地址', 'aksrt'), 422);
return $this->error(__('请输入有效的 IP 地址', 'jio'), 422);
}
if (!class_exists('\AKSRT\Extensions\IPGeo')) {
return $this->error(__('IP 归属地服务不可用', 'aksrt'), 500);
if (!class_exists('\Jio\Extensions\IPGeo')) {
return $this->error(__('IP 归属地服务不可用', 'jio'), 500);
}
$geo = \AKSRT\Extensions\IPGeo::get_instance();
$geo = \Jio\Extensions\IPGeo::get_instance();
$status = $geo->get_status();
$result = $geo->lookup($ip, true);
@@ -157,12 +157,12 @@ class ExtensionController extends ControllerBase {
// Persist "verified" once a live lookup succeeds so overview cards leave "待补全".
if ($ok) {
$all = \AKSRT\Settings\Settings::get_all();
$all = \Jio\Settings\Settings::get_all();
if (empty($all['ip_geo_verified'])) {
$all['ip_geo_verified'] = true;
update_option('aksrt_settings', $all);
if (method_exists('\AKSRT\Settings\Settings', 'flush_settings_cache')) {
\AKSRT\Settings\Settings::flush_settings_cache();
update_option('jio_settings', $all);
if (method_exists('\Jio\Settings\Settings', 'flush_settings_cache')) {
\Jio\Settings\Settings::flush_settings_cache();
}
$status = $geo->get_status();
}
@@ -172,55 +172,55 @@ class ExtensionController extends ControllerBase {
$detail = trim((string) ($result['detail'] ?? ''));
switch ($reason) {
case 'private_ip':
$message = __('该地址为私有/保留 IP(如 127.0.0.1 / 内网),无法查询。请用公网 IP 测试,例如 114.114.114.114。', 'aksrt');
$message = __('该地址为私有/保留 IP(如 127.0.0.1 / 内网),无法查询。请用公网 IP 测试,例如 114.114.114.114。', 'jio');
break;
case 'invalid_ip':
$message = __('IP 格式无效。', 'aksrt');
$message = __('IP 格式无效。', 'jio');
break;
case 'missing_key':
$message = __('当前服务商 AppKey 未填写,请在「扩展 → IP归属地」补全凭据。', 'aksrt');
$message = __('当前服务商 AppKey 未填写,请在「扩展 → IP归属地」补全凭据。', 'jio');
break;
case 'provider_error':
// e.g. USERKEY_PLAT_NOMATCH / INVALID_USER_KEY from Amap
if (stripos($detail, 'USERKEY_PLAT_NOMATCH') !== false) {
$message = __('高德返回 USERKEY_PLAT_NOMATCH:当前 Key 平台类型不匹配。请在高德开放平台新建「Web服务」类型 Key,并开通 IP 定位,不要用 Web端(JS)/Android/iOS Key。', 'aksrt');
$message = __('高德返回 USERKEY_PLAT_NOMATCH:当前 Key 平台类型不匹配。请在高德开放平台新建「Web服务」类型 Key,并开通 IP 定位,不要用 Web端(JS)/Android/iOS Key。', 'jio');
} elseif (stripos($detail, 'INVALID_USER_KEY') !== false) {
$message = __('高德返回 INVALID_USER_KEY:AppKey 无效或未启用,请核对 Key 是否复制完整、是否已启用。', 'aksrt');
$message = __('高德返回 INVALID_USER_KEY:AppKey 无效或未启用,请核对 Key 是否复制完整、是否已启用。', 'jio');
} else {
$message = $detail !== ''
? sprintf(
/* translators: %s: provider error code/message */
__('服务商拒绝请求:%s。请检查 Key 权限(IP定位)与配额。', 'aksrt'),
__('服务商拒绝请求:%s。请检查 Key 权限(IP定位)与配额。', 'jio'),
$detail
)
: __('服务商返回错误,请检查 Key 权限与配额。', 'aksrt');
: __('服务商返回错误,请检查 Key 权限与配额。', 'jio');
}
break;
case 'http_error':
$message = $detail !== ''
? sprintf(
/* translators: %s: transport error */
__('无法访问归属地接口:%s。请检查服务器出站网络/防火墙。', 'aksrt'),
__('无法访问归属地接口:%s。请检查服务器出站网络/防火墙。', 'jio'),
$detail
)
: __('无法访问归属地接口,请检查服务器出站网络。', 'aksrt');
: __('无法访问归属地接口,请检查服务器出站网络。', 'jio');
break;
case 'provider_empty':
if (!empty($status['missing_fields'])) {
$message = sprintf(
/* translators: %s: missing config field keys */
__('当前服务商凭据不完整(缺少:%s)。', 'aksrt'),
__('当前服务商凭据不完整(缺少:%s)。', 'jio'),
implode(', ', (array) $status['missing_fields'])
);
} else {
$message = __('服务商未返回有效归属地,请换公网 IP 或检查 Key 权限。', 'aksrt');
$message = __('服务商未返回有效归属地,请换公网 IP 或检查 Key 权限。', 'jio');
}
break;
case 'disabled':
$message = __('IP 归属地功能未启用。', 'aksrt');
$message = __('IP 归属地功能未启用。', 'jio');
break;
default:
$message = $detail !== '' ? $detail : __('未查询到归属地信息。', 'aksrt');
$message = $detail !== '' ? $detail : __('未查询到归属地信息。', 'jio');
}
}
@@ -247,14 +247,14 @@ class ExtensionController extends ControllerBase {
$text = trim((string) ($params['text'] ?? ''));
if ($text === '') {
return $this->error(__('请输入要测试的文本内容', 'aksrt'), 422);
return $this->error(__('请输入要测试的文本内容', 'jio'), 422);
}
if (!class_exists('\AKSRT\Extensions\ContentAudit')) {
return $this->error(__('内容审核服务不可用', 'aksrt'), 500);
if (!class_exists('\Jio\Extensions\ContentAudit')) {
return $this->error(__('内容审核服务不可用', 'jio'), 500);
}
$audit = \AKSRT\Extensions\ContentAudit::get_instance();
$audit = \Jio\Extensions\ContentAudit::get_instance();
$status = $audit->get_status();
$result = $audit->audit_text($text, true);
@@ -273,43 +273,43 @@ class ExtensionController extends ControllerBase {
if (!empty($status['missing_fields'])) {
$message = sprintf(
/* translators: %s: missing config field keys */
__('当前审核服务商凭据不完整(缺少:%s),请先在「扩展 → 内容审核」补全配置。', 'aksrt'),
__('当前审核服务商凭据不完整(缺少:%s),请先在「扩展 → 内容审核」补全配置。', 'jio'),
implode(', ', (array) $status['missing_fields'])
);
} else {
$message = __('审核服务商凭据缺失,请先补全 API Key / Secret。', 'aksrt');
$message = __('审核服务商凭据缺失,请先补全 API Key / Secret。', 'jio');
}
$ok = false;
break;
case 'api_error':
$message = __('调用审核接口失败,请检查凭据、网络出口或服务商状态。', 'aksrt');
$message = __('调用审核接口失败,请检查凭据、网络出口或服务商状态。', 'jio');
$ok = false;
break;
case 'disabled':
$message = __('内容审核功能未启用。', 'aksrt');
$message = __('内容审核功能未启用。', 'jio');
$ok = false;
break;
case 'non_compliant':
$message = __('审核结论:不合规(已拦截)。', 'aksrt');
$message = __('审核结论:不合规(已拦截)。', 'jio');
break;
case 'suspected':
$message = __('审核结论:疑似违规。', 'aksrt');
$message = __('审核结论:疑似违规。', 'jio');
break;
case 'review_failed':
$message = __('审核结论:服务商审核失败。', 'aksrt');
$message = __('审核结论:服务商审核失败。', 'jio');
break;
default:
if (!$ok) {
$message = $reason !== ''
? sprintf(/* translators: %s: provider reason */ __('审核未通过:%s', 'aksrt'), $reason)
: __('审核未通过。', 'aksrt');
? sprintf(/* translators: %s: provider reason */ __('审核未通过:%s', 'jio'), $reason)
: __('审核未通过。', 'jio');
}
}
} elseif ($ok) {
if ($reason === 'passthrough' || $reason === '') {
$message = __('审核通过。', 'aksrt');
$message = __('审核通过。', 'jio');
} else {
$message = sprintf(/* translators: %s: provider reason */ __('审核通过(%s)。', 'aksrt'), $reason);
$message = sprintf(/* translators: %s: provider reason */ __('审核通过(%s)。', 'jio'), $reason);
}
}
@@ -331,22 +331,22 @@ class ExtensionController extends ControllerBase {
$services = [];
// SMS
if (class_exists('\AKSRT\Sms\SMS')) {
$services['sms'] = \AKSRT\Sms\SMS::get_instance()->get_provider_status();
if (class_exists('\Jio\Sms\SMS')) {
$services['sms'] = \Jio\Sms\SMS::get_instance()->get_provider_status();
} else {
$services['sms'] = ['enabled' => false, 'provider' => 'none', 'configured' => false, 'available' => false, 'verified' => false, 'transport' => '', 'missing_fields' => []];
}
// WeChat
if (class_exists('\AKSRT\Payment\WeChatNotify')) {
$services['wechat'] = \AKSRT\Payment\WeChatNotify::get_instance()->get_status();
if (class_exists('\Jio\Payment\WeChatNotify')) {
$services['wechat'] = \Jio\Payment\WeChatNotify::get_instance()->get_status();
} else {
$services['wechat'] = ['enabled' => false, 'provider' => '', 'configured' => false, 'available' => false, 'verified' => false, 'transport' => '', 'missing_fields' => []];
}
// Captcha — 唯一真相源 aksrt_captcha_config
$captcha = function_exists('aksrt_get_captcha_status_summary')
? aksrt_get_captcha_status_summary()
// Captcha — 唯一真相源 jio_captcha_config
$captcha = function_exists('jio_get_captcha_status_summary')
? jio_get_captcha_status_summary()
: ['enabled' => false, 'provider' => '', 'configured' => false, 'scenes' => []];
$services['captcha'] = [
'enabled' => !empty($captcha['enabled']) && !empty($captcha['configured']),
@@ -360,22 +360,22 @@ class ExtensionController extends ControllerBase {
];
// IP Geo
if (class_exists('\AKSRT\Extensions\IPGeo')) {
$services['ip_geo'] = \AKSRT\Extensions\IPGeo::get_instance()->get_status();
if (class_exists('\Jio\Extensions\IPGeo')) {
$services['ip_geo'] = \Jio\Extensions\IPGeo::get_instance()->get_status();
} else {
$services['ip_geo'] = ['enabled' => false, 'provider' => '', 'configured' => false, 'available' => false, 'verified' => false, 'transport' => '', 'missing_fields' => []];
}
// Content Audit
if (class_exists('\AKSRT\Extensions\ContentAudit')) {
$services['audit'] = \AKSRT\Extensions\ContentAudit::get_instance()->get_status();
if (class_exists('\Jio\Extensions\ContentAudit')) {
$services['audit'] = \Jio\Extensions\ContentAudit::get_instance()->get_status();
} else {
$services['audit'] = ['enabled' => false, 'provider' => '', 'configured' => false, 'available' => false, 'verified' => false, 'transport' => '', 'missing_fields' => []];
}
// Search Push
if (class_exists('\AKSRT\Extensions\SearchPush')) {
$services['push'] = \AKSRT\Extensions\SearchPush::get_instance()->get_status();
if (class_exists('\Jio\Extensions\SearchPush')) {
$services['push'] = \Jio\Extensions\SearchPush::get_instance()->get_status();
} else {
$services['push'] = ['enabled' => false, 'provider' => '', 'configured' => false, 'available' => false, 'verified' => false, 'transport' => '', 'missing_fields' => []];
}
+31 -31
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -114,7 +114,7 @@ class GlobalController extends ControllerBase {
public function rest_handle_cms_features(\WP_REST_Request $request): \WP_REST_Response {
if ($request->get_method() === 'GET') {
return $this->success(['cms_features' => \AKSRT\Settings\Settings::get_cms_features()]);
return $this->success(['cms_features' => \Jio\Settings\Settings::get_cms_features()]);
}
$params = $this->get_json_params($request);
@@ -123,27 +123,27 @@ class GlobalController extends ControllerBase {
'search_include_resource' => 'bool',
];
$sanitized = $this->sanitize_fields($params, $allowed);
$settings = \AKSRT\Settings\Settings::get_all();
$settings = \Jio\Settings\Settings::get_all();
$settings['cms_features'] = array_merge(
is_array($settings['cms_features'] ?? null) ? $settings['cms_features'] : [],
$sanitized
);
update_option('aksrt_settings', $settings);
\AKSRT\Settings\Settings::flush_settings_cache();
update_option('jio_settings', $settings);
\Jio\Settings\Settings::flush_settings_cache();
return $this->success(['message' => 'CMS 功能已保存', 'cms_features' => \AKSRT\Settings\Settings::get_cms_features()]);
return $this->success(['message' => 'CMS 功能已保存', 'cms_features' => \Jio\Settings\Settings::get_cms_features()]);
}
public function rest_test_mail(\WP_REST_Request $request): \WP_REST_Response {
$params = $this->get_json_params($request);
$to = sanitize_email($params['to'] ?? '');
if (!is_email($to)) {
return $this->error(__('请输入有效的邮箱地址', 'aksrt'));
return $this->error(__('请输入有效的邮箱地址', 'jio'));
}
$result = \AKSRT\Settings\Mail::send_test_email($to);
$result = \Jio\Settings\Mail::send_test_email($to);
if (is_wp_error($result)) {
return $this->error(__('邮件发送失败,详细原因已写入服务器错误日志', 'aksrt'));
return $this->error(__('邮件发送失败,详细原因已写入服务器错误日志', 'jio'));
}
return $this->success(['message' => '测试邮件已发送']);
}
@@ -169,12 +169,12 @@ class GlobalController extends ControllerBase {
public function rest_handle_friend_links(\WP_REST_Request $request): \WP_REST_Response {
if ($request->get_method() === 'GET') {
$links = aksrt_get_friend_links();
$links = jio_get_friend_links();
return rest_ensure_response(['code' => 'success', 'data' => $links]);
}
$params = $this->get_json_params($request);
$links = get_option('aksrt_friend_links', []);
$links = get_option('jio_friend_links', []);
$new_link = [
'id' => uniqid('fl_'),
'name' => sanitize_text_field($params['name'] ?? ''),
@@ -188,29 +188,29 @@ class GlobalController extends ControllerBase {
usort($links, function ($a, $b) {
return ($a['sort_order'] ?? 0) <=> ($b['sort_order'] ?? 0);
});
update_option('aksrt_friend_links', $links);
do_action('aksrt_friend_link_submitted', $new_link);
update_option('jio_friend_links', $links);
do_action('jio_friend_link_submitted', $new_link);
return rest_ensure_response(['code' => 'success', 'data' => $new_link, 'message' => __('Friend link added.', 'aksrt')]);
return rest_ensure_response(['code' => 'success', 'data' => $new_link, 'message' => __('Friend link added.', 'jio')]);
}
public function rest_delete_friend_link(\WP_REST_Request $request): \WP_REST_Response {
$id = $request->get_param('id');
$links = get_option('aksrt_friend_links', []);
$links = get_option('jio_friend_links', []);
$links = array_filter($links, function ($link) use ($id) {
return ($link['id'] ?? '') !== $id;
});
$links = array_values($links);
update_option('aksrt_friend_links', $links);
return rest_ensure_response(['code' => 'success', 'message' => __('Friend link deleted.', 'aksrt')]);
update_option('jio_friend_links', $links);
return rest_ensure_response(['code' => 'success', 'message' => __('Friend link deleted.', 'jio')]);
}
public function rest_reorder_friend_links(\WP_REST_Request $request): \WP_REST_Response {
$ordered_ids = $request->get_param('ids');
if (!is_array($ordered_ids)) {
return new \WP_REST_Response(['code' => 'error', 'message' => __('参数错误', 'aksrt')], 400);
return new \WP_REST_Response(['code' => 'error', 'message' => __('参数错误', 'jio')], 400);
}
$links = get_option('aksrt_friend_links', []);
$links = get_option('jio_friend_links', []);
$order_map = array_flip($ordered_ids);
usort($links, function ($a, $b) use ($order_map) {
$idx_a = $order_map[($a['id'] ?? '')] ?? 999;
@@ -220,14 +220,14 @@ class GlobalController extends ControllerBase {
foreach ($links as $index => &$link) {
$link['sort_order'] = $index;
}
update_option('aksrt_friend_links', $links);
return rest_ensure_response(['code' => 'success', 'message' => __('Order saved.', 'aksrt')]);
update_option('jio_friend_links', $links);
return rest_ensure_response(['code' => 'success', 'message' => __('Order saved.', 'jio')]);
}
public function rest_handle_footer_tabbar(\WP_REST_Request $request): \WP_REST_Response {
if ($request->get_method() === 'GET') {
$settings = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_footer_tabbar_settings()
$settings = class_exists('\Jio\Settings\Settings')
? \Jio\Settings\Settings::get_footer_tabbar_settings()
: ['enabled' => true, 'scroll_hide' => false, 'scroll_down_hide' => true, 'items' => []];
return rest_ensure_response(['code' => 'success', 'data' => $settings]);
}
@@ -239,7 +239,7 @@ class GlobalController extends ControllerBase {
if (count($items) > $max_items) {
return new \WP_REST_Response([
'code' => 'error',
'message' => sprintf(__('Footer tabbar supports at most %d items.', 'aksrt'), $max_items),
'message' => sprintf(__('Footer tabbar supports at most %d items.', 'jio'), $max_items),
], 400);
}
@@ -251,8 +251,8 @@ class GlobalController extends ControllerBase {
'icon_size' => max(16, min(32, (int) ($params['icon_size'] ?? 22))),
'text_size' => max(8, min(14, (int) ($params['text_size'] ?? 10))),
'show_text' => !empty($params['show_text']),
'active_color' => \AKSRT\Core\Color::sanitize($params['active_color'] ?? '') ?: '#f04494',
'bg_color' => \AKSRT\Core\Color::sanitize($params['bg_color'] ?? '') ?: '',
'active_color' => \Jio\Core\Color::sanitize($params['active_color'] ?? '') ?: '#f04494',
'bg_color' => \Jio\Core\Color::sanitize($params['bg_color'] ?? '') ?: '',
'border' => !empty($params['border']),
'shadow' => !empty($params['shadow']),
'animation' => !empty($params['animation']),
@@ -260,13 +260,13 @@ class GlobalController extends ControllerBase {
'badge_dot' => !empty($params['badge_dot']),
'items' => $items,
];
update_option('aksrt_footer_tabbar', $tabbar);
update_option('jio_footer_tabbar', $tabbar);
if (class_exists('\AKSRT\Settings\Settings')) {
\AKSRT\Settings\Settings::flush_settings_cache();
if (class_exists('\Jio\Settings\Settings')) {
\Jio\Settings\Settings::flush_settings_cache();
}
return rest_ensure_response(['code' => 'success', 'message' => __('Footer tabbar settings saved.', 'aksrt')]);
return rest_ensure_response(['code' => 'success', 'message' => __('Footer tabbar settings saved.', 'jio')]);
}
}
+14 -14
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -14,14 +14,14 @@ trait GlobalControllerPart1 {
$ordered_ids = $params['ids'] ?? [];
if (!is_array($ordered_ids)) {
return new \WP_REST_Response(['code' => 'error', 'message' => __('参数错误', 'aksrt')], 400);
return new \WP_REST_Response(['code' => 'error', 'message' => __('参数错误', 'jio')], 400);
}
if (count($ordered_ids) > 5) {
return new \WP_REST_Response(['code' => 'error', 'message' => __('Footer tabbar supports at most 5 items.', 'aksrt')], 400);
return new \WP_REST_Response(['code' => 'error', 'message' => __('Footer tabbar supports at most 5 items.', 'jio')], 400);
}
$tabbar = get_option('aksrt_footer_tabbar', []);
$tabbar = get_option('jio_footer_tabbar', []);
$items = $tabbar['items'] ?? [];
$order_map = array_flip($ordered_ids);
@@ -36,9 +36,9 @@ trait GlobalControllerPart1 {
}
$tabbar['items'] = $items;
update_option('aksrt_footer_tabbar', $tabbar);
update_option('jio_footer_tabbar', $tabbar);
return rest_ensure_response(['code' => 'success', 'message' => __('Order saved.', 'aksrt')]);
return rest_ensure_response(['code' => 'success', 'message' => __('Order saved.', 'jio')]);
}
private function sanitize_tabbar_item(array $item): array {
@@ -56,7 +56,7 @@ trait GlobalControllerPart1 {
if (!empty($item['add_items']) && is_array($item['add_items'])) {
foreach ($item['add_items'] as $sub_item) {
$label = sanitize_text_field($sub_item['label'] ?? '');
$icon = aksrt_sanitize_icon_value((string) ($sub_item['icon'] ?? 'fa fas fa-pen'));
$icon = jio_sanitize_icon_value((string) ($sub_item['icon'] ?? 'fa fas fa-pen'));
$url = esc_url_raw($sub_item['url'] ?? '');
if ($label === '') {
continue;
@@ -73,7 +73,7 @@ trait GlobalControllerPart1 {
return [
'id' => sanitize_text_field($item['id'] ?? uniqid('tab_')),
'type' => $type,
'icon' => aksrt_sanitize_icon_value((string) ($item['icon'] ?? '')),
'icon' => jio_sanitize_icon_value((string) ($item['icon'] ?? '')),
'text' => sanitize_text_field($item['text'] ?? ''),
'link' => esc_url_raw($item['link'] ?? ''),
'badge' => sanitize_text_field($item['badge'] ?? ''),
@@ -93,16 +93,16 @@ trait GlobalControllerPart1 {
public function rest_handle_floating_buttons(\WP_REST_Request $request): \WP_REST_Response {
if ($request->get_method() === 'GET') {
return rest_ensure_response(['code' => 'success', 'data' => \AKSRT\Settings\FloatButtons::get()]);
return rest_ensure_response(['code' => 'success', 'data' => \Jio\Settings\FloatButtons::get()]);
}
$settings = \AKSRT\Settings\FloatButtons::sanitize($this->get_json_params($request));
update_option(\AKSRT\Settings\FloatButtons::OPTION, $settings);
\AKSRT\Settings\Settings::flush_settings_cache();
$settings = \Jio\Settings\FloatButtons::sanitize($this->get_json_params($request));
update_option(\Jio\Settings\FloatButtons::OPTION, $settings);
\Jio\Settings\Settings::flush_settings_cache();
return rest_ensure_response([
'code' => 'success',
'message' => __('Floating button settings saved.', 'aksrt'),
'message' => __('Floating button settings saved.', 'jio'),
'data' => $settings,
]);
}
+16 -16
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -11,7 +11,7 @@ trait GlobalControllerPart2 {
public function rest_handle_category_recommend(\WP_REST_Request $request): \WP_REST_Response {
if ($request->get_method() === 'GET') {
$settings = \AKSRT\Settings\Settings::get_category_recommend_settings();
$settings = \Jio\Settings\Settings::get_category_recommend_settings();
return rest_ensure_response(['code' => 'success', 'data' => $settings]);
}
@@ -30,10 +30,10 @@ trait GlobalControllerPart2 {
'category_recommend_show_dates' => !empty($params['category_recommend_show_dates']),
'category_recommend_show_more_link' => !empty($params['category_recommend_show_more_link']),
];
update_option('aksrt_category_recommend', $settings);
\AKSRT\Settings\Settings::flush_category_recommend_cache();
update_option('jio_category_recommend', $settings);
\Jio\Settings\Settings::flush_category_recommend_cache();
return rest_ensure_response(['code' => 'success', 'message' => __('Category recommendation settings saved.', 'aksrt')]);
return rest_ensure_response(['code' => 'success', 'message' => __('Category recommendation settings saved.', 'jio')]);
}
public function rest_get_categories(\WP_REST_Request $request): \WP_REST_Response {
@@ -62,9 +62,9 @@ trait GlobalControllerPart2 {
'taxonomy' => $cat->taxonomy,
'count' => (int) $cat->count,
'description' => $cat->description,
'cover_id' => $is_category ? (int) get_term_meta($cat->term_id, 'aksrt_category_cover_id', true) : 0,
'cover_url' => $is_category ? \AKSRT\Settings\Settings::get_category_cover_url((int) $cat->term_id) : '',
'recommend_desc' => $is_category ? (string) get_term_meta($cat->term_id, 'aksrt_category_recommend_desc', true) : '',
'cover_id' => $is_category ? (int) get_term_meta($cat->term_id, 'jio_category_cover_id', true) : 0,
'cover_url' => $is_category ? \Jio\Settings\Settings::get_category_cover_url((int) $cat->term_id) : '',
'recommend_desc' => $is_category ? (string) get_term_meta($cat->term_id, 'jio_category_recommend_desc', true) : '',
];
}, $categories);
@@ -102,7 +102,7 @@ trait GlobalControllerPart2 {
$id = (int) $request->get_param('id');
$term = get_term($id, 'category');
if (!$term || is_wp_error($term)) {
return new \WP_REST_Response(['code' => 'error', 'message' => __('Category does not exist.', 'aksrt')], 404);
return new \WP_REST_Response(['code' => 'error', 'message' => __('Category does not exist.', 'jio')], 404);
}
$params = $this->get_json_params($request);
@@ -110,19 +110,19 @@ trait GlobalControllerPart2 {
$recommend_desc = sanitize_textarea_field($params['recommend_desc'] ?? '');
if ($cover_id > 0) {
update_term_meta($id, 'aksrt_category_cover_id', $cover_id);
update_term_meta($id, 'jio_category_cover_id', $cover_id);
} else {
delete_term_meta($id, 'aksrt_category_cover_id');
delete_term_meta($id, 'jio_category_cover_id');
}
if ($recommend_desc !== '') {
update_term_meta($id, 'aksrt_category_recommend_desc', $recommend_desc);
update_term_meta($id, 'jio_category_recommend_desc', $recommend_desc);
} else {
delete_term_meta($id, 'aksrt_category_recommend_desc');
delete_term_meta($id, 'jio_category_recommend_desc');
}
\AKSRT\Settings\Settings::flush_category_recommend_cache();
\Jio\Settings\Settings::flush_category_recommend_cache();
return rest_ensure_response(['code' => 'success', 'message' => __('分类元数据已保存', 'aksrt')]);
return rest_ensure_response(['code' => 'success', 'message' => __('分类元数据已保存', 'jio')]);
}
}
+33 -33
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -13,8 +13,8 @@ class ImportExportController extends ControllerBase {
private static $instance = null;
private const SCHEDULED_BACKUP_HOOK = 'aksrt_settings_scheduled_backup';
private const MONTHLY_SCHEDULE = 'aksrt_monthly';
private const SCHEDULED_BACKUP_HOOK = 'jio_settings_scheduled_backup';
private const MONTHLY_SCHEDULE = 'jio_monthly';
private function __construct() {
add_filter('cron_schedules', [$this, 'register_backup_schedules']);
@@ -138,37 +138,37 @@ class ImportExportController extends ControllerBase {
foreach ($token as $key => $value) {
$token[$key] = is_scalar($value) ? sanitize_text_field((string) $value) : '';
}
$config = function_exists('aksrt_get_captcha_config') ? aksrt_get_captcha_config() : [];
$config = function_exists('jio_get_captcha_config') ? jio_get_captcha_config() : [];
$stored_provider = (string) ($config['active_provider'] ?? '');
if ($provider === '' || $provider !== $stored_provider || !in_array($provider, aksrt_captcha_providers(), true)) {
return $this->error(__('请先保存并启用要测试的人机验证服务商', 'aksrt'), 422);
if ($provider === '' || $provider !== $stored_provider || !in_array($provider, jio_captcha_providers(), true)) {
return $this->error(__('请先保存并启用要测试的人机验证服务商', 'jio'), 422);
}
$provider_config = isset($config[$provider]) && is_array($config[$provider]) ? $config[$provider] : [];
if (!aksrt_captcha_provider_is_configured($provider, $provider_config)) {
return $this->error(__('当前人机验证服务商配置不完整', 'aksrt'), 422);
if (!jio_captcha_provider_is_configured($provider, $provider_config)) {
return $this->error(__('当前人机验证服务商配置不完整', 'jio'), 422);
}
$scene = 'admin-captcha-test';
$action = 'aksrt-admin-test';
$result = aksrt_verify_captcha_provider($provider, $provider_config, $token, aksrt_get_client_ip(), $scene, $action);
$action = 'jio-admin-test';
$result = jio_verify_captcha_provider($provider, $provider_config, $token, jio_get_client_ip(), $scene, $action);
if (is_wp_error($result)) {
return $this->error($result->get_error_message(), 422);
}
return $this->success(['message' => __('服务端同源验签通过', 'aksrt')]);
return $this->success(['message' => __('服务端同源验签通过', 'jio')]);
}
/**
* Export all AKSRT settings as a JSON-serializable array.
* Export all Jio settings as a JSON-serializable array.
*/
public function rest_export_settings(\WP_REST_Request $request): \WP_REST_Response {
$schema_provider = SchemaProvider::get_instance();
$domains = $schema_provider->get_domain_keys();
$export = [
'version' => AKSRT_VERSION ?? '3.4.0',
'version' => JIO_VERSION ?? '3.4.0',
'exported' => gmdate('Y-m-d H:i:s'),
'domains' => [],
];
@@ -181,7 +181,7 @@ class ImportExportController extends ControllerBase {
}
// Also export standalone options from the registry
$standalone_keys = \AKSRT\Settings\Settings::standalone_export_keys();
$standalone_keys = \Jio\Settings\Settings::standalone_export_keys();
$export['standalone'] = [];
foreach ($standalone_keys as $key) {
@@ -191,7 +191,7 @@ class ImportExportController extends ControllerBase {
}
}
$export = \AKSRT\Settings\Settings::redact_sensitive_settings($export);
$export = \Jio\Settings\Settings::redact_sensitive_settings($export);
return $this->success(['data' => $export]);
}
@@ -215,7 +215,7 @@ class ImportExportController extends ControllerBase {
// Import domain settings
if (!$dry_run) {
$backup = $this->create_backup_record('manual', __('导入前自动快照', 'aksrt'));
$backup = $this->create_backup_record('manual', __('导入前自动快照', 'jio'));
$this->persist_setting_backup($backup);
}
@@ -250,12 +250,12 @@ class ImportExportController extends ControllerBase {
// Import standalone options. Secret-bearing options are never accepted from exports.
$standalone = !empty($params['standalone']) && is_array($params['standalone'])
? \AKSRT\Settings\Settings::filter_sensitive_import($params['standalone'])
? \Jio\Settings\Settings::filter_sensitive_import($params['standalone'])
: [];
if ($standalone !== []) {
foreach ($standalone as $key => $value) {
if (!preg_match('/^aksrt_/', $key)) {
$results[$key] = ['status' => 'skipped', 'message' => 'Not an AKSRT option'];
if (!preg_match('/^jio_/', $key)) {
$results[$key] = ['status' => 'skipped', 'message' => 'Not an Jio option'];
continue;
}
if (!$dry_run) {
@@ -268,7 +268,7 @@ class ImportExportController extends ControllerBase {
}
if (!$dry_run) {
\AKSRT\Settings\Settings::flush_settings_cache();
\Jio\Settings\Settings::flush_settings_cache();
}
return $this->success([
@@ -288,7 +288,7 @@ class ImportExportController extends ControllerBase {
return $this->error('未知的设置域: ' . esc_html($domain), 404);
}
$backup = $this->create_backup_record('reset_domain', sprintf(__('重置 %s 前快照', 'aksrt'), $domain));
$backup = $this->create_backup_record('reset_domain', sprintf(__('重置 %s 前快照', 'jio'), $domain));
$this->persist_setting_backup($backup);
$schema_provider->reset_to_defaults($domain);
@@ -303,17 +303,17 @@ class ImportExportController extends ControllerBase {
public function rest_reset_all(\WP_REST_Request $request): \WP_REST_Response {
$schema_provider = SchemaProvider::get_instance();
$backup = $this->create_backup_record('reset_all', __('重置全部前快照', 'aksrt'));
$backup = $this->create_backup_record('reset_all', __('重置全部前快照', 'jio'));
$this->persist_setting_backup($backup);
foreach ($schema_provider->get_domain_keys() as $domain) {
$schema_provider->reset_to_defaults($domain);
}
\AKSRT\Settings\Settings::flush_settings_cache();
\Jio\Settings\Settings::flush_settings_cache();
return $this->success([
'message' => __('全部 schema 托管设置已重置为默认值', 'aksrt'),
'message' => __('全部 schema 托管设置已重置为默认值', 'jio'),
'data' => [
'backup_id' => $backup['id'],
],
@@ -353,12 +353,12 @@ class ImportExportController extends ControllerBase {
// Database tables
$expected_tables = [
$wpdb->prefix . 'aksrt_asset_account',
$wpdb->prefix . 'aksrt_asset_transaction',
$wpdb->prefix . 'aksrt_asset_entry',
$wpdb->prefix . 'aksrt_signin_log',
$wpdb->prefix . 'aksrt_dl_token',
$wpdb->prefix . 'aksrt_notification',
$wpdb->prefix . 'jio_asset_account',
$wpdb->prefix . 'jio_asset_transaction',
$wpdb->prefix . 'jio_asset_entry',
$wpdb->prefix . 'jio_signin_log',
$wpdb->prefix . 'jio_dl_token',
$wpdb->prefix . 'jio_notification',
];
$missing_tables = [];
foreach ($expected_tables as $table) {
@@ -374,10 +374,10 @@ class ImportExportController extends ControllerBase {
// Option count
$option_count = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE 'aksrt_%'"
"SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE 'jio_%'"
);
$checks['option_count'] = [
'label' => 'AKSRT 选项数',
'label' => 'Jio 选项数',
'value' => $option_count,
'status' => $option_count < 100 ? 'ok' : 'warning',
];
+17 -17
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -33,7 +33,7 @@ trait ImportExportControllerPart1 {
}
public function list_setting_backups(\WP_REST_Request $request): \WP_REST_Response {
$backups = get_option('aksrt_settings_backups', []);
$backups = get_option('jio_settings_backups', []);
if (!is_array($backups)) {
$backups = [];
}
@@ -64,14 +64,14 @@ trait ImportExportControllerPart1 {
$this->persist_setting_backup($backup);
return $this->success([
'message' => __('设置快照已创建', 'aksrt'),
'message' => __('设置快照已创建', 'jio'),
'data' => $backup,
]);
}
public function restore_setting_backup(\WP_REST_Request $request): \WP_REST_Response {
$id = (int) $request->get_param('id');
$backups = get_option('aksrt_settings_backups', []);
$backups = get_option('jio_settings_backups', []);
if (!is_array($backups)) {
$backups = [];
}
@@ -85,12 +85,12 @@ trait ImportExportControllerPart1 {
}
if (!$target || !is_array($target['payload'] ?? null)) {
return $this->error(__('备份快照不存在', 'aksrt'), 404);
return $this->error(__('备份快照不存在', 'jio'), 404);
}
$payload = $target['payload'];
$domains = $payload['domains'] ?? [];
$standalone = \AKSRT\Settings\Settings::filter_sensitive_import(
$standalone = \Jio\Settings\Settings::filter_sensitive_import(
is_array($payload['standalone'] ?? null) ? $payload['standalone'] : []
);
$provider = SchemaProvider::get_instance();
@@ -107,15 +107,15 @@ trait ImportExportControllerPart1 {
}
foreach ($standalone as $key => $value) {
if (is_string($key) && in_array($key, \AKSRT\Settings\Settings::standalone_export_keys(), true)) {
if (is_string($key) && in_array($key, \Jio\Settings\Settings::standalone_export_keys(), true)) {
update_option($key, $value);
}
}
\AKSRT\Settings\Settings::flush_settings_cache();
\Jio\Settings\Settings::flush_settings_cache();
return $this->success([
'message' => __('设置已从快照恢复', 'aksrt'),
'message' => __('设置已从快照恢复', 'jio'),
'data' => [
'id' => $id,
],
@@ -124,7 +124,7 @@ trait ImportExportControllerPart1 {
public function delete_setting_backup(\WP_REST_Request $request): \WP_REST_Response {
$id = (int) $request->get_param('id');
$backups = get_option('aksrt_settings_backups', []);
$backups = get_option('jio_settings_backups', []);
if (!is_array($backups)) {
$backups = [];
}
@@ -140,25 +140,25 @@ trait ImportExportControllerPart1 {
}
if (!$found) {
return $this->error(__('备份快照不存在', 'aksrt'), 404);
return $this->error(__('备份快照不存在', 'jio'), 404);
}
update_option('aksrt_settings_backups', array_values($filtered));
update_option('jio_settings_backups', array_values($filtered));
return $this->success([
'message' => __('备份快照已删除', 'aksrt'),
'message' => __('备份快照已删除', 'jio'),
]);
}
private function persist_setting_backup(array $backup): void {
$backups = get_option('aksrt_settings_backups', []);
$backups = get_option('jio_settings_backups', []);
if (!is_array($backups)) {
$backups = [];
}
array_unshift($backups, $backup);
update_option('aksrt_settings_backups', $this->trim_setting_backups($backups));
update_option('jio_settings_backups', $this->trim_setting_backups($backups));
}
private function create_backup_record(string $type, string $label = ''): array {
@@ -169,7 +169,7 @@ trait ImportExportControllerPart1 {
if (!isset($schedules[self::MONTHLY_SCHEDULE])) {
$schedules[self::MONTHLY_SCHEDULE] = [
'interval' => MONTH_IN_SECONDS,
'display' => __('每月', 'aksrt'),
'display' => __('每月', 'jio'),
];
}
+12 -12
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -30,7 +30,7 @@ trait ImportExportControllerPart2 {
$domains = $schema_provider->get_domain_keys();
$payload = [
'version' => AKSRT_VERSION ?? '3.4.0',
'version' => JIO_VERSION ?? '3.4.0',
'exported' => gmdate('Y-m-d H:i:s'),
'domains' => [],
'standalone'=> [],
@@ -43,14 +43,14 @@ trait ImportExportControllerPart2 {
];
}
foreach (\AKSRT\Settings\Settings::standalone_export_keys() as $key) {
foreach (\Jio\Settings\Settings::standalone_export_keys() as $key) {
$value = get_option($key);
if ($value !== false) {
$payload['standalone'][$key] = $value;
}
}
$payload = \AKSRT\Settings\Settings::redact_sensitive_settings($payload);
$payload = \Jio\Settings\Settings::redact_sensitive_settings($payload);
return [
'id' => time(),
@@ -62,7 +62,7 @@ trait ImportExportControllerPart2 {
}
private function trim_setting_backups(array $backups): array {
$retain = (int) (\AKSRT\Settings\Settings::get_value('backup_retain', 7));
$retain = (int) (\Jio\Settings\Settings::get_value('backup_retain', 7));
$retain = max(3, min(20, $retain));
if (count($backups) <= $retain) {
@@ -74,13 +74,13 @@ trait ImportExportControllerPart2 {
private function default_backup_label(string $type): string {
$map = [
'manual' => __('手动备份', 'aksrt'),
'scheduled' => __('定期备份', 'aksrt'),
'reset_all' => __('重置全部前快照', 'aksrt'),
'reset_domain' => __('重置分区前快照', 'aksrt'),
'update_theme' => __('更新主题前快照', 'aksrt'),
'manual' => __('手动备份', 'jio'),
'scheduled' => __('定期备份', 'jio'),
'reset_all' => __('重置全部前快照', 'jio'),
'reset_domain' => __('重置分区前快照', 'jio'),
'update_theme' => __('更新主题前快照', 'jio'),
];
return $map[$type] ?? __('设置快照', 'aksrt');
return $map[$type] ?? __('设置快照', 'jio');
}
}
+10 -10
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -63,11 +63,11 @@ class MessageController extends ControllerBase {
$per_page = min(100, (int) $request->get_param('per_page'));
$page = max(1, (int) $request->get_param('page'));
if (!class_exists('\AKSRT\Messages\Message')) {
if (!class_exists('\Jio\Messages\Message')) {
return $this->success(['data' => [], 'total' => 0]);
}
$msg = \AKSRT\Messages\Message::get_instance();
$msg = \Jio\Messages\Message::get_instance();
$args = [
'limit' => $per_page,
'offset' => ($page - 1) * $per_page,
@@ -87,29 +87,29 @@ class MessageController extends ControllerBase {
public function get_unread_count(\WP_REST_Request $request): \WP_REST_Response {
$user_id = get_current_user_id();
if (!class_exists('\AKSRT\Messages\Message')) {
if (!class_exists('\Jio\Messages\Message')) {
return $this->success(['data' => ['unread' => 0]]);
}
$count = \AKSRT\Messages\Message::get_instance()->get_unread_count($user_id);
$count = \Jio\Messages\Message::get_instance()->get_unread_count($user_id);
return $this->success(['data' => ['unread' => $count]]);
}
public function mark_read(\WP_REST_Request $request): \WP_REST_Response {
$user_id = get_current_user_id();
$id = (string) $request->get_param('id');
if (!class_exists('\AKSRT\Messages\Message')) {
if (!class_exists('\Jio\Messages\Message')) {
return $this->error('not_available', '消息系统未启用。', 501);
}
$result = \AKSRT\Messages\Message::get_instance()->mark_read($user_id, $id);
$result = \Jio\Messages\Message::get_instance()->mark_read($user_id, $id);
return $this->success(['data' => ['success' => $result]]);
}
public function mark_all_read(\WP_REST_Request $request): \WP_REST_Response {
$user_id = get_current_user_id();
if (!class_exists('\AKSRT\Messages\Message')) {
if (!class_exists('\Jio\Messages\Message')) {
return $this->error('not_available', '消息系统未启用。', 501);
}
$result = \AKSRT\Messages\Message::get_instance()->mark_all_read($user_id);
$result = \Jio\Messages\Message::get_instance()->mark_all_read($user_id);
return $this->success(['data' => ['success' => $result]]);
}
+8 -8
View File
@@ -1,6 +1,6 @@
<?php
/**
* @package AKSRT
* @package Jio
*
* 后台列表页分页条 —— 1:1 复刻「卡密管理」页的 zibpay_admin_pagenavi 结构。
*
@@ -20,7 +20,7 @@
* 这样的口径,四枚按钮全 disabled 也明确了「没有更多页」。
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
@@ -78,13 +78,13 @@ class AdminListPagination {
?>
<div class="tablenav bottom">
<div class="tablenav-pages">
<span class="displaying-num"><?php printf(esc_html__('每页 %1$s 共%2$s', 'aksrt'), esc_html((string) $per_page), esc_html((string) $total_count)); ?></span>
<span class="displaying-num"><?php printf(esc_html__('每页 %1$s 共%2$s', 'jio'), esc_html((string) $per_page), esc_html((string) $total_count)); ?></span>
<span class="pagination-links">
<a class="first-page button<?php echo $on_first ? ' disabled' : ''; ?>" title="<?php echo esc_attr__('前往第一页', 'aksrt'); ?>" href="<?php echo esc_url($first_page_url); ?>">«</a>
<a class="prev-page button<?php echo $on_first ? ' disabled' : ''; ?>" title="<?php echo esc_attr__('前往上一页', 'aksrt'); ?>" href="<?php echo esc_url($prev_page_url); ?>">‹</a>
<span class="paging-input"><?php printf(esc_html__('第 %1$s 页,共 %2$s 页', 'aksrt'), esc_html((string) $current_page), '<span class="total-pages">' . esc_html((string) $total_pages) . '</span>'); ?></span>
<a class="next-page button<?php echo $on_last ? ' disabled' : ''; ?>" title="<?php echo esc_attr__('前往下一页', 'aksrt'); ?>" href="<?php echo esc_url($next_page_url); ?>">›</a>
<a class="last-page button<?php echo $on_last ? ' disabled' : ''; ?>" title="<?php echo esc_attr__('前往最后一页', 'aksrt'); ?>" href="<?php echo esc_url($last_page_url); ?>">»</a>
<a class="first-page button<?php echo $on_first ? ' disabled' : ''; ?>" title="<?php echo esc_attr__('前往第一页', 'jio'); ?>" href="<?php echo esc_url($first_page_url); ?>">«</a>
<a class="prev-page button<?php echo $on_first ? ' disabled' : ''; ?>" title="<?php echo esc_attr__('前往上一页', 'jio'); ?>" href="<?php echo esc_url($prev_page_url); ?>">‹</a>
<span class="paging-input"><?php printf(esc_html__('第 %1$s 页,共 %2$s 页', 'jio'), esc_html((string) $current_page), '<span class="total-pages">' . esc_html((string) $total_pages) . '</span>'); ?></span>
<a class="next-page button<?php echo $on_last ? ' disabled' : ''; ?>" title="<?php echo esc_attr__('前往下一页', 'jio'); ?>" href="<?php echo esc_url($next_page_url); ?>">›</a>
<a class="last-page button<?php echo $on_last ? ' disabled' : ''; ?>" title="<?php echo esc_attr__('前往最后一页', 'jio'); ?>" href="<?php echo esc_url($last_page_url); ?>">»</a>
</span>
</div>
</div>
+49 -49
View File
@@ -2,16 +2,16 @@
/**
* 原生后台运营页共享 UI。
*
* 支付中心(aksrt_pay 菜单)与用户子菜单(users.php)的运营页统一使用这里的
* 支付中心(jio_pay 菜单)与用户子菜单(users.php)的运营页统一使用这里的
* 页面外壳、页头、统计卡、卡片、徽标与空状态。样式在 assets/css/admin/native-ui.css,
* 只在 SLUGS 列出的页面入队,规则全部限定在 .aksrt-native 下。
* 只在 SLUGS 列出的页面入队,规则全部限定在 .jio-native 下。
*
* 输出方法直接 echo,参数在内部转义;$html 形参约定为调用方已转义的片段。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
@@ -19,22 +19,22 @@ final class NativeUi {
/** 使用共享 UI 的 admin page 参数。 */
public const SLUGS = [
'aksrt_pay',
'aksrt_orders',
'aksrt_pay_products',
'aksrt_coupons',
'aksrt_charge_cards',
'aksrt_income',
'aksrt_withdraw',
'aksrt_invite_codes',
'aksrt_messages',
'aksrt_user_vip',
'aksrt_user_auth',
'aksrt_user_ban',
'aksrt_tickets',
'jio_pay',
'jio_orders',
'jio_pay_products',
'jio_coupons',
'jio_charge_cards',
'jio_income',
'jio_withdraw',
'jio_invite_codes',
'jio_messages',
'jio_user_vip',
'jio_user_auth',
'jio_user_ban',
'jio_tickets',
];
public const STYLE_HANDLE = 'aksrt-admin-native-ui';
public const STYLE_HANDLE = 'jio-admin-native-ui';
private const TONES = ['success', 'info', 'warning', 'danger', 'neutral', 'primary'];
@@ -59,8 +59,8 @@ final class NativeUi {
return;
}
$css = '/assets/css/admin/native-ui.css';
if (is_file(AKSRT_DIR . $css)) {
wp_enqueue_style(self::STYLE_HANDLE, AKSRT_URI . $css, [], (string) filemtime(AKSRT_DIR . $css));
if (is_file(JIO_DIR . $css)) {
wp_enqueue_style(self::STYLE_HANDLE, JIO_URI . $css, [], (string) filemtime(JIO_DIR . $css));
}
}
@@ -70,7 +70,7 @@ final class NativeUi {
/** 打开 .wrap 外壳。$modifier 追加到 class(如页面专属根类)。 */
public static function page_open(string $modifier = ''): void {
$class = trim('wrap aksrt-native ' . $modifier);
$class = trim('wrap jio-native ' . $modifier);
echo '<div class="' . esc_attr($class) . '">';
}
@@ -86,19 +86,19 @@ final class NativeUi {
* @param array<int, array{label:string,url?:string,dialog?:string,primary?:bool,icon?:string}> $actions
*/
public static function header(string $title, string $description = '', string $icon = '', array $actions = []): void {
echo '<div class="aksrt-native-header"><div class="aksrt-native-header__main">';
echo '<div class="jio-native-header"><div class="jio-native-header__main">';
// h1 让 WP 把 admin_notices 挪到标题之后。
echo '<h1 class="aksrt-native-header__title">';
echo '<h1 class="jio-native-header__title">';
if ($icon !== '') {
echo '<span class="aksrt-native-header__icon dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span>';
echo '<span class="jio-native-header__icon dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span>';
}
echo esc_html($title) . '</h1>';
if ($description !== '') {
echo '<p class="aksrt-native-header__desc">' . esc_html($description) . '</p>';
echo '<p class="jio-native-header__desc">' . esc_html($description) . '</p>';
}
echo '</div>';
if ($actions !== []) {
echo '<div class="aksrt-native-header__actions">';
echo '<div class="jio-native-header__actions">';
foreach ($actions as $action) {
echo isset($action['dialog'])
? self::button_dialog($action['label'], (string) $action['dialog'], !empty($action['primary']), (string) ($action['icon'] ?? ''))
@@ -117,11 +117,11 @@ final class NativeUi {
return '<a class="' . esc_attr($class) . '" href="' . esc_url($url) . '">' . $icon_html . esc_html($label) . '</a>';
}
/** 打开页面内 <dialog id="$dialog_id"> 的按钮;点击行为由页面脚本按 data-aksrt-dialog-open 绑定。 */
/** 打开页面内 <dialog id="$dialog_id"> 的按钮;点击行为由页面脚本按 data-jio-dialog-open 绑定。 */
public static function button_dialog(string $label, string $dialog_id, bool $primary = false, string $icon = ''): string {
$class = $primary ? 'button button-primary' : 'button';
$icon_html = $icon !== '' ? '<span class="dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span>' : '';
return '<button type="button" class="' . esc_attr($class) . '" data-aksrt-dialog-open="' . esc_attr($dialog_id) . '" aria-haspopup="dialog" aria-controls="' . esc_attr($dialog_id) . '">'
return '<button type="button" class="' . esc_attr($class) . '" data-jio-dialog-open="' . esc_attr($dialog_id) . '" aria-haspopup="dialog" aria-controls="' . esc_attr($dialog_id) . '">'
. $icon_html . esc_html($label) . '</button>';
}
@@ -136,7 +136,7 @@ final class NativeUi {
if ($items === []) {
return;
}
echo '<div class="aksrt-native-stats">';
echo '<div class="jio-native-stats">';
foreach ($items as $item) {
self::stat($item);
}
@@ -150,19 +150,19 @@ final class NativeUi {
$tag = $url !== '' ? 'a' : 'div';
$attr = $url !== '' ? ' href="' . esc_url($url) . '"' : '';
echo '<' . $tag . ' class="aksrt-native-stat aksrt-native-stat--' . esc_attr($tone) . '"' . $attr . '>';
echo '<span class="aksrt-native-stat__label">';
echo '<' . $tag . ' class="jio-native-stat jio-native-stat--' . esc_attr($tone) . '"' . $attr . '>';
echo '<span class="jio-native-stat__label">';
if (!empty($item['icon'])) {
echo '<span class="dashicons ' . esc_attr((string) $item['icon']) . '" aria-hidden="true"></span>';
}
echo esc_html($item['label']) . '</span>';
echo '<span class="aksrt-native-stat__value">';
echo '<span class="jio-native-stat__value">';
if (!empty($item['unit'])) {
echo '<span class="aksrt-native-stat__unit">' . esc_html((string) $item['unit']) . '</span>';
echo '<span class="jio-native-stat__unit">' . esc_html((string) $item['unit']) . '</span>';
}
echo esc_html((string) $item['value']) . '</span>';
if (!empty($item['hint'])) {
echo '<span class="aksrt-native-stat__hint">' . esc_html((string) $item['hint']) . '</span>';
echo '<span class="jio-native-stat__hint">' . esc_html((string) $item['hint']) . '</span>';
}
echo '</' . $tag . '>';
}
@@ -176,19 +176,19 @@ final class NativeUi {
* $flush=true 时正文无内边距(承载整表)。
*/
public static function card_open(string $title = '', string $actions_html = '', bool $flush = false, string $modifier = ''): void {
$class = 'aksrt-native-card' . ($flush ? ' aksrt-native-card--flush' : '') . ($modifier !== '' ? ' ' . $modifier : '');
$class = 'jio-native-card' . ($flush ? ' jio-native-card--flush' : '') . ($modifier !== '' ? ' ' . $modifier : '');
echo '<section class="' . esc_attr($class) . '">';
if ($title !== '' || $actions_html !== '') {
echo '<div class="aksrt-native-card__head">';
echo '<div class="jio-native-card__head">';
if ($title !== '') {
echo '<h2 class="aksrt-native-card__title">' . esc_html($title) . '</h2>';
echo '<h2 class="jio-native-card__title">' . esc_html($title) . '</h2>';
}
if ($actions_html !== '') {
echo '<div class="aksrt-native-card__actions">' . $actions_html . '</div>';
echo '<div class="jio-native-card__actions">' . $actions_html . '</div>';
}
echo '</div>';
}
echo '<div class="aksrt-native-card__body">';
echo '<div class="jio-native-card__body">';
}
public static function card_close(): void {
@@ -198,7 +198,7 @@ final class NativeUi {
/** 表格卡:整表铺满卡片,带横向滚动容器。 */
public static function table_card_open(string $title = '', string $actions_html = ''): void {
self::card_open($title, $actions_html, true);
echo '<div class="aksrt-native-table-wrap">';
echo '<div class="jio-native-table-wrap">';
}
/** 关闭表格卡;分页条应在 table-wrap 关闭后、卡片关闭前输出,故拆成两步。 */
@@ -211,7 +211,7 @@ final class NativeUi {
* ----------------------------------------------------------------*/
public static function badge(string $label, string $tone = 'neutral', bool $dot = true): string {
$class = 'aksrt-native-badge aksrt-native-badge--' . self::tone($tone) . ($dot ? ' has-dot' : '');
$class = 'jio-native-badge jio-native-badge--' . self::tone($tone) . ($dot ? ' has-dot' : '');
return '<span class="' . esc_attr($class) . '">' . esc_html($label) . '</span>';
}
@@ -228,9 +228,9 @@ final class NativeUi {
}
public static function empty_state(string $title, string $hint = '', string $icon = 'dashicons-portfolio'): string {
$html = '<div class="aksrt-native-empty">';
$html = '<div class="jio-native-empty">';
$html .= '<span class="dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span>';
$html .= '<span class="aksrt-native-empty__title">' . esc_html($title) . '</span>';
$html .= '<span class="jio-native-empty__title">' . esc_html($title) . '</span>';
if ($hint !== '') {
$html .= '<span>' . esc_html($hint) . '</span>';
}
@@ -239,7 +239,7 @@ final class NativeUi {
/** 表格内的空状态整行。 */
public static function empty_row(int $colspan, string $title, string $hint = ''): string {
return '<tr class="no-items"><td class="aksrt-native-empty-cell" colspan="' . esc_attr((string) max(1, $colspan)) . '">'
return '<tr class="no-items"><td class="jio-native-empty-cell" colspan="' . esc_attr((string) max(1, $colspan)) . '">'
. self::empty_state($title, $hint)
. '</td></tr>';
}
@@ -248,13 +248,13 @@ final class NativeUi {
public static function user_cell(int $user_id, string $fallback = ''): string {
$user = $user_id > 0 ? get_userdata($user_id) : false;
if (!$user) {
return '<span class="aksrt-native-muted">' . esc_html($fallback !== '' ? $fallback : ($user_id > 0 ? '#' . $user_id : '—')) . '</span>';
return '<span class="jio-native-muted">' . esc_html($fallback !== '' ? $fallback : ($user_id > 0 ? '#' . $user_id : '—')) . '</span>';
}
$avatar = function_exists('aksrt_get_avatar_url') ? (string) aksrt_get_avatar_url($user_id, 28) : (string) get_avatar_url($user_id, ['size' => 28]);
return '<a class="aksrt-native-user" href="' . esc_url(get_edit_user_link($user_id)) . '">'
$avatar = function_exists('jio_get_avatar_url') ? (string) jio_get_avatar_url($user_id, 28) : (string) get_avatar_url($user_id, ['size' => 28]);
return '<a class="jio-native-user" href="' . esc_url(get_edit_user_link($user_id)) . '">'
. ($avatar !== '' ? '<img src="' . esc_url($avatar) . '" width="28" height="28" alt="">' : '')
. '<span><span class="aksrt-native-user__name">' . esc_html($user->display_name) . '</span>'
. '<span class="aksrt-native-user__meta">#' . esc_html((string) $user_id) . '</span></span></a>';
. '<span><span class="jio-native-user__name">' . esc_html($user->display_name) . '</span>'
. '<span class="jio-native-user__meta">#' . esc_html((string) $user_id) . '</span></span></a>';
}
private static function tone(string $tone): string {
+53 -53
View File
@@ -1,22 +1,22 @@
<?php
/**
* @package AKSRT
* @package Jio
*
* 后台「卡密管理」页面 — 1:1 复刻参考主题 zibll 的 zibpay/page/charge-card.php。
*
* 结构:列表(过滤/搜索/排序/批量删除/分页) + 添加卡密(自动生成 / 导入) + 导出卡密(text / xls)。
* 卡密类型与参考主题一致:余额充值 / 会员兑换 / 积分兑换 / 自定义卡密;
* 本主题内部类型映射:balance / vip / credits / custom。
* 差异点:AKSRT 无商城发货(shipped)子系统,故不含「已售」标记与链接;删除取代参考主题的批量操作语义一致。
* 差异点:Jio 无商城发货(shipped)子系统,故不含「已售」标记与链接;删除取代参考主题的批量操作语义一致。
*
* 外壳与配色走 NativeUi(.aksrt-native),页面专属交互样式仍由 print_assets() 输出。
* 外壳与配色走 NativeUi(.jio-native),页面专属交互样式仍由 print_assets() 输出。
* 类体按关注点拆成 flat sibling traits(PSR-4 要求 trait 与宿主类同目录,不能放子目录):
* - PayChargeCardsList 列表渲染 + 翻页参数
* - PayChargeCardsAssets 页面专属内联样式/脚本
* - PayChargeCardsForms 添加/导出表单 + 依赖显隐助手
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
@@ -27,9 +27,9 @@ class PayChargeCards {
use PayChargeCardsForms;
public const CAPABILITY = 'manage_options';
public const PAGE_SLUG = 'aksrt_charge_cards';
public const NONCE = 'aksrt_card_pass_page';
public const EXPORT_AJAX_ACTION = 'aksrt_card_pass_export';
public const PAGE_SLUG = 'jio_charge_cards';
public const NONCE = 'jio_card_pass_page';
public const EXPORT_AJAX_ACTION = 'jio_card_pass_export';
private static $instance = null;
@@ -54,17 +54,17 @@ class PayChargeCards {
$this->ensure_cap();
$this->page_url = admin_url('admin.php?page=' . self::PAGE_SLUG);
if (!class_exists('\AKSRT\Payment\ChargeCard')) {
if (!class_exists('\Jio\Payment\ChargeCard')) {
NativeUi::page_open();
echo '<h1 class="wp-heading-inline">' . esc_html__('卡密管理', 'aksrt') . '</h1>';
echo '<div class="notice notice-error"><p>' . esc_html__('卡密模块未初始化。', 'aksrt') . '</p></div>';
echo '<h1 class="wp-heading-inline">' . esc_html__('卡密管理', 'jio') . '</h1>';
echo '<div class="notice notice-error"><p>' . esc_html__('卡密模块未初始化。', 'jio') . '</p></div>';
NativeUi::page_close();
return;
}
$tab = isset($_GET['tab']) ? sanitize_key((string) wp_unslash($_GET['tab'])) : '';
$s = isset($_REQUEST['s']) ? sanitize_text_field((string) wp_unslash($_REQUEST['s'])) : '';
$page_title = __('卡密管理', 'aksrt');
$page_title = __('卡密管理', 'jio');
$head_but = '';
// 页面级动作(参考主题在渲染前处理 add / delete)。
@@ -78,20 +78,20 @@ class PayChargeCards {
// 标题右侧按钮是页面头部的一部分,只在此处输出一次(列表页同样如此,由 render_list 负责列表本体)。
switch ($tab) {
case 'add':
$page_title = __('添加卡密', 'aksrt');
$head_but = '<a href="' . esc_url($this->page_url) . '" class="page-title-action">' . esc_html__('返回列表', 'aksrt') . '</a>';
$page_title = __('添加卡密', 'jio');
$head_but = '<a href="' . esc_url($this->page_url) . '" class="page-title-action">' . esc_html__('返回列表', 'jio') . '</a>';
break;
case 'export':
$page_title = __('导出卡密', 'aksrt');
$head_but = '<a href="' . esc_url($this->page_url) . '" class="page-title-action">' . esc_html__('返回列表', 'aksrt') . '</a>';
$page_title = __('导出卡密', 'jio');
$head_but = '<a href="' . esc_url($this->page_url) . '" class="page-title-action">' . esc_html__('返回列表', 'jio') . '</a>';
break;
default:
$head_but = '<a href="' . esc_url(add_query_arg('tab', 'add', $this->page_url)) . '" class="page-title-action">' . esc_html__('添加卡密', 'aksrt') . '</a>';
$head_but .= '<a href="' . esc_url(add_query_arg('tab', 'export', $this->page_url)) . '" class="page-title-action">' . esc_html__('导出卡密', 'aksrt') . '</a>';
$head_but = '<a href="' . esc_url(add_query_arg('tab', 'add', $this->page_url)) . '" class="page-title-action">' . esc_html__('添加卡密', 'jio') . '</a>';
$head_but .= '<a href="' . esc_url(add_query_arg('tab', 'export', $this->page_url)) . '" class="page-title-action">' . esc_html__('导出卡密', 'jio') . '</a>';
if ($s !== '') {
$head_but .= '<div><div class="update-nag" style="margin: 10px 0 0;">' . sprintf(esc_html__('搜索 “%s” 的内容', 'aksrt'), esc_html($s)) . ' </div></div>';
$head_but .= '<div><div class="update-nag" style="margin: 10px 0 0;">' . sprintf(esc_html__('搜索 “%s” 的内容', 'jio'), esc_html($s)) . ' </div></div>';
}
break;
}
@@ -118,7 +118,7 @@ class PayChargeCards {
*/
public function handle_export(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('暂无此权限', 'aksrt'));
wp_die(esc_html__('暂无此权限', 'jio'));
}
@set_time_limit(0);
@@ -133,9 +133,9 @@ class PayChargeCards {
$status = $status_raw === '0' ? 'active' : $status_raw;
}
$rows = \AKSRT\Payment\ChargeCard::get_instance()->get_export_rows($type, $status);
$rows = \Jio\Payment\ChargeCard::get_instance()->get_export_rows($type, $status);
if (!$rows) {
wp_die(esc_html__('暂无可导出的内容', 'aksrt'));
wp_die(esc_html__('暂无可导出的内容', 'jio'));
}
$filename = $type . '_' . gmdate('d_m_Y');
@@ -150,24 +150,24 @@ class PayChargeCards {
$meta = is_array($decoded) ? $decoded : [];
}
$status_text = $row['status'] === 'used'
? __('已使用', 'aksrt')
: ($row['status'] === 'expired' ? __('已过期', 'aksrt') : ($row['status'] === 'disabled' ? __('已禁用', 'aksrt') : __('未使用', 'aksrt')));
? __('已使用', 'jio')
: ($row['status'] === 'expired' ? __('已过期', 'jio') : ($row['status'] === 'disabled' ? __('已禁用', 'jio') : __('未使用', 'jio')));
switch ($type) {
case 'credits': // 积分兑换
$title = [__('卡号', 'aksrt'), __('密码', 'aksrt'), __('面额', 'aksrt'), __('备注', 'aksrt'), __('状态', 'aksrt')];
$title = [__('卡号', 'jio'), __('密码', 'jio'), __('面额', 'jio'), __('备注', 'jio'), __('状态', 'jio')];
$data[] = [(string) $row['card'], (string) $row['password'], (string) (int) ($meta['points'] ?? 0), (string) $row['note'], $status_text];
break;
case 'vip': // 会员兑换
$title = [__('卡号', 'aksrt'), __('密码', 'aksrt'), __('兑换会员', 'aksrt'), __('备注', 'aksrt'), __('状态', 'aksrt')];
$title = [__('卡号', 'jio'), __('密码', 'jio'), __('兑换会员', 'jio'), __('备注', 'jio'), __('状态', 'jio')];
$data[] = [(string) $row['card'], (string) $row['password'], $this->vip_meta_query($meta), (string) $row['note'], $status_text];
break;
case 'custom': // 自定义
$title = [__('卡号', 'aksrt'), __('密码', 'aksrt'), __('备注', 'aksrt'), __('状态', 'aksrt')];
$title = [__('卡号', 'jio'), __('密码', 'jio'), __('备注', 'jio'), __('状态', 'jio')];
$data[] = [(string) $row['card'], (string) $row['password'], (string) $row['note'], ''];
break;
default: // 余额充值
$title = [__('卡号', 'aksrt'), __('密码', 'aksrt'), __('面额', 'aksrt'), __('备注', 'aksrt'), __('状态', 'aksrt')];
$title = [__('卡号', 'jio'), __('密码', 'jio'), __('面额', 'jio'), __('备注', 'jio'), __('状态', 'jio')];
$data[] = [(string) $row['card'], (string) $row['password'], $this->balance_amount_text($meta), (string) $row['note'], $status_text];
}
}
@@ -237,7 +237,7 @@ class PayChargeCards {
$add_type = isset($_REQUEST['add_type']) ? sanitize_key((string) wp_unslash($_REQUEST['add_type'])) : 'auto';
$pass_type = isset($_REQUEST['type']) ? sanitize_key((string) wp_unslash($_REQUEST['type'])) : '';
if (!in_array($pass_type, ['balance', 'vip', 'credits', 'custom'], true)) {
$this->page_notice(__('错误!', 'aksrt'), __('参数传入错误', 'aksrt'), 'error');
$this->page_notice(__('错误!', 'jio'), __('参数传入错误', 'jio'), 'error');
return;
}
@@ -250,7 +250,7 @@ class PayChargeCards {
// 一并落在区间外,因此用一个区间判定即可覆盖全部非法输入,不另设分支。
$auto_num = isset($_REQUEST['auto_num']) ? (int) $_REQUEST['auto_num'] : 0;
if ($auto_num < 1 || $auto_num > 1000) {
$this->page_notice(__('错误!', 'aksrt'), __('生成数量需为 1 - 1000 的整数', 'aksrt'), 'error');
$this->page_notice(__('错误!', 'jio'), __('生成数量需为 1 - 1000 的整数', 'jio'), 'error');
return;
}
@@ -263,7 +263,7 @@ class PayChargeCards {
// 阻止表单提交),因此下限由服务端兜底。
$auto_price = max(0.0, $auto_price);
if (!$auto_price) {
$this->page_notice(__('错误!', 'aksrt'), __('请输入卡密的面额', 'aksrt'), 'error');
$this->page_notice(__('错误!', 'jio'), __('请输入卡密的面额', 'jio'), 'error');
return;
}
$pass_meta = $pass_type === 'credits'
@@ -273,7 +273,7 @@ class PayChargeCards {
case 'vip':
$vip = isset($_REQUEST['vip_exchange']) && is_array($_REQUEST['vip_exchange']) ? wp_unslash($_REQUEST['vip_exchange']) : [];
if (empty($vip['time'])) {
$this->page_notice(__('错误!', 'aksrt'), __('请填写兑换会员参数', 'aksrt'), 'error');
$this->page_notice(__('错误!', 'jio'), __('请填写兑换会员参数', 'jio'), 'error');
return;
}
$pass_meta = $this->vip_meta($vip);
@@ -298,15 +298,15 @@ class PayChargeCards {
$remarks = isset($_REQUEST['auto_remarks']) ? sanitize_text_field((string) wp_unslash($_REQUEST['auto_remarks'])) : '';
$created = \AKSRT\Payment\ChargeCard::get_instance()->generate_cards($pass_type, $auto_num, $pass_meta, $rand_number, $rand_password, $remarks);
$created = \Jio\Payment\ChargeCard::get_instance()->generate_cards($pass_type, $auto_num, $pass_meta, $rand_number, $rand_password, $remarks);
$this->page_notice(__('完成!', 'aksrt'), sprintf(__('已自动生成%s个卡密', 'aksrt'), (string) $created));
$this->page_notice(__('完成!', 'jio'), sprintf(__('已自动生成%s个卡密', 'jio'), (string) $created));
}
private function handle_import(string $pass_type): void {
$import_data = isset($_REQUEST['import_data']) ? (string) wp_unslash($_REQUEST['import_data']) : '';
if (!$import_data) {
$this->page_notice(__('错误!', 'aksrt'), __('请粘贴您需要导入的数据', 'aksrt'), 'error');
$this->page_notice(__('错误!', 'jio'), __('请粘贴您需要导入的数据', 'jio'), 'error');
return;
}
@@ -318,7 +318,7 @@ class PayChargeCards {
$success_i = 0;
$error_i = 0;
$svc = \AKSRT\Payment\ChargeCard::get_instance();
$svc = \Jio\Payment\ChargeCard::get_instance();
foreach ($import_data_array as $line) {
$v = explode($import_division, $line);
@@ -395,11 +395,11 @@ class PayChargeCards {
if ($success_i) {
$this->page_notice(
__('导入完成', 'aksrt'),
sprintf(__('成功导入%s个卡密%s', 'aksrt'), (string) $success_i, $error_i ? sprintf(__(',%s个导入失败', 'aksrt'), (string) $error_i) : '')
__('导入完成', 'jio'),
sprintf(__('成功导入%s个卡密%s', 'jio'), (string) $success_i, $error_i ? sprintf(__(',%s个导入失败', 'jio'), (string) $error_i) : '')
);
} else {
$this->page_notice(__('导入失败', 'aksrt'), sprintf(__('%s个导入失败,数据格式错误', 'aksrt'), (string) $error_i), 'error');
$this->page_notice(__('导入失败', 'jio'), sprintf(__('%s个导入失败,数据格式错误', 'jio'), (string) $error_i), 'error');
}
}
@@ -409,13 +409,13 @@ class PayChargeCards {
$delete_ids = isset($_REQUEST['action_id']) ? array_map('absint', (array) wp_unslash($_REQUEST['action_id'])) : [];
$delete_ids = array_filter($delete_ids);
if (!$delete_ids) {
$this->page_notice(__('错误!', 'aksrt'), __('未选择需要删除的内容', 'aksrt'), 'error');
$this->page_notice(__('错误!', 'jio'), __('未选择需要删除的内容', 'jio'), 'error');
return;
}
$delete_i = \AKSRT\Payment\ChargeCard::get_instance()->delete_by_ids($delete_ids);
$delete_i = \Jio\Payment\ChargeCard::get_instance()->delete_by_ids($delete_ids);
$this->page_notice(__('删除完成', 'aksrt'), sprintf(__('已删除%s个卡密', 'aksrt'), (string) $delete_i));
$this->page_notice(__('删除完成', 'jio'), sprintf(__('已删除%s个卡密', 'jio'), (string) $delete_i));
}
/* ------------------------------------------------------------------
@@ -429,20 +429,20 @@ class PayChargeCards {
*/
private function type_labels(): array {
return [
'balance' => __('余额充值', 'aksrt'),
'vip' => __('会员兑换', 'aksrt'),
'credits' => __('积分兑换', 'aksrt'),
'custom' => __('自定义卡密', 'aksrt'),
'balance' => __('余额充值', 'jio'),
'vip' => __('会员兑换', 'jio'),
'credits' => __('积分兑换', 'jio'),
'custom' => __('自定义卡密', 'jio'),
];
}
private function vip_level_name(int $level): string {
$s = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
$s = class_exists('\Jio\Settings\Settings') ? \Jio\Settings\Settings::get_all() : [];
$name = trim((string) ($s['pay_user_vip_' . max(1, min(2, $level)) . '_name'] ?? ''));
if ($name !== '') {
return $name;
}
return $level === 2 ? __('二级会员', 'aksrt') : __('一级会员', 'aksrt');
return $level === 2 ? __('二级会员', 'jio') : __('一级会员', 'jio');
}
/**
@@ -454,13 +454,13 @@ class PayChargeCards {
$time = array_key_exists('time', $meta) ? (string) $meta['time'] : null;
if ($time !== null && $time !== '') {
if ($time === 'Permanent') {
return __('永久', 'aksrt');
return __('永久', 'jio');
}
$unit = (isset($meta['unit']) && $meta['unit'] === 'month') ? __('个月', 'aksrt') : __('天', 'aksrt');
$unit = (isset($meta['unit']) && $meta['unit'] === 'month') ? __('个月', 'jio') : __('天', 'jio');
return (string) ((int) $time) . $unit;
}
$days = (int) ($meta['days'] ?? 0);
return $days > 0 ? $days . __('天', 'aksrt') : __('永久', 'aksrt');
return $days > 0 ? $days . __('天', 'jio') : __('永久', 'jio');
}
/**
@@ -524,13 +524,13 @@ class PayChargeCards {
}
private function currency_unit(): string {
$s = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
return trim((string) ($s['pay_currency_unit'] ?? '')) !== '' ? trim((string) $s['pay_currency_unit']) : __('元', 'aksrt');
$s = class_exists('\Jio\Settings\Settings') ? \Jio\Settings\Settings::get_all() : [];
return trim((string) ($s['pay_currency_unit'] ?? '')) !== '' ? trim((string) $s['pay_currency_unit']) : __('元', 'jio');
}
private function ensure_cap(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
wp_die(esc_html__('您没有权限访问此页面。', 'jio'));
}
}
}
+16 -16
View File
@@ -1,6 +1,6 @@
<?php
/**
* @package AKSRT
* @package Jio
*
* PayChargeCards 页面专属内联样式与脚本:条件依赖显隐 + 点击复制。
* 这些是卡密管理页特有的表现(排序箭头基线锚定、按钮组、导入提示块、复制提示),
@@ -9,7 +9,7 @@
* 作为 flat sibling trait 被 PayChargeCards 使用(PSR-4:trait 必须与宿主类同目录)。
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
@@ -32,23 +32,23 @@ trait PayChargeCardsAssets {
.orderby-but > a + a { bottom: .075em; }
.orderby-but:hover a { opacity: .6; }
.orderby-but > a:hover { opacity: 1; }
.aksrt-cardpass-bulk { margin: 0 0 12px; }
.jio-cardpass-bulk { margin: 0 0 12px; }
/* 卡密表列多(≥10 列),窄视口下须保持最小宽度并由表格卡片横向滚动,
而非让各列挤扁。复刻原先列表表格的内联 style="min-width: 1000px;"。 */
.aksrt-cardpass-table { min-width: 1000px; }
.aksrt-btnset { display: flex; flex-wrap: wrap; gap: 6px; }
.aksrt-btnset-item { display: inline-block; cursor: pointer; }
.aksrt-btnset-item input { position: absolute; opacity: 0; pointer-events: none; }
.aksrt-btnset-item span { display: inline-block; padding: 3px 12px; border: 1px solid #c3c4c7; border-radius: 3px; background: #fff; }
.aksrt-btnset-item input:checked + span { background: #2271b1; border-color: #2271b1; color: #fff; }
.jio-cardpass-table { min-width: 1000px; }
.jio-btnset { display: flex; flex-wrap: wrap; gap: 6px; }
.jio-btnset-item { display: inline-block; cursor: pointer; }
.jio-btnset-item input { position: absolute; opacity: 0; pointer-events: none; }
.jio-btnset-item span { display: inline-block; padding: 3px 12px; border: 1px solid #c3c4c7; border-radius: 3px; background: #fff; }
.jio-btnset-item input:checked + span { background: #2271b1; border-color: #2271b1; color: #fff; }
/* 导入提示:复刻后台 notice-warning 的外观,但**不用** .notice 类 —— 见 submessage_row()
的说明,带 .notice 会被 wp-admin/js/common.js 搬到页面顶部。改这里请连带改那里。 */
.aksrt-submsg { margin: 6px 0; padding: 6px 12px; background: #fef8ee; border-left: 4px solid #f0b849; }
.jio-submsg { margin: 6px 0; padding: 6px 12px; background: #fef8ee; border-left: 4px solid #f0b849; }
/* 提示内的条目列表必须有 <ul> 承载:list-style-position 默认 outside,
圆点落在内容盒左侧的 padding 区里;若 <li> 没有列表容器提供缩进,
圆点就会溢出到提示块的文字边界之外。这里显式给出缩进,避免依赖 UA/后台的 ul 默认值。 */
.aksrt-submsg-list { margin: 6px 0; padding-left: 20px; list-style: disc; }
.aksrt-copy-tip { margin-left: 6px; color: #00a32a; font-size: 12px; white-space: nowrap; }
.jio-submsg-list { margin: 6px 0; padding-left: 20px; list-style: disc; }
.jio-copy-tip { margin-left: 6px; color: #00a32a; font-size: 12px; white-space: nowrap; }
</style>
<script>
(function () {
@@ -56,7 +56,7 @@ trait PayChargeCardsAssets {
// 含方括号的字段名(如 vip_exchange[time])会让选择器抛异常并被 catch 吞掉,
// 从而把依赖行永久留在初始状态。
function fieldsByName(key) {
var all = document.querySelectorAll('.aksrt-cardpass-form [name]');
var all = document.querySelectorAll('.jio-cardpass-form [name]');
var out = [];
for (var i = 0; i < all.length; i++) {
if (all[i].getAttribute('name') === key) out.push(all[i]);
@@ -77,7 +77,7 @@ trait PayChargeCardsAssets {
}
function evalDeps() {
// 全文档查找 data-dep 节点:行与提示块各自带状态、各自落 display。
// 刻意不做限域(不写 .aksrt-cardpass-form 前缀):提示块一旦被 HTML 解析
// 刻意不做限域(不写 .jio-cardpass-form 前缀):提示块一旦被 HTML 解析
// 移出表格甚至移出表单子树,限域查找会让它停在初始隐藏状态,表现为「提示不见了」。
document.querySelectorAll('[data-dep]').forEach(function (el) {
try {
@@ -115,9 +115,9 @@ trait PayChargeCardsAssets {
if (!text) return;
var done = function () {
var tip = document.createElement('span');
tip.className = 'aksrt-copy-tip';
tip.className = 'jio-copy-tip';
var tag = el.getAttribute('data-clipboard-tag') || '';
tip.textContent = (tag ? tag + ' ' : '') + '<?php echo esc_js(__('已复制', 'aksrt')); ?>';
tip.textContent = (tag ? tag + ' ' : '') + '<?php echo esc_js(__('已复制', 'jio')); ?>';
el.appendChild(tip);
setTimeout(function () { tip.remove(); }, 1200);
};
+91 -91
View File
@@ -1,6 +1,6 @@
<?php
/**
* @package AKSRT
* @package Jio
*
* PayChargeCards 的「添加卡密 / 导出卡密」表单渲染与依赖显隐求值。
* 表单结构、字段依赖(field_row / submessage_row / deps_visible)、按钮组与数字输入
@@ -9,7 +9,7 @@
* 作为 flat sibling trait 被 PayChargeCards 使用(PSR-4:trait 必须与宿主类同目录)。
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
@@ -18,55 +18,55 @@ trait PayChargeCardsForms {
private function render_add_form(): void {
$type_labels = $this->type_labels();
?>
<p><b><?php esc_html_e('在此添加卡密', 'aksrt'); ?></b></p>
<p><b><?php esc_html_e('在此添加卡密', 'jio'); ?></b></p>
<ul style="list-style:disc;margin:0 0 12px 18px;line-height:1.9">
<li><?php esc_html_e('如果您已经准备好了卡密资料,请选择导入的方式添加', 'aksrt'); ?></li>
<li><?php esc_html_e('您也可以采用系统生成的方式,自动批量添加卡密', 'aksrt'); ?></li>
<li><?php esc_html_e('如果您已经准备好了卡密资料,请选择导入的方式添加', 'jio'); ?></li>
<li><?php esc_html_e('您也可以采用系统生成的方式,自动批量添加卡密', 'jio'); ?></li>
</ul>
<form class="aksrt-cardpass-form" method="post">
<form class="jio-cardpass-form" method="post">
<?php wp_nonce_field(self::NONCE); ?>
<input type="hidden" name="action" value="add" />
<table class="form-table" role="presentation">
<?php
$this->field_row(
__('卡密类型', 'aksrt'),
__('卡密类型', 'jio'),
$this->button_set('type', $type_labels, 'balance'),
[]
);
$this->field_row(
__('添加方式', 'aksrt'),
__('添加方式', 'jio'),
$this->button_set('add_type', [
'auto' => __('系统自动生成', 'aksrt'),
'import' => __('导入卡密', 'aksrt'),
'auto' => __('系统自动生成', 'jio'),
'import' => __('导入卡密', 'jio'),
], 'auto'),
[]
);
// 自动生成
$this->field_row(
__('生成数量', 'aksrt'),
$this->spinner('auto_num', 20, __('张', 'aksrt')),
__('生成数量', 'jio'),
$this->spinner('auto_num', 20, __('张', 'jio')),
[['key' => 'add_type', 'op' => '==', 'val' => 'auto']],
__('需要生成多少张卡密(1 - 1000 的整数,单次生成数量太多可能会对服务器性能造成影响)', 'aksrt')
__('需要生成多少张卡密(1 - 1000 的整数,单次生成数量太多可能会对服务器性能造成影响)', 'jio')
);
$this->field_row(
__('标识', 'aksrt'),
__('标识', 'jio'),
'<input type="text" class="regular-text" name="auto_remarks" value="' . esc_attr('cardpass_' . current_time('YmdHis')) . '" />',
[['key' => 'add_type', 'op' => '==', 'val' => 'auto']],
__('对生成的卡密做标记标识,方便后期查找管理', 'aksrt') . '<br>' . sprintf(esc_html__('注意:标识中不能有%1$s字符,系统已占用', 'aksrt'), '<code>shipped</code>')
__('对生成的卡密做标记标识,方便后期查找管理', 'jio') . '<br>' . sprintf(esc_html__('注意:标识中不能有%1$s字符,系统已占用', 'jio'), '<code>shipped</code>')
);
$this->field_row(
__('面额', 'aksrt'),
__('面额', 'jio'),
'<input type="number" step="any" name="auto_price" value="0" />',
[
['key' => 'add_type', 'op' => '==', 'val' => 'auto'],
['key' => 'type', 'op' => 'any', 'val' => 'balance,credits'],
],
__('单张卡密的面额', 'aksrt')
__('单张卡密的面额', 'jio')
);
$this->field_row(
__('兑换会员', 'aksrt'),
__('会员等级', 'aksrt') . ' ' . $this->button_set('vip_exchange[level]', [
__('兑换会员', 'jio'),
__('会员等级', 'jio') . ' ' . $this->button_set('vip_exchange[level]', [
'1' => $this->vip_level_name(1),
'2' => $this->vip_level_name(2),
], '1'),
@@ -77,18 +77,18 @@ trait PayChargeCardsForms {
);
$this->field_row(
' ',
__('兑换会员时长', 'aksrt') . '<br><input type="text" name="vip_exchange[time]" value="" />',
__('兑换会员时长', 'jio') . '<br><input type="text" name="vip_exchange[time]" value="" />',
[
['key' => 'add_type', 'op' => '==', 'val' => 'auto'],
['key' => 'type', 'op' => '==', 'val' => 'vip'],
],
__('填', 'aksrt') . '<code>Permanent</code>' . __('为永久', 'aksrt')
__('填', 'jio') . '<code>Permanent</code>' . __('为永久', 'jio')
);
$this->field_row(
' ',
__('时长单位', 'aksrt') . ' ' . $this->button_set('vip_exchange[unit]', [
'day' => __('天', 'aksrt'),
'month' => __('个月', 'aksrt'),
__('时长单位', 'jio') . ' ' . $this->button_set('vip_exchange[unit]', [
'day' => __('天', 'jio'),
'month' => __('个月', 'jio'),
], 'month'),
[
['key' => 'add_type', 'op' => '==', 'val' => 'auto'],
@@ -97,41 +97,41 @@ trait PayChargeCardsForms {
]
);
$this->field_row(
__('自定义显示名称', 'aksrt'),
__('卡号显示名称', 'aksrt') . '<br><input type="text" class="regular-text" name="custom_name[card_name]" value="" /><br>' .
__('卡密显示名称', 'aksrt') . '<br><input type="text" class="regular-text" name="custom_name[password_name]" value="" />',
__('自定义显示名称', 'jio'),
__('卡号显示名称', 'jio') . '<br><input type="text" class="regular-text" name="custom_name[card_name]" value="" /><br>' .
__('卡密显示名称', 'jio') . '<br><input type="text" class="regular-text" name="custom_name[password_name]" value="" />',
[
['key' => 'add_type', 'op' => '==', 'val' => 'auto'],
['key' => 'type', 'op' => '==', 'val' => 'custom'],
]
);
$this->field_row(
__('高级选项', 'aksrt'),
__('高级选项', 'jio'),
'<input type="checkbox" name="auto_top_s" value="1" />',
[['key' => 'add_type', 'op' => '==', 'val' => 'auto']]
);
$this->field_row(
__('自定义卡号位数', 'aksrt'),
$this->spinner('auto_rand_number_limit', 20, __('位数', 'aksrt')),
__('自定义卡号位数', 'jio'),
$this->spinner('auto_rand_number_limit', 20, __('位数', 'jio')),
[
['key' => 'add_type', 'op' => '==', 'val' => 'auto'],
['key' => 'auto_top_s', 'op' => 'checked'],
]
);
$this->field_row(
__('自定义密码位数', 'aksrt'),
$this->spinner('auto_rand_password_limit', 35, __('位数', 'aksrt')),
__('自定义密码位数', 'jio'),
$this->spinner('auto_rand_password_limit', 35, __('位数', 'jio')),
[
['key' => 'add_type', 'op' => '==', 'val' => 'auto'],
['key' => 'auto_top_s', 'op' => 'checked'],
],
__('自定义自动生成的长度(不能太短,太短可能会出现重复)如果启用了单密码模式,可以将卡号位数设置为0', 'aksrt')
__('自定义自动生成的长度(不能太短,太短可能会出现重复)如果启用了单密码模式,可以将卡号位数设置为0', 'jio')
);
// 导入提示统一放在「卡密数据」正上方:切换卡密类型/添加方式时,
// 命中的那一条紧邻示例数据与输入框,便于对照填写。
$this->field_row(
__('单密码模式', 'aksrt'),
__('单密码模式', 'jio'),
'<input type="checkbox" name="only_pass_s" value="1" />',
[
['key' => 'add_type', 'op' => '==', 'val' => 'import'],
@@ -143,32 +143,32 @@ trait PayChargeCardsForms {
['key' => 'add_type', 'op' => '!=', 'val' => 'auto'],
['key' => 'type', 'op' => 'any', 'val' => 'balance,credits'],
],
'<p><b>' . esc_html__('导入卡密', 'aksrt') . '</b></p>
<ul class="aksrt-submsg-list">
<li>' . esc_html__('一行一个卡密,单行格式为:', 'aksrt') . '<code>' . esc_html__('卡号 密码 面额 标识', 'aksrt') . '</code></li>
<li>' . esc_html__('卡号、密码、面额、标识默认使用空格分割,您可以在下方自定义分割符号,与您的数据对应即可', 'aksrt') . '</li>
<li>' . esc_html__('单次导入数量太多可能会对服务器性能造成影响', 'aksrt') . '</li><li>' . esc_html__('如果使用的是单密码模式,卡号部分随便填什么都行,但不能留空', 'aksrt') . '</li>
'<p><b>' . esc_html__('导入卡密', 'jio') . '</b></p>
<ul class="jio-submsg-list">
<li>' . esc_html__('一行一个卡密,单行格式为:', 'jio') . '<code>' . esc_html__('卡号 密码 面额 标识', 'jio') . '</code></li>
<li>' . esc_html__('卡号、密码、面额、标识默认使用空格分割,您可以在下方自定义分割符号,与您的数据对应即可', 'jio') . '</li>
<li>' . esc_html__('单次导入数量太多可能会对服务器性能造成影响', 'jio') . '</li><li>' . esc_html__('如果使用的是单密码模式,卡号部分随便填什么都行,但不能留空', 'jio') . '</li>
</ul>
<p><b>' . esc_html__('数据示例', 'aksrt') . '</b></p>
<div>4849977843617 3w20V4Qe7dRhAIoPYum6KSA 50 ' . sprintf(esc_html__('%1$s余额', 'aksrt'), esc_html('50' . $this->currency_unit())) . '</div>
<div>6484027668705 SkdJZ5vWcpqPxrbs3pqPcpq 100 ' . sprintf(esc_html__('%1$s余额', 'aksrt'), esc_html('100' . $this->currency_unit())) . '</div>'
<p><b>' . esc_html__('数据示例', 'jio') . '</b></p>
<div>4849977843617 3w20V4Qe7dRhAIoPYum6KSA 50 ' . sprintf(esc_html__('%1$s余额', 'jio'), esc_html('50' . $this->currency_unit())) . '</div>
<div>6484027668705 SkdJZ5vWcpqPxrbs3pqPcpq 100 ' . sprintf(esc_html__('%1$s余额', 'jio'), esc_html('100' . $this->currency_unit())) . '</div>'
);
$this->submessage_row(
[
['key' => 'add_type', 'op' => '!=', 'val' => 'auto'],
['key' => 'type', 'op' => '==', 'val' => 'vip'],
],
'<p><b>' . esc_html__('导入会员兑换卡密', 'aksrt') . '</b></p>
<ul class="aksrt-submsg-list">
<li>' . esc_html__('一行一个卡密,单行格式为:', 'aksrt') . '<code>' . esc_html__('卡号 密码 会员参数 标识', 'aksrt') . '</code></li>
<li>' . esc_html__('卡号、密码、面额、标识默认使用空格分割,您可以在下方自定义分割符号,与您的数据对应即可', 'aksrt') . '</li>
<li>' . esc_html__('会员参数格式为:', 'aksrt') . '<code>level=1&time=5&unit=day</code></li>
<li>' . esc_html__('参数说明:level为等级,只能为1或2。time为时长,填Permanent或数字。unit为时长单位,只能填day(天)或者month(月)', 'aksrt') . '</li>
<li>' . esc_html__('单次导入数量太多可能会对服务器性能造成影响', 'aksrt') . '</li><li>' . esc_html__('如果使用的是单密码模式,卡号部分随便填什么都行,但不能留空', 'aksrt') . '</li>
'<p><b>' . esc_html__('导入会员兑换卡密', 'jio') . '</b></p>
<ul class="jio-submsg-list">
<li>' . esc_html__('一行一个卡密,单行格式为:', 'jio') . '<code>' . esc_html__('卡号 密码 会员参数 标识', 'jio') . '</code></li>
<li>' . esc_html__('卡号、密码、面额、标识默认使用空格分割,您可以在下方自定义分割符号,与您的数据对应即可', 'jio') . '</li>
<li>' . esc_html__('会员参数格式为:', 'jio') . '<code>level=1&time=5&unit=day</code></li>
<li>' . esc_html__('参数说明:level为等级,只能为1或2。time为时长,填Permanent或数字。unit为时长单位,只能填day(天)或者month(月)', 'jio') . '</li>
<li>' . esc_html__('单次导入数量太多可能会对服务器性能造成影响', 'jio') . '</li><li>' . esc_html__('如果使用的是单密码模式,卡号部分随便填什么都行,但不能留空', 'jio') . '</li>
</ul>
<p><b>' . esc_html__('数据示例', 'aksrt') . '</b></p>
<div>4849977843617 3w20V4Qe7dRhAIoPYum6KSA level=1&time=5&unit=day ' . esc_html__('5天一级会员', 'aksrt') . '</div>
<div>6484027668705 SkdJZ5vWcpqPxrbs3pqPcpq level=2&time=Permanent ' . esc_html__('永久2级会员', 'aksrt') . '</div>
<p><b>' . esc_html__('数据示例', 'jio') . '</b></p>
<div>4849977843617 3w20V4Qe7dRhAIoPYum6KSA level=1&time=5&unit=day ' . esc_html__('5天一级会员', 'jio') . '</div>
<div>6484027668705 SkdJZ5vWcpqPxrbs3pqPcpq level=2&time=Permanent ' . esc_html__('永久2级会员', 'jio') . '</div>
'
);
$this->submessage_row(
@@ -177,17 +177,17 @@ trait PayChargeCardsForms {
['key' => 'type', 'op' => '==', 'val' => 'custom'],
['key' => 'only_pass_s', 'op' => 'unchecked'],
],
'<p><b>' . esc_html__('自定义卡密导入', 'aksrt') . '</b></p>
<ul class="aksrt-submsg-list">
<li>' . esc_html__('一行一个卡密,单行格式为:', 'aksrt') . '<code>' . esc_html__('卡号 密码 标识 自定义参数', 'aksrt') . '</code></li>
<li>' . esc_html__('默认使用空格分割,您可以在下方自定义分割符号,与您的数据对应即可', 'aksrt') . '</li>
<li>' . esc_html__('自定义参数格式为:', 'aksrt') . '<code>card_name=授权号&password_name=授权密码</code>,' . esc_html__('card_name为卡号的显示名称,password_name为卡密的显示名称,可以留空', 'aksrt') . '</li>
<li>' . esc_html__('单次导入数量太多可能会对服务器性能造成影响', 'aksrt') . '</li>
'<p><b>' . esc_html__('自定义卡密导入', 'jio') . '</b></p>
<ul class="jio-submsg-list">
<li>' . esc_html__('一行一个卡密,单行格式为:', 'jio') . '<code>' . esc_html__('卡号 密码 标识 自定义参数', 'jio') . '</code></li>
<li>' . esc_html__('默认使用空格分割,您可以在下方自定义分割符号,与您的数据对应即可', 'jio') . '</li>
<li>' . esc_html__('自定义参数格式为:', 'jio') . '<code>card_name=授权号&password_name=授权密码</code>,' . esc_html__('card_name为卡号的显示名称,password_name为卡密的显示名称,可以留空', 'jio') . '</li>
<li>' . esc_html__('单次导入数量太多可能会对服务器性能造成影响', 'jio') . '</li>
</ul>
<p><b>' . esc_html__('数据示例', 'aksrt') . '</b></p>
<div>4849977843617 3w20V4Qe7dRhAIoPYum6KSA ' . esc_html__('平台卡密01', 'aksrt') . ' card_name=' . esc_html__('授权号', 'aksrt') . '&password_name=' . esc_html__('授权码', 'aksrt') . '</div>
<div>6484027668705 SkdJZ5vWcpqPxrbs3pqPcpq ' . esc_html__('平台卡密01', 'aksrt') . ' card_name=' . esc_html__('授权号', 'aksrt') . '&password_name=' . esc_html__('授权码', 'aksrt') . '</div>
<div>111111@qq.com 11assssddsds ' . esc_html__('某某账号', 'aksrt') . ' card_name=' . esc_html__('账号', 'aksrt') . '&password_name=' . esc_html__('密码', 'aksrt') . '</div>'
<p><b>' . esc_html__('数据示例', 'jio') . '</b></p>
<div>4849977843617 3w20V4Qe7dRhAIoPYum6KSA ' . esc_html__('平台卡密01', 'jio') . ' card_name=' . esc_html__('授权号', 'jio') . '&password_name=' . esc_html__('授权码', 'jio') . '</div>
<div>6484027668705 SkdJZ5vWcpqPxrbs3pqPcpq ' . esc_html__('平台卡密01', 'jio') . ' card_name=' . esc_html__('授权号', 'jio') . '&password_name=' . esc_html__('授权码', 'jio') . '</div>
<div>111111@qq.com 11assssddsds ' . esc_html__('某某账号', 'jio') . ' card_name=' . esc_html__('账号', 'jio') . '&password_name=' . esc_html__('密码', 'jio') . '</div>'
);
$this->submessage_row(
[
@@ -195,72 +195,72 @@ trait PayChargeCardsForms {
['key' => 'type', 'op' => '==', 'val' => 'custom'],
['key' => 'only_pass_s', 'op' => 'checked'],
],
'<p><b>' . esc_html__('自定义卡密导入(单密码模式)', 'aksrt') . '</b></p>
<ul class="aksrt-submsg-list">
<li>' . esc_html__('一行一个卡密,单行格式为:', 'aksrt') . '<code>' . esc_html__('密码 标识 自定义参数', 'aksrt') . '</code></li>
<li>' . esc_html__('默认使用空格分割,您可以在下方自定义分割符号,与您的数据对应即可', 'aksrt') . '</li>
<li>' . esc_html__('单次导入数量太多可能会对服务器性能造成影响', 'aksrt') . '</li>
<li>' . esc_html__('自定义参数格式为:', 'aksrt') . '<code>password_name=授权密码</code>,' . esc_html__('password_name为卡密的显示名称,可以留空', 'aksrt') . '</li>
'<p><b>' . esc_html__('自定义卡密导入(单密码模式)', 'jio') . '</b></p>
<ul class="jio-submsg-list">
<li>' . esc_html__('一行一个卡密,单行格式为:', 'jio') . '<code>' . esc_html__('密码 标识 自定义参数', 'jio') . '</code></li>
<li>' . esc_html__('默认使用空格分割,您可以在下方自定义分割符号,与您的数据对应即可', 'jio') . '</li>
<li>' . esc_html__('单次导入数量太多可能会对服务器性能造成影响', 'jio') . '</li>
<li>' . esc_html__('自定义参数格式为:', 'jio') . '<code>password_name=授权密码</code>,' . esc_html__('password_name为卡密的显示名称,可以留空', 'jio') . '</li>
</ul>
<p><b>' . esc_html__('数据示例', 'aksrt') . '</b></p>
<div>3w20V4Qe7dRhAIoPYum6KSA ' . esc_html__('平台卡密01', 'aksrt') . ' password_name=' . esc_html__('授权码', 'aksrt') . '</div>
<div>SkdJZ5vWcpqPxrbs3pqPcpq ' . esc_html__('平台卡密01', 'aksrt') . ' password_name=' . esc_html__('授权码', 'aksrt') . '</div>'
<p><b>' . esc_html__('数据示例', 'jio') . '</b></p>
<div>3w20V4Qe7dRhAIoPYum6KSA ' . esc_html__('平台卡密01', 'jio') . ' password_name=' . esc_html__('授权码', 'jio') . '</div>
<div>SkdJZ5vWcpqPxrbs3pqPcpq ' . esc_html__('平台卡密01', 'jio') . ' password_name=' . esc_html__('授权码', 'jio') . '</div>'
);
$this->field_row(
__('卡密数据', 'aksrt'),
__('卡密数据', 'jio'),
'<textarea name="import_data" rows="10" class="large-text code" style="resize: both;max-width: none;"></textarea>',
[['key' => 'add_type', 'op' => '==', 'val' => 'import']]
);
$this->field_row(
__('自定义分隔符号', 'aksrt'),
__('自定义分隔符号', 'jio'),
'<input type="text" class="mini-input" style="width:80px;" name="import_division" value=" " />',
[['key' => 'add_type', 'op' => '==', 'val' => 'import']],
__('卡号和密码之间分割符号(默认为空格分割)', 'aksrt')
__('卡号和密码之间分割符号(默认为空格分割)', 'jio')
);
?>
</table>
<?php submit_button(__('确认提交', 'aksrt'), 'primary', 'submit', true); ?>
<?php submit_button(__('确认提交', 'jio'), 'primary', 'submit', true); ?>
</form>
<?php
}
private function render_export_form(): void {
?>
<form class="aksrt-cardpass-form" method="post" action="<?php echo esc_url(admin_url('admin-ajax.php')); ?>">
<form class="jio-cardpass-form" method="post" action="<?php echo esc_url(admin_url('admin-ajax.php')); ?>">
<input type="hidden" name="action" value="<?php echo esc_attr(self::EXPORT_AJAX_ACTION); ?>" />
<table class="form-table" role="presentation">
<?php
$this->field_row(
__('卡密类型', 'aksrt'),
__('卡密类型', 'jio'),
$this->button_set('type', $this->type_labels(), 'balance'),
[]
);
$this->field_row(
__('选择状态', 'aksrt'),
__('选择状态', 'jio'),
$this->button_set('status', [
'all' => __('全部', 'aksrt'),
'0' => __('未使用', 'aksrt'),
'used' => __('已使用', 'aksrt'),
'all' => __('全部', 'jio'),
'0' => __('未使用', 'jio'),
'used' => __('已使用', 'jio'),
], 'all'),
[['key' => 'type', 'op' => '!=', 'val' => 'custom']]
);
$this->field_row(
__('导出格式', 'aksrt'),
__('导出格式', 'jio'),
$this->button_set('export_format', [
'text' => __('文本文档', 'aksrt'),
'xls' => __('Excel表格', 'aksrt'),
'text' => __('文本文档', 'jio'),
'xls' => __('Excel表格', 'jio'),
], 'xls'),
[]
);
$this->field_row(
' ',
__('分隔符号', 'aksrt') . '<br><input type="text" class="mini-input" style="width:80px;" name="text_division" value=" " />',
__('分隔符号', 'jio') . '<br><input type="text" class="mini-input" style="width:80px;" name="text_division" value=" " />',
[['key' => 'export_format', 'op' => '==', 'val' => 'text']],
__('卡号和密码之间分割符号(默认为空格分割)', 'aksrt')
__('卡号和密码之间分割符号(默认为空格分割)', 'jio')
);
?>
</table>
<?php submit_button(__('确认提交', 'aksrt'), 'primary', 'submit', true); ?>
<?php submit_button(__('确认提交', 'jio'), 'primary', 'submit', true); ?>
</form>
<?php
}
@@ -294,9 +294,9 @@ trait PayChargeCardsForms {
// 把页面上所有「非 inline」的 notice 整块搬到 .wp-header-end(不存在时取 .wrap 内首个
// h1/h2)之后。提示块一旦带上 notice,就会被搬到页面顶部、脱离「添加方式—卡密数据」
// 之间的字段区,而且搬迁发生在求值之后,行内的显隐逻辑无从纠正。
// 外观改由 .aksrt-submsg 自带(见 print_assets),与参考主题 CSF content 字段一致。
// 外观改由 .jio-submsg 自带(见 print_assets),与参考主题 CSF content 字段一致。
// data-dep 与初始隐藏状态同时挂在 <tr> 和内层提示块上:提示块若脱离所在行,仍能自行显隐。
echo '<tr' . $dep_attr . $hidden . '><td colspan="2"><div' . $dep_attr . ' class="aksrt-submsg"' . $hidden . '>' . $content . '</div></td></tr>'; // phpcs:ignore WordPress.Security.EscapeOutput -- 文案已转义。
echo '<tr' . $dep_attr . $hidden . '><td colspan="2"><div' . $dep_attr . ' class="jio-submsg"' . $hidden . '>' . $content . '</div></td></tr>'; // phpcs:ignore WordPress.Security.EscapeOutput -- 文案已转义。
}
/**
@@ -398,9 +398,9 @@ trait PayChargeCardsForms {
* @param array<string,string> $options
*/
private function button_set(string $name, array $options, string $current): string {
$html = '<div class="aksrt-btnset">';
$html = '<div class="jio-btnset">';
foreach ($options as $value => $label) {
$html .= '<label class="aksrt-btnset-item"><input type="radio" name="' . esc_attr($name) . '" value="' . esc_attr((string) $value) . '"' . checked((string) $current, (string) $value, false) . '><span>' . esc_html((string) $label) . '</span></label>';
$html .= '<label class="jio-btnset-item"><input type="radio" name="' . esc_attr($name) . '" value="' . esc_attr((string) $value) . '"' . checked((string) $current, (string) $value, false) . '><span>' . esc_html((string) $label) . '</span></label>';
}
return $html . '</div>';
}
+42 -42
View File
@@ -1,12 +1,12 @@
<?php
/**
* @package AKSRT
* @package Jio
*
* PayChargeCards 列表渲染(过滤 / 搜索 / 排序 / 批量删除 / 分页)与翻页参数拼装。
* 作为 flat sibling trait 被 PayChargeCards 使用(PSR-4:trait 必须与宿主类同目录)。
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
@@ -16,7 +16,7 @@ trait PayChargeCardsList {
* @param string $s 搜索词(由页面头部统一解析后传入,用于提示、过滤链接与查询)。
*/
private function render_list(string $s): void {
$svc = \AKSRT\Payment\ChargeCard::get_instance();
$svc = \Jio\Payment\ChargeCard::get_instance();
$status = isset($_GET['status']) ? sanitize_key((string) wp_unslash($_GET['status'])) : '';
$type = isset($_GET['type']) ? sanitize_key((string) wp_unslash($_GET['type'])) : '';
@@ -39,11 +39,11 @@ trait PayChargeCardsList {
// 过滤链接(标题右侧按钮与搜索提示由页面头部统一输出)。
$sub_but = [
['name' => __('全部', 'aksrt'), 'href' => $this->page_url],
['name' => __('全部', 'jio'), 'href' => $this->page_url],
];
if ($s === '') {
$sub_but[] = ['name' => __('未使用', 'aksrt'), 'href' => add_query_arg('status', '0', $this->page_url)];
$sub_but[] = ['name' => __('已使用', 'aksrt'), 'href' => add_query_arg('status', 'used', $this->page_url)];
$sub_but[] = ['name' => __('未使用', 'jio'), 'href' => add_query_arg('status', '0', $this->page_url)];
$sub_but[] = ['name' => __('已使用', 'jio'), 'href' => add_query_arg('status', 'used', $this->page_url)];
foreach ($this->type_labels() as $_k => $_v) {
$sub_but[] = ['name' => $_v, 'href' => add_query_arg('type', $_k, $this->page_url)];
}
@@ -56,8 +56,8 @@ trait PayChargeCardsList {
$search = '<form class="form-inline form-order" method="post">
<div class="form-group" style="float: right;">
<input type="text" class="form-control" name="s" placeholder="' . esc_attr__('搜索卡密', 'aksrt') . '">
<button type="submit" class="button">' . esc_html__('提交', 'aksrt') . '</button>
<input type="text" class="form-control" name="s" placeholder="' . esc_attr__('搜索卡密', 'jio') . '">
<button type="submit" class="button">' . esc_html__('提交', 'jio') . '</button>
</div>
</form>';
@@ -93,8 +93,8 @@ trait PayChargeCardsList {
if (!$items) {
NativeUi::table_card_open();
echo '<table class="widefat fixed striped posts table table-bordered aksrt-cardpass-table">';
echo '<tbody>' . NativeUi::empty_row(1, esc_html__('未找到对应内容,或暂无内容', 'aksrt')) . '</tbody>';
echo '<table class="widefat fixed striped posts table table-bordered jio-cardpass-table">';
echo '<tbody>' . NativeUi::empty_row(1, esc_html__('未找到对应内容,或暂无内容', 'jio')) . '</tbody>';
echo '</table>';
NativeUi::table_wrap_close();
NativeUi::card_close();
@@ -103,22 +103,22 @@ trait PayChargeCardsList {
// 表头(与参考主题同列、同排序链接)。
$theads = [
['width' => '6%', 'orderby' => 'card', 'name' => __('卡号', 'aksrt')],
['width' => '10%', 'orderby' => 'password', 'name' => __('密码', 'aksrt')],
['width' => '5%', 'orderby' => 'type', 'name' => __('类型', 'aksrt')],
['width' => '5%', 'orderby' => 'create_time', 'name' => __('创建时间', 'aksrt')],
['width' => '5%', 'orderby' => 'modified_time', 'name' => __('更新时间', 'aksrt')],
['width' => '5%', 'orderby' => 'status', 'name' => __('状态', 'aksrt')],
['width' => '8%', 'orderby' => 'other', 'name' => __('标识', 'aksrt')],
['width' => '6%', 'orderby' => 'card', 'name' => __('卡号', 'jio')],
['width' => '10%', 'orderby' => 'password', 'name' => __('密码', 'jio')],
['width' => '5%', 'orderby' => 'type', 'name' => __('类型', 'jio')],
['width' => '5%', 'orderby' => 'create_time', 'name' => __('创建时间', 'jio')],
['width' => '5%', 'orderby' => 'modified_time', 'name' => __('更新时间', 'jio')],
['width' => '5%', 'orderby' => 'status', 'name' => __('状态', 'jio')],
['width' => '8%', 'orderby' => 'other', 'name' => __('标识', 'jio')],
];
$thead_th = '<td id="cb" class="manage-column column-cb check-column" style="width: 2%;"><label class="screen-reader-text" for="cb-select-all-1">' . esc_html__('全选', 'aksrt') . '</label><input id="cb-select-all-1" type="checkbox"></td>';
$thead_th = '<td id="cb" class="manage-column column-cb check-column" style="width: 2%;"><label class="screen-reader-text" for="cb-select-all-1">' . esc_html__('全选', 'jio') . '</label><input id="cb-select-all-1" type="checkbox"></td>';
foreach ($theads as $thead) {
$orderby_links = '';
if ($thead['orderby']) {
$orderby_url = add_query_arg('orderby', $thead['orderby']);
$orderby_links .= '<a title="' . esc_attr__('降序', 'aksrt') . '" href="' . esc_url(add_query_arg('desc', 'ASC', $orderby_url)) . '"><span class="dashicons dashicons-arrow-up"></span></a>';
$orderby_links .= '<a title="' . esc_attr__('升序', 'aksrt') . '" href="' . esc_url(add_query_arg('desc', 'DESC', $orderby_url)) . '"><span class="dashicons dashicons-arrow-down"></span></a>';
$orderby_links .= '<a title="' . esc_attr__('降序', 'jio') . '" href="' . esc_url(add_query_arg('desc', 'ASC', $orderby_url)) . '"><span class="dashicons dashicons-arrow-up"></span></a>';
$orderby_links .= '<a title="' . esc_attr__('升序', 'jio') . '" href="' . esc_url(add_query_arg('desc', 'DESC', $orderby_url)) . '"><span class="dashicons dashicons-arrow-down"></span></a>';
$orderby_links = '<span class="orderby-but">' . $orderby_links . '</span>';
}
$thead_th .= '<th class="" width="' . esc_attr($thead['width']) . '">' . esc_html($thead['name']) . $orderby_links . '</th>';
@@ -135,17 +135,17 @@ trait PayChargeCardsList {
// 自定义卡密不展示状态;其余类型按状态着色(类型链接统一在下方链表中生成)。
$status_html = '';
if ($msg['type'] !== 'custom') {
$status_html = '<span style="color: #3d7ffd;">' . esc_html__('未使用', 'aksrt') . '</span>';
$status_html = '<span style="color: #3d7ffd;">' . esc_html__('未使用', 'jio') . '</span>';
if ($msg['status'] === 'used') {
$status_html = '<span style="color: #f93b3b;">' . esc_html__('已使用', 'aksrt') . '</span>';
$status_html = '<span style="color: #f93b3b;">' . esc_html__('已使用', 'jio') . '</span>';
if (!empty($msg['order_number'])) {
$order_link_url = admin_url('admin.php?page=aksrt_orders&s=' . urlencode((string) $msg['order_number']));
$status_html .= '<a style="color: #ff6215;" target="_blank" href="' . esc_url($order_link_url) . '"> [' . esc_html__('查看', 'aksrt') . ']</a>';
$order_link_url = admin_url('admin.php?page=jio_orders&s=' . urlencode((string) $msg['order_number']));
$status_html .= '<a style="color: #ff6215;" target="_blank" href="' . esc_url($order_link_url) . '"> [' . esc_html__('查看', 'jio') . ']</a>';
}
} elseif ($msg['status'] === 'expired') {
$status_html = '<span style="color: #8c8f94;">' . esc_html__('已过期', 'aksrt') . '</span>';
$status_html = '<span style="color: #8c8f94;">' . esc_html__('已过期', 'jio') . '</span>';
} elseif ($msg['status'] === 'disabled') {
$status_html = '<span style="color: #8c8f94;">' . esc_html__('已禁用', 'aksrt') . '</span>';
$status_html = '<span style="color: #8c8f94;">' . esc_html__('已禁用', 'jio') . '</span>';
}
}
@@ -155,28 +155,28 @@ trait PayChargeCardsList {
}
if ($msg['type'] === 'vip') {
$type_html = '<a style="color:#975106" href="' . esc_url(add_query_arg('type', 'vip', $this->page_url)) . '">' . esc_html__('会员兑换', 'aksrt') . '</a>';
$type_html = '<a style="color:#975106" href="' . esc_url(add_query_arg('type', 'vip', $this->page_url)) . '">' . esc_html__('会员兑换', 'jio') . '</a>';
$level = (int) ($meta['level'] ?? 1);
$type_html .= '<br>' . esc_html($this->vip_level_name($level)) . ':' . esc_html($this->vip_time_text($meta));
} elseif ($msg['type'] === 'credits') {
$type_html = '<a style="color:#289d0f" href="' . esc_url(add_query_arg('type', 'credits', $this->page_url)) . '">' . esc_html__('积分兑换', 'aksrt') . '</a>';
$type_html .= '<br>' . esc_html__('积分:', 'aksrt') . esc_html((string) (int) ($meta['points'] ?? 0));
$type_html = '<a style="color:#289d0f" href="' . esc_url(add_query_arg('type', 'credits', $this->page_url)) . '">' . esc_html__('积分兑换', 'jio') . '</a>';
$type_html .= '<br>' . esc_html__('积分:', 'jio') . esc_html((string) (int) ($meta['points'] ?? 0));
} elseif ($msg['type'] === 'balance') {
$type_html = '<a style="color:#0c76e6" href="' . esc_url(add_query_arg('type', 'balance', $this->page_url)) . '">' . esc_html__('余额充值', 'aksrt') . '</a>';
$type_html .= '<br>' . esc_html__('金额:', 'aksrt') . esc_html($this->balance_amount_text($meta));
$type_html = '<a style="color:#0c76e6" href="' . esc_url(add_query_arg('type', 'balance', $this->page_url)) . '">' . esc_html__('余额充值', 'jio') . '</a>';
$type_html .= '<br>' . esc_html__('金额:', 'jio') . esc_html($this->balance_amount_text($meta));
} else {
$type_html = '<a style="color:#a42ade" href="' . esc_url(add_query_arg('type', 'custom', $this->page_url)) . '">' . esc_html__('自定义卡密', 'aksrt') . '</a>';
$type_html = '<a style="color:#a42ade" href="' . esc_url(add_query_arg('type', 'custom', $this->page_url)) . '">' . esc_html__('自定义卡密', 'jio') . '</a>';
}
$card_name = (string) ($meta['custom_name']['card_name'] ?? '');
$password_name = (string) ($meta['custom_name']['password_name'] ?? '');
$tbody .= '<tr>';
$tbody .= '<th scope="row" class="check-column"><label class="screen-reader-text" for="cb-select-' . (int) $msg['id'] . '">' . esc_html__('选择', 'aksrt') . '</label>
$tbody .= '<th scope="row" class="check-column"><label class="screen-reader-text" for="cb-select-' . (int) $msg['id'] . '">' . esc_html__('选择', 'jio') . '</label>
<input id="cb-select-' . (int) $msg['id'] . '" type="checkbox" name="action_id[]" value="' . (int) $msg['id'] . '">
</th>';
$tbody .= "<td><span data-clipboard-text='" . esc_attr((string) $msg['card']) . "' data-clipboard-tag=\"" . esc_attr($card_name !== '' ? $card_name : __('卡号', 'aksrt')) . "\">" . esc_html($card_name) . ' ' . esc_html((string) $msg['card']) . '</span></td>';
$tbody .= "<td><span data-clipboard-text='" . esc_attr((string) $msg['password']) . "' data-clipboard-tag=\"" . esc_attr($password_name !== '' ? $password_name : __('密码', 'aksrt')) . "\">" . esc_html($password_name) . ' ' . esc_html((string) $msg['password']) . '</span></td>';
$tbody .= "<td><span data-clipboard-text='" . esc_attr((string) $msg['card']) . "' data-clipboard-tag=\"" . esc_attr($card_name !== '' ? $card_name : __('卡号', 'jio')) . "\">" . esc_html($card_name) . ' ' . esc_html((string) $msg['card']) . '</span></td>';
$tbody .= "<td><span data-clipboard-text='" . esc_attr((string) $msg['password']) . "' data-clipboard-tag=\"" . esc_attr($password_name !== '' ? $password_name : __('密码', 'jio')) . "\">" . esc_html($password_name) . ' ' . esc_html((string) $msg['password']) . '</span></td>';
$tbody .= "<td>$type_html</td>";
$tbody .= '<td>' . esc_html((string) $msg['created_at']) . '</td>';
$tbody .= '<td>' . esc_html((string) ($msg['used_at'] ?: $msg['created_at'])) . '</td>';
@@ -185,18 +185,18 @@ trait PayChargeCardsList {
$tbody .= '</tr>';
}
echo '<form class="aksrt-cardpass-list-form" method="post">';
echo '<form class="jio-cardpass-list-form" method="post">';
wp_nonce_field(self::NONCE);
echo '<div class="bulkactions aksrt-native-bulk aksrt-cardpass-bulk">
<label for="bulk-action-selector-top" class="screen-reader-text">' . esc_html__('选择批量操作', 'aksrt') . '</label><select name="action" id="bulk-action-selector-top">
<option value="-1">' . esc_html__('批量操作', 'aksrt') . '</option>
<option value="delete">' . esc_html__('删除', 'aksrt') . '</option>
echo '<div class="bulkactions jio-native-bulk jio-cardpass-bulk">
<label for="bulk-action-selector-top" class="screen-reader-text">' . esc_html__('选择批量操作', 'jio') . '</label><select name="action" id="bulk-action-selector-top">
<option value="-1">' . esc_html__('批量操作', 'jio') . '</option>
<option value="delete">' . esc_html__('删除', 'jio') . '</option>
</select>
<input type="submit" class="button action" value="' . esc_attr__('应用', 'aksrt') . '">
<input type="submit" class="button action" value="' . esc_attr__('应用', 'jio') . '">
</div>';
NativeUi::table_card_open();
echo '<table class="widefat fixed striped posts table table-bordered aksrt-cardpass-table">';
echo '<table class="widefat fixed striped posts table table-bordered jio-cardpass-table">';
echo '<thead><tr>' . $thead_th . '</tr></thead>'; // phpcs:ignore WordPress.Security.EscapeOutput -- 已转义。
echo '<tbody>' . $tbody . '</tbody>'; // phpcs:ignore WordPress.Security.EscapeOutput -- 已转义。
echo '</table>';
+94 -94
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
@@ -12,9 +12,9 @@ class PayCoupons {
use PayCouponsForms;
public const CAPABILITY = 'manage_options';
public const PAGE_SLUG = 'aksrt_coupons';
public const CREATE_DIALOG = 'aksrt-coupon-create';
public const BATCH_DIALOG = 'aksrt-coupon-batch';
public const PAGE_SLUG = 'jio_coupons';
public const CREATE_DIALOG = 'jio-coupon-create';
public const BATCH_DIALOG = 'jio-coupon-batch';
private static $instance = null;
@@ -30,11 +30,11 @@ class PayCoupons {
return;
}
add_action('admin_enqueue_scripts', [$this, 'enqueue_assets']);
add_action('admin_post_aksrt_pay_create_coupon', [$this, 'handle_create_coupon']);
add_action('admin_post_aksrt_pay_batch_coupons', [$this, 'handle_batch_coupons']);
add_action('admin_post_aksrt_pay_update_coupon', [$this, 'handle_update_coupon']);
add_action('admin_post_aksrt_pay_disable_coupon', [$this, 'handle_disable_coupon']);
add_action('admin_post_aksrt_pay_enable_coupon', [$this, 'handle_enable_coupon']);
add_action('admin_post_jio_pay_create_coupon', [$this, 'handle_create_coupon']);
add_action('admin_post_jio_pay_batch_coupons', [$this, 'handle_batch_coupons']);
add_action('admin_post_jio_pay_update_coupon', [$this, 'handle_update_coupon']);
add_action('admin_post_jio_pay_disable_coupon', [$this, 'handle_disable_coupon']);
add_action('admin_post_jio_pay_enable_coupon', [$this, 'handle_enable_coupon']);
}
public function enqueue_assets(): void {
@@ -44,56 +44,56 @@ class PayCoupons {
}
$css = '/assets/css/admin/coupons.css';
$js = '/assets/js/admin-coupons.js';
wp_enqueue_style('aksrt-admin-coupons', AKSRT_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(AKSRT_DIR . $css));
wp_enqueue_script('aksrt-admin-coupons', AKSRT_URI . $js, [], (string) filemtime(AKSRT_DIR . $js), true);
wp_enqueue_style('jio-admin-coupons', JIO_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(JIO_DIR . $css));
wp_enqueue_script('jio-admin-coupons', JIO_URI . $js, [], (string) filemtime(JIO_DIR . $js), true);
}
public function render_page(): void {
$this->ensure_cap();
$ready = class_exists('\AKSRT\Payment\Coupon');
NativeUi::page_open('aksrt-coupons-page');
$ready = class_exists('\Jio\Payment\Coupon');
NativeUi::page_open('jio-coupons-page');
NativeUi::header(
__('优惠码', 'aksrt'),
__('创建、批量生成与管理结算优惠码。配置项仍在 AKSRT 主题设置 SPA。', 'aksrt'),
__('优惠码', 'jio'),
__('创建、批量生成与管理结算优惠码。配置项仍在 Jio 主题设置 SPA。', 'jio'),
'dashicons-tickets-alt',
$ready ? [
['label' => __('新建优惠码', 'aksrt'), 'dialog' => self::CREATE_DIALOG, 'primary' => true, 'icon' => 'dashicons-plus-alt2'],
['label' => __('批量生成', 'aksrt'), 'dialog' => self::BATCH_DIALOG, 'icon' => 'dashicons-randomize'],
['label' => __('新建优惠码', 'jio'), 'dialog' => self::CREATE_DIALOG, 'primary' => true, 'icon' => 'dashicons-plus-alt2'],
['label' => __('批量生成', 'jio'), 'dialog' => self::BATCH_DIALOG, 'icon' => 'dashicons-randomize'],
] : []
);
$this->render_flash();
if (!$ready) {
echo '<div class="notice notice-error"><p>' . esc_html__('优惠码模块未初始化。', 'aksrt') . '</p></div>';
echo '<div class="notice notice-error"><p>' . esc_html__('优惠码模块未初始化。', 'jio') . '</p></div>';
NativeUi::page_close();
return;
}
$svc = \AKSRT\Payment\Coupon::get_instance();
$svc = \Jio\Payment\Coupon::get_instance();
$edit_id = isset($_GET['edit_id']) ? (int) $_GET['edit_id'] : 0;
$edit_row = $edit_id > 0 ? $svc->get($edit_id) : null;
if ($edit_id > 0 && $edit_row) {
$this->render_edit_form($edit_row);
} elseif ($edit_id > 0) {
echo '<div class="notice notice-error"><p>' . esc_html__('优惠码不存在。', 'aksrt') . '</p></div>';
echo '<div class="notice notice-error"><p>' . esc_html__('优惠码不存在。', 'jio') . '</p></div>';
}
$this->render_generated_codes();
$this->render_list($svc);
$this->render_create_dialog();
$this->render_batch_dialog();
echo '<div class="aksrt-coupon-toast" role="status" aria-live="polite" data-aksrt-coupon-toast'
. ' data-copied="' . esc_attr__('已复制到剪贴板', 'aksrt') . '"'
. ' data-copy-failed="' . esc_attr__('自动复制失败,请手动选中后复制。', 'aksrt') . '"></div>';
echo '<div class="jio-coupon-toast" role="status" aria-live="polite" data-jio-coupon-toast'
. ' data-copied="' . esc_attr__('已复制到剪贴板', 'jio') . '"'
. ' data-copy-failed="' . esc_attr__('自动复制失败,请手动选中后复制。', 'jio') . '"></div>';
NativeUi::page_close();
}
/** 批量生成后一次性展示本批代码(transient 读取即删),附「复制全部」。 */
private function render_generated_codes(): void {
$key = 'aksrt_pay_coupon_codes_' . get_current_user_id();
$key = 'jio_pay_coupon_codes_' . get_current_user_id();
$codes = get_transient($key);
if (!is_array($codes) || !$codes) {
return;
@@ -101,46 +101,46 @@ class PayCoupons {
delete_transient($key);
$text = implode("\n", array_map('strval', $codes));
$copy = '<button type="button" class="button button-small" data-clipboard-text="' . esc_attr($text) . '">'
. '<span class="dashicons dashicons-admin-page" aria-hidden="true"></span>' . esc_html__('复制全部', 'aksrt') . '</button>';
. '<span class="dashicons dashicons-admin-page" aria-hidden="true"></span>' . esc_html__('复制全部', 'jio') . '</button>';
NativeUi::card_open(
sprintf(
/* translators: %d count */
__('本次生成的 %d 个优惠码', 'aksrt'),
__('本次生成的 %d 个优惠码', 'jio'),
count($codes)
),
$copy,
false,
'aksrt-coupon-generated'
'jio-coupon-generated'
);
echo '<p class="aksrt-native-field__hint">' . esc_html__('离开或刷新本页后不再显示,请及时复制保存。', 'aksrt') . '</p>';
echo '<textarea class="large-text code" rows="' . esc_attr((string) min(12, max(3, count($codes)))) . '" readonly aria-label="' . esc_attr__('本次生成的优惠码', 'aksrt') . '">' . esc_textarea($text) . '</textarea>';
echo '<p class="jio-native-field__hint">' . esc_html__('离开或刷新本页后不再显示,请及时复制保存。', 'jio') . '</p>';
echo '<textarea class="large-text code" rows="' . esc_attr((string) min(12, max(3, count($codes)))) . '" readonly aria-label="' . esc_attr__('本次生成的优惠码', 'jio') . '">' . esc_textarea($text) . '</textarea>';
NativeUi::card_close();
}
private function render_list(\AKSRT\Payment\Coupon $svc): void {
private function render_list(\Jio\Payment\Coupon $svc): void {
$status = isset($_GET['status']) ? sanitize_key((string) wp_unslash($_GET['status'])) : '';
$search = isset($_GET['s']) ? sanitize_text_field((string) wp_unslash($_GET['s'])) : '';
$page_num = max(1, (int) ($_GET['paged'] ?? 1));
$per_page = AdminListPagination::DEFAULT_PER_PAGE;
$type_labels = [
'fixed' => __('固定金额', 'aksrt'),
'percent' => __('折扣百分比', 'aksrt'),
'fixed' => __('固定金额', 'jio'),
'percent' => __('折扣百分比', 'jio'),
];
$status_map = [
'active' => [__('可用', 'aksrt'), 'success'],
'disabled' => [__('已禁用', 'aksrt'), 'neutral'],
'active' => [__('可用', 'jio'), 'success'],
'disabled' => [__('已禁用', 'jio'), 'neutral'],
];
// 筛选栏(卡片头部的 .aksrt-native-filters 表单)。
$filters = '<form method="get" class="aksrt-native-filters">';
// 筛选栏(卡片头部的 .jio-native-filters 表单)。
$filters = '<form method="get" class="jio-native-filters">';
$filters .= '<input type="hidden" name="page" value="' . esc_attr(self::PAGE_SLUG) . '" />';
$filters .= '<select name="status">';
foreach (['' => __('全部状态', 'aksrt'), 'active' => __('可用', 'aksrt'), 'disabled' => __('已禁用', 'aksrt')] as $key => $label) {
foreach (['' => __('全部状态', 'jio'), 'active' => __('可用', 'jio'), 'disabled' => __('已禁用', 'jio')] as $key => $label) {
$filters .= sprintf('<option value="%s"%s>%s</option>', esc_attr($key), selected($status, $key, false), esc_html($label));
}
$filters .= '</select>';
$filters .= '<input type="search" name="s" value="' . esc_attr($search) . '" placeholder="' . esc_attr__('搜索代码', 'aksrt') . '" />';
$filters .= '<button type="submit" class="button">' . esc_html__('筛选', 'aksrt') . '</button>';
$filters .= '<input type="search" name="s" value="' . esc_attr($search) . '" placeholder="' . esc_attr__('搜索代码', 'jio') . '" />';
$filters .= '<button type="submit" class="button">' . esc_html__('筛选', 'jio') . '</button>';
$filters .= '</form>';
$result = $svc->get_list([
@@ -166,12 +166,12 @@ class PayCoupons {
$items = $result['items'] ?? [];
}
NativeUi::table_card_open(__('优惠码列表', 'aksrt'), $filters);
NativeUi::table_card_open(__('优惠码列表', 'jio'), $filters);
echo '<table class="wp-list-table widefat fixed striped"><thead><tr>';
echo '<th>ID</th><th>' . esc_html__('代码', 'aksrt') . '</th><th>' . esc_html__('类型', 'aksrt') . '</th><th>' . esc_html__('面值', 'aksrt') . '</th><th>' . esc_html__('门槛/封顶', 'aksrt') . '</th><th>' . esc_html__('范围', 'aksrt') . '</th><th>' . esc_html__('已用/上限/每人', 'aksrt') . '</th><th>' . esc_html__('状态', 'aksrt') . '</th><th>' . esc_html__('过期', 'aksrt') . '</th><th>' . esc_html__('操作', 'aksrt') . '</th>';
echo '<th>ID</th><th>' . esc_html__('代码', 'jio') . '</th><th>' . esc_html__('类型', 'jio') . '</th><th>' . esc_html__('面值', 'jio') . '</th><th>' . esc_html__('门槛/封顶', 'jio') . '</th><th>' . esc_html__('范围', 'jio') . '</th><th>' . esc_html__('已用/上限/每人', 'jio') . '</th><th>' . esc_html__('状态', 'jio') . '</th><th>' . esc_html__('过期', 'jio') . '</th><th>' . esc_html__('操作', 'jio') . '</th>';
echo '</tr></thead><tbody>';
if (!$items) {
echo NativeUi::empty_row(10, __('暂无优惠码', 'aksrt'), __('点击右上角「新建优惠码」或「批量生成」添加。', 'aksrt')); // phpcs:ignore WordPress.Security.EscapeOutput -- 内部构造,已转义。
echo NativeUi::empty_row(10, __('暂无优惠码', 'jio'), __('点击右上角「新建优惠码」或「批量生成」添加。', 'jio')); // phpcs:ignore WordPress.Security.EscapeOutput -- 内部构造,已转义。
}
foreach ($items as $row) {
$id = (int) ($row['id'] ?? 0);
@@ -180,7 +180,7 @@ class PayCoupons {
echo '<tr>';
echo '<td>' . esc_html((string) $id) . '</td>';
$code = (string) ($row['code'] ?? '');
echo '<td><button type="button" class="aksrt-coupon-code aksrt-native-mono" data-clipboard-text="' . esc_attr($code) . '" aria-label="' . esc_attr(sprintf(__('复制优惠码 %s', 'aksrt'), $code)) . '">'
echo '<td><button type="button" class="jio-coupon-code jio-native-mono" data-clipboard-text="' . esc_attr($code) . '" aria-label="' . esc_attr(sprintf(__('复制优惠码 %s', 'jio'), $code)) . '">'
. esc_html($code) . '<span class="dashicons dashicons-admin-page" aria-hidden="true"></span></button></td>';
echo '<td>' . esc_html($type_labels[$row_type] ?? $row_type) . '</td>';
echo '<td>' . esc_html((string) ($row['value'] ?? '')) . ($row_type === 'percent' ? '%' : '') . '</td>';
@@ -189,21 +189,21 @@ class PayCoupons {
echo '<td>' . esc_html((string) ((int) ($row['used_count'] ?? 0)) . '/' . (string) ((int) ($row['usage_limit'] ?? 0)) . '/' . (string) ((int) ($row['per_user_limit'] ?? 0))) . '</td>';
echo '<td>' . NativeUi::status_badge($st, $status_map) . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput -- 内部构造,已转义。
echo '<td>' . esc_html((string) ($row['expires_at'] ?? '')) . '</td>';
echo '<td><div class="aksrt-native-inline">';
echo '<a class="button button-small" href="' . esc_url($this->page_url(['edit_id' => $id])) . '">' . esc_html__('编辑', 'aksrt') . '</a>';
echo '<td><div class="jio-native-inline">';
echo '<a class="button button-small" href="' . esc_url($this->page_url(['edit_id' => $id])) . '">' . esc_html__('编辑', 'jio') . '</a>';
if ($st === 'active') {
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-native-inline">';
echo '<input type="hidden" name="action" value="aksrt_pay_disable_coupon" />';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="jio-native-inline">';
echo '<input type="hidden" name="action" value="jio_pay_disable_coupon" />';
echo '<input type="hidden" name="coupon_id" value="' . esc_attr((string) $id) . '" />';
wp_nonce_field('aksrt_pay_disable_coupon_' . $id);
submit_button(__('禁用', 'aksrt'), 'small', '', false);
wp_nonce_field('jio_pay_disable_coupon_' . $id);
submit_button(__('禁用', 'jio'), 'small', '', false);
echo '</form>';
} elseif ($st === 'disabled') {
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-native-inline">';
echo '<input type="hidden" name="action" value="aksrt_pay_enable_coupon" />';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="jio-native-inline">';
echo '<input type="hidden" name="action" value="jio_pay_enable_coupon" />';
echo '<input type="hidden" name="coupon_id" value="' . esc_attr((string) $id) . '" />';
wp_nonce_field('aksrt_pay_enable_coupon_' . $id);
submit_button(__('启用', 'aksrt'), 'small', '', false);
wp_nonce_field('jio_pay_enable_coupon_' . $id);
submit_button(__('启用', 'jio'), 'small', '', false);
echo '</form>';
}
echo '</div></td></tr>';
@@ -224,13 +224,13 @@ class PayCoupons {
public function handle_create_coupon(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
wp_die(esc_html__('权限不足', 'jio'));
}
check_admin_referer('aksrt_pay_create_coupon');
check_admin_referer('jio_pay_create_coupon');
$ok = false;
if (class_exists('\AKSRT\Payment\Coupon')) {
$id = \AKSRT\Payment\Coupon::get_instance()->create([
if (class_exists('\Jio\Payment\Coupon')) {
$id = \Jio\Payment\Coupon::get_instance()->create([
'code' => sanitize_text_field((string) wp_unslash($_POST['code'] ?? '')),
'type' => sanitize_key((string) wp_unslash($_POST['type'] ?? 'fixed')),
'value' => trim((string) wp_unslash($_POST['value'] ?? '0')),
@@ -243,19 +243,19 @@ class PayCoupons {
]);
$ok = $id > 0;
}
wp_safe_redirect($this->page_url(['aksrt_pay_msg' => $ok ? 'coupon_created' : 'coupon_create_failed']));
wp_safe_redirect($this->page_url(['jio_pay_msg' => $ok ? 'coupon_created' : 'coupon_create_failed']));
exit;
}
public function handle_batch_coupons(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
wp_die(esc_html__('权限不足', 'jio'));
}
check_admin_referer('aksrt_pay_batch_coupons');
check_admin_referer('jio_pay_batch_coupons');
$ok = false;
if (class_exists('\AKSRT\Payment\Coupon')) {
$result = \AKSRT\Payment\Coupon::get_instance()->batch_create([
if (class_exists('\Jio\Payment\Coupon')) {
$result = \Jio\Payment\Coupon::get_instance()->batch_create([
'type' => sanitize_key((string) wp_unslash($_POST['type'] ?? 'fixed')),
'value' => trim((string) wp_unslash($_POST['value'] ?? '0')),
'min_amount' => trim((string) wp_unslash($_POST['min_amount'] ?? '0')),
@@ -268,24 +268,24 @@ class PayCoupons {
$codes = is_array($result['codes'] ?? null) ? $result['codes'] : [];
$ok = (int) ($result['created'] ?? 0) > 0;
if ($ok) {
set_transient('aksrt_pay_coupon_codes_' . get_current_user_id(), $codes, 10 * MINUTE_IN_SECONDS);
set_transient('jio_pay_coupon_codes_' . get_current_user_id(), $codes, 10 * MINUTE_IN_SECONDS);
}
}
wp_safe_redirect($this->page_url(['aksrt_pay_msg' => $ok ? 'coupon_batch_ok' : 'coupon_batch_failed']));
wp_safe_redirect($this->page_url(['jio_pay_msg' => $ok ? 'coupon_batch_ok' : 'coupon_batch_failed']));
exit;
}
public function handle_update_coupon(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
wp_die(esc_html__('权限不足', 'jio'));
}
$id = isset($_POST['coupon_id']) ? (int) $_POST['coupon_id'] : 0;
check_admin_referer('aksrt_pay_update_coupon_' . $id);
check_admin_referer('jio_pay_update_coupon_' . $id);
$ok = false;
if ($id > 0 && class_exists('\AKSRT\Payment\Coupon')) {
$ok = \AKSRT\Payment\Coupon::get_instance()->update($id, [
if ($id > 0 && class_exists('\Jio\Payment\Coupon')) {
$ok = \Jio\Payment\Coupon::get_instance()->update($id, [
'type' => sanitize_key((string) wp_unslash($_POST['type'] ?? 'fixed')),
'value' => trim((string) wp_unslash($_POST['value'] ?? '0')),
'min_amount' => trim((string) wp_unslash($_POST['min_amount'] ?? '0')),
@@ -297,7 +297,7 @@ class PayCoupons {
]);
}
wp_safe_redirect($this->page_url([
'aksrt_pay_msg' => $ok ? 'coupon_updated' : 'coupon_update_failed',
'jio_pay_msg' => $ok ? 'coupon_updated' : 'coupon_update_failed',
'edit_id' => $id,
]));
exit;
@@ -305,23 +305,23 @@ class PayCoupons {
public function handle_disable_coupon(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
wp_die(esc_html__('权限不足', 'jio'));
}
$id = isset($_POST['coupon_id']) ? (int) $_POST['coupon_id'] : 0;
check_admin_referer('aksrt_pay_disable_coupon_' . $id);
$ok = $id > 0 && class_exists('\AKSRT\Payment\Coupon') && \AKSRT\Payment\Coupon::get_instance()->disable($id);
wp_safe_redirect($this->page_url(['aksrt_pay_msg' => $ok ? 'coupon_disabled' : 'coupon_failed']));
check_admin_referer('jio_pay_disable_coupon_' . $id);
$ok = $id > 0 && class_exists('\Jio\Payment\Coupon') && \Jio\Payment\Coupon::get_instance()->disable($id);
wp_safe_redirect($this->page_url(['jio_pay_msg' => $ok ? 'coupon_disabled' : 'coupon_failed']));
exit;
}
public function handle_enable_coupon(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
wp_die(esc_html__('权限不足', 'jio'));
}
$id = isset($_POST['coupon_id']) ? (int) $_POST['coupon_id'] : 0;
check_admin_referer('aksrt_pay_enable_coupon_' . $id);
$ok = $id > 0 && class_exists('\AKSRT\Payment\Coupon') && \AKSRT\Payment\Coupon::get_instance()->enable($id);
wp_safe_redirect($this->page_url(['aksrt_pay_msg' => $ok ? 'coupon_enabled' : 'coupon_failed']));
check_admin_referer('jio_pay_enable_coupon_' . $id);
$ok = $id > 0 && class_exists('\Jio\Payment\Coupon') && \Jio\Payment\Coupon::get_instance()->enable($id);
wp_safe_redirect($this->page_url(['jio_pay_msg' => $ok ? 'coupon_enabled' : 'coupon_failed']));
exit;
}
@@ -370,15 +370,15 @@ class PayCoupons {
$scope = is_array($decoded) ? $decoded : [];
}
if (!is_array($scope) || empty($scope)) {
return __('全部', 'aksrt');
return __('全部', 'jio');
}
$type = sanitize_key((string) ($scope['type'] ?? ''));
$ids = array_map('intval', (array) ($scope['ids'] ?? []));
$id_text = $ids ? implode(',', $ids) : '—';
$map = [
'include' => __('含商品', 'aksrt'),
'exclude' => __('排除商品', 'aksrt'),
'category' => __('分类', 'aksrt'),
'include' => __('含商品', 'jio'),
'exclude' => __('排除商品', 'jio'),
'category' => __('分类', 'jio'),
];
$label = $map[$type] ?? $type;
return $label . ':' . $id_text;
@@ -386,25 +386,25 @@ class PayCoupons {
private function ensure_cap(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
wp_die(esc_html__('您没有权限访问此页面。', 'jio'));
}
}
private function render_flash(): void {
if (empty($_GET['aksrt_pay_msg'])) {
if (empty($_GET['jio_pay_msg'])) {
return;
}
$code = sanitize_key((string) wp_unslash($_GET['aksrt_pay_msg']));
$code = sanitize_key((string) wp_unslash($_GET['jio_pay_msg']));
$map = [
'coupon_created' => __('优惠码已创建。', 'aksrt'),
'coupon_create_failed' => __('优惠码创建失败(可能重复)。', 'aksrt'),
'coupon_batch_ok' => __('优惠码批量生成完成。', 'aksrt'),
'coupon_batch_failed' => __('优惠码批量生成失败。', 'aksrt'),
'coupon_updated' => __('优惠码已更新。', 'aksrt'),
'coupon_update_failed' => __('优惠码更新失败。', 'aksrt'),
'coupon_disabled' => __('优惠码已禁用。', 'aksrt'),
'coupon_enabled' => __('优惠码已启用。', 'aksrt'),
'coupon_failed' => __('优惠码操作失败。', 'aksrt'),
'coupon_created' => __('优惠码已创建。', 'jio'),
'coupon_create_failed' => __('优惠码创建失败(可能重复)。', 'jio'),
'coupon_batch_ok' => __('优惠码批量生成完成。', 'jio'),
'coupon_batch_failed' => __('优惠码批量生成失败。', 'jio'),
'coupon_updated' => __('优惠码已更新。', 'jio'),
'coupon_update_failed' => __('优惠码更新失败。', 'jio'),
'coupon_disabled' => __('优惠码已禁用。', 'jio'),
'coupon_enabled' => __('优惠码已启用。', 'jio'),
'coupon_failed' => __('优惠码操作失败。', 'jio'),
];
if (!isset($map[$code])) {
return;
+63 -63
View File
@@ -3,13 +3,13 @@
* 优惠码:新建 / 批量生成弹窗与编辑表单。
*
* 作为 flat sibling trait 被 PayCoupons 使用(PSR-4:trait 必须与宿主类同目录)。
* 两个弹窗由页头按钮(data-aksrt-dialog-open)打开,交互在 admin-coupons.js;
* 两个弹窗由页头按钮(data-jio-dialog-open)打开,交互在 admin-coupons.js;
* 三张表单共用 render_rule_fields() 输出的优惠规则字段。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
@@ -18,15 +18,15 @@ trait PayCouponsForms {
private function render_create_dialog(): void {
$this->dialog_open(
self::CREATE_DIALOG,
__('新建优惠码', 'aksrt'),
__('手动指定一个优惠码,用户结算时输入即可抵扣。', 'aksrt'),
'aksrt_pay_create_coupon'
__('新建优惠码', 'jio'),
__('手动指定一个优惠码,用户结算时输入即可抵扣。', 'jio'),
'jio_pay_create_coupon'
);
echo '<div class="aksrt-native-form-grid">';
echo '<div class="jio-native-form-grid">';
$this->field(
__('代码', 'aksrt'),
'<input type="text" name="code" maxlength="60" required autofocus autocomplete="off" spellcheck="false" class="aksrt-native-mono">',
__('需全站唯一,最多 60 个字符。', 'aksrt'),
__('代码', 'jio'),
'<input type="text" name="code" maxlength="60" required autofocus autocomplete="off" spellcheck="false" class="jio-native-mono">',
__('需全站唯一,最多 60 个字符。', 'jio'),
true
);
$this->render_rule_fields([
@@ -38,24 +38,24 @@ trait PayCouponsForms {
'per_user_limit' => '0',
]);
echo '</div>';
$this->dialog_close(__('创建优惠码', 'aksrt'));
$this->dialog_close(__('创建优惠码', 'jio'));
}
private function render_batch_dialog(): void {
$this->dialog_open(
self::BATCH_DIALOG,
__('批量生成优惠码', 'aksrt'),
__('按相同规则随机生成一批优惠码,生成后会在页面上列出供复制。', 'aksrt'),
'aksrt_pay_batch_coupons'
__('批量生成优惠码', 'jio'),
__('按相同规则随机生成一批优惠码,生成后会在页面上列出供复制。', 'jio'),
'jio_pay_batch_coupons'
);
echo '<h3 class="aksrt-coupon-section">' . esc_html__('生成规则', 'aksrt') . '</h3>';
echo '<div class="aksrt-native-form-grid aksrt-coupon-batch-grid">';
$this->field(__('数量', 'aksrt'), '<input type="number" name="count" min="1" max="100" step="1" value="10" required autofocus>', __('单次 1–100 个。', 'aksrt'));
$this->field(__('随机位数', 'aksrt'), '<input type="number" name="length" min="6" max="24" step="1" value="10" required>', __('6–24 位,不含前缀。', 'aksrt'));
$this->field(__('前缀', 'aksrt'), '<input type="text" name="prefix" maxlength="36" autocomplete="off" spellcheck="false" class="aksrt-native-mono">', __('选填,如 VIP2026-。', 'aksrt'));
echo '<h3 class="jio-coupon-section">' . esc_html__('生成规则', 'jio') . '</h3>';
echo '<div class="jio-native-form-grid jio-coupon-batch-grid">';
$this->field(__('数量', 'jio'), '<input type="number" name="count" min="1" max="100" step="1" value="10" required autofocus>', __('单次 1–100 个。', 'jio'));
$this->field(__('随机位数', 'jio'), '<input type="number" name="length" min="6" max="24" step="1" value="10" required>', __('6–24 位,不含前缀。', 'jio'));
$this->field(__('前缀', 'jio'), '<input type="text" name="prefix" maxlength="36" autocomplete="off" spellcheck="false" class="jio-native-mono">', __('选填,如 VIP2026-。', 'jio'));
echo '</div>';
echo '<h3 class="aksrt-coupon-section">' . esc_html__('优惠规则', 'aksrt') . '</h3>';
echo '<div class="aksrt-native-form-grid">';
echo '<h3 class="jio-coupon-section">' . esc_html__('优惠规则', 'jio') . '</h3>';
echo '<div class="jio-native-form-grid">';
$this->render_rule_fields([
'type' => 'fixed',
'value' => '10',
@@ -65,7 +65,7 @@ trait PayCouponsForms {
'per_user_limit' => '1',
]);
echo '</div>';
$this->dialog_close(__('批量生成', 'aksrt'));
$this->dialog_close(__('批量生成', 'jio'));
}
/**
@@ -78,18 +78,18 @@ trait PayCouponsForms {
$title = sprintf(
/* translators: %d coupon id */
__('编辑优惠码 #%d', 'aksrt'),
__('编辑优惠码 #%d', 'jio'),
$id
);
$back = NativeUi::button_link(__('返回列表', 'aksrt'), $this->page_url(), false, 'dashicons-arrow-left-alt');
$back = NativeUi::button_link(__('返回列表', 'jio'), $this->page_url(), false, 'dashicons-arrow-left-alt');
NativeUi::card_open($title, $back);
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" data-aksrt-coupon-form data-busy-label="' . esc_attr__('正在保存…', 'aksrt') . '">';
echo '<input type="hidden" name="action" value="aksrt_pay_update_coupon" />';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" data-jio-coupon-form data-busy-label="' . esc_attr__('正在保存…', 'jio') . '">';
echo '<input type="hidden" name="action" value="jio_pay_update_coupon" />';
echo '<input type="hidden" name="coupon_id" value="' . esc_attr((string) $id) . '" />';
wp_nonce_field('aksrt_pay_update_coupon_' . $id);
echo '<div class="aksrt-native-form-grid">';
$this->field(__('代码', 'aksrt'), '<input type="text" value="' . esc_attr((string) ($row['code'] ?? '')) . '" readonly class="aksrt-native-mono" />', __('代码创建后不可修改。', 'aksrt'));
wp_nonce_field('jio_pay_update_coupon_' . $id);
echo '<div class="jio-native-form-grid">';
$this->field(__('代码', 'jio'), '<input type="text" value="' . esc_attr((string) ($row['code'] ?? '')) . '" readonly class="jio-native-mono" />', __('代码创建后不可修改。', 'jio'));
$this->render_rule_fields([
'type' => (string) ($row['type'] ?? 'fixed'),
'value' => (string) ($row['value'] ?? ''),
@@ -102,8 +102,8 @@ trait PayCouponsForms {
'expires_at' => $expires !== '' ? str_replace(' ', 'T', substr($expires, 0, 16)) : '',
]);
echo '</div>';
echo '<div class="aksrt-native-form-foot">';
echo '<button type="submit" class="button button-primary" data-aksrt-coupon-submit>' . esc_html__('保存修改', 'aksrt') . '</button>';
echo '<div class="jio-native-form-foot">';
echo '<button type="submit" class="button button-primary" data-jio-coupon-submit>' . esc_html__('保存修改', 'jio') . '</button>';
echo '</div>';
echo '</form>';
NativeUi::card_close();
@@ -119,39 +119,39 @@ trait PayCouponsForms {
$percent = $type === 'percent';
$currency = $this->currency_symbol();
$this->field(__('类型', 'aksrt'), $this->type_select($type));
$this->field(__('类型', 'jio'), $this->type_select($type));
$this->field(
__('面值', 'aksrt'),
'<span class="aksrt-coupon-inline"><input type="number" name="value" min="0" step="' . ($percent ? '1' : '0.01') . '"' . ($percent ? ' max="100"' : '')
__('面值', 'jio'),
'<span class="jio-coupon-inline"><input type="number" name="value" min="0" step="' . ($percent ? '1' : '0.01') . '"' . ($percent ? ' max="100"' : '')
. ' value="' . esc_attr($v['value'] ?? '') . '" required>'
. '<span class="aksrt-coupon-unit" data-aksrt-coupon-unit data-fixed="' . esc_attr($currency) . '" data-percent="%">' . esc_html($percent ? '%' : $currency) . '</span></span>',
__('固定金额直接抵扣;折扣百分比填 20 表示减免 20%(0–100 的整数)。', 'aksrt')
. '<span class="jio-coupon-unit" data-jio-coupon-unit data-fixed="' . esc_attr($currency) . '" data-percent="%">' . esc_html($percent ? '%' : $currency) . '</span></span>',
__('固定金额直接抵扣;折扣百分比填 20 表示减免 20%(0–100 的整数)。', 'jio')
);
$this->field(__('最低消费', 'aksrt'), $this->money_input('min_amount', $v['min_amount'] ?? '0'), __('订单金额达到该值才可使用,0 表示无门槛。', 'aksrt'));
$this->field(__('最高优惠', 'aksrt'), $this->money_input('max_discount', $v['max_discount'] ?? '0'), __('折扣百分比的封顶金额,0 表示不封顶。', 'aksrt'));
$this->field(__('总次数', 'aksrt'), '<input type="number" name="usage_limit" min="0" step="1" value="' . esc_attr($v['usage_limit'] ?? '0') . '">', __('所有用户合计可用次数,0 表示不限。', 'aksrt'));
$this->field(__('每人限次', 'aksrt'), '<input type="number" name="per_user_limit" min="0" step="1" value="' . esc_attr($v['per_user_limit'] ?? '0') . '">', __('单个用户可用次数,0 表示不限。', 'aksrt'));
$this->field(__('最低消费', 'jio'), $this->money_input('min_amount', $v['min_amount'] ?? '0'), __('订单金额达到该值才可使用,0 表示无门槛。', 'jio'));
$this->field(__('最高优惠', 'jio'), $this->money_input('max_discount', $v['max_discount'] ?? '0'), __('折扣百分比的封顶金额,0 表示不封顶。', 'jio'));
$this->field(__('总次数', 'jio'), '<input type="number" name="usage_limit" min="0" step="1" value="' . esc_attr($v['usage_limit'] ?? '0') . '">', __('所有用户合计可用次数,0 表示不限。', 'jio'));
$this->field(__('每人限次', 'jio'), '<input type="number" name="per_user_limit" min="0" step="1" value="' . esc_attr($v['per_user_limit'] ?? '0') . '">', __('单个用户可用次数,0 表示不限。', 'jio'));
$select = '<select name="scope_type">';
foreach ([
'' => __('全部商品', 'aksrt'),
'include' => __('仅指定商品 ID', 'aksrt'),
'exclude' => __('排除商品 ID', 'aksrt'),
'category' => __('指定分类 ID', 'aksrt'),
'' => __('全部商品', 'jio'),
'include' => __('仅指定商品 ID', 'jio'),
'exclude' => __('排除商品 ID', 'jio'),
'category' => __('指定分类 ID', 'jio'),
] as $k => $label) {
$select .= sprintf('<option value="%s"%s>%s</option>', esc_attr($k), selected($v['scope_type'] ?? '', $k, false), esc_html($label));
}
$select .= '</select>';
$this->field(__('适用范围', 'aksrt'), $select);
$this->field(__('适用范围', 'jio'), $select);
$this->field(
__('范围 ID', 'aksrt'),
'<input type="text" name="scope_ids" value="' . esc_attr($v['scope_ids'] ?? '') . '" placeholder="' . esc_attr__('如 12,34', 'aksrt') . '" autocomplete="off">',
__('逗号分隔;选择「全部商品」时无需填写。', 'aksrt')
__('范围 ID', 'jio'),
'<input type="text" name="scope_ids" value="' . esc_attr($v['scope_ids'] ?? '') . '" placeholder="' . esc_attr__('如 12,34', 'jio') . '" autocomplete="off">',
__('逗号分隔;选择「全部商品」时无需填写。', 'jio')
);
$this->field(
__('过期时间', 'aksrt'),
__('过期时间', 'jio'),
'<input type="datetime-local" name="expires_at" value="' . esc_attr($v['expires_at'] ?? '') . '">',
__('留空表示永不过期。', 'aksrt')
__('留空表示永不过期。', 'jio')
);
}
@@ -161,40 +161,40 @@ trait PayCouponsForms {
private function type_select(string $current): string {
$html = '<select name="type">';
foreach (['fixed' => __('固定金额', 'aksrt'), 'percent' => __('折扣百分比', 'aksrt')] as $k => $label) {
foreach (['fixed' => __('固定金额', 'jio'), 'percent' => __('折扣百分比', 'jio')] as $k => $label) {
$html .= sprintf('<option value="%s"%s>%s</option>', esc_attr($k), selected($current, $k, false), esc_html($label));
}
return $html . '</select>';
}
/** 单字段单元(.aksrt-native-field);$html 为调用方已转义的控件片段。 */
/** 单字段单元(.jio-native-field);$html 为调用方已转义的控件片段。 */
private function field(string $label, string $html, string $hint = '', bool $wide = false): void {
echo '<label class="aksrt-native-field' . ($wide ? ' is-wide' : '') . '">';
echo '<span class="aksrt-native-field__label">' . esc_html($label) . '</span>';
echo '<label class="jio-native-field' . ($wide ? ' is-wide' : '') . '">';
echo '<span class="jio-native-field__label">' . esc_html($label) . '</span>';
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput -- 调用方已转义。
if ($hint !== '') {
echo '<small class="aksrt-native-field__hint">' . esc_html($hint) . '</small>';
echo '<small class="jio-native-field__hint">' . esc_html($hint) . '</small>';
}
echo '</label>';
}
private function dialog_open(string $id, string $title, string $description, string $action): void {
echo '<dialog class="aksrt-coupon-dialog" id="' . esc_attr($id) . '" aria-labelledby="' . esc_attr($id) . '-title" aria-describedby="' . esc_attr($id) . '-description">';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-coupon-dialog__frame" data-aksrt-coupon-form data-busy-label="' . esc_attr__('正在提交…', 'aksrt') . '">';
echo '<dialog class="jio-coupon-dialog" id="' . esc_attr($id) . '" aria-labelledby="' . esc_attr($id) . '-title" aria-describedby="' . esc_attr($id) . '-description">';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="jio-coupon-dialog__frame" data-jio-coupon-form data-busy-label="' . esc_attr__('正在提交…', 'jio') . '">';
echo '<input type="hidden" name="action" value="' . esc_attr($action) . '">';
wp_nonce_field($action);
echo '<header class="aksrt-coupon-dialog__head"><div><h2 id="' . esc_attr($id) . '-title">' . esc_html($title) . '</h2><p id="' . esc_attr($id) . '-description">' . esc_html($description) . '</p></div>';
echo '<button type="button" class="aksrt-coupon-dialog__close" data-aksrt-dialog-close aria-label="' . esc_attr__('关闭弹窗', 'aksrt') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></header>';
echo '<div class="aksrt-coupon-dialog__body">';
echo '<header class="jio-coupon-dialog__head"><div><h2 id="' . esc_attr($id) . '-title">' . esc_html($title) . '</h2><p id="' . esc_attr($id) . '-description">' . esc_html($description) . '</p></div>';
echo '<button type="button" class="jio-coupon-dialog__close" data-jio-dialog-close aria-label="' . esc_attr__('关闭弹窗', 'jio') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></header>';
echo '<div class="jio-coupon-dialog__body">';
}
private function dialog_close(string $submit_label): void {
echo '</div><footer class="aksrt-coupon-dialog__foot"><button type="button" class="button" data-aksrt-dialog-close>' . esc_html__('取消', 'aksrt') . '</button>';
echo '<button type="submit" class="button button-primary" data-aksrt-coupon-submit>' . esc_html($submit_label) . '</button></footer></form></dialog>';
echo '</div><footer class="jio-coupon-dialog__foot"><button type="button" class="button" data-jio-dialog-close>' . esc_html__('取消', 'jio') . '</button>';
echo '<button type="submit" class="button button-primary" data-jio-coupon-submit>' . esc_html($submit_label) . '</button></footer></form></dialog>';
}
private function currency_symbol(): string {
$paid = \AKSRT\Settings\Settings::get_paid_content_settings();
$paid = \Jio\Settings\Settings::get_paid_content_settings();
return (string) ($paid['display']['currency_symbol'] ?? '¥');
}
}
+44 -44
View File
@@ -6,10 +6,10 @@
* admin-post 处理器(结算 / 删除 / 批量操作)与共用的展示助手。页面样式在 assets/css/admin/income.css,
* 批量操作、二次确认、全选与防重复提交在 assets/js/admin-income.js。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
@@ -18,7 +18,7 @@ class PayIncome {
use PayIncomeList;
public const CAPABILITY = 'manage_options';
public const PAGE_SLUG = 'aksrt_income';
public const PAGE_SLUG = 'jio_income';
private static $instance = null;
@@ -34,9 +34,9 @@ class PayIncome {
return;
}
add_action('admin_enqueue_scripts', [$this, 'enqueue_assets']);
add_action('admin_post_aksrt_pay_settle_income', [$this, 'handle_settle_income']);
add_action('admin_post_aksrt_pay_delete_income', [$this, 'handle_delete_income']);
add_action('admin_post_aksrt_pay_bulk_income', [$this, 'handle_bulk_income']);
add_action('admin_post_jio_pay_settle_income', [$this, 'handle_settle_income']);
add_action('admin_post_jio_pay_delete_income', [$this, 'handle_delete_income']);
add_action('admin_post_jio_pay_bulk_income', [$this, 'handle_bulk_income']);
}
public function enqueue_assets(): void {
@@ -46,17 +46,17 @@ class PayIncome {
}
$css = '/assets/css/admin/income.css';
$js = '/assets/js/admin-income.js';
wp_enqueue_style('aksrt-admin-income', AKSRT_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(AKSRT_DIR . $css));
wp_enqueue_script('aksrt-admin-income', AKSRT_URI . $js, [], (string) filemtime(AKSRT_DIR . $js), true);
wp_enqueue_style('jio-admin-income', JIO_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(JIO_DIR . $css));
wp_enqueue_script('jio-admin-income', JIO_URI . $js, [], (string) filemtime(JIO_DIR . $js), true);
}
public function render_income_page(): void {
$this->ensure_cap();
NativeUi::page_open('aksrt-income-page');
NativeUi::page_open('jio-income-page');
if (!class_exists('\AKSRT\Payment\Revenue')) {
NativeUi::header(__('创作分成', 'aksrt'), '', 'dashicons-chart-pie');
echo '<div class="notice notice-error"><p>' . esc_html__('分成模块未初始化。', 'aksrt') . '</p></div>';
if (!class_exists('\Jio\Payment\Revenue')) {
NativeUi::header(__('创作分成', 'jio'), '', 'dashicons-chart-pie');
echo '<div class="notice notice-error"><p>' . esc_html__('分成模块未初始化。', 'jio') . '</p></div>';
NativeUi::page_close();
return;
}
@@ -72,28 +72,28 @@ class PayIncome {
public function handle_settle_income(): void {
$this->ensure_cap();
$id = isset($_POST['revenue_id']) ? (int) $_POST['revenue_id'] : 0;
check_admin_referer('aksrt_pay_settle_income_' . $id);
check_admin_referer('jio_pay_settle_income_' . $id);
$operation_key = $this->request_operation_key('admin-revenue-settle');
$ok = $id > 0 && $operation_key !== ''
&& \AKSRT\Payment\Revenue::get_instance()->settle_result($id, get_current_user_id(), $operation_key) instanceof \AKSRT\Ledger\LedgerResult;
$this->redirect_back(['aksrt_pay_msg' => $ok ? 'income_settled' : 'income_failed']);
&& \Jio\Payment\Revenue::get_instance()->settle_result($id, get_current_user_id(), $operation_key) instanceof \Jio\Ledger\LedgerResult;
$this->redirect_back(['jio_pay_msg' => $ok ? 'income_settled' : 'income_failed']);
}
/** 单条删除:只删待结算记录,已结算记录由 Revenue::delete_pending() 的状态条件挡住。 */
public function handle_delete_income(): void {
$this->ensure_cap();
$id = isset($_POST['revenue_id']) ? (int) $_POST['revenue_id'] : 0;
check_admin_referer('aksrt_pay_delete_income_' . $id);
check_admin_referer('jio_pay_delete_income_' . $id);
$deleted = $id > 0 ? \AKSRT\Payment\Revenue::get_instance()->delete_pending([$id]) : 0;
$this->redirect_back(['aksrt_pay_msg' => $deleted > 0 ? 'income_deleted' : 'income_delete_failed', 'done' => $deleted]);
$deleted = $id > 0 ? \Jio\Payment\Revenue::get_instance()->delete_pending([$id]) : 0;
$this->redirect_back(['jio_pay_msg' => $deleted > 0 ? 'income_deleted' : 'income_delete_failed', 'done' => $deleted]);
}
/** 批量操作:bulk_action = settle | delete,作用于勾选的记录。 */
public function handle_bulk_income(): void {
$this->ensure_cap();
check_admin_referer('aksrt_pay_bulk_income');
check_admin_referer('jio_pay_bulk_income');
$action = isset($_POST['bulk_action']) ? sanitize_key((string) wp_unslash($_POST['bulk_action'])) : '';
$raw_ids = isset($_POST['revenue_ids']) ? wp_unslash($_POST['revenue_ids']) : [];
@@ -101,17 +101,17 @@ class PayIncome {
? array_values(array_unique(array_filter(array_map('intval', $raw_ids), static fn (int $id): bool => $id > 0)))
: [];
if (!in_array($action, ['settle', 'delete'], true)) {
$this->redirect_back(['aksrt_pay_msg' => 'income_bulk_invalid']);
$this->redirect_back(['jio_pay_msg' => 'income_bulk_invalid']);
}
if ($ids === []) {
$this->redirect_back(['aksrt_pay_msg' => 'income_none_selected']);
$this->redirect_back(['jio_pay_msg' => 'income_none_selected']);
}
if ($action === 'delete') {
$deleted = \AKSRT\Payment\Revenue::get_instance()->delete_pending($ids);
$deleted = \Jio\Payment\Revenue::get_instance()->delete_pending($ids);
$failed = count($ids) - $deleted;
$this->redirect_back([
'aksrt_pay_msg' => $deleted === 0 ? 'income_delete_failed' : ($failed > 0 ? 'income_delete_partial' : 'income_deleted'),
'jio_pay_msg' => $deleted === 0 ? 'income_delete_failed' : ($failed > 0 ? 'income_delete_partial' : 'income_deleted'),
'done' => $deleted,
'failed' => $failed,
]);
@@ -119,13 +119,13 @@ class PayIncome {
$operation_key = $this->request_operation_key('admin-revenue-settle-batch');
$result = $operation_key !== ''
? \AKSRT\Payment\Revenue::get_instance()->settle_batch($ids, get_current_user_id(), $operation_key)
? \Jio\Payment\Revenue::get_instance()->settle_batch($ids, get_current_user_id(), $operation_key)
: ['settled' => [], 'failed' => $ids];
$settled = count($result['settled']);
$failed = count($result['failed']);
$this->redirect_back([
'aksrt_pay_msg' => $settled === 0 ? 'income_failed' : ($failed > 0 ? 'income_batch_partial' : 'income_batch_settled'),
'jio_pay_msg' => $settled === 0 ? 'income_failed' : ($failed > 0 ? 'income_batch_partial' : 'income_batch_settled'),
'done' => $settled,
'failed' => $failed,
]);
@@ -135,7 +135,7 @@ class PayIncome {
private function request_operation_key(string $scope): string {
$request_key = sanitize_text_field((string) wp_unslash($_POST['operation_key'] ?? ''));
return wp_is_uuid($request_key)
? \AKSRT\Ledger\OperationKey::deterministic($scope, [get_current_user_id(), strtolower($request_key)])
? \Jio\Ledger\OperationKey::deterministic($scope, [get_current_user_id(), strtolower($request_key)])
: '';
}
@@ -150,7 +150,7 @@ class PayIncome {
if (is_string($referer) && $referer !== '') {
parse_str((string) wp_parse_url($referer, PHP_URL_QUERY), $parsed);
if (($parsed['page'] ?? '') === self::PAGE_SLUG) {
unset($parsed['page'], $parsed['aksrt_pay_msg'], $parsed['done'], $parsed['failed']);
unset($parsed['page'], $parsed['jio_pay_msg'], $parsed['done'], $parsed['failed']);
$query = $parsed;
}
}
@@ -164,7 +164,7 @@ class PayIncome {
/** @return array{enabled:bool,cycle_days:int} */
private function revenue_policy(): array {
$settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_payment_revenue_settings() : [];
$settings = class_exists('\Jio\Settings\Settings') ? \Jio\Settings\Settings::get_payment_revenue_settings() : [];
return [
'enabled' => !empty($settings['enabled']),
'cycle_days' => max(1, (int) ($settings['cycle_days'] ?? 30)),
@@ -174,8 +174,8 @@ class PayIncome {
/** @return array<string, string> */
private function status_labels(): array {
return [
'pending' => __('待结算', 'aksrt'),
'settled' => __('已结算', 'aksrt'),
'pending' => __('待结算', 'jio'),
'settled' => __('已结算', 'jio'),
];
}
@@ -191,51 +191,51 @@ class PayIncome {
/** 分成金额:现金带两位小数与 ¥,积分取整加单位;现金与积分从不相加。 */
private function format_amount($value, bool $is_points): string {
if ($is_points) {
return number_format_i18n(max(0, (int) $value)) . ' ' . __('积分', 'aksrt');
return number_format_i18n(max(0, (int) $value)) . ' ' . __('积分', 'jio');
}
try {
return '¥' . \AKSRT\Ledger\AssetAmount::balance(is_string($value) ? $value : '0')->formatted();
return '¥' . \Jio\Ledger\AssetAmount::balance(is_string($value) ? $value : '0')->formatted();
} catch (\InvalidArgumentException $e) {
return '¥0.00';
}
}
private function render_flash(): void {
if (empty($_GET['aksrt_pay_msg'])) {
if (empty($_GET['jio_pay_msg'])) {
return;
}
$code = sanitize_key((string) wp_unslash($_GET['aksrt_pay_msg']));
$code = sanitize_key((string) wp_unslash($_GET['jio_pay_msg']));
$done = max(0, (int) ($_GET['done'] ?? 0));
$failed = max(0, (int) ($_GET['failed'] ?? 0));
// 码 => [文案, notice 类型]
$map = [
'income_settled' => [__('分成已结算,已转入作者钱包。', 'aksrt'), 'success'],
'income_settled' => [__('分成已结算,已转入作者钱包。', 'jio'), 'success'],
'income_batch_settled' => [sprintf(
/* translators: %d settled count */
__('已结算 %d 条分成记录。', 'aksrt'),
__('已结算 %d 条分成记录。', 'jio'),
$done
), 'success'],
'income_batch_partial' => [sprintf(
/* translators: 1 settled count 2 failed count */
__('已结算 %1$d 条,%2$d 条失败(记录状态可能已变化),可刷新后重试。', 'aksrt'),
__('已结算 %1$d 条,%2$d 条失败(记录状态可能已变化),可刷新后重试。', 'jio'),
$done,
$failed
), 'warning'],
'income_none_selected' => [__('请先勾选待结算记录。', 'aksrt'), 'warning'],
'income_bulk_invalid' => [__('请先选择批量操作。', 'aksrt'), 'warning'],
'income_failed' => [__('分成结算失败:记录可能已被结算,或分成功能未启用。', 'aksrt'), 'error'],
'income_none_selected' => [__('请先勾选待结算记录。', 'jio'), 'warning'],
'income_bulk_invalid' => [__('请先选择批量操作。', 'jio'), 'warning'],
'income_failed' => [__('分成结算失败:记录可能已被结算,或分成功能未启用。', 'jio'), 'error'],
'income_deleted' => [sprintf(
/* translators: %d deleted count */
__('已删除 %d 条待结算记录。', 'aksrt'),
__('已删除 %d 条待结算记录。', 'jio'),
$done
), 'success'],
'income_delete_partial' => [sprintf(
/* translators: 1 deleted count 2 skipped count */
__('已删除 %1$d 条,%2$d 条未删除(仅待结算记录可删除,可能已被结算)。', 'aksrt'),
__('已删除 %1$d 条,%2$d 条未删除(仅待结算记录可删除,可能已被结算)。', 'jio'),
$done,
$failed
), 'warning'],
'income_delete_failed' => [__('删除失败:仅待结算记录可删除,记录可能已被结算或不存在。', 'aksrt'), 'error'],
'income_delete_failed' => [__('删除失败:仅待结算记录可删除,记录可能已被结算或不存在。', 'jio'), 'error'],
];
if (!isset($map[$code])) {
return;
@@ -246,7 +246,7 @@ class PayIncome {
private function ensure_cap(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
wp_die(esc_html__('您没有权限访问此页面。', 'jio'));
}
}
+100 -100
View File
@@ -4,12 +4,12 @@
*
* 作为 flat sibling trait 被 PayIncome 使用(PSR-4:trait 必须与宿主类同目录)。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Payment\Revenue;
use Jio\Payment\Revenue;
defined('ABSPATH') || exit;
@@ -17,12 +17,12 @@ trait PayIncomeList {
private function render_income_list_page(): void {
NativeUi::header(
__('创作分成', 'aksrt'),
__('作者从付费内容订单获得的分成记录。待结算记录到期后自动结算,也可在这里提前手动结算。', 'aksrt'),
__('创作分成', 'jio'),
__('作者从付费内容订单获得的分成记录。待结算记录到期后自动结算,也可在这里提前手动结算。', 'jio'),
'dashicons-chart-pie',
[[
'label' => __('分成配置', 'aksrt'),
'url' => admin_url('admin.php?page=aksrt-settings#/payment/revenue'),
'label' => __('分成配置', 'jio'),
'url' => admin_url('admin.php?page=jio-settings#/payment/revenue'),
'icon' => 'dashicons-admin-generic',
]]
);
@@ -65,20 +65,20 @@ trait PayIncomeList {
$has_pending = in_array('pending', array_column($items, 'status'), true);
// 复选框列与批量栏始终渲染;不可用时禁用并用 title 说明原因,而不是整块隐藏。
NativeUi::table_card_open(__('分成记录', 'aksrt'), $this->bulk_form_html($can_settle, $has_pending));
echo '<table class="wp-list-table widefat striped aksrt-income-table">';
echo '<caption class="screen-reader-text">' . esc_html__('分成记录', 'aksrt') . '</caption><thead><tr>';
NativeUi::table_card_open(__('分成记录', 'jio'), $this->bulk_form_html($can_settle, $has_pending));
echo '<table class="wp-list-table widefat striped jio-income-table">';
echo '<caption class="screen-reader-text">' . esc_html__('分成记录', 'jio') . '</caption><thead><tr>';
echo '<td class="manage-column column-cb check-column">'
. '<input type="checkbox" data-aksrt-income-check-all aria-label="' . esc_attr__('全选本页待结算记录', 'aksrt') . '"'
. ($has_pending ? '' : ' disabled title="' . esc_attr__('本页没有待结算记录', 'aksrt') . '"') . ' /></td>';
. '<input type="checkbox" data-jio-income-check-all aria-label="' . esc_attr__('全选本页待结算记录', 'jio') . '"'
. ($has_pending ? '' : ' disabled title="' . esc_attr__('本页没有待结算记录', 'jio') . '"') . ' /></td>';
foreach ([
'record' => __('记录 / 产生时间', 'aksrt'),
'author' => __('作者', 'aksrt'),
'source' => __('来源文章 / 订单', 'aksrt'),
'base' => __('订单金额 × 比例', 'aksrt'),
'amount' => __('分成', 'aksrt'),
'status' => __('状态', 'aksrt'),
'actions' => __('操作', 'aksrt'),
'record' => __('记录 / 产生时间', 'jio'),
'author' => __('作者', 'jio'),
'source' => __('来源文章 / 订单', 'jio'),
'base' => __('订单金额 × 比例', 'jio'),
'amount' => __('分成', 'jio'),
'status' => __('状态', 'jio'),
'actions' => __('操作', 'jio'),
] as $key => $label) {
echo '<th scope="col" class="column-' . esc_attr($key) . '">' . esc_html($label) . '</th>';
}
@@ -86,9 +86,9 @@ trait PayIncomeList {
if (!$items) {
$filtered = array_diff_key($query_args, ['status' => true]) !== [];
echo NativeUi::empty_row(8, __('暂无分成记录', 'aksrt'), $filtered // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
? __('没有符合当前筛选条件的记录,请调整或重置筛选。', 'aksrt')
: __('付费内容订单支付成功后,作者分成会出现在这里。', 'aksrt'));
echo NativeUi::empty_row(8, __('暂无分成记录', 'jio'), $filtered // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
? __('没有符合当前筛选条件的记录,请调整或重置筛选。', 'jio')
: __('付费内容订单支付成功后,作者分成会出现在这里。', 'jio'));
}
foreach ($items as $row) {
$this->render_income_row($row, $can_settle, $policy['cycle_days']);
@@ -112,47 +112,47 @@ trait PayIncomeList {
echo '<tr>';
// 已结算记录已入账到钱包,不可结算也不可删除:复选框禁用占位,保持列对齐。
echo '<th scope="row" class="check-column">'
. '<input type="checkbox" form="aksrt-income-bulk" name="revenue_ids[]" value="' . esc_attr((string) $id) . '"'
. ($pending ? ' data-aksrt-income-check' : ' disabled title="' . esc_attr__('已结算记录不可操作', 'aksrt') . '"')
. ' aria-label="' . esc_attr(sprintf(__('选择分成记录 #%d', 'aksrt'), $id)) . '" /></th>';
. '<input type="checkbox" form="jio-income-bulk" name="revenue_ids[]" value="' . esc_attr((string) $id) . '"'
. ($pending ? ' data-jio-income-check' : ' disabled title="' . esc_attr__('已结算记录不可操作', 'jio') . '"')
. ' aria-label="' . esc_attr(sprintf(__('选择分成记录 #%d', 'jio'), $id)) . '" /></th>';
echo '<td class="column-record"><span class="aksrt-income-id">#' . esc_html((string) $id) . '</span>'
. '<div class="aksrt-income-sub">' . esc_html((string) ($row['created_at'] ?? '')) . '</div></td>';
echo '<td class="column-record"><span class="jio-income-id">#' . esc_html((string) $id) . '</span>'
. '<div class="jio-income-sub">' . esc_html((string) ($row['created_at'] ?? '')) . '</div></td>';
echo '<td class="column-author">' . NativeUi::user_cell((int) ($row['author_id'] ?? 0)) . '</td>';
$title = $post_id > 0 ? (string) get_the_title($post_id) : '';
$post_html = $post_id > 0
? '<a class="aksrt-income-post" href="' . esc_url((string) get_edit_post_link($post_id)) . '">' . esc_html($title !== '' ? $title : '#' . $post_id) . '</a>'
: '<span class="aksrt-native-muted">—</span>';
? '<a class="jio-income-post" href="' . esc_url((string) get_edit_post_link($post_id)) . '">' . esc_html($title !== '' ? $title : '#' . $post_id) . '</a>'
: '<span class="jio-native-muted">—</span>';
$buyer = $buyer_id > 0 ? get_userdata($buyer_id) : false;
$order_url = admin_url('admin.php?page=aksrt_orders&order=' . rawurlencode($order_number));
$order_url = admin_url('admin.php?page=jio_orders&order=' . rawurlencode($order_number));
echo '<td class="column-source">' . $post_html // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped above.
. '<div class="aksrt-income-sub"><a class="aksrt-income-order" href="' . esc_url($order_url) . '">' . esc_html($order_number) . '</a>'
. ($buyer ? ' · ' . esc_html(sprintf(__('买家 %s', 'aksrt'), $buyer->display_name)) : '')
. '<div class="jio-income-sub"><a class="jio-income-order" href="' . esc_url($order_url) . '">' . esc_html($order_number) . '</a>'
. ($buyer ? ' · ' . esc_html(sprintf(__('买家 %s', 'jio'), $buyer->display_name)) : '')
. '</div></td>';
echo '<td class="column-base">' . esc_html($this->format_amount($row['original_amount'] ?? 0, $is_points))
. '<div class="aksrt-income-sub">' . esc_html(sprintf(
. '<div class="jio-income-sub">' . esc_html(sprintf(
/* translators: 1 share ratio 2 author level label */
__('比例 %1$d%% · %2$s', 'aksrt'),
__('比例 %1$d%% · %2$s', 'jio'),
(int) ($row['share_ratio'] ?? 0),
$this->author_level_label((int) ($row['author_level'] ?? 0))
)) . '</div></td>';
echo '<td class="column-amount"><strong class="aksrt-native-amount' . ($pending ? '' : ' is-positive') . '">'
echo '<td class="column-amount"><strong class="jio-native-amount' . ($pending ? '' : ' is-positive') . '">'
. esc_html($this->format_amount($row['amount'] ?? 0, $is_points)) . '</strong>'
. '<div class="aksrt-income-sub">' . esc_html($is_points ? __('积分钱包', 'aksrt') : __('余额钱包', 'aksrt')) . '</div></td>';
. '<div class="jio-income-sub">' . esc_html($is_points ? __('积分钱包', 'jio') : __('余额钱包', 'jio')) . '</div></td>';
echo '<td class="column-status">' . NativeUi::status_badge($status, $this->status_badge_map())
. $this->status_hint($row, $cycle_days) . '</td>';
echo '<td class="column-actions">';
if ($pending) {
echo '<div class="aksrt-income-actions">'
echo '<div class="jio-income-actions">'
. ($can_settle ? $this->settle_form_html($id) : '')
. $this->delete_form_html($id)
. '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
echo '<span class="aksrt-native-muted">—</span>';
echo '<span class="jio-native-muted">—</span>';
}
echo '</td></tr>';
}
@@ -161,7 +161,7 @@ trait PayIncomeList {
private function status_hint(array $row, int $cycle_days): string {
if (($row['status'] ?? '') === 'settled') {
$at = (string) ($row['settled_at'] ?? '');
return $at !== '' ? '<div class="aksrt-income-sub">' . esc_html(sprintf(__('%s 结算', 'aksrt'), $at)) . '</div>' : '';
return $at !== '' ? '<div class="jio-income-sub">' . esc_html(sprintf(__('%s 结算', 'jio'), $at)) . '</div>' : '';
}
$created = (string) ($row['created_at'] ?? '');
if ($created === '') {
@@ -173,29 +173,29 @@ trait PayIncomeList {
return '';
}
$text = $due <= new \DateTimeImmutable('now', wp_timezone())
? __('已到期,等待自动结算', 'aksrt')
? __('已到期,等待自动结算', 'jio')
/* translators: %s expected settle time */
: sprintf(__('预计 %s 自动结算', 'aksrt'), $due->format('m-d H:i'));
return '<div class="aksrt-income-sub">' . esc_html($text) . '</div>';
: sprintf(__('预计 %s 自动结算', 'jio'), $due->format('m-d H:i'));
return '<div class="jio-income-sub">' . esc_html($text) . '</div>';
}
private function author_level_label(int $level): string {
return $level > 0
/* translators: %d VIP level */
? sprintf(__('VIP%d 作者', 'aksrt'), $level)
: __('普通作者', 'aksrt');
? sprintf(__('VIP%d 作者', 'jio'), $level)
: __('普通作者', 'jio');
}
/** @param array{enabled:bool,cycle_days:int} $policy */
private function render_policy_callout(array $policy): void {
if (!$policy['enabled']) {
echo '<div class="aksrt-native-callout is-warning">' . esc_html__('创作分成功能未启用:不会产生新的分成记录,已有的待结算记录也无法结算。', 'aksrt')
. ' <a href="' . esc_url(admin_url('admin.php?page=aksrt-settings#/payment/revenue')) . '">' . esc_html__('前往启用', 'aksrt') . '</a></div>';
echo '<div class="jio-native-callout is-warning">' . esc_html__('创作分成功能未启用:不会产生新的分成记录,已有的待结算记录也无法结算。', 'jio')
. ' <a href="' . esc_url(admin_url('admin.php?page=jio-settings#/payment/revenue')) . '">' . esc_html__('前往启用', 'jio') . '</a></div>';
return;
}
echo '<div class="aksrt-native-callout">' . esc_html(sprintf(
echo '<div class="jio-native-callout">' . esc_html(sprintf(
/* translators: %d settlement cycle days */
__('结算周期 %d 天:分成产生满周期后由系统每小时自动结算到作者钱包。手动结算会立即入账,不可撤销。', 'aksrt'),
__('结算周期 %d 天:分成产生满周期后由系统每小时自动结算到作者钱包。手动结算会立即入账,不可撤销。', 'jio'),
$policy['cycle_days']
)) . '</div>';
}
@@ -206,22 +206,22 @@ trait PayIncomeList {
$points = (array) ($stats['points'] ?? []);
$scope = $user_id > 0
/* translators: %d user ID */
? sprintf(__('用户 #%d', 'aksrt'), $user_id)
: __('全站', 'aksrt');
? sprintf(__('用户 #%d', 'jio'), $user_id)
: __('全站', 'jio');
$count_hint = static function (array $bucket) use ($scope): string {
/* translators: 1 scope 2 record count */
return sprintf(__('%1$s · %2$s 条记录', 'aksrt'), $scope, number_format_i18n((int) ($bucket['count'] ?? 0)));
return sprintf(__('%1$s · %2$s 条记录', 'jio'), $scope, number_format_i18n((int) ($bucket['count'] ?? 0)));
};
$pending_url = $this->page_url(array_filter(['status' => 'pending', 'user_id' => $user_id ?: null]));
$settled_url = $this->page_url(array_filter(['status' => 'settled', 'user_id' => $user_id ?: null]));
NativeUi::stats([
['label' => __('现金待结算', 'aksrt'), 'value' => $this->format_amount($cash['pending'] ?? '0', false), 'url' => add_query_arg('currency', 'cny', $pending_url), 'icon' => 'dashicons-clock', 'tone' => 'warning'],
['label' => __('现金已结算', 'aksrt'), 'value' => $this->format_amount($cash['settled'] ?? '0', false), 'url' => add_query_arg('currency', 'cny', $settled_url), 'icon' => 'dashicons-yes-alt', 'tone' => 'success'],
['label' => __('现金分成合计', 'aksrt'), 'value' => $this->format_amount($cash['total'] ?? '0', false), 'hint' => $count_hint($cash), 'icon' => 'dashicons-chart-area', 'tone' => 'info'],
['label' => __('积分待结算', 'aksrt'), 'value' => $this->format_amount($points['pending'] ?? 0, true), 'url' => add_query_arg('currency', 'points', $pending_url), 'icon' => 'dashicons-clock', 'tone' => 'warning'],
['label' => __('积分已结算', 'aksrt'), 'value' => $this->format_amount($points['settled'] ?? 0, true), 'url' => add_query_arg('currency', 'points', $settled_url), 'icon' => 'dashicons-yes-alt', 'tone' => 'success'],
['label' => __('积分分成合计', 'aksrt'), 'value' => $this->format_amount($points['total'] ?? 0, true), 'hint' => $count_hint($points), 'icon' => 'dashicons-star-filled', 'tone' => 'info'],
['label' => __('现金待结算', 'jio'), 'value' => $this->format_amount($cash['pending'] ?? '0', false), 'url' => add_query_arg('currency', 'cny', $pending_url), 'icon' => 'dashicons-clock', 'tone' => 'warning'],
['label' => __('现金已结算', 'jio'), 'value' => $this->format_amount($cash['settled'] ?? '0', false), 'url' => add_query_arg('currency', 'cny', $settled_url), 'icon' => 'dashicons-yes-alt', 'tone' => 'success'],
['label' => __('现金分成合计', 'jio'), 'value' => $this->format_amount($cash['total'] ?? '0', false), 'hint' => $count_hint($cash), 'icon' => 'dashicons-chart-area', 'tone' => 'info'],
['label' => __('积分待结算', 'jio'), 'value' => $this->format_amount($points['pending'] ?? 0, true), 'url' => add_query_arg('currency', 'points', $pending_url), 'icon' => 'dashicons-clock', 'tone' => 'warning'],
['label' => __('积分已结算', 'jio'), 'value' => $this->format_amount($points['settled'] ?? 0, true), 'url' => add_query_arg('currency', 'points', $settled_url), 'icon' => 'dashicons-yes-alt', 'tone' => 'success'],
['label' => __('积分分成合计', 'jio'), 'value' => $this->format_amount($points['total'] ?? 0, true), 'hint' => $count_hint($points), 'icon' => 'dashicons-star-filled', 'tone' => 'info'],
]);
}
@@ -232,16 +232,16 @@ trait PayIncomeList {
*/
private function render_status_views(array $filters, array $query_args, array $counts): void {
$base = array_diff_key($query_args, ['status' => true]);
$views = ['' => [__('全部', 'aksrt'), array_sum($counts)]];
$views = ['' => [__('全部', 'jio'), array_sum($counts)]];
foreach ($this->status_labels() as $key => $label) {
$views[$key] = [$label, (int) ($counts[$key] ?? 0)];
}
echo '<nav class="aksrt-native-tabs aksrt-income-views" aria-label="' . esc_attr__('按结算状态查看', 'aksrt') . '">';
echo '<nav class="jio-native-tabs jio-income-views" aria-label="' . esc_attr__('按结算状态查看', 'jio') . '">';
foreach ($views as $key => [$label, $count]) {
$url = $this->page_url($base + ($key !== '' ? ['status' => $key] : []));
$active = $filters['status'] === $key;
echo '<a class="aksrt-native-tabs__link' . ($active ? ' is-active' : '') . '" href="' . esc_url($url) . '"' . ($active ? ' aria-current="page"' : '') . '>'
echo '<a class="jio-native-tabs__link' . ($active ? ' is-active' : '') . '" href="' . esc_url($url) . '"' . ($active ? ' aria-current="page"' : '') . '>'
. esc_html($label) . ' <span class="count">' . esc_html(number_format_i18n($count)) . '</span></a>';
}
echo '</nav>';
@@ -249,30 +249,30 @@ trait PayIncomeList {
/** @param array<string, mixed> $filters */
private function render_filter_card(array $filters): void {
NativeUi::card_open('', '', false, 'aksrt-income-filter-card');
echo '<form method="get" class="aksrt-income-filters" role="search">';
NativeUi::card_open('', '', false, 'jio-income-filter-card');
echo '<form method="get" class="jio-income-filters" role="search">';
echo '<input type="hidden" name="page" value="' . esc_attr(self::PAGE_SLUG) . '" />';
if ($filters['status'] !== '') {
echo '<input type="hidden" name="status" value="' . esc_attr($filters['status']) . '" />';
}
echo '<label class="aksrt-income-filters__search"><span class="screen-reader-text">' . esc_html__('订单号', 'aksrt') . '</span>'
. '<input type="search" name="order_number" value="' . esc_attr($filters['order_number']) . '" placeholder="' . esc_attr__('搜索订单号', 'aksrt') . '" /></label>';
echo $this->filter_select('currency', __('分成币种', 'aksrt'), $this->currency_options(), $filters['currency']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $this->filter_select('order_status', __('订单状态', 'aksrt'), $this->order_status_options(), $filters['order_status']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<span class="aksrt-income-filters__range">'
. '<label><span class="screen-reader-text">' . esc_html__('开始日期', 'aksrt') . '</span><input type="date" name="date_from" value="' . esc_attr($filters['date_from']) . '" /></label>'
echo '<label class="jio-income-filters__search"><span class="screen-reader-text">' . esc_html__('订单号', 'jio') . '</span>'
. '<input type="search" name="order_number" value="' . esc_attr($filters['order_number']) . '" placeholder="' . esc_attr__('搜索订单号', 'jio') . '" /></label>';
echo $this->filter_select('currency', __('分成币种', 'jio'), $this->currency_options(), $filters['currency']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $this->filter_select('order_status', __('订单状态', 'jio'), $this->order_status_options(), $filters['order_status']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<span class="jio-income-filters__range">'
. '<label><span class="screen-reader-text">' . esc_html__('开始日期', 'jio') . '</span><input type="date" name="date_from" value="' . esc_attr($filters['date_from']) . '" /></label>'
. '<span aria-hidden="true">–</span>'
. '<label><span class="screen-reader-text">' . esc_html__('结束日期', 'aksrt') . '</span><input type="date" name="date_to" value="' . esc_attr($filters['date_to']) . '" /></label>'
. '<label><span class="screen-reader-text">' . esc_html__('结束日期', 'jio') . '</span><input type="date" name="date_to" value="' . esc_attr($filters['date_to']) . '" /></label>'
. '</span>';
foreach (['user_id' => __('作者 ID', 'aksrt'), 'post_id' => __('文章 ID', 'aksrt')] as $name => $label) {
echo '<label><span class="screen-reader-text">' . esc_html($label) . '</span><input type="number" min="1" name="' . esc_attr($name) . '" class="aksrt-income-filters__id" value="'
foreach (['user_id' => __('作者 ID', 'jio'), 'post_id' => __('文章 ID', 'jio')] as $name => $label) {
echo '<label><span class="screen-reader-text">' . esc_html($label) . '</span><input type="number" min="1" name="' . esc_attr($name) . '" class="jio-income-filters__id" value="'
. esc_attr($filters[$name] > 0 ? (string) $filters[$name] : '') . '" placeholder="' . esc_attr($label) . '" /></label>';
}
echo '<span class="aksrt-income-filters__submit"><button type="submit" class="button button-primary">' . esc_html__('筛选', 'aksrt') . '</button>';
echo '<span class="jio-income-filters__submit"><button type="submit" class="button button-primary">' . esc_html__('筛选', 'jio') . '</button>';
if (array_diff_key($this->list_query_args($filters), ['status' => true]) !== []) {
$reset = $this->page_url($filters['status'] !== '' ? ['status' => $filters['status']] : []);
echo '<a class="button" href="' . esc_url($reset) . '">' . esc_html__('重置', 'aksrt') . '</a>';
echo '<a class="button" href="' . esc_url($reset) . '">' . esc_html__('重置', 'jio') . '</a>';
}
echo '</span></form>';
NativeUi::card_close();
@@ -290,60 +290,60 @@ trait PayIncomeList {
/** @return array<string, string> */
private function currency_options(): array {
return [
'' => __('全部币种', 'aksrt'),
'cny' => __('现金(CNY)', 'aksrt'),
'points' => __('积分', 'aksrt'),
'' => __('全部币种', 'jio'),
'cny' => __('现金(CNY)', 'jio'),
'points' => __('积分', 'jio'),
];
}
/** @return array<string, string> */
private function order_status_options(): array {
return ['' => __('全部订单状态', 'aksrt')] + \AKSRT\Core\DomainModel::order_status_labels();
return ['' => __('全部订单状态', 'jio')] + \Jio\Core\DomainModel::order_status_labels();
}
/**
* 批量操作栏:已选计数 + 「批量操作」下拉 + 应用。表格里的复选框通过 form="aksrt-income-bulk" 关联进来。
* 批量操作栏:已选计数 + 「批量操作」下拉 + 应用。表格里的复选框通过 form="jio-income-bulk" 关联进来。
* 各操作的确认文案挂在 option 的 data-confirm 上,由 admin-income.js 按所选操作取用。
*/
private function bulk_form_html(bool $can_settle, bool $has_pending): string {
$settle_attrs = $can_settle
? ' data-confirm="' . esc_attr__('确认立即结算选中的 {count} 条分成?金额会转入作者钱包,不可撤销。', 'aksrt') . '"'
? ' data-confirm="' . esc_attr__('确认立即结算选中的 {count} 条分成?金额会转入作者钱包,不可撤销。', 'jio') . '"'
: ' disabled';
$settle_label = $can_settle ? __('立即结算', 'aksrt') : __('立即结算(分成功能未启用)', 'aksrt');
$settle_label = $can_settle ? __('立即结算', 'jio') : __('立即结算(分成功能未启用)', 'jio');
return '<form id="aksrt-income-bulk" class="aksrt-native-bulk aksrt-income-bulk" method="post" action="' . esc_url(admin_url('admin-post.php')) . '">'
. '<input type="hidden" name="action" value="aksrt_pay_bulk_income" />'
return '<form id="jio-income-bulk" class="jio-native-bulk jio-income-bulk" method="post" action="' . esc_url(admin_url('admin-post.php')) . '">'
. '<input type="hidden" name="action" value="jio_pay_bulk_income" />'
. $this->operation_key_field()
. wp_nonce_field('aksrt_pay_bulk_income', '_wpnonce', true, false)
. '<span class="aksrt-income-bulk__count" data-aksrt-income-selected aria-live="polite">'
. esc_html($has_pending ? __('未选择记录', 'aksrt') : __('本页没有待结算记录', 'aksrt')) . '</span>'
. '<label class="screen-reader-text" for="aksrt-income-bulk-action">' . esc_html__('批量操作', 'aksrt') . '</label>'
. '<select name="bulk_action" id="aksrt-income-bulk-action" data-aksrt-income-bulk-action>'
. '<option value="">' . esc_html__('批量操作', 'aksrt') . '</option>'
. wp_nonce_field('jio_pay_bulk_income', '_wpnonce', true, false)
. '<span class="jio-income-bulk__count" data-jio-income-selected aria-live="polite">'
. esc_html($has_pending ? __('未选择记录', 'jio') : __('本页没有待结算记录', 'jio')) . '</span>'
. '<label class="screen-reader-text" for="jio-income-bulk-action">' . esc_html__('批量操作', 'jio') . '</label>'
. '<select name="bulk_action" id="jio-income-bulk-action" data-jio-income-bulk-action>'
. '<option value="">' . esc_html__('批量操作', 'jio') . '</option>'
. '<option value="settle"' . $settle_attrs . '>' . esc_html($settle_label) . '</option>'
. '<option value="delete" data-confirm="' . esc_attr__('确认删除选中的 {count} 条待结算分成?删除后作者不会获得这些分成,不可恢复。', 'aksrt') . '">' . esc_html__('删除', 'aksrt') . '</option>'
. '<option value="delete" data-confirm="' . esc_attr__('确认删除选中的 {count} 条待结算分成?删除后作者不会获得这些分成,不可恢复。', 'jio') . '">' . esc_html__('删除', 'jio') . '</option>'
. '</select>'
. '<button type="submit" class="button" disabled data-aksrt-income-bulk-submit>' . esc_html__('应用', 'aksrt') . '</button>'
. '<button type="submit" class="button" disabled data-jio-income-bulk-submit>' . esc_html__('应用', 'jio') . '</button>'
. '</form>';
}
private function delete_form_html(int $id): string {
return '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-income-action"'
. ' data-aksrt-confirm="' . esc_attr(sprintf(__('确认删除待结算分成记录 #%d?删除后作者不会获得这笔分成,不可恢复。', 'aksrt'), $id)) . '">'
. '<input type="hidden" name="action" value="aksrt_pay_delete_income" />'
return '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="jio-income-action"'
. ' data-jio-confirm="' . esc_attr(sprintf(__('确认删除待结算分成记录 #%d?删除后作者不会获得这笔分成,不可恢复。', 'jio'), $id)) . '">'
. '<input type="hidden" name="action" value="jio_pay_delete_income" />'
. '<input type="hidden" name="revenue_id" value="' . esc_attr((string) $id) . '" />'
. wp_nonce_field('aksrt_pay_delete_income_' . $id, '_wpnonce', true, false)
. '<button type="submit" class="button button-small is-danger">' . esc_html__('删除', 'aksrt') . '</button></form>';
. wp_nonce_field('jio_pay_delete_income_' . $id, '_wpnonce', true, false)
. '<button type="submit" class="button button-small is-danger">' . esc_html__('删除', 'jio') . '</button></form>';
}
private function settle_form_html(int $id): string {
return '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-income-action"'
. ' data-aksrt-confirm="' . esc_attr(sprintf(__('确认立即结算分成记录 #%d?金额会转入作者钱包,不可撤销。', 'aksrt'), $id)) . '">'
. '<input type="hidden" name="action" value="aksrt_pay_settle_income" />'
return '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="jio-income-action"'
. ' data-jio-confirm="' . esc_attr(sprintf(__('确认立即结算分成记录 #%d?金额会转入作者钱包,不可撤销。', 'jio'), $id)) . '">'
. '<input type="hidden" name="action" value="jio_pay_settle_income" />'
. '<input type="hidden" name="revenue_id" value="' . esc_attr((string) $id) . '" />'
. $this->operation_key_field()
. wp_nonce_field('aksrt_pay_settle_income_' . $id, '_wpnonce', true, false)
. '<button type="submit" class="button button-small">' . esc_html__('立即结算', 'aksrt') . '</button></form>';
. wp_nonce_field('jio_pay_settle_income_' . $id, '_wpnonce', true, false)
. '<button type="submit" class="button button-small">' . esc_html__('立即结算', 'jio') . '</button></form>';
}
/**
+43 -43
View File
@@ -1,16 +1,16 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
class PayMenus {
public const CAPABILITY = 'manage_options';
public const MENU_SLUG = 'aksrt_pay';
public const MENU_SLUG = 'jio_pay';
private static $instance = null;
@@ -42,85 +42,85 @@ class PayMenus {
'slug' => self::MENU_SLUG,
'icon' => 'dashicons-cart',
'desc' => '',
'title' => __('概览', 'aksrt'),
'title' => __('概览', 'jio'),
'callback' => [$this, 'render_overview_page'],
],
'orders' => [
'slug' => 'aksrt_orders',
'slug' => 'jio_orders',
'icon' => 'dashicons-list-view',
'desc' => __('收款确认、关闭与退款标记', 'aksrt'),
'title' => __('订单明细', 'aksrt'),
'desc' => __('收款确认、关闭与退款标记', 'jio'),
'title' => __('订单明细', 'jio'),
'callback' => static function (): void {
if (class_exists('AKSRT\Admin\Native\PayOrders')) {
if (class_exists('Jio\Admin\Native\PayOrders')) {
PayOrders::get_instance()->render_orders_page();
return;
}
wp_die(esc_html__('订单模块未初始化。', 'aksrt'));
wp_die(esc_html__('订单模块未初始化。', 'jio'));
},
],
'products' => [
'slug' => 'aksrt_pay_products',
'slug' => 'jio_pay_products',
'icon' => 'dashicons-media-document',
'desc' => __('付费文章定价与销量', 'aksrt'),
'title' => __('付费文章', 'aksrt'),
'desc' => __('付费文章定价与销量', 'jio'),
'title' => __('付费文章', 'jio'),
'callback' => static function (): void {
if (class_exists('AKSRT\\Admin\\Native\\PayProducts')) {
if (class_exists('Jio\\Admin\\Native\\PayProducts')) {
PayProducts::get_instance()->render_products_page();
return;
}
wp_die(esc_html__('付费文章模块未初始化。', 'aksrt'));
wp_die(esc_html__('付费文章模块未初始化。', 'jio'));
},
],
'coupons' => [
'slug' => 'aksrt_coupons',
'slug' => 'jio_coupons',
'icon' => 'dashicons-tickets-alt',
'desc' => __('创建与批量生成优惠码', 'aksrt'),
'title' => __('优惠码', 'aksrt'),
'desc' => __('创建与批量生成优惠码', 'jio'),
'title' => __('优惠码', 'jio'),
'callback' => static function (): void {
if (class_exists('AKSRT\\Admin\\Native\\PayCoupons')) {
if (class_exists('Jio\\Admin\\Native\\PayCoupons')) {
PayCoupons::get_instance()->render_page();
return;
}
wp_die(esc_html__('优惠码模块未初始化。', 'aksrt'));
wp_die(esc_html__('优惠码模块未初始化。', 'jio'));
},
],
'charge_cards' => [
'slug' => 'aksrt_charge_cards',
'slug' => 'jio_charge_cards',
'icon' => 'dashicons-id-alt',
'desc' => __('充值卡密生成与导出', 'aksrt'),
'title' => __('卡密管理', 'aksrt'),
'desc' => __('充值卡密生成与导出', 'jio'),
'title' => __('卡密管理', 'jio'),
'callback' => static function (): void {
if (class_exists('AKSRT\\Admin\\Native\\PayChargeCards')) {
if (class_exists('Jio\\Admin\\Native\\PayChargeCards')) {
PayChargeCards::get_instance()->render_charge_cards_page();
return;
}
wp_die(esc_html__('卡密模块未初始化。', 'aksrt'));
wp_die(esc_html__('卡密模块未初始化。', 'jio'));
},
],
'income' => [
'slug' => 'aksrt_income',
'slug' => 'jio_income',
'icon' => 'dashicons-chart-pie',
'desc' => __('创作者分成结算', 'aksrt'),
'title' => __('创作分成', 'aksrt'),
'desc' => __('创作者分成结算', 'jio'),
'title' => __('创作分成', 'jio'),
'callback' => static function (): void {
if (class_exists('AKSRT\\Admin\\Native\\PayIncome')) {
if (class_exists('Jio\\Admin\\Native\\PayIncome')) {
PayIncome::get_instance()->render_income_page();
return;
}
wp_die(esc_html__('分成模块未初始化。', 'aksrt'));
wp_die(esc_html__('分成模块未初始化。', 'jio'));
},
],
'withdraw' => [
'slug' => 'aksrt_withdraw',
'slug' => 'jio_withdraw',
'icon' => 'dashicons-money-alt',
'desc' => __('提现申请审核与打款', 'aksrt'),
'title' => __('提现审核', 'aksrt'),
'desc' => __('提现申请审核与打款', 'jio'),
'title' => __('提现审核', 'jio'),
'callback' => static function (): void {
if (class_exists('AKSRT\\Admin\\Native\\PayWithdrawals')) {
if (class_exists('Jio\\Admin\\Native\\PayWithdrawals')) {
PayWithdrawals::get_instance()->render_withdrawals_page();
return;
}
wp_die(esc_html__('提现模块未初始化。', 'aksrt'));
wp_die(esc_html__('提现模块未初始化。', 'jio'));
},
],
];
@@ -132,8 +132,8 @@ class PayMenus {
}
add_menu_page(
__('支付中心', 'aksrt'),
__('支付中心', 'aksrt'),
__('支付中心', 'jio'),
__('支付中心', 'jio'),
self::CAPABILITY,
self::MENU_SLUG,
[$this, 'render_overview_page'],
@@ -160,20 +160,20 @@ class PayMenus {
$page = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : '';
if ($page !== 'aksrt_withdraw' && class_exists('\AKSRT\Payment\Withdrawal')) {
$pending = (int) \AKSRT\Payment\Withdrawal::get_instance()->get_count(['status' => \AKSRT\Payment\Withdrawal::STATUS_PENDING]);
$processing = (int) \AKSRT\Payment\Withdrawal::get_instance()->get_count(['status' => \AKSRT\Payment\Withdrawal::STATUS_PROCESSING]);
if ($page !== 'jio_withdraw' && class_exists('\Jio\Payment\Withdrawal')) {
$pending = (int) \Jio\Payment\Withdrawal::get_instance()->get_count(['status' => \Jio\Payment\Withdrawal::STATUS_PENDING]);
$processing = (int) \Jio\Payment\Withdrawal::get_instance()->get_count(['status' => \Jio\Payment\Withdrawal::STATUS_PROCESSING]);
$active = $pending + $processing;
if ($active > 0) {
$url = admin_url('admin.php?page=aksrt_withdraw&status=pending');
$url = admin_url('admin.php?page=jio_withdraw&status=pending');
echo '<div class="notice notice-info is-dismissible">';
echo '<h3>' . esc_html__('提现申请待处理', 'aksrt') . '</h3>';
echo '<h3>' . esc_html__('提现申请待处理', 'jio') . '</h3>';
echo '<p>' . esc_html(sprintf(
/* translators: %d: open withdrawal count */
__('您有 %d 个提现申请待处理', 'aksrt'),
__('您有 %d 个提现申请待处理', 'jio'),
$active
)) . '</p>';
echo '<p><a class="button button-primary" href="' . esc_url($url) . '">' . esc_html__('立即处理', 'aksrt') . '</a></p>';
echo '<p><a class="button button-primary" href="' . esc_url($url) . '">' . esc_html__('立即处理', 'jio') . '</a></p>';
echo '</div>';
}
}
+76 -76
View File
@@ -6,12 +6,12 @@
* PSR-4 要求与宿主类同目录);这里保留入口、admin-post 处理器与两个视图共用的展示助手。
* 页面样式在 assets/css/admin/orders.css,危险操作确认在 assets/js/admin-orders.js。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Core\DomainModel;
use Jio\Core\DomainModel;
defined('ABSPATH') || exit;
@@ -21,7 +21,7 @@ class PayOrders {
use PayOrdersDetail;
public const CAPABILITY = 'manage_options';
public const PAGE_SLUG = 'aksrt_orders';
public const PAGE_SLUG = 'jio_orders';
private static $instance = null;
@@ -40,11 +40,11 @@ class PayOrders {
return;
}
add_action('admin_enqueue_scripts', [$this, 'enqueue_assets']);
add_action('admin_post_aksrt_pay_update_order_status', [$this, 'handle_update_order_status']);
add_action('admin_post_aksrt_pay_update_order_note', [$this, 'handle_update_order_note']);
add_action('admin_post_aksrt_pay_clear_closed_orders', [$this, 'handle_clear_closed_orders']);
add_action('admin_post_aksrt_pay_delete_order', [$this, 'handle_delete_order']);
add_action('admin_post_aksrt_pay_refund_order', [$this, 'handle_refund_order']);
add_action('admin_post_jio_pay_update_order_status', [$this, 'handle_update_order_status']);
add_action('admin_post_jio_pay_update_order_note', [$this, 'handle_update_order_note']);
add_action('admin_post_jio_pay_clear_closed_orders', [$this, 'handle_clear_closed_orders']);
add_action('admin_post_jio_pay_delete_order', [$this, 'handle_delete_order']);
add_action('admin_post_jio_pay_refund_order', [$this, 'handle_refund_order']);
}
public function enqueue_assets(): void {
@@ -54,17 +54,17 @@ class PayOrders {
}
$css = '/assets/css/admin/orders.css';
$js = '/assets/js/admin-orders.js';
wp_enqueue_style('aksrt-admin-orders', AKSRT_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(AKSRT_DIR . $css));
wp_enqueue_script('aksrt-admin-orders', AKSRT_URI . $js, [], (string) filemtime(AKSRT_DIR . $js), true);
wp_enqueue_style('jio-admin-orders', JIO_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(JIO_DIR . $css));
wp_enqueue_script('jio-admin-orders', JIO_URI . $js, [], (string) filemtime(JIO_DIR . $js), true);
}
public function render_orders_page(): void {
$this->ensure_cap();
NativeUi::page_open('aksrt-orders-page');
NativeUi::page_open('jio-orders-page');
if (!class_exists('\AKSRT\Payment\OrderRepository')) {
NativeUi::header(__('订单明细', 'aksrt'), '', 'dashicons-list-view');
echo '<div class="notice notice-error"><p>' . esc_html__('订单服务不可用。', 'aksrt') . '</p></div>';
if (!class_exists('\Jio\Payment\OrderRepository')) {
NativeUi::header(__('订单明细', 'jio'), '', 'dashicons-list-view');
echo '<div class="notice notice-error"><p>' . esc_html__('订单服务不可用。', 'jio') . '</p></div>';
NativeUi::page_close();
return;
}
@@ -87,13 +87,13 @@ class PayOrders {
$this->ensure_cap();
$order_number = isset($_POST['order_number']) ? sanitize_text_field((string) wp_unslash($_POST['order_number'])) : '';
$new_status = isset($_POST['new_status']) ? sanitize_key((string) wp_unslash($_POST['new_status'])) : '';
check_admin_referer('aksrt_pay_update_order_status_' . $order_number);
check_admin_referer('jio_pay_update_order_status_' . $order_number);
$ok = false;
if ($order_number !== '' && $new_status !== '' && class_exists('\AKSRT\Payment\OrderRepository')) {
$ok = \AKSRT\Payment\OrderRepository::get_instance()->update_status($order_number, $new_status);
if ($order_number !== '' && $new_status !== '' && class_exists('\Jio\Payment\OrderRepository')) {
$ok = \Jio\Payment\OrderRepository::get_instance()->update_status($order_number, $new_status);
}
$this->redirect_back(['aksrt_pay_msg' => $ok ? 'order_updated' : 'order_failed']);
$this->redirect_back(['jio_pay_msg' => $ok ? 'order_updated' : 'order_failed']);
}
/**
@@ -105,11 +105,11 @@ class PayOrders {
public function handle_refund_order(): void {
$this->ensure_cap();
$order_number = isset($_POST['order_number']) ? sanitize_text_field((string) wp_unslash($_POST['order_number'])) : '';
check_admin_referer('aksrt_pay_refund_order_' . $order_number);
check_admin_referer('jio_pay_refund_order_' . $order_number);
$code = 'order_refund_failed';
if ($order_number !== '' && class_exists('\AKSRT\Payment\OrderRepository')) {
$repo = \AKSRT\Payment\OrderRepository::get_instance();
if ($order_number !== '' && class_exists('\Jio\Payment\OrderRepository')) {
$repo = \Jio\Payment\OrderRepository::get_instance();
$order = $repo->get($order_number);
if (!is_array($order)) {
$code = 'order_refund_missing';
@@ -118,37 +118,37 @@ class PayOrders {
} else {
$ok = $repo->update_status($order_number, DomainModel::ORDER_STATUS_REFUNDED, [
'allow_refund' => true,
'note' => __('管理员在订单明细中标记退款', 'aksrt'),
'note' => __('管理员在订单明细中标记退款', 'jio'),
]);
$code = $ok ? 'order_refunded' : 'order_refund_failed';
}
}
$this->redirect_back(['aksrt_pay_msg' => $code]);
$this->redirect_back(['jio_pay_msg' => $code]);
}
public function handle_update_order_note(): void {
$this->ensure_cap();
$order_number = isset($_POST['order_number']) ? sanitize_text_field((string) wp_unslash($_POST['order_number'])) : '';
$admin_note = isset($_POST['admin_note']) ? sanitize_textarea_field((string) wp_unslash($_POST['admin_note'])) : '';
check_admin_referer('aksrt_pay_update_order_note_' . $order_number);
check_admin_referer('jio_pay_update_order_note_' . $order_number);
$ok = false;
if ($order_number !== '' && class_exists('\AKSRT\Payment\OrderRepository')) {
$ok = \AKSRT\Payment\OrderRepository::get_instance()->update_admin_note($order_number, $admin_note);
if ($order_number !== '' && class_exists('\Jio\Payment\OrderRepository')) {
$ok = \Jio\Payment\OrderRepository::get_instance()->update_admin_note($order_number, $admin_note);
}
$this->redirect_back(['aksrt_pay_msg' => $ok ? 'order_note_updated' : 'order_note_failed']);
$this->redirect_back(['jio_pay_msg' => $ok ? 'order_note_updated' : 'order_note_failed']);
}
public function handle_clear_closed_orders(): void {
$this->ensure_cap();
check_admin_referer('aksrt_pay_clear_closed_orders');
check_admin_referer('jio_pay_clear_closed_orders');
$days = isset($_POST['days']) ? max(1, min(3650, (int) $_POST['days'])) : 14;
$deleted = 0;
if (class_exists('\AKSRT\Payment\OrderRepository')) {
$deleted = \AKSRT\Payment\OrderRepository::get_instance()->clear_closed_orders($days);
if (class_exists('\Jio\Payment\OrderRepository')) {
$deleted = \Jio\Payment\OrderRepository::get_instance()->clear_closed_orders($days);
}
$this->redirect_back([
'aksrt_pay_msg' => $deleted > 0 ? 'orders_cleared' : 'orders_clear_empty',
'jio_pay_msg' => $deleted > 0 ? 'orders_cleared' : 'orders_clear_empty',
'cleared' => $deleted,
], true);
}
@@ -156,10 +156,10 @@ class PayOrders {
public function handle_delete_order(): void {
$this->ensure_cap();
$order_number = isset($_POST['order_number']) ? sanitize_text_field((string) wp_unslash($_POST['order_number'])) : '';
check_admin_referer('aksrt_pay_delete_order_' . $order_number);
check_admin_referer('jio_pay_delete_order_' . $order_number);
$ok = false;
if ($order_number !== '' && class_exists('\AKSRT\Payment\OrderRepository')) {
$repo = \AKSRT\Payment\OrderRepository::get_instance();
if ($order_number !== '' && class_exists('\Jio\Payment\OrderRepository')) {
$repo = \Jio\Payment\OrderRepository::get_instance();
$order = $repo->get($order_number);
// 只允许硬删已关闭订单。
if (is_array($order) && (string) ($order['status'] ?? '') === DomainModel::ORDER_STATUS_CLOSED) {
@@ -167,7 +167,7 @@ class PayOrders {
}
}
// 删除成功后详情已不存在,回到列表;失败时留在原处。
$this->redirect_back(['aksrt_pay_msg' => $ok ? 'order_deleted' : 'order_delete_failed'], $ok);
$this->redirect_back(['jio_pay_msg' => $ok ? 'order_deleted' : 'order_delete_failed'], $ok);
}
/**
@@ -180,7 +180,7 @@ class PayOrders {
if (is_string($referer) && $referer !== '') {
parse_str((string) wp_parse_url($referer, PHP_URL_QUERY), $parsed);
if (($parsed['page'] ?? '') === self::PAGE_SLUG) {
unset($parsed['page'], $parsed['aksrt_pay_msg'], $parsed['cleared']);
unset($parsed['page'], $parsed['jio_pay_msg'], $parsed['cleared']);
if ($to_list) {
unset($parsed['order']);
}
@@ -198,12 +198,12 @@ class PayOrders {
/** 当前状态可执行的动作按钮;没有可执行动作时返回空串。 */
private function order_actions_html(string $order_number, string $status, bool $small = true): string {
$labels = [
DomainModel::ORDER_STATUS_PAID => __('确认收款', 'aksrt'),
DomainModel::ORDER_STATUS_CLOSED => __('关闭订单', 'aksrt'),
DomainModel::ORDER_STATUS_PAID => __('确认收款', 'jio'),
DomainModel::ORDER_STATUS_CLOSED => __('关闭订单', 'jio'),
];
$confirms = [
DomainModel::ORDER_STATUS_PAID => __('确认将该订单标记为已支付?系统会立即为用户发放对应权益。', 'aksrt'),
DomainModel::ORDER_STATUS_CLOSED => __('确认关闭该订单?关闭后用户无法继续支付。', 'aksrt'),
DomainModel::ORDER_STATUS_PAID => __('确认将该订单标记为已支付?系统会立即为用户发放对应权益。', 'jio'),
DomainModel::ORDER_STATUS_CLOSED => __('确认关闭该订单?关闭后用户无法继续支付。', 'jio'),
];
$html = '';
@@ -213,8 +213,8 @@ class PayOrders {
continue;
}
$html .= $this->action_form(
'aksrt_pay_update_order_status',
'aksrt_pay_update_order_status_' . $order_number,
'jio_pay_update_order_status',
'jio_pay_update_order_status_' . $order_number,
['order_number' => $order_number, 'new_status' => $target],
$labels[$target],
$confirms[$target],
@@ -225,22 +225,22 @@ class PayOrders {
// 已支付是已退款的唯一前置状态。
if ($status === DomainModel::ORDER_STATUS_PAID) {
$html .= $this->action_form(
'aksrt_pay_refund_order',
'aksrt_pay_refund_order_' . $order_number,
'jio_pay_refund_order',
'jio_pay_refund_order_' . $order_number,
['order_number' => $order_number],
__('标记退款', 'aksrt'),
__('只会把订单标记为已退款,不会自动原路退回款项。确认继续?此操作不可撤销。', 'aksrt'),
__('标记退款', 'jio'),
__('只会把订单标记为已退款,不会自动原路退回款项。确认继续?此操作不可撤销。', 'jio'),
true,
$small
);
}
if ($status === DomainModel::ORDER_STATUS_CLOSED) {
$html .= $this->action_form(
'aksrt_pay_delete_order',
'aksrt_pay_delete_order_' . $order_number,
'jio_pay_delete_order',
'jio_pay_delete_order_' . $order_number,
['order_number' => $order_number],
__('删除', 'aksrt'),
__('确认永久删除此订单?不可恢复。', 'aksrt'),
__('删除', 'jio'),
__('确认永久删除此订单?不可恢复。', 'jio'),
true,
$small
);
@@ -249,13 +249,13 @@ class PayOrders {
}
/**
* 单按钮 POST 表单。确认文案放在 data-aksrt-confirm,由 admin-orders.js 拦截提交。
* 单按钮 POST 表单。确认文案放在 data-jio-confirm,由 admin-orders.js 拦截提交。
*
* @param array<string, string> $fields
*/
private function action_form(string $action, string $nonce_action, array $fields, string $label, string $confirm = '', bool $danger = false, bool $small = true): string {
$html = '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-orders-action"'
. ($confirm !== '' ? ' data-aksrt-confirm="' . esc_attr($confirm) . '"' : '') . '>';
$html = '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="jio-orders-action"'
. ($confirm !== '' ? ' data-jio-confirm="' . esc_attr($confirm) . '"' : '') . '>';
$html .= '<input type="hidden" name="action" value="' . esc_attr($action) . '" />';
foreach ($fields as $name => $value) {
$html .= '<input type="hidden" name="' . esc_attr($name) . '" value="' . esc_attr($value) . '" />';
@@ -312,11 +312,11 @@ class PayOrders {
private function payment_method_label(string $method): string {
if ($this->payment_labels === null) {
$this->payment_labels = [
'points' => __('积分', 'aksrt'),
'free' => __('免费', 'aksrt'),
'points' => __('积分', 'jio'),
'free' => __('免费', 'jio'),
];
if (class_exists('\AKSRT\Payment\Gateway')) {
foreach (\AKSRT\Payment\Gateway::get_instance()->get_all() as $id => $gateway) {
if (class_exists('\Jio\Payment\Gateway')) {
foreach (\Jio\Payment\Gateway::get_instance()->get_all() as $id => $gateway) {
$this->payment_labels[(string) $id] = $gateway->get_name();
}
}
@@ -327,7 +327,7 @@ class PayOrders {
/** @return string[] */
private function third_party_gateway_ids(): array {
return array_values(array_diff(
array_keys(\AKSRT\Payment\Gateway::get_instance()->get_all()),
array_keys(\Jio\Payment\Gateway::get_instance()->get_all()),
['balance', 'points']
));
}
@@ -340,7 +340,7 @@ class PayOrders {
/** @param array<string,mixed> $order */
private function order_amount(array $order): string {
if ($this->is_points_order($order)) {
return max(0, (int) ($order['amount'] ?? 0)) . ' ' . __('积分', 'aksrt');
return max(0, (int) ($order['amount'] ?? 0)) . ' ' . __('积分', 'jio');
}
return $this->cash_amount((string) ($order['amount'] ?? '0'), (string) ($order['currency'] ?? ''));
}
@@ -351,35 +351,35 @@ class PayOrders {
private function cash_decimal(string $value): string {
try {
return \AKSRT\Ledger\AssetAmount::balance($value)->decimal();
return \Jio\Ledger\AssetAmount::balance($value)->decimal();
} catch (\InvalidArgumentException $e) {
return '0.00';
}
}
private function render_flash(): void {
if (empty($_GET['aksrt_pay_msg'])) {
if (empty($_GET['jio_pay_msg'])) {
return;
}
$code = sanitize_key((string) wp_unslash($_GET['aksrt_pay_msg']));
$code = sanitize_key((string) wp_unslash($_GET['jio_pay_msg']));
// 码 => [文案, 是否失败]
$map = [
'order_updated' => [__('订单状态已更新。', 'aksrt'), false],
'order_failed' => [__('订单状态更新失败,订单可能已被其他操作改变,请刷新后重试。', 'aksrt'), true],
'order_refunded' => [__('订单已标记为已退款。款项请在支付渠道后台原路退回。', 'aksrt'), false],
'order_refund_failed' => [__('订单标记退款失败。', 'aksrt'), true],
'order_refund_missing' => [__('订单不存在。', 'aksrt'), true],
'order_refund_not_paid' => [__('仅已支付订单可以标记退款。', 'aksrt'), true],
'order_note_updated' => [__('运营备注已保存。', 'aksrt'), false],
'order_note_failed' => [__('运营备注保存失败。', 'aksrt'), true],
'order_updated' => [__('订单状态已更新。', 'jio'), false],
'order_failed' => [__('订单状态更新失败,订单可能已被其他操作改变,请刷新后重试。', 'jio'), true],
'order_refunded' => [__('订单已标记为已退款。款项请在支付渠道后台原路退回。', 'jio'), false],
'order_refund_failed' => [__('订单标记退款失败。', 'jio'), true],
'order_refund_missing' => [__('订单不存在。', 'jio'), true],
'order_refund_not_paid' => [__('仅已支付订单可以标记退款。', 'jio'), true],
'order_note_updated' => [__('运营备注已保存。', 'jio'), false],
'order_note_failed' => [__('运营备注保存失败。', 'jio'), true],
'orders_cleared' => [sprintf(
/* translators: %d deleted count */
__('已清理 %d 条关闭订单。', 'aksrt'),
__('已清理 %d 条关闭订单。', 'jio'),
max(0, (int) ($_GET['cleared'] ?? 0))
), false],
'orders_clear_empty' => [__('没有可清理的关闭订单。', 'aksrt'), false],
'order_deleted' => [__('订单已删除。', 'aksrt'), false],
'order_delete_failed' => [__('订单删除失败(仅允许删除已关闭订单)。', 'aksrt'), true],
'orders_clear_empty' => [__('没有可清理的关闭订单。', 'jio'), false],
'order_deleted' => [__('订单已删除。', 'jio'), false],
'order_delete_failed' => [__('订单删除失败(仅允许删除已关闭订单)。', 'jio'), true],
];
if (!isset($map[$code])) {
return;
@@ -390,7 +390,7 @@ class PayOrders {
private function ensure_cap(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
wp_die(esc_html__('您没有权限访问此页面。', 'jio'));
}
}
+59 -59
View File
@@ -4,52 +4,52 @@
*
* 作为 flat sibling trait 被 PayOrders 使用(PSR-4:trait 必须与宿主类同目录)。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Core\DomainModel;
use AKSRT\Payment\OrderRepository;
use Jio\Core\DomainModel;
use Jio\Payment\OrderRepository;
defined('ABSPATH') || exit;
trait PayOrdersDetail {
private function render_order_detail_page(string $order_number): void {
$back = NativeUi::button_link(__('返回订单列表', 'aksrt'), $this->list_back_url(), false, 'dashicons-arrow-left-alt');
$back = NativeUi::button_link(__('返回订单列表', 'jio'), $this->list_back_url(), false, 'dashicons-arrow-left-alt');
$repo = OrderRepository::get_instance();
$order = $repo->get($order_number);
if (!$order) {
echo '<div class="aksrt-native-header"><div class="aksrt-native-header__main"><h1 class="aksrt-native-header__title">'
. esc_html__('订单详情', 'aksrt') . '</h1></div><div class="aksrt-native-header__actions">' . $back . '</div></div>';
echo '<div class="jio-native-header"><div class="jio-native-header__main"><h1 class="jio-native-header__title">'
. esc_html__('订单详情', 'jio') . '</h1></div><div class="jio-native-header__actions">' . $back . '</div></div>';
echo '<hr class="wp-header-end">';
$this->render_flash();
echo NativeUi::empty_state(__('订单不存在', 'aksrt'), __('订单可能已被删除,或订单号有误。', 'aksrt'), 'dashicons-search'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo NativeUi::empty_state(__('订单不存在', 'jio'), __('订单可能已被删除,或订单号有误。', 'jio'), 'dashicons-search'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
}
$status = (string) ($order['status'] ?? '');
$context = $repo->get_admin_order_context($order_number);
echo '<div class="aksrt-native-header aksrt-orders-detail__header"><div class="aksrt-native-header__main">';
echo '<h1 class="aksrt-native-header__title"><span class="aksrt-native-header__icon dashicons dashicons-media-spreadsheet" aria-hidden="true"></span>'
. esc_html__('订单详情', 'aksrt') . '</h1>';
echo '<p class="aksrt-native-header__desc"><span class="aksrt-orders-number">' . esc_html($order_number) . '</span> '
echo '<div class="jio-native-header jio-orders-detail__header"><div class="jio-native-header__main">';
echo '<h1 class="jio-native-header__title"><span class="jio-native-header__icon dashicons dashicons-media-spreadsheet" aria-hidden="true"></span>'
. esc_html__('订单详情', 'jio') . '</h1>';
echo '<p class="jio-native-header__desc"><span class="jio-orders-number">' . esc_html($order_number) . '</span> '
. NativeUi::status_badge($status, $this->status_badge_map()) . '</p>';
echo '</div><div class="aksrt-native-header__actions">' . $back . '</div></div>';
echo '</div><div class="jio-native-header__actions">' . $back . '</div></div>';
echo '<hr class="wp-header-end">';
$this->render_flash();
echo '<div class="aksrt-orders-detail">';
echo '<div class="aksrt-orders-detail__main">';
echo '<div class="jio-orders-detail">';
echo '<div class="jio-orders-detail__main">';
$this->render_detail_summary($order, $context);
$this->render_detail_timeline($order);
$this->render_detail_snapshot($order);
echo '</div>';
echo '<aside class="aksrt-orders-detail__side">';
echo '<aside class="jio-orders-detail__side">';
$this->render_detail_actions($order_number, $status);
$this->render_detail_buyer($order, $context);
$this->render_detail_note($order_number, $order, $context);
@@ -72,31 +72,31 @@ trait PayOrdersDetail {
}
}
NativeUi::card_open(__('订单概要', 'aksrt'));
echo '<dl class="aksrt-orders-dl">';
$this->dl_row(__('商品', 'aksrt'), $product);
$this->dl_row(__('商品类型', 'aksrt'), esc_html($this->product_type_label($type) . ($id > 0 ? ' · #' . $id : '')));
$this->dl_row(__('支付方式', 'aksrt'), esc_html($this->payment_method_label((string) ($order['payment_method'] ?? ''))));
NativeUi::card_open(__('订单概要', 'jio'));
echo '<dl class="jio-orders-dl">';
$this->dl_row(__('商品', 'jio'), $product);
$this->dl_row(__('商品类型', 'jio'), esc_html($this->product_type_label($type) . ($id > 0 ? ' · #' . $id : '')));
$this->dl_row(__('支付方式', 'jio'), esc_html($this->payment_method_label((string) ($order['payment_method'] ?? ''))));
$txn = (string) ($order['payment_transaction_id'] ?? '');
$this->dl_row(__('渠道交易号', 'aksrt'), $txn !== '' ? '<code class="aksrt-native-mono">' . esc_html($txn) . '</code>' : '<span class="aksrt-native-muted">—</span>');
$this->dl_row(__('渠道交易号', 'jio'), $txn !== '' ? '<code class="jio-native-mono">' . esc_html($txn) . '</code>' : '<span class="jio-native-muted">—</span>');
echo '</dl>';
// 金额拆分:只在有折扣 / 运费时展开,否则只显示实付。
$discount = (string) ($pricing['discount'] ?? '0');
$shipping = (string) ($pricing['shipping_fee'] ?? '0');
echo '<div class="aksrt-orders-amounts">';
echo '<div class="jio-orders-amounts">';
if ($this->has_amount($discount) || $this->has_amount($shipping)) {
$this->amount_row(__('商品金额', 'aksrt'), $this->format_amount($order, (string) ($pricing['subtotal'] ?? '0')));
$this->amount_row(__('商品金额', 'jio'), $this->format_amount($order, (string) ($pricing['subtotal'] ?? '0')));
if ($this->has_amount($shipping)) {
$this->amount_row(__('运费', 'aksrt'), $this->format_amount($order, $shipping));
$this->amount_row(__('运费', 'jio'), $this->format_amount($order, $shipping));
}
if ($this->has_amount($discount)) {
$code = (string) ($coupon['code'] ?? '');
$label = $code !== '' ? sprintf(__('优惠(%s)', 'aksrt'), $code) : __('优惠', 'aksrt');
$label = $code !== '' ? sprintf(__('优惠(%s)', 'jio'), $code) : __('优惠', 'jio');
$this->amount_row($label, '− ' . $this->format_amount($order, $discount));
}
}
echo '<div class="aksrt-orders-amounts__row is-total"><span>' . esc_html__('实付金额', 'aksrt') . '</span><strong class="aksrt-native-amount">'
echo '<div class="jio-orders-amounts__row is-total"><span>' . esc_html__('实付金额', 'jio') . '</span><strong class="jio-native-amount">'
. esc_html($this->order_amount($order)) . '</strong></div>';
echo '</div>';
NativeUi::card_close();
@@ -105,26 +105,26 @@ trait PayOrdersDetail {
private function render_detail_timeline(array $order): void {
// created_at / paid_at / refunded_at 为站点时间;expires_at 为 UTC。
$events = [
[__('创建订单', 'aksrt'), (string) ($order['created_at'] ?? ''), 'info'],
[__('完成支付', 'aksrt'), (string) ($order['paid_at'] ?? ''), 'success'],
[__('标记退款', 'aksrt'), (string) ($order['refunded_at'] ?? ''), 'info'],
[__('订单关闭', 'aksrt'), (string) ($order['cancelled_at'] ?? ''), 'neutral'],
[__('创建订单', 'jio'), (string) ($order['created_at'] ?? ''), 'info'],
[__('完成支付', 'jio'), (string) ($order['paid_at'] ?? ''), 'success'],
[__('标记退款', 'jio'), (string) ($order['refunded_at'] ?? ''), 'info'],
[__('订单关闭', 'jio'), (string) ($order['cancelled_at'] ?? ''), 'neutral'],
];
$expires = (string) ($order['expires_at'] ?? '');
if (($order['status'] ?? '') === DomainModel::ORDER_STATUS_PENDING && $expires !== '') {
$events[] = [__('超时自动关闭', 'aksrt'), get_date_from_gmt($expires, 'Y-m-d H:i:s'), 'warning', true];
$events[] = [__('超时自动关闭', 'jio'), get_date_from_gmt($expires, 'Y-m-d H:i:s'), 'warning', true];
}
NativeUi::card_open(__('订单进度', 'aksrt'));
echo '<ol class="aksrt-orders-timeline">';
NativeUi::card_open(__('订单进度', 'jio'));
echo '<ol class="jio-orders-timeline">';
foreach ($events as $event) {
[$label, $time, $tone] = $event;
if ($time === '' || str_starts_with($time, '0000-00-00')) {
continue;
}
$planned = !empty($event[3]);
echo '<li class="aksrt-orders-timeline__item is-' . esc_attr($tone) . ($planned ? ' is-planned' : '') . '">'
. '<span class="aksrt-orders-timeline__label">' . esc_html($label) . ($planned ? ' <span class="aksrt-native-muted">' . esc_html__('(预计)', 'aksrt') . '</span>' : '') . '</span>'
echo '<li class="jio-orders-timeline__item is-' . esc_attr($tone) . ($planned ? ' is-planned' : '') . '">'
. '<span class="jio-orders-timeline__label">' . esc_html($label) . ($planned ? ' <span class="jio-native-muted">' . esc_html__('(预计)', 'jio') . '</span>' : '') . '</span>'
. '<time>' . esc_html($time) . '</time></li>';
}
echo '</ol>';
@@ -138,22 +138,22 @@ trait PayOrdersDetail {
if ($meta === []) {
return;
}
echo '<details class="aksrt-native-card aksrt-native-collapse">';
echo '<summary><span class="dashicons dashicons-media-code" aria-hidden="true"></span>' . esc_html__('订单快照(原始数据)', 'aksrt') . '</summary>';
echo '<div class="aksrt-native-card__body"><pre class="aksrt-orders-snapshot">'
echo '<details class="jio-native-card jio-native-collapse">';
echo '<summary><span class="dashicons dashicons-media-code" aria-hidden="true"></span>' . esc_html__('订单快照(原始数据)', 'jio') . '</summary>';
echo '<div class="jio-native-card__body"><pre class="jio-orders-snapshot">'
. esc_html((string) wp_json_encode($meta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT))
. '</pre></div></details>';
}
private function render_detail_actions(string $order_number, string $status): void {
$actions = $this->order_actions_html($order_number, $status, false);
NativeUi::card_open(__('处理订单', 'aksrt'));
NativeUi::card_open(__('处理订单', 'jio'));
if ($actions === '') {
echo '<p class="aksrt-native-muted">' . esc_html__('该订单已结束,没有可执行的操作。', 'aksrt') . '</p>';
echo '<p class="jio-native-muted">' . esc_html__('该订单已结束,没有可执行的操作。', 'jio') . '</p>';
} else {
echo '<div class="aksrt-orders-actions is-stacked">' . $actions . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="jio-orders-actions is-stacked">' . $actions . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ($status === DomainModel::ORDER_STATUS_PAID) {
echo '<p class="aksrt-native-field__hint">' . esc_html__('「标记退款」只改订单状态;款项需在支付渠道后台原路退回。', 'aksrt') . '</p>';
echo '<p class="jio-native-field__hint">' . esc_html__('「标记退款」只改订单状态;款项需在支付渠道后台原路退回。', 'jio') . '</p>';
}
}
NativeUi::card_close();
@@ -163,15 +163,15 @@ trait PayOrdersDetail {
$user_id = (int) ($order['user_id'] ?? 0);
$buyer = is_array($context['buyer'] ?? null) ? $context['buyer'] : [];
NativeUi::card_open(__('买家', 'aksrt'));
echo '<div class="aksrt-orders-buyer">' . NativeUi::user_cell($user_id, __('游客', 'aksrt')) . '</div>';
NativeUi::card_open(__('买家', 'jio'));
echo '<div class="jio-orders-buyer">' . NativeUi::user_cell($user_id, __('游客', 'jio')) . '</div>';
if ($user_id > 0) {
echo '<dl class="aksrt-orders-dl is-compact">';
$this->dl_row(__('用户名', 'aksrt'), esc_html((string) ($buyer['user_login'] ?? '')) ?: '—');
$this->dl_row(__('邮箱', 'aksrt'), esc_html((string) ($buyer['user_email'] ?? '')) ?: '—');
echo '<dl class="jio-orders-dl is-compact">';
$this->dl_row(__('用户名', 'jio'), esc_html((string) ($buyer['user_login'] ?? '')) ?: '—');
$this->dl_row(__('邮箱', 'jio'), esc_html((string) ($buyer['user_email'] ?? '')) ?: '—');
echo '</dl>';
echo '<p><a class="button button-small" href="' . esc_url($this->page_url(self::PAGE_SLUG, ['user_id' => $user_id])) . '">'
. esc_html__('查看该用户全部订单', 'aksrt') . '</a></p>';
. esc_html__('查看该用户全部订单', 'jio') . '</a></p>';
}
NativeUi::card_close();
}
@@ -181,17 +181,17 @@ trait PayOrdersDetail {
$admin_note = (string) ($meta['admin_note'] ?? '');
$customer_note = (string) ($context['customer_note'] ?? '');
NativeUi::card_open(__('备注', 'aksrt'));
NativeUi::card_open(__('备注', 'jio'));
if ($customer_note !== '') {
echo '<div class="aksrt-native-callout"><strong>' . esc_html__('买家备注', 'aksrt') . '</strong><br>' . nl2br(esc_html($customer_note)) . '</div>';
echo '<div class="jio-native-callout"><strong>' . esc_html__('买家备注', 'jio') . '</strong><br>' . nl2br(esc_html($customer_note)) . '</div>';
}
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-orders-note">';
echo '<input type="hidden" name="action" value="aksrt_pay_update_order_note" />';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="jio-orders-note">';
echo '<input type="hidden" name="action" value="jio_pay_update_order_note" />';
echo '<input type="hidden" name="order_number" value="' . esc_attr($order_number) . '" />';
wp_nonce_field('aksrt_pay_update_order_note_' . $order_number);
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html__('运营备注', 'aksrt') . '</span>'
. '<textarea name="admin_note" rows="4" placeholder="' . esc_attr__('仅管理员可见', 'aksrt') . '">' . esc_textarea($admin_note) . '</textarea></label>';
echo '<div class="aksrt-native-form-foot"><button type="submit" class="button">' . esc_html__('保存备注', 'aksrt') . '</button></div>';
wp_nonce_field('jio_pay_update_order_note_' . $order_number);
echo '<label class="jio-native-field"><span class="jio-native-field__label">' . esc_html__('运营备注', 'jio') . '</span>'
. '<textarea name="admin_note" rows="4" placeholder="' . esc_attr__('仅管理员可见', 'jio') . '">' . esc_textarea($admin_note) . '</textarea></label>';
echo '<div class="jio-native-form-foot"><button type="submit" class="button">' . esc_html__('保存备注', 'jio') . '</button></div>';
echo '</form>';
NativeUi::card_close();
}
@@ -209,7 +209,7 @@ trait PayOrdersDetail {
}
private function amount_row(string $label, string $value): void {
echo '<div class="aksrt-orders-amounts__row"><span>' . esc_html($label) . '</span><span>' . esc_html($value) . '</span></div>';
echo '<div class="jio-orders-amounts__row"><span>' . esc_html($label) . '</span><span>' . esc_html($value) . '</span></div>';
}
private function has_amount(string $value): bool {
@@ -218,7 +218,7 @@ trait PayOrdersDetail {
private function format_amount(array $order, string $value): string {
if ($this->is_points_order($order)) {
return max(0, (int) $value) . ' ' . __('积分', 'aksrt');
return max(0, (int) $value) . ' ' . __('积分', 'jio');
}
return $this->cash_amount($value, (string) ($order['currency'] ?? ''));
}
+66 -66
View File
@@ -4,13 +4,13 @@
*
* 作为 flat sibling trait 被 PayOrders 使用(PSR-4:trait 必须与宿主类同目录)。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Core\DomainModel;
use AKSRT\Payment\OrderRepository;
use Jio\Core\DomainModel;
use Jio\Payment\OrderRepository;
defined('ABSPATH') || exit;
@@ -18,8 +18,8 @@ trait PayOrdersList {
private function render_order_list_page(): void {
NativeUi::header(
__('订单明细', 'aksrt'),
__('查看全站付费订单,处理收款确认、关闭订单与退款标记。', 'aksrt'),
__('订单明细', 'jio'),
__('查看全站付费订单,处理收款确认、关闭订单与退款标记。', 'jio'),
'dashicons-list-view'
);
$this->render_flash();
@@ -55,17 +55,17 @@ trait PayOrdersList {
$this->render_filter_card($filters);
$row_args = $query_args + ($page_num > 1 ? ['paged' => $page_num] : []);
NativeUi::table_card_open(__('订单列表', 'aksrt'), $this->cleanup_form_html());
echo '<table class="wp-list-table widefat striped aksrt-orders-table">';
echo '<caption class="screen-reader-text">' . esc_html__('订单列表', 'aksrt') . '</caption><thead><tr>';
NativeUi::table_card_open(__('订单列表', 'jio'), $this->cleanup_form_html());
echo '<table class="wp-list-table widefat striped jio-orders-table">';
echo '<caption class="screen-reader-text">' . esc_html__('订单列表', 'jio') . '</caption><thead><tr>';
foreach ([
'order' => __('订单号 / 下单时间', 'aksrt'),
'user' => __('用户', 'aksrt'),
'product' => __('商品', 'aksrt'),
'amount' => __('金额 / 支付方式', 'aksrt'),
'status' => __('状态', 'aksrt'),
'paid' => __('支付时间', 'aksrt'),
'actions' => __('操作', 'aksrt'),
'order' => __('订单号 / 下单时间', 'jio'),
'user' => __('用户', 'jio'),
'product' => __('商品', 'jio'),
'amount' => __('金额 / 支付方式', 'jio'),
'status' => __('状态', 'jio'),
'paid' => __('支付时间', 'jio'),
'actions' => __('操作', 'jio'),
] as $key => $label) {
echo '<th scope="col" class="column-' . esc_attr($key) . '">' . esc_html($label) . '</th>';
}
@@ -73,9 +73,9 @@ trait PayOrdersList {
if (!$items) {
$filtered = array_diff_key($query_args, ['status' => true]) !== [];
echo NativeUi::empty_row(7, __('暂无订单', 'aksrt'), $filtered // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
? __('没有符合当前筛选条件的订单,请调整或重置筛选。', 'aksrt')
: __('用户下单后,订单会出现在这里。', 'aksrt'));
echo NativeUi::empty_row(7, __('暂无订单', 'jio'), $filtered // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
? __('没有符合当前筛选条件的订单,请调整或重置筛选。', 'jio')
: __('用户下单后,订单会出现在这里。', 'jio'));
}
foreach ($items as $row) {
$this->render_order_row($row, $row_args);
@@ -98,18 +98,18 @@ trait PayOrdersList {
$detail_url = $this->page_url(self::PAGE_SLUG, ['order' => $order_number] + $row_args);
echo '<tr>';
echo '<td class="column-order"><a class="aksrt-orders-number" href="' . esc_url($detail_url) . '">' . esc_html($order_number) . '</a>'
. '<div class="aksrt-orders-sub">' . esc_html((string) ($row['created_at'] ?? '')) . '</div></td>';
echo '<td class="column-user">' . NativeUi::user_cell((int) ($row['user_id'] ?? 0), __('游客', 'aksrt')) . '</td>';
echo '<td class="column-product"><span class="aksrt-orders-product">' . esc_html($title !== '' ? $title : $type_label) . '</span>'
. '<div class="aksrt-orders-sub">' . esc_html($type_label . ($product_id > 0 ? ' · #' . $product_id : '')) . '</div></td>';
echo '<td class="column-amount"><strong class="aksrt-native-amount">' . esc_html($this->order_amount($row)) . '</strong>'
. '<div class="aksrt-orders-sub">' . esc_html($this->payment_method_label((string) ($row['payment_method'] ?? ''))) . '</div></td>';
echo '<td class="column-order"><a class="jio-orders-number" href="' . esc_url($detail_url) . '">' . esc_html($order_number) . '</a>'
. '<div class="jio-orders-sub">' . esc_html((string) ($row['created_at'] ?? '')) . '</div></td>';
echo '<td class="column-user">' . NativeUi::user_cell((int) ($row['user_id'] ?? 0), __('游客', 'jio')) . '</td>';
echo '<td class="column-product"><span class="jio-orders-product">' . esc_html($title !== '' ? $title : $type_label) . '</span>'
. '<div class="jio-orders-sub">' . esc_html($type_label . ($product_id > 0 ? ' · #' . $product_id : '')) . '</div></td>';
echo '<td class="column-amount"><strong class="jio-native-amount">' . esc_html($this->order_amount($row)) . '</strong>'
. '<div class="jio-orders-sub">' . esc_html($this->payment_method_label((string) ($row['payment_method'] ?? ''))) . '</div></td>';
echo '<td class="column-status">' . NativeUi::status_badge($status, $this->status_badge_map()) . $this->expiry_hint($row) . '</td>';
$paid_at = (string) ($row['paid_at'] ?? '');
echo '<td class="column-paid">' . ($paid_at !== '' ? esc_html($paid_at) : '<span class="aksrt-native-muted">—</span>') . '</td>';
echo '<td class="column-actions"><div class="aksrt-orders-actions">'
. '<a class="button button-small" href="' . esc_url($detail_url) . '">' . esc_html__('详情', 'aksrt') . '</a>'
echo '<td class="column-paid">' . ($paid_at !== '' ? esc_html($paid_at) : '<span class="jio-native-muted">—</span>') . '</td>';
echo '<td class="column-actions"><div class="jio-orders-actions">'
. '<a class="button button-small" href="' . esc_url($detail_url) . '">' . esc_html__('详情', 'jio') . '</a>'
. $this->order_actions_html($order_number, $status)
. '</div></td>';
echo '</tr>';
@@ -121,9 +121,9 @@ trait PayOrdersList {
if (($order['status'] ?? '') !== DomainModel::ORDER_STATUS_PENDING || $expires === '') {
return '';
}
return '<div class="aksrt-orders-sub">' . esc_html(sprintf(
return '<div class="jio-orders-sub">' . esc_html(sprintf(
/* translators: %s local expiry time */
__('%s 前未支付将自动关闭', 'aksrt'),
__('%s 前未支付将自动关闭', 'jio'),
get_date_from_gmt($expires, 'm-d H:i')
)) . '</div>';
}
@@ -135,16 +135,16 @@ trait PayOrdersList {
*/
private function render_status_views(array $filters, array $query_args, array $counts): void {
$base = array_diff_key($query_args, ['status' => true]);
$views = ['' => [__('全部', 'aksrt'), array_sum($counts)]];
$views = ['' => [__('全部', 'jio'), array_sum($counts)]];
foreach ($this->status_labels() as $key => $label) {
$views[$key] = [$label, (int) ($counts[$key] ?? 0)];
}
echo '<nav class="aksrt-native-tabs aksrt-orders-views" aria-label="' . esc_attr__('按订单状态查看', 'aksrt') . '">';
echo '<nav class="jio-native-tabs jio-orders-views" aria-label="' . esc_attr__('按订单状态查看', 'jio') . '">';
foreach ($views as $key => [$label, $count]) {
$url = $this->page_url(self::PAGE_SLUG, $base + ($key !== '' ? ['status' => $key] : []));
$active = $filters['status'] === $key;
echo '<a class="aksrt-native-tabs__link' . ($active ? ' is-active' : '') . '" href="' . esc_url($url) . '"' . ($active ? ' aria-current="page"' : '') . '>'
echo '<a class="jio-native-tabs__link' . ($active ? ' is-active' : '') . '" href="' . esc_url($url) . '"' . ($active ? ' aria-current="page"' : '') . '>'
. esc_html($label) . ' <span class="count">' . esc_html(number_format_i18n($count)) . '</span></a>';
}
echo '</nav>';
@@ -152,8 +152,8 @@ trait PayOrdersList {
/** @param array<string, mixed> $filters */
private function render_filter_card(array $filters): void {
NativeUi::card_open('', '', false, 'aksrt-orders-filter-card');
echo '<form method="get" class="aksrt-orders-filters" role="search">';
NativeUi::card_open('', '', false, 'jio-orders-filter-card');
echo '<form method="get" class="jio-orders-filters" role="search">';
echo '<input type="hidden" name="page" value="' . esc_attr(self::PAGE_SLUG) . '" />';
if ($filters['status'] !== '') {
echo '<input type="hidden" name="status" value="' . esc_attr($filters['status']) . '" />';
@@ -162,21 +162,21 @@ trait PayOrdersList {
echo '<input type="hidden" name="product_id" value="' . esc_attr((string) $filters['product_id']) . '" />';
}
echo '<label class="aksrt-orders-filters__search"><span class="screen-reader-text">' . esc_html__('搜索订单', 'aksrt') . '</span>'
. '<input type="search" name="s" value="' . esc_attr($filters['s']) . '" placeholder="' . esc_attr__('订单号 / 用户名 / 邮箱 / 商品名', 'aksrt') . '" /></label>';
echo $this->filter_select('product_type', __('商品类型', 'aksrt'), $this->type_options(), $filters['product_type']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $this->filter_select('payment_method', __('支付方式', 'aksrt'), $this->method_options(), $filters['payment_method']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<span class="aksrt-orders-filters__range">'
. '<label><span class="screen-reader-text">' . esc_html__('开始日期', 'aksrt') . '</span><input type="date" name="date_from" value="' . esc_attr($filters['date_from']) . '" /></label>'
echo '<label class="jio-orders-filters__search"><span class="screen-reader-text">' . esc_html__('搜索订单', 'jio') . '</span>'
. '<input type="search" name="s" value="' . esc_attr($filters['s']) . '" placeholder="' . esc_attr__('订单号 / 用户名 / 邮箱 / 商品名', 'jio') . '" /></label>';
echo $this->filter_select('product_type', __('商品类型', 'jio'), $this->type_options(), $filters['product_type']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $this->filter_select('payment_method', __('支付方式', 'jio'), $this->method_options(), $filters['payment_method']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<span class="jio-orders-filters__range">'
. '<label><span class="screen-reader-text">' . esc_html__('开始日期', 'jio') . '</span><input type="date" name="date_from" value="' . esc_attr($filters['date_from']) . '" /></label>'
. '<span aria-hidden="true">–</span>'
. '<label><span class="screen-reader-text">' . esc_html__('结束日期', 'aksrt') . '</span><input type="date" name="date_to" value="' . esc_attr($filters['date_to']) . '" /></label>'
. '<label><span class="screen-reader-text">' . esc_html__('结束日期', 'jio') . '</span><input type="date" name="date_to" value="' . esc_attr($filters['date_to']) . '" /></label>'
. '</span>';
echo '<label><span class="screen-reader-text">' . esc_html__('用户 ID', 'aksrt') . '</span><input type="number" min="1" name="user_id" class="aksrt-orders-filters__user" value="'
. esc_attr($filters['user_id'] > 0 ? (string) $filters['user_id'] : '') . '" placeholder="' . esc_attr__('用户 ID', 'aksrt') . '" /></label>';
echo '<span class="aksrt-orders-filters__submit"><button type="submit" class="button button-primary">' . esc_html__('筛选', 'aksrt') . '</button>';
echo '<label><span class="screen-reader-text">' . esc_html__('用户 ID', 'jio') . '</span><input type="number" min="1" name="user_id" class="jio-orders-filters__user" value="'
. esc_attr($filters['user_id'] > 0 ? (string) $filters['user_id'] : '') . '" placeholder="' . esc_attr__('用户 ID', 'jio') . '" /></label>';
echo '<span class="jio-orders-filters__submit"><button type="submit" class="button button-primary">' . esc_html__('筛选', 'jio') . '</button>';
if (array_diff_key($this->list_query_args($filters), ['status' => true]) !== []) {
$reset = $this->page_url(self::PAGE_SLUG, $filters['status'] !== '' ? ['status' => $filters['status']] : []);
echo '<a class="button" href="' . esc_url($reset) . '">' . esc_html__('重置', 'aksrt') . '</a>';
echo '<a class="button" href="' . esc_url($reset) . '">' . esc_html__('重置', 'jio') . '</a>';
}
echo '</span></form>';
@@ -185,9 +185,9 @@ trait PayOrdersList {
if ($filters['product_id'] > 0) {
$remove = $this->page_url(self::PAGE_SLUG, array_diff_key($this->list_query_args($filters), ['product_id' => true]));
$title = (string) get_the_title($filters['product_id']);
echo '<p class="aksrt-orders-chips"><span class="aksrt-orders-chip">'
. esc_html(sprintf(__('商品:%s', 'aksrt'), $title !== '' ? $title . ' #' . $filters['product_id'] : '#' . $filters['product_id']))
. '<a href="' . esc_url($remove) . '" aria-label="' . esc_attr__('移除商品筛选', 'aksrt') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></a></span></p>';
echo '<p class="jio-orders-chips"><span class="jio-orders-chip">'
. esc_html(sprintf(__('商品:%s', 'jio'), $title !== '' ? $title . ' #' . $filters['product_id'] : '#' . $filters['product_id']))
. '<a href="' . esc_url($remove) . '" aria-label="' . esc_attr__('移除商品筛选', 'jio') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></a></span></p>';
}
NativeUi::card_close();
}
@@ -203,11 +203,11 @@ trait PayOrdersList {
private function cleanup_form_html(): string {
return $this->action_form(
'aksrt_pay_clear_closed_orders',
'aksrt_pay_clear_closed_orders',
'jio_pay_clear_closed_orders',
'jio_pay_clear_closed_orders',
['days' => '14'],
__('清理 14 天前关闭订单', 'aksrt'),
__('将永久删除 14 天前关闭的全部订单,不可恢复。确认清理?', 'aksrt'),
__('清理 14 天前关闭订单', 'jio'),
__('将永久删除 14 天前关闭的全部订单,不可恢复。确认清理?', 'jio'),
true
);
}
@@ -215,24 +215,24 @@ trait PayOrdersList {
/** @return array<string, string> */
private function type_options(): array {
return [
'' => __('全部类型', 'aksrt'),
DomainModel::ORDER_PRODUCT_READING => __('付费阅读', 'aksrt'),
DomainModel::ORDER_PRODUCT_DOWNLOAD => __('付费下载', 'aksrt'),
DomainModel::ORDER_PRODUCT_IMAGE => __('付费图片', 'aksrt'),
DomainModel::ORDER_PRODUCT_VIDEO => __('付费视频', 'aksrt'),
DomainModel::ORDER_PRODUCT_VIP => __('购买会员', 'aksrt'),
DomainModel::ORDER_PRODUCT_RECHARGE => __('余额充值', 'aksrt'),
DomainModel::ORDER_PRODUCT_POINTS => __('购买积分', 'aksrt'),
'' => __('全部类型', 'jio'),
DomainModel::ORDER_PRODUCT_READING => __('付费阅读', 'jio'),
DomainModel::ORDER_PRODUCT_DOWNLOAD => __('付费下载', 'jio'),
DomainModel::ORDER_PRODUCT_IMAGE => __('付费图片', 'jio'),
DomainModel::ORDER_PRODUCT_VIDEO => __('付费视频', 'jio'),
DomainModel::ORDER_PRODUCT_VIP => __('购买会员', 'jio'),
DomainModel::ORDER_PRODUCT_RECHARGE => __('余额充值', 'jio'),
DomainModel::ORDER_PRODUCT_POINTS => __('购买积分', 'jio'),
];
}
/** @return array<string, string> */
private function method_options(): array {
return [
'' => __('支付方式', 'aksrt'),
'balance' => __('余额', 'aksrt'),
'points' => __('积分', 'aksrt'),
'gateway' => __('第三方网关支付', 'aksrt'),
'' => __('支付方式', 'jio'),
'balance' => __('余额', 'jio'),
'points' => __('积分', 'jio'),
'gateway' => __('第三方网关支付', 'jio'),
];
}
+118 -118
View File
@@ -2,25 +2,25 @@
/**
* 支付中心「概览」:今日实时、区间指标卡、成交趋势、分类成交、支付方式、转化与退款、热销排行、待办与快捷入口。
*
* 统计口径见 \AKSRT\Payment\OrderAnalytics。首屏报表(近 30 天)随页面输出为 JSON,
* 之后切换区间 / 分类走 REST(aksrt/v1/admin/pay-overview,见 \AKSRT\Admin\PayOverviewController)。
* 统计口径见 \Jio\Payment\OrderAnalytics。首屏报表(近 30 天)随页面输出为 JSON,
* 之后切换区间 / 分类走 REST(jio/v1/admin/pay-overview,见 \Jio\Admin\PayOverviewController)。
* 图表用按需打包的 ECharts(assets/js/runtime/libs/echarts-pay.min.js,tools/build-pay-charts.mjs 生成),
* 绘制逻辑在 assets/js/admin-pay-overview.js,样式在 assets/css/admin/pay-overview.css。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Payment\OrderAnalytics;
use AKSRT\Payment\OrderAnalyticsDaily;
use Jio\Payment\OrderAnalytics;
use Jio\Payment\OrderAnalyticsDaily;
defined('ABSPATH') || exit;
final class PayOverview {
private const DEFAULT_DAYS = 30;
private const DATA_ID = 'aksrt-pay-overview-data';
private const DATA_ID = 'jio-pay-overview-data';
private static $instance = null;
@@ -43,14 +43,14 @@ final class PayOverview {
$lib = '/assets/js/runtime/libs/echarts-pay.min.js';
$css = '/assets/css/admin/pay-overview.css';
$js = '/assets/js/admin-pay-overview.js';
wp_enqueue_style('aksrt-admin-pay-overview', AKSRT_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(AKSRT_DIR . $css));
wp_enqueue_script('aksrt-echarts-pay', AKSRT_URI . $lib, [], (string) filemtime(AKSRT_DIR . $lib), true);
wp_enqueue_script('aksrt-admin-pay-overview', AKSRT_URI . $js, ['aksrt-echarts-pay'], (string) filemtime(AKSRT_DIR . $js), true);
wp_enqueue_style('jio-admin-pay-overview', JIO_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(JIO_DIR . $css));
wp_enqueue_script('jio-echarts-pay', JIO_URI . $lib, [], (string) filemtime(JIO_DIR . $lib), true);
wp_enqueue_script('jio-admin-pay-overview', JIO_URI . $js, ['jio-echarts-pay'], (string) filemtime(JIO_DIR . $js), true);
}
public function render_page(): void {
if (!current_user_can(PayMenus::CAPABILITY)) {
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
wp_die(esc_html__('您没有权限访问此页面。', 'jio'));
}
$analytics = OrderAnalytics::get_instance();
@@ -60,35 +60,35 @@ final class PayOverview {
$realtime = $analytics->today_vs_yesterday();
$symbol = $this->currency_symbol();
NativeUi::page_open('aksrt-po-page');
NativeUi::page_open('jio-po-page');
NativeUi::header(
__('支付中心', 'aksrt'),
__('成交走势、收入结构与待办一览。收款渠道、价格策略等配置在主题设置中修改。', 'aksrt'),
__('支付中心', 'jio'),
__('成交走势、收入结构与待办一览。收款渠道、价格策略等配置在主题设置中修改。', 'jio'),
'dashicons-cart',
[
['label' => __('订单明细', 'aksrt'), 'url' => $this->page_url('aksrt_orders'), 'icon' => 'dashicons-list-view'],
['label' => __('支付配置', 'aksrt'), 'url' => admin_url('admin.php?page=aksrt-settings#/payment/gateway'), 'icon' => 'dashicons-admin-generic', 'primary' => true],
['label' => __('订单明细', 'jio'), 'url' => $this->page_url('jio_orders'), 'icon' => 'dashicons-list-view'],
['label' => __('支付配置', 'jio'), 'url' => admin_url('admin.php?page=jio-settings#/payment/gateway'), 'icon' => 'dashicons-admin-generic', 'primary' => true],
]
);
$this->render_realtime($realtime, $symbol);
$this->render_toolbar($from, $today);
echo '<div class="aksrt-po-notice" data-aksrt-po-excluded hidden></div>';
echo '<div class="aksrt-po-error notice notice-error" data-aksrt-po-error hidden><p></p></div>';
echo '<div class="jio-po-notice" data-jio-po-excluded hidden></div>';
echo '<div class="jio-po-error notice notice-error" data-jio-po-error hidden><p></p></div>';
$this->render_kpis();
echo '<div class="aksrt-po-body" data-aksrt-po-body>';
echo '<div class="aksrt-po-grid aksrt-po-grid--main">';
$this->chart_card('trend', __('成交趋势', 'aksrt'), __('支付金额、净收入、退款金额曲线与支付订单数折线', 'aksrt'), 'lg');
echo '<div class="jio-po-body" data-jio-po-body>';
echo '<div class="jio-po-grid jio-po-grid--main">';
$this->chart_card('trend', __('成交趋势', 'jio'), __('支付金额、净收入、退款金额曲线与支付订单数折线', 'jio'), 'lg');
$this->render_funnel_card();
echo '</div>';
echo '<div class="aksrt-po-grid aksrt-po-grid--main">';
$this->chart_card('groups', __('分类成交', 'aksrt'), __('按商品分类的支付金额曲线', 'aksrt'), 'md');
$this->chart_card('methods', __('支付方式', 'aksrt'), __('按支付金额的支付方式占比', 'aksrt'), 'md');
echo '<div class="jio-po-grid jio-po-grid--main">';
$this->chart_card('groups', __('分类成交', 'jio'), __('按商品分类的支付金额曲线', 'jio'), 'md');
$this->chart_card('methods', __('支付方式', 'jio'), __('按支付金额的支付方式占比', 'jio'), 'md');
echo '</div>';
echo '<div class="aksrt-po-grid aksrt-po-grid--main">';
echo '<div class="jio-po-grid jio-po-grid--main">';
$this->render_top_card();
$this->render_todo_card();
echo '</div>';
@@ -100,23 +100,23 @@ final class PayOverview {
'currency' => $symbol,
'today' => $today,
'maxDays' => OrderAnalytics::MAX_RANGE_DAYS,
'rest' => ['url' => rest_url('aksrt/v1/admin/pay-overview'), 'nonce' => wp_create_nonce('wp_rest')],
'rest' => ['url' => rest_url('jio/v1/admin/pay-overview'), 'nonce' => wp_create_nonce('wp_rest')],
'initial' => ['from' => $from, 'to' => $today, 'group' => '', 'report' => $report],
'i18n' => [
'paid' => __('支付金额', 'aksrt'),
'net' => __('净收入', 'aksrt'),
'refund' => __('退款金额', 'aksrt'),
'count' => __('支付订单数', 'aksrt'),
'unit' => __('笔', 'aksrt'),
'people' => __('人', 'aksrt'),
'empty' => __('所选区间暂无成交', 'aksrt'),
'loadError' => __('统计数据加载失败,请稍后重试。', 'aksrt'),
'basis' => __('较上一周期', 'aksrt'),
'new' => __('新增', 'aksrt'),
'total' => __('合计', 'aksrt'),
'excluded' => __('以下外币订单未计入统计:', 'aksrt'),
'noTop' => __('所选区间暂无付费内容成交', 'aksrt'),
'badRange' => __('请选择有效的日期区间。', 'aksrt'),
'paid' => __('支付金额', 'jio'),
'net' => __('净收入', 'jio'),
'refund' => __('退款金额', 'jio'),
'count' => __('支付订单数', 'jio'),
'unit' => __('笔', 'jio'),
'people' => __('人', 'jio'),
'empty' => __('所选区间暂无成交', 'jio'),
'loadError' => __('统计数据加载失败,请稍后重试。', 'jio'),
'basis' => __('较上一周期', 'jio'),
'new' => __('新增', 'jio'),
'total' => __('合计', 'jio'),
'excluded' => __('以下外币订单未计入统计:', 'jio'),
'noTop' => __('所选区间暂无付费内容成交', 'jio'),
'badRange' => __('请选择有效的日期区间。', 'jio'),
],
];
echo '<script type="application/json" id="' . esc_attr(self::DATA_ID) . '">'
@@ -134,17 +134,17 @@ final class PayOverview {
private function render_realtime(array $data, string $symbol): void {
$amount = (float) $data['today']['amount'];
$prev = (float) $data['yesterday']['amount'];
echo '<section class="aksrt-po-live" aria-label="' . esc_attr__('今日实时', 'aksrt') . '">';
echo '<div class="aksrt-po-live__main"><span class="aksrt-po-live__dot" aria-hidden="true"></span>'
. '<span class="aksrt-po-live__label">' . esc_html__('今日支付金额', 'aksrt') . '</span>'
. '<strong class="aksrt-po-live__value">' . esc_html($symbol . number_format_i18n($amount, 2)) . '</strong>'
echo '<section class="jio-po-live" aria-label="' . esc_attr__('今日实时', 'jio') . '">';
echo '<div class="jio-po-live__main"><span class="jio-po-live__dot" aria-hidden="true"></span>'
. '<span class="jio-po-live__label">' . esc_html__('今日支付金额', 'jio') . '</span>'
. '<strong class="jio-po-live__value">' . esc_html($symbol . number_format_i18n($amount, 2)) . '</strong>'
. $this->delta_html($amount, $prev) . '</div>';
echo '<div class="aksrt-po-live__meta">';
echo '<div class="jio-po-live__meta">';
/* translators: %d order count */
echo '<span>' . esc_html(sprintf(__('今日 %d 笔', 'aksrt'), (int) $data['today']['count'])) . '</span>';
echo '<span>' . esc_html(sprintf(__('今日 %d 笔', 'jio'), (int) $data['today']['count'])) . '</span>';
echo '<span>' . esc_html(sprintf(
/* translators: 1 time 2 amount 3 order count */
__('昨日 00:00–%1$s:%2$s,%3$d 笔', 'aksrt'),
__('昨日 00:00–%1$s:%2$s,%3$d 笔', 'jio'),
$data['until'],
$symbol . number_format_i18n($prev, 2),
(int) $data['yesterday']['count']
@@ -154,16 +154,16 @@ final class PayOverview {
/** 同时段环比徽标(服务端渲染,仅今日实时使用;区间指标的环比由前端计算)。 */
private function delta_html(float $current, float $previous): string {
$basis = __('较昨日同时段', 'aksrt');
$basis = __('较昨日同时段', 'jio');
if ($previous <= 0) {
$text = $current > 0 ? __('新增', 'aksrt') : '—';
$text = $current > 0 ? __('新增', 'jio') : '—';
$class = $current > 0 ? 'is-up' : 'is-flat';
} else {
$delta = round(($current - $previous) / $previous * 100, 1);
$class = $delta > 0 ? 'is-up' : ($delta < 0 ? 'is-down' : 'is-flat');
$text = ($delta > 0 ? '↑' : ($delta < 0 ? '↓' : '')) . number_format_i18n(abs($delta), 1) . '%';
}
return '<span class="aksrt-po-delta ' . esc_attr($class) . '">' . esc_html($basis . ' ' . $text) . '</span>';
return '<span class="jio-po-delta ' . esc_attr($class) . '">' . esc_html($basis . ' ' . $text) . '</span>';
}
/* ------------------------------------------------------------------
@@ -172,54 +172,54 @@ final class PayOverview {
private function render_toolbar(string $from, string $to): void {
$presets = [
'd7' => __('近 7 天', 'aksrt'),
'd30' => __('近 30 天', 'aksrt'),
'd90' => __('近 90 天', 'aksrt'),
'this_month' => __('本月', 'aksrt'),
'last_month' => __('上月', 'aksrt'),
'd365' => __('近一年', 'aksrt'),
'd7' => __('近 7 天', 'jio'),
'd30' => __('近 30 天', 'jio'),
'd90' => __('近 90 天', 'jio'),
'this_month' => __('本月', 'jio'),
'last_month' => __('上月', 'jio'),
'd365' => __('近一年', 'jio'),
];
echo '<div class="aksrt-po-toolbar">';
echo '<div class="aksrt-po-seg" role="group" data-aksrt-po-presets aria-label="' . esc_attr__('统计区间', 'aksrt') . '">';
echo '<div class="jio-po-toolbar">';
echo '<div class="jio-po-seg" role="group" data-jio-po-presets aria-label="' . esc_attr__('统计区间', 'jio') . '">';
foreach ($presets as $key => $label) {
echo '<button type="button" data-preset="' . esc_attr($key) . '" aria-pressed="' . ($key === 'd30' ? 'true' : 'false') . '">' . esc_html($label) . '</button>';
}
echo '</div>';
echo '<form class="aksrt-po-range" data-aksrt-po-range>';
echo '<label><span class="screen-reader-text">' . esc_html__('开始日期', 'aksrt') . '</span>'
echo '<form class="jio-po-range" data-jio-po-range>';
echo '<label><span class="screen-reader-text">' . esc_html__('开始日期', 'jio') . '</span>'
. '<input type="date" name="from" value="' . esc_attr($from) . '" max="' . esc_attr($to) . '" required></label>';
echo '<span aria-hidden="true">–</span>';
echo '<label><span class="screen-reader-text">' . esc_html__('结束日期', 'aksrt') . '</span>'
echo '<label><span class="screen-reader-text">' . esc_html__('结束日期', 'jio') . '</span>'
. '<input type="date" name="to" value="' . esc_attr($to) . '" max="' . esc_attr($to) . '" required></label>';
echo '<label><span class="screen-reader-text">' . esc_html__('商品分类', 'aksrt') . '</span><select name="group">'
. '<option value="">' . esc_html__('全部分类', 'aksrt') . '</option>';
echo '<label><span class="screen-reader-text">' . esc_html__('商品分类', 'jio') . '</span><select name="group">'
. '<option value="">' . esc_html__('全部分类', 'jio') . '</option>';
foreach (OrderAnalytics::group_labels() as $key => $label) {
echo '<option value="' . esc_attr($key) . '">' . esc_html($label) . '</option>';
}
echo '</select></label>';
echo '<button type="submit" class="button">' . esc_html__('查询', 'aksrt') . '</button>';
echo '<button type="submit" class="button">' . esc_html__('查询', 'jio') . '</button>';
echo '</form>';
echo '<span class="aksrt-po-range-label" data-aksrt-po-range-label aria-live="polite"></span>';
echo '<span class="jio-po-range-label" data-jio-po-range-label aria-live="polite"></span>';
echo '</div>';
}
private function render_kpis(): void {
$cards = [
['paid_amount', __('支付金额', 'aksrt'), 'success'],
['net_amount', __('净收入', 'aksrt'), 'info'],
['paid_count', __('支付订单数', 'aksrt'), 'primary'],
['buyers', __('支付人数', 'aksrt'), 'primary'],
['avg_amount', __('客单价', 'aksrt'), 'warning'],
['refund_amount', __('退款金额', 'aksrt'), 'danger'],
['paid_amount', __('支付金额', 'jio'), 'success'],
['net_amount', __('净收入', 'jio'), 'info'],
['paid_count', __('支付订单数', 'jio'), 'primary'],
['buyers', __('支付人数', 'jio'), 'primary'],
['avg_amount', __('客单价', 'jio'), 'warning'],
['refund_amount', __('退款金额', 'jio'), 'danger'],
];
echo '<div class="aksrt-po-kpis" data-aksrt-po-kpis>';
echo '<div class="jio-po-kpis" data-jio-po-kpis>';
foreach ($cards as [$key, $label, $tone]) {
echo '<div class="aksrt-po-kpi aksrt-po-kpi--' . esc_attr($tone) . '" data-kpi="' . esc_attr($key) . '">'
. '<span class="aksrt-po-kpi__label">' . esc_html($label) . '</span>'
. '<span class="aksrt-po-kpi__value" data-kpi-value>—</span>'
. '<span class="aksrt-po-kpi__foot"><span class="aksrt-po-delta is-flat" data-kpi-delta>—</span>'
. '<span class="aksrt-po-kpi__meta" data-kpi-meta></span></span>'
echo '<div class="jio-po-kpi jio-po-kpi--' . esc_attr($tone) . '" data-kpi="' . esc_attr($key) . '">'
. '<span class="jio-po-kpi__label">' . esc_html($label) . '</span>'
. '<span class="jio-po-kpi__value" data-kpi-value>—</span>'
. '<span class="jio-po-kpi__foot"><span class="jio-po-delta is-flat" data-kpi-delta>—</span>'
. '<span class="jio-po-kpi__meta" data-kpi-meta></span></span>'
. '</div>';
}
echo '</div>';
@@ -230,65 +230,65 @@ final class PayOverview {
* ----------------------------------------------------------------*/
private function chart_card(string $key, string $title, string $label, string $size): void {
NativeUi::card_open($title, '', false, 'aksrt-po-card');
echo '<div class="aksrt-po-chart aksrt-po-chart--' . esc_attr($size) . '" data-aksrt-po-chart="' . esc_attr($key) . '" role="img" aria-label="' . esc_attr($label) . '"></div>';
NativeUi::card_open($title, '', false, 'jio-po-card');
echo '<div class="jio-po-chart jio-po-chart--' . esc_attr($size) . '" data-jio-po-chart="' . esc_attr($key) . '" role="img" aria-label="' . esc_attr($label) . '"></div>';
NativeUi::card_close();
}
private function render_funnel_card(): void {
$rows = [
['created_count', __('下单数', 'aksrt'), __('区间内创建的订单', 'aksrt')],
['conversion', __('支付转化率', 'aksrt'), __('下单后完成支付的比例', 'aksrt')],
['close_rate', __('超时关闭率', 'aksrt'), __('未支付而关闭的比例', 'aksrt')],
['refund_rate', __('退款率', 'aksrt'), __('退款金额 ÷ 支付金额', 'aksrt')],
['created_count', __('下单数', 'jio'), __('区间内创建的订单', 'jio')],
['conversion', __('支付转化率', 'jio'), __('下单后完成支付的比例', 'jio')],
['close_rate', __('超时关闭率', 'jio'), __('未支付而关闭的比例', 'jio')],
['refund_rate', __('退款率', 'jio'), __('退款金额 ÷ 支付金额', 'jio')],
];
NativeUi::card_open(__('转化与退款', 'aksrt'), '', false, 'aksrt-po-card');
echo '<ul class="aksrt-po-funnel" data-aksrt-po-funnel>';
NativeUi::card_open(__('转化与退款', 'jio'), '', false, 'jio-po-card');
echo '<ul class="jio-po-funnel" data-jio-po-funnel>';
foreach ($rows as [$key, $label, $hint]) {
echo '<li data-metric="' . esc_attr($key) . '"><div class="aksrt-po-funnel__head">'
. '<span class="aksrt-po-funnel__label">' . esc_html($label) . '</span>'
. '<strong class="aksrt-po-funnel__value" data-value>—</strong></div>'
. '<span class="aksrt-po-funnel__bar" aria-hidden="true"><i data-bar></i></span>'
. '<span class="aksrt-po-funnel__hint">' . esc_html($hint) . ' <span data-delta></span></span></li>';
echo '<li data-metric="' . esc_attr($key) . '"><div class="jio-po-funnel__head">'
. '<span class="jio-po-funnel__label">' . esc_html($label) . '</span>'
. '<strong class="jio-po-funnel__value" data-value>—</strong></div>'
. '<span class="jio-po-funnel__bar" aria-hidden="true"><i data-bar></i></span>'
. '<span class="jio-po-funnel__hint">' . esc_html($hint) . ' <span data-delta></span></span></li>';
}
echo '</ul>';
NativeUi::card_close();
}
private function render_top_card(): void {
NativeUi::card_open(__('付费内容热销 TOP 10', 'aksrt'), '', true, 'aksrt-po-card');
echo '<div class="aksrt-native-table-wrap"><table class="widefat aksrt-po-top">'
. '<caption class="screen-reader-text">' . esc_html__('付费内容热销排行', 'aksrt') . '</caption>'
. '<thead><tr><th scope="col" class="column-rank">#</th><th scope="col">' . esc_html__('文章', 'aksrt') . '</th>'
. '<th scope="col" class="column-num">' . esc_html__('销量', 'aksrt') . '</th>'
. '<th scope="col" class="column-num">' . esc_html__('支付金额', 'aksrt') . '</th>'
. '<th scope="col" class="column-share">' . esc_html__('占比', 'aksrt') . '</th></tr></thead>'
. '<tbody data-aksrt-po-top></tbody></table></div>';
NativeUi::card_open(__('付费内容热销 TOP 10', 'jio'), '', true, 'jio-po-card');
echo '<div class="jio-native-table-wrap"><table class="widefat jio-po-top">'
. '<caption class="screen-reader-text">' . esc_html__('付费内容热销排行', 'jio') . '</caption>'
. '<thead><tr><th scope="col" class="column-rank">#</th><th scope="col">' . esc_html__('文章', 'jio') . '</th>'
. '<th scope="col" class="column-num">' . esc_html__('销量', 'jio') . '</th>'
. '<th scope="col" class="column-num">' . esc_html__('支付金额', 'jio') . '</th>'
. '<th scope="col" class="column-share">' . esc_html__('占比', 'jio') . '</th></tr></thead>'
. '<tbody data-jio-po-top></tbody></table></div>';
NativeUi::card_close();
}
private function render_todo_card(): void {
$orders = class_exists('\AKSRT\Payment\OrderRepository') ? \AKSRT\Payment\OrderRepository::get_instance()->get_orders_status_counts() : [];
$withdraw = class_exists('\AKSRT\Payment\Withdrawal') ? \AKSRT\Payment\Withdrawal::get_instance()->get_stats() : [];
$income = class_exists('\AKSRT\Payment\Revenue') ? \AKSRT\Payment\Revenue::get_instance()->get_stats() : [];
$cards = class_exists('\AKSRT\Payment\ChargeCard') ? \AKSRT\Payment\ChargeCard::get_instance()->get_stats() : [];
$orders = class_exists('\Jio\Payment\OrderRepository') ? \Jio\Payment\OrderRepository::get_instance()->get_orders_status_counts() : [];
$withdraw = class_exists('\Jio\Payment\Withdrawal') ? \Jio\Payment\Withdrawal::get_instance()->get_stats() : [];
$income = class_exists('\Jio\Payment\Revenue') ? \Jio\Payment\Revenue::get_instance()->get_stats() : [];
$cards = class_exists('\Jio\Payment\ChargeCard') ? \Jio\Payment\ChargeCard::get_instance()->get_stats() : [];
$items = [
[__('待审提现', 'aksrt'), (int) ($withdraw['pending'] ?? 0), $this->page_url('aksrt_withdraw', ['status' => 'pending']), 'dashicons-money-alt', 'danger'],
[__('打款中提现', 'aksrt'), (int) ($withdraw['processing'] ?? 0), $this->page_url('aksrt_withdraw', ['status' => 'processing']), 'dashicons-update', 'warning'],
[__('待结算分成', 'aksrt'), (string) ($income['cash']['pending'] ?? '0.00'), $this->page_url('aksrt_income', ['status' => 'pending']), 'dashicons-chart-pie', 'warning'],
[__('待支付订单', 'aksrt'), (int) ($orders['pending'] ?? 0), $this->page_url('aksrt_orders', ['status' => 'pending']), 'dashicons-clock', 'info'],
[__('可用卡密', 'aksrt'), (int) ($cards['active'] ?? 0), $this->page_url('aksrt_charge_cards', ['status' => 'active']), 'dashicons-id-alt', 'neutral'],
[__('待审提现', 'jio'), (int) ($withdraw['pending'] ?? 0), $this->page_url('jio_withdraw', ['status' => 'pending']), 'dashicons-money-alt', 'danger'],
[__('打款中提现', 'jio'), (int) ($withdraw['processing'] ?? 0), $this->page_url('jio_withdraw', ['status' => 'processing']), 'dashicons-update', 'warning'],
[__('待结算分成', 'jio'), (string) ($income['cash']['pending'] ?? '0.00'), $this->page_url('jio_income', ['status' => 'pending']), 'dashicons-chart-pie', 'warning'],
[__('待支付订单', 'jio'), (int) ($orders['pending'] ?? 0), $this->page_url('jio_orders', ['status' => 'pending']), 'dashicons-clock', 'info'],
[__('可用卡密', 'jio'), (int) ($cards['active'] ?? 0), $this->page_url('jio_charge_cards', ['status' => 'active']), 'dashicons-id-alt', 'neutral'],
];
NativeUi::card_open(__('待办事项', 'aksrt'), '', false, 'aksrt-po-card');
echo '<ul class="aksrt-po-todo">';
NativeUi::card_open(__('待办事项', 'jio'), '', false, 'jio-po-card');
echo '<ul class="jio-po-todo">';
foreach ($items as [$label, $value, $url, $icon, $tone]) {
$idle = (string) $value === '0' || (string) $value === '0.00';
echo '<li><a class="aksrt-po-todo__item aksrt-po-todo__item--' . esc_attr($idle ? 'neutral' : $tone) . '" href="' . esc_url($url) . '">'
. '<span class="aksrt-po-todo__icon dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span>'
. '<span class="aksrt-po-todo__label">' . esc_html($label) . '</span>'
. '<span class="aksrt-po-todo__value">' . esc_html(is_int($value) ? number_format_i18n($value) : $this->currency_symbol() . $value) . '</span>'
echo '<li><a class="jio-po-todo__item jio-po-todo__item--' . esc_attr($idle ? 'neutral' : $tone) . '" href="' . esc_url($url) . '">'
. '<span class="jio-po-todo__icon dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span>'
. '<span class="jio-po-todo__label">' . esc_html($label) . '</span>'
. '<span class="jio-po-todo__value">' . esc_html(is_int($value) ? number_format_i18n($value) : $this->currency_symbol() . $value) . '</span>'
. '</a></li>';
}
echo '</ul>';
@@ -296,12 +296,12 @@ final class PayOverview {
}
private function render_shortcuts_card(): void {
NativeUi::card_open(__('快捷入口', 'aksrt'), '', false, 'aksrt-po-card');
echo '<ul class="aksrt-po-links">';
NativeUi::card_open(__('快捷入口', 'jio'), '', false, 'jio-po-card');
echo '<ul class="jio-po-links">';
foreach (PayMenus::get_instance()->shortcuts() as $item) {
echo '<li><a href="' . esc_url($this->page_url($item['slug'])) . '">'
. '<span class="aksrt-po-links__icon dashicons ' . esc_attr($item['icon']) . '" aria-hidden="true"></span>'
. '<span class="aksrt-po-links__text"><strong>' . esc_html($item['title']) . '</strong>'
. '<span class="jio-po-links__icon dashicons ' . esc_attr($item['icon']) . '" aria-hidden="true"></span>'
. '<span class="jio-po-links__text"><strong>' . esc_html($item['title']) . '</strong>'
. ($item['desc'] !== '' ? '<span>' . esc_html($item['desc']) . '</span>' : '')
. '</span></a></li>';
}
@@ -314,7 +314,7 @@ final class PayOverview {
* ----------------------------------------------------------------*/
private function currency_symbol(): string {
$paid = \AKSRT\Settings\Settings::get_paid_content_settings();
$paid = \Jio\Settings\Settings::get_paid_content_settings();
return (string) ($paid['display']['currency_symbol'] ?? '¥');
}
+24 -24
View File
@@ -10,12 +10,12 @@
* - PayProductsList 列表查询与表格渲染
* - PayProductsEditor 快速设置弹窗与保存
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Core\DomainModel;
use Jio\Core\DomainModel;
defined('ABSPATH') || exit;
@@ -25,8 +25,8 @@ class PayProducts {
use PayProductsEditor;
public const CAPABILITY = 'manage_options';
public const PAGE_SLUG = 'aksrt_pay_products';
public const SAVE_ACTION = 'aksrt_pay_save_product';
public const PAGE_SLUG = 'jio_pay_products';
public const SAVE_ACTION = 'jio_pay_save_product';
/** 列表纳入的文章状态。 */
private const LIST_STATUSES = ['publish', 'future', 'draft', 'pending', 'private'];
@@ -74,21 +74,21 @@ class PayProducts {
}
$css = '/assets/css/admin/pay-products.css';
$js = '/assets/js/admin-pay-products.js';
wp_enqueue_style('aksrt-admin-pay-products', AKSRT_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(AKSRT_DIR . $css));
wp_enqueue_script('aksrt-admin-pay-products', AKSRT_URI . $js, [], (string) filemtime(AKSRT_DIR . $js), true);
wp_enqueue_style('jio-admin-pay-products', JIO_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(JIO_DIR . $css));
wp_enqueue_script('jio-admin-pay-products', JIO_URI . $js, [], (string) filemtime(JIO_DIR . $js), true);
}
public function render_products_page(): void {
$this->ensure_cap();
NativeUi::page_open('aksrt-products-page');
NativeUi::page_open('jio-products-page');
NativeUi::header(
__('付费文章', 'aksrt'),
__('管理付费内容的售卖配置。点击「快速设置」即可在本页修改价格、权限与销量,无需进入文章编辑页。', 'aksrt'),
__('付费文章', 'jio'),
__('管理付费内容的售卖配置。点击「快速设置」即可在本页修改价格、权限与销量,无需进入文章编辑页。', 'jio'),
'dashicons-media-document'
);
if (!class_exists('\AKSRT\Payment\OrderRepository')) {
echo '<div class="notice notice-error"><p>' . esc_html__('订单服务不可用。', 'aksrt') . '</p></div>';
if (!class_exists('\Jio\Payment\OrderRepository')) {
echo '<div class="notice notice-error"><p>' . esc_html__('订单服务不可用。', 'jio') . '</p></div>';
NativeUi::page_close();
return;
}
@@ -109,19 +109,19 @@ class PayProducts {
/** @return array<int, string> */
private function pay_limit_labels(): array {
return [
0 => __('所有人可购买', 'aksrt'),
1 => __('VIP1 及以上可购买', 'aksrt'),
2 => __('仅 VIP2 可购买', 'aksrt'),
0 => __('所有人可购买', 'jio'),
1 => __('VIP1 及以上可购买', 'jio'),
2 => __('仅 VIP2 可购买', 'jio'),
];
}
private function currency_symbol(): string {
$paid = \AKSRT\Settings\Settings::get_paid_content_settings();
$paid = \Jio\Settings\Settings::get_paid_content_settings();
return (string) ($paid['display']['currency_symbol'] ?? '¥');
}
private function render_flash(): void {
$code = isset($_GET['aksrt_pay_msg']) ? sanitize_key((string) wp_unslash($_GET['aksrt_pay_msg'])) : '';
$code = isset($_GET['jio_pay_msg']) ? sanitize_key((string) wp_unslash($_GET['jio_pay_msg'])) : '';
if ($code === '') {
return;
}
@@ -129,11 +129,11 @@ class PayProducts {
$title = $post instanceof \WP_Post ? get_the_title($post) : '';
// 码 => [文案, 是否失败]
$map = [
'product_saved' => [sprintf(__('已保存「%s」的商品配置。', 'aksrt'), $title), false],
'product_disabled' => [sprintf(__('已关闭「%s」的付费功能,文章已移出本列表。', 'aksrt'), $title), false],
'product_invalid_amount' => [__('保存失败:金额最多两位小数,积分须为非负整数。', 'aksrt'), true],
'product_vip_price' => [__('保存失败:会员价格不能高于执行价(积分商品不能高于积分售价)。', 'aksrt'), true],
'product_missing' => [__('保存失败:文章不存在或您无权编辑。', 'aksrt'), true],
'product_saved' => [sprintf(__('已保存「%s」的商品配置。', 'jio'), $title), false],
'product_disabled' => [sprintf(__('已关闭「%s」的付费功能,文章已移出本列表。', 'jio'), $title), false],
'product_invalid_amount' => [__('保存失败:金额最多两位小数,积分须为非负整数。', 'jio'), true],
'product_vip_price' => [__('保存失败:会员价格不能高于执行价(积分商品不能高于积分售价)。', 'jio'), true],
'product_missing' => [__('保存失败:文章不存在或您无权编辑。', 'jio'), true],
];
if (!isset($map[$code])) {
return;
@@ -144,13 +144,13 @@ class PayProducts {
private function ensure_cap(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
wp_die(esc_html__('您没有权限访问此页面。', 'jio'));
}
}
private function cash_decimal(string $value): string {
try {
return \AKSRT\Ledger\AssetAmount::balance($value)->decimal();
return \Jio\Ledger\AssetAmount::balance($value)->decimal();
} catch (\InvalidArgumentException $e) {
return '0.00';
}
+76 -76
View File
@@ -3,15 +3,15 @@
* 付费文章:快速设置弹窗与保存。
*
* 作为 flat sibling trait 被 PayProducts 使用(PSR-4:trait 必须与宿主类同目录)。
* 弹窗是单个 <dialog>,由 admin-pay-products.js 按行内 data-aksrt-product-edit 的 JSON 填充。
* 弹窗是单个 <dialog>,由 admin-pay-products.js 按行内 data-jio-product-edit 的 JSON 填充。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Core\DomainModel;
use AKSRT\Ledger\AssetAmount;
use Jio\Core\DomainModel;
use Jio\Ledger\AssetAmount;
defined('ABSPATH') || exit;
@@ -32,7 +32,7 @@ trait PayProductsEditor {
$title = html_entity_decode(get_the_title($post), ENT_QUOTES, 'UTF-8');
return [
'id' => (int) $post->ID,
'title' => $title !== '' ? $title : sprintf(__('(无标题 #%d)', 'aksrt'), (int) $post->ID),
'title' => $title !== '' ? $title : sprintf(__('(无标题 #%d)', 'jio'), (int) $post->ID),
'editUrl' => $edit_url,
'fields' => $fields,
];
@@ -41,12 +41,12 @@ trait PayProductsEditor {
/** @param array<string, string|int> $context 列表筛选与页码,保存后据此回到原位置。 */
private function render_product_editor(array $context): void {
$currency = $this->currency_symbol();
echo '<dialog class="aksrt-product-dialog" id="aksrt-product-editor" aria-labelledby="aksrt-product-editor-title" aria-describedby="aksrt-product-editor-subject">';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-product-dialog__frame" data-aksrt-product-form'
. ' data-discard="' . esc_attr__('有未保存的修改,确定放弃?', 'aksrt') . '"'
. ' data-vip-error="' . esc_attr__('会员价格不能高于执行价。', 'aksrt') . '"'
. ' data-vip-points-error="' . esc_attr__('会员积分价不能高于积分售价。', 'aksrt') . '"'
. ' data-busy-label="' . esc_attr__('正在保存…', 'aksrt') . '">';
echo '<dialog class="jio-product-dialog" id="jio-product-editor" aria-labelledby="jio-product-editor-title" aria-describedby="jio-product-editor-subject">';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="jio-product-dialog__frame" data-jio-product-form'
. ' data-discard="' . esc_attr__('有未保存的修改,确定放弃?', 'jio') . '"'
. ' data-vip-error="' . esc_attr__('会员价格不能高于执行价。', 'jio') . '"'
. ' data-vip-points-error="' . esc_attr__('会员积分价不能高于积分售价。', 'jio') . '"'
. ' data-busy-label="' . esc_attr__('正在保存…', 'jio') . '">';
echo '<input type="hidden" name="action" value="' . esc_attr(self::SAVE_ACTION) . '">';
echo '<input type="hidden" name="post_id" value="">';
wp_nonce_field(self::SAVE_ACTION, '_wpnonce', false);
@@ -54,83 +54,83 @@ trait PayProductsEditor {
echo '<input type="hidden" name="ctx[' . esc_attr((string) $key) . ']" value="' . esc_attr((string) $value) . '">';
}
echo '<header class="aksrt-product-dialog__head"><div>';
echo '<h2 id="aksrt-product-editor-title">' . esc_html__('快速设置', 'aksrt') . '</h2>';
echo '<p id="aksrt-product-editor-subject" data-aksrt-product-subject></p></div>';
echo '<button type="button" class="aksrt-product-dialog__close" data-aksrt-dialog-close aria-label="' . esc_attr__('关闭弹窗', 'aksrt') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button>';
echo '</header><div class="aksrt-product-dialog__body">';
echo '<header class="jio-product-dialog__head"><div>';
echo '<h2 id="jio-product-editor-title">' . esc_html__('快速设置', 'jio') . '</h2>';
echo '<p id="jio-product-editor-subject" data-jio-product-subject></p></div>';
echo '<button type="button" class="jio-product-dialog__close" data-jio-dialog-close aria-label="' . esc_attr__('关闭弹窗', 'jio') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button>';
echo '</header><div class="jio-product-dialog__body">';
$this->editor_section_open(__('售卖方式', 'aksrt'));
echo '<div class="aksrt-native-form-grid">';
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html__('付费类型', 'aksrt') . '</span><select name="aksrt_pay[pay_type]">';
$this->editor_section_open(__('售卖方式', 'jio'));
echo '<div class="jio-native-form-grid">';
echo '<label class="jio-native-field"><span class="jio-native-field__label">' . esc_html__('付费类型', 'jio') . '</span><select name="jio_pay[pay_type]">';
foreach ($this->paid_type_labels() as $key => $label) {
echo '<option value="' . esc_attr($key) . '">' . esc_html($label) . '</option>';
}
echo '<option value="' . esc_attr(DomainModel::PAY_TYPE_NO) . '">' . esc_html__('关闭付费', 'aksrt') . '</option></select></label>';
$this->editor_radios('pay_modo', __('支付方式', 'aksrt'), ['0' => __('现金', 'aksrt'), 'points' => __('积分', 'aksrt')], 'enabled');
echo '<option value="' . esc_attr(DomainModel::PAY_TYPE_NO) . '">' . esc_html__('关闭付费', 'jio') . '</option></select></label>';
$this->editor_radios('pay_modo', __('支付方式', 'jio'), ['0' => __('现金', 'jio'), 'points' => __('积分', 'jio')], 'enabled');
echo '</div>';
echo '<p class="aksrt-native-callout is-warning" data-aksrt-product-pane="disabled" hidden>'
. esc_html__('关闭后文章恢复免费阅读并移出本列表,价格等配置会保留,重新开启即可恢复。', 'aksrt') . '</p>';
echo '<div data-aksrt-product-pane="enabled">';
$this->editor_radios('pay_limit', __('购买权限', 'aksrt'), array_map('strval', $this->pay_limit_labels()));
echo '<p class="jio-native-callout is-warning" data-jio-product-pane="disabled" hidden>'
. esc_html__('关闭后文章恢复免费阅读并移出本列表,价格等配置会保留,重新开启即可恢复。', 'jio') . '</p>';
echo '<div data-jio-product-pane="enabled">';
$this->editor_radios('pay_limit', __('购买权限', 'jio'), array_map('strval', $this->pay_limit_labels()));
echo '</div>';
$this->editor_section_close();
echo '<div data-aksrt-product-pane="enabled">';
$this->editor_section_open(__('价格', 'aksrt'), __('会员价填 0 表示该等级免费,且不能高于售价。', 'aksrt'));
echo '<fieldset class="aksrt-native-form-grid aksrt-product-pane" data-aksrt-product-pane="cash"><legend class="screen-reader-text">' . esc_html__('现金价格', 'aksrt') . '</legend>';
$this->editor_number('price', __('执行价', 'aksrt'), $currency, '0.01', 'data-aksrt-product-base="cash"');
$this->editor_number('original_price', __('原价', 'aksrt'), $currency, '0.01', '', __('高于执行价时划线显示。', 'aksrt'));
$this->editor_number('vip1_price', __('VIP1 价格', 'aksrt'), $currency, '0.01', 'data-aksrt-product-vip="cash"');
$this->editor_number('vip2_price', __('VIP2 价格', 'aksrt'), $currency, '0.01', 'data-aksrt-product-vip="cash"');
echo '<label class="aksrt-native-field is-wide"><span class="aksrt-native-field__label">' . esc_html__('促销标签', 'aksrt') . '</span>'
. '<input type="text" name="aksrt_pay[promotion_tag]"><small class="aksrt-native-field__hint">' . esc_html__('显示在原价上方,支持安全 HTML,例如图标。', 'aksrt') . '</small></label>';
echo '<div data-jio-product-pane="enabled">';
$this->editor_section_open(__('价格', 'jio'), __('会员价填 0 表示该等级免费,且不能高于售价。', 'jio'));
echo '<fieldset class="jio-native-form-grid jio-product-pane" data-jio-product-pane="cash"><legend class="screen-reader-text">' . esc_html__('现金价格', 'jio') . '</legend>';
$this->editor_number('price', __('执行价', 'jio'), $currency, '0.01', 'data-jio-product-base="cash"');
$this->editor_number('original_price', __('原价', 'jio'), $currency, '0.01', '', __('高于执行价时划线显示。', 'jio'));
$this->editor_number('vip1_price', __('VIP1 价格', 'jio'), $currency, '0.01', 'data-jio-product-vip="cash"');
$this->editor_number('vip2_price', __('VIP2 价格', 'jio'), $currency, '0.01', 'data-jio-product-vip="cash"');
echo '<label class="jio-native-field is-wide"><span class="jio-native-field__label">' . esc_html__('促销标签', 'jio') . '</span>'
. '<input type="text" name="jio_pay[promotion_tag]"><small class="jio-native-field__hint">' . esc_html__('显示在原价上方,支持安全 HTML,例如图标。', 'jio') . '</small></label>';
echo '</fieldset>';
echo '<fieldset class="aksrt-native-form-grid aksrt-product-pane" data-aksrt-product-pane="points" hidden disabled><legend class="screen-reader-text">' . esc_html__('积分价格', 'aksrt') . '</legend>';
$this->editor_number('points_price', __('积分售价', 'aksrt'), __('积分', 'aksrt'), '1', 'data-aksrt-product-base="points"');
$this->editor_number('vip1_points', __('VIP1 积分价', 'aksrt'), __('积分', 'aksrt'), '1', 'data-aksrt-product-vip="points"');
$this->editor_number('vip2_points', __('VIP2 积分价', 'aksrt'), __('积分', 'aksrt'), '1', 'data-aksrt-product-vip="points"');
echo '<fieldset class="jio-native-form-grid jio-product-pane" data-jio-product-pane="points" hidden disabled><legend class="screen-reader-text">' . esc_html__('积分价格', 'jio') . '</legend>';
$this->editor_number('points_price', __('积分售价', 'jio'), __('积分', 'jio'), '1', 'data-jio-product-base="points"');
$this->editor_number('vip1_points', __('VIP1 积分价', 'jio'), __('积分', 'jio'), '1', 'data-jio-product-vip="points"');
$this->editor_number('vip2_points', __('VIP2 积分价', 'jio'), __('积分', 'jio'), '1', 'data-jio-product-vip="points"');
echo '</fieldset>';
$this->editor_section_close();
$this->editor_section_open(__('销售', 'aksrt'));
echo '<div class="aksrt-native-form-grid">';
$this->editor_number('pay_count', __('销量浮动', 'aksrt'), '', '1', '', __('在真实销量上增加或减少的数量,可填负数。', 'aksrt'), false);
echo '<div class="aksrt-product-switches" role="group" aria-label="' . esc_attr__('销售选项', 'aksrt') . '">';
$this->editor_checkbox('show_sales', __('前台显示销量', 'aksrt'));
$this->editor_checkbox('coupon_s', __('允许使用优惠码', 'aksrt'));
$this->editor_checkbox('order_expired_s', __('启用订单有效期', 'aksrt'));
$this->editor_section_open(__('销售', 'jio'));
echo '<div class="jio-native-form-grid">';
$this->editor_number('pay_count', __('销量浮动', 'jio'), '', '1', '', __('在真实销量上增加或减少的数量,可填负数。', 'jio'), false);
echo '<div class="jio-product-switches" role="group" aria-label="' . esc_attr__('销售选项', 'jio') . '">';
$this->editor_checkbox('show_sales', __('前台显示销量', 'jio'));
$this->editor_checkbox('coupon_s', __('允许使用优惠码', 'jio'));
$this->editor_checkbox('order_expired_s', __('启用订单有效期', 'jio'));
echo '</div></div>';
echo '<fieldset class="aksrt-product-pane aksrt-product-expire" data-aksrt-product-pane="expire" hidden disabled><legend class="screen-reader-text">' . esc_html__('订单有效期', 'aksrt') . '</legend>';
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html__('有效时长', 'aksrt') . '</span><span class="aksrt-product-inline">'
. '<input type="number" name="aksrt_pay[order_expired_time]" min="1" step="1" required>'
. '<select name="aksrt_pay[order_expired_unit]" aria-label="' . esc_attr__('时长单位', 'aksrt') . '">'
. '<option value="day">' . esc_html__('天', 'aksrt') . '</option><option value="hour">' . esc_html__('小时', 'aksrt') . '</option><option value="minute">' . esc_html__('分钟', 'aksrt') . '</option>'
. '</select></span><small class="aksrt-native-field__hint">' . esc_html__('购买后仅在有效期内可查看,过期需再次购买。', 'aksrt') . '</small></label></fieldset>';
echo '<fieldset class="jio-product-pane jio-product-expire" data-jio-product-pane="expire" hidden disabled><legend class="screen-reader-text">' . esc_html__('订单有效期', 'jio') . '</legend>';
echo '<label class="jio-native-field"><span class="jio-native-field__label">' . esc_html__('有效时长', 'jio') . '</span><span class="jio-product-inline">'
. '<input type="number" name="jio_pay[order_expired_time]" min="1" step="1" required>'
. '<select name="jio_pay[order_expired_unit]" aria-label="' . esc_attr__('时长单位', 'jio') . '">'
. '<option value="day">' . esc_html__('天', 'jio') . '</option><option value="hour">' . esc_html__('小时', 'jio') . '</option><option value="minute">' . esc_html__('分钟', 'jio') . '</option>'
. '</select></span><small class="jio-native-field__hint">' . esc_html__('购买后仅在有效期内可查看,过期需再次购买。', 'jio') . '</small></label></fieldset>';
$this->editor_section_close();
$this->editor_section_open(__('商品文案', 'aksrt'));
echo '<div class="aksrt-native-form-grid">';
echo '<label class="aksrt-native-field is-wide"><span class="aksrt-native-field__label">' . esc_html__('商品标题', 'aksrt') . '</span><input type="text" name="aksrt_pay[pay_title]" maxlength="200">'
. '<small class="aksrt-native-field__hint">' . esc_html__('留空则使用文章标题。', 'aksrt') . '</small></label>';
echo '<label class="aksrt-native-field is-wide"><span class="aksrt-native-field__label">' . esc_html__('商品简介', 'aksrt') . '</span><textarea name="aksrt_pay[pay_doc]" rows="2"></textarea></label>';
$this->editor_section_open(__('商品文案', 'jio'));
echo '<div class="jio-native-form-grid">';
echo '<label class="jio-native-field is-wide"><span class="jio-native-field__label">' . esc_html__('商品标题', 'jio') . '</span><input type="text" name="jio_pay[pay_title]" maxlength="200">'
. '<small class="jio-native-field__hint">' . esc_html__('留空则使用文章标题。', 'jio') . '</small></label>';
echo '<label class="jio-native-field is-wide"><span class="jio-native-field__label">' . esc_html__('商品简介', 'jio') . '</span><textarea name="jio_pay[pay_doc]" rows="2"></textarea></label>';
echo '</div>';
$this->editor_section_close();
echo '</div>';
echo '<p class="aksrt-native-callout aksrt-product-note">' . esc_html__('下载资源、付费图片、视频剧集和隐藏内容请在文章编辑页的「付费功能」面板维护。', 'aksrt')
. ' <a href="#" target="_blank" rel="noopener noreferrer" data-aksrt-product-edit-link>' . esc_html__('打开文章编辑页', 'aksrt') . '</a></p>';
echo '<p class="jio-native-callout jio-product-note">' . esc_html__('下载资源、付费图片、视频剧集和隐藏内容请在文章编辑页的「付费功能」面板维护。', 'jio')
. ' <a href="#" target="_blank" rel="noopener noreferrer" data-jio-product-edit-link>' . esc_html__('打开文章编辑页', 'jio') . '</a></p>';
echo '</div><footer class="aksrt-product-dialog__foot">';
echo '<button type="button" class="button" data-aksrt-dialog-close>' . esc_html__('取消', 'aksrt') . '</button>';
echo '<button type="submit" class="button button-primary" data-aksrt-product-submit>' . esc_html__('保存设置', 'aksrt') . '</button>';
echo '</div><footer class="jio-product-dialog__foot">';
echo '<button type="button" class="button" data-jio-dialog-close>' . esc_html__('取消', 'jio') . '</button>';
echo '<button type="submit" class="button button-primary" data-jio-product-submit>' . esc_html__('保存设置', 'jio') . '</button>';
echo '</footer></form></dialog>';
}
private function editor_section_open(string $title, string $hint = ''): void {
echo '<section class="aksrt-product-section"><h3>' . esc_html($title) . '</h3>';
echo '<section class="jio-product-section"><h3>' . esc_html($title) . '</h3>';
if ($hint !== '') {
echo '<p class="aksrt-product-section__hint">' . esc_html($hint) . '</p>';
echo '<p class="jio-product-section__hint">' . esc_html($hint) . '</p>';
}
}
@@ -140,29 +140,29 @@ trait PayProductsEditor {
/** @param array<string, string> $options */
private function editor_radios(string $key, string $title, array $options, string $pane = ''): void {
echo '<fieldset class="aksrt-native-field aksrt-product-radios"' . ($pane !== '' ? ' data-aksrt-product-pane="' . esc_attr($pane) . '"' : '') . '>';
echo '<legend class="aksrt-native-field__label">' . esc_html($title) . '</legend><div>';
echo '<fieldset class="jio-native-field jio-product-radios"' . ($pane !== '' ? ' data-jio-product-pane="' . esc_attr($pane) . '"' : '') . '>';
echo '<legend class="jio-native-field__label">' . esc_html($title) . '</legend><div>';
foreach ($options as $value => $label) {
echo '<label><input type="radio" name="aksrt_pay[' . esc_attr($key) . ']" value="' . esc_attr((string) $value) . '"><span>' . esc_html($label) . '</span></label>';
echo '<label><input type="radio" name="jio_pay[' . esc_attr($key) . ']" value="' . esc_attr((string) $value) . '"><span>' . esc_html($label) . '</span></label>';
}
echo '</div></fieldset>';
}
private function editor_number(string $key, string $title, string $unit, string $step, string $attributes = '', string $hint = '', bool $non_negative = true): void {
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html($title) . '</span><span class="aksrt-product-inline">';
echo '<input type="number" name="aksrt_pay[' . esc_attr($key) . ']" step="' . esc_attr($step) . '"' . ($non_negative ? ' min="0"' : '') . ' required ' . $attributes . '>';
echo '<label class="jio-native-field"><span class="jio-native-field__label">' . esc_html($title) . '</span><span class="jio-product-inline">';
echo '<input type="number" name="jio_pay[' . esc_attr($key) . ']" step="' . esc_attr($step) . '"' . ($non_negative ? ' min="0"' : '') . ' required ' . $attributes . '>';
if ($unit !== '') {
echo '<span class="aksrt-product-unit">' . esc_html($unit) . '</span>';
echo '<span class="jio-product-unit">' . esc_html($unit) . '</span>';
}
echo '</span>';
if ($hint !== '') {
echo '<small class="aksrt-native-field__hint">' . esc_html($hint) . '</small>';
echo '<small class="jio-native-field__hint">' . esc_html($hint) . '</small>';
}
echo '</label>';
}
private function editor_checkbox(string $key, string $label): void {
echo '<label class="aksrt-product-check"><input type="checkbox" name="aksrt_pay[' . esc_attr($key) . ']" value="1">' . esc_html($label) . '</label>';
echo '<label class="jio-product-check"><input type="checkbox" name="jio_pay[' . esc_attr($key) . ']" value="1">' . esc_html($label) . '</label>';
}
/* ------------------------------------------------------------------
@@ -171,7 +171,7 @@ trait PayProductsEditor {
public function handle_save(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('您没有权限执行此操作。', 'aksrt'), '', ['response' => 403]);
wp_die(esc_html__('您没有权限执行此操作。', 'jio'), '', ['response' => 403]);
}
check_admin_referer(self::SAVE_ACTION);
@@ -182,8 +182,8 @@ trait PayProductsEditor {
$this->redirect_after_save($context, 'product_missing', 0);
}
$raw = isset($_POST['aksrt_pay']) && is_array($_POST['aksrt_pay']) ? wp_unslash($_POST['aksrt_pay']) : [];
$meta = aksrt_get_pay_meta($post);
$raw = isset($_POST['jio_pay']) && is_array($_POST['jio_pay']) ? wp_unslash($_POST['jio_pay']) : [];
$meta = jio_get_pay_meta($post);
$pay_type = DomainModel::normalize_pay_type((string) ($raw['pay_type'] ?? ''));
if ($pay_type === DomainModel::PAY_TYPE_NO) {
@@ -282,7 +282,7 @@ trait PayProductsEditor {
/** @param array<string, string|int> $context */
private function redirect_after_save(array $context, string $code, int $post_id): void {
$args = $context + ['aksrt_pay_msg' => $code];
$args = $context + ['jio_pay_msg' => $code];
if ($post_id > 0) {
$args['product'] = $post_id;
}
+50 -50
View File
@@ -4,14 +4,14 @@
*
* 作为 flat sibling trait 被 PayProducts 使用(PSR-4:trait 必须与宿主类同目录)。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Core\DomainModel;
use AKSRT\Ledger\AssetAmount;
use AKSRT\Payment\OrderRepository;
use Jio\Core\DomainModel;
use Jio\Ledger\AssetAmount;
use Jio\Payment\OrderRepository;
defined('ABSPATH') || exit;
@@ -54,25 +54,25 @@ trait PayProductsList {
// 翻页链接显式带上当前筛选条件(不含 paged)。
$query_args = array_filter(['pay_type' => $pay_type, 's' => $search], static fn(string $value): bool => $value !== '');
NativeUi::card_open('', '', true, 'aksrt-products-card');
NativeUi::card_open('', '', true, 'jio-products-card');
$this->render_product_toolbar($pay_type, $search, $type_labels);
echo '<div class="aksrt-native-table-wrap">';
echo '<table class="wp-list-table widefat fixed striped aksrt-products-table"><thead><tr>';
echo '<th scope="col" class="column-product">' . esc_html__('商品', 'aksrt') . '</th>';
echo '<th scope="col" class="column-type">' . esc_html__('付费类型', 'aksrt') . '</th>';
echo '<th scope="col" class="column-price">' . esc_html__('价格', 'aksrt') . '</th>';
echo '<th scope="col" class="column-sales">' . esc_html__('销量', 'aksrt') . '</th>';
echo '<th scope="col" class="column-revenue">' . esc_html__('销售额', 'aksrt') . '</th>';
echo '<th scope="col" class="column-actions">' . esc_html__('操作', 'aksrt') . '</th>';
echo '<div class="jio-native-table-wrap">';
echo '<table class="wp-list-table widefat fixed striped jio-products-table"><thead><tr>';
echo '<th scope="col" class="column-product">' . esc_html__('商品', 'jio') . '</th>';
echo '<th scope="col" class="column-type">' . esc_html__('付费类型', 'jio') . '</th>';
echo '<th scope="col" class="column-price">' . esc_html__('价格', 'jio') . '</th>';
echo '<th scope="col" class="column-sales">' . esc_html__('销量', 'jio') . '</th>';
echo '<th scope="col" class="column-revenue">' . esc_html__('销售额', 'jio') . '</th>';
echo '<th scope="col" class="column-actions">' . esc_html__('操作', 'jio') . '</th>';
echo '</tr></thead><tbody>';
if (!$posts) {
echo NativeUi::empty_row(
6,
__('暂无付费文章', 'aksrt'),
__('暂无付费文章', 'jio'),
$query_args !== []
? __('调整类型或搜索条件后重试。', 'aksrt')
: __('在文章编辑页的「付费功能」面板开启付费后,文章会出现在这里。', 'aksrt')
? __('调整类型或搜索条件后重试。', 'jio')
: __('在文章编辑页的「付费功能」面板开启付费后,文章会出现在这里。', 'jio')
);
}
@@ -90,7 +90,7 @@ trait PayProductsList {
}
/**
* 付费文章 = `_aksrt_pay_type` 为任一付费类型;所有写入口都同步这个派生键,直接按它查询分页。
* 付费文章 = `_jio_pay_type` 为任一付费类型;所有写入口都同步这个派生键,直接按它查询分页。
*
* @return array{posts: \WP_Post[], total: int}
*/
@@ -152,9 +152,9 @@ trait PayProductsList {
$counts = $this->paid_type_counts();
$keep = $search !== '' ? ['s' => $search] : [];
echo '<div class="aksrt-products-toolbar">';
echo '<ul class="subsubsub" aria-label="' . esc_attr__('付费类型筛选', 'aksrt') . '">';
foreach (['' => __('全部', 'aksrt')] + $type_labels as $key => $label) {
echo '<div class="jio-products-toolbar">';
echo '<ul class="subsubsub" aria-label="' . esc_attr__('付费类型筛选', 'jio') . '">';
foreach (['' => __('全部', 'jio')] + $type_labels as $key => $label) {
$key = (string) $key;
$count = $key === '' ? array_sum($counts) : (int) ($counts[$key] ?? 0);
$url = $this->page_url(self::PAGE_SLUG, $key === '' ? $keep : $keep + ['pay_type' => $key]);
@@ -163,17 +163,17 @@ trait PayProductsList {
}
echo '</ul>';
echo '<form method="get" action="' . esc_url(admin_url('admin.php')) . '" class="aksrt-native-filters" role="search">';
echo '<form method="get" action="' . esc_url(admin_url('admin.php')) . '" class="jio-native-filters" role="search">';
echo '<input type="hidden" name="page" value="' . esc_attr(self::PAGE_SLUG) . '">';
if ($pay_type !== '') {
echo '<input type="hidden" name="pay_type" value="' . esc_attr($pay_type) . '">';
}
echo '<label class="screen-reader-text" for="aksrt-products-search">' . esc_html__('搜索标题或文章 ID', 'aksrt') . '</label>';
echo '<input type="search" id="aksrt-products-search" name="s" value="' . esc_attr($search) . '" placeholder="' . esc_attr__('搜索标题或文章 ID', 'aksrt') . '">';
echo '<button type="submit" class="button">' . esc_html__('搜索', 'aksrt') . '</button>';
echo '<label class="screen-reader-text" for="jio-products-search">' . esc_html__('搜索标题或文章 ID', 'jio') . '</label>';
echo '<input type="search" id="jio-products-search" name="s" value="' . esc_attr($search) . '" placeholder="' . esc_attr__('搜索标题或文章 ID', 'jio') . '">';
echo '<button type="submit" class="button">' . esc_html__('搜索', 'jio') . '</button>';
if ($search !== '') {
$clear = $this->page_url(self::PAGE_SLUG, $pay_type !== '' ? ['pay_type' => $pay_type] : []);
echo '<a class="button-link" href="' . esc_url($clear) . '">' . esc_html__('清除搜索', 'aksrt') . '</a>';
echo '<a class="button-link" href="' . esc_url($clear) . '">' . esc_html__('清除搜索', 'jio') . '</a>';
}
echo '</form></div>';
}
@@ -184,11 +184,11 @@ trait PayProductsList {
*/
private function render_product_row(\WP_Post $post, array $sales, array $type_labels, string $currency): void {
$post_id = (int) $post->ID;
$meta = aksrt_get_pay_meta($post);
$meta = jio_get_pay_meta($post);
$pay_type = DomainModel::normalize_pay_type((string) $meta['pay_type']);
$title = get_the_title($post);
if ($title === '') {
$title = sprintf(__('(无标题 #%d)', 'aksrt'), $post_id);
$title = sprintf(__('(无标题 #%d)', 'jio'), $post_id);
}
$edit_url = (string) get_edit_post_link($post_id, 'raw');
$permalink = (string) get_permalink($post_id);
@@ -201,60 +201,60 @@ trait PayProductsList {
echo $edit_url !== ''
? '<a class="row-title" href="' . esc_url($edit_url) . '">' . esc_html($title) . '</a>'
: esc_html($title);
echo '</strong><div class="aksrt-products-sub">#' . esc_html((string) $post_id);
echo '</strong><div class="jio-products-sub">#' . esc_html((string) $post_id);
if ($post->post_status !== 'publish') {
$status = get_post_status_object($post->post_status);
echo ' · ' . esc_html($status ? (string) $status->label : $post->post_status);
}
if ((string) $meta['pay_title'] !== '') {
echo ' · ' . esc_html(sprintf(__('商品标题:%s', 'aksrt'), (string) $meta['pay_title']));
echo ' · ' . esc_html(sprintf(__('商品标题:%s', 'jio'), (string) $meta['pay_title']));
}
echo '</div></td>';
echo '<td class="column-type">' . NativeUi::badge($type_labels[$pay_type] ?? $pay_type, self::TYPE_TONES[$pay_type] ?? 'neutral');
$limit = (int) $meta['pay_limit'];
if ($limit > 0) {
echo '<div class="aksrt-products-sub">' . esc_html($this->pay_limit_labels()[$limit] ?? '') . '</div>';
echo '<div class="jio-products-sub">' . esc_html($this->pay_limit_labels()[$limit] ?? '') . '</div>';
}
echo '</td>';
echo '<td class="column-price">' . $this->price_cell_html($meta, $currency) . '</td>';
echo '<td class="column-sales"><span class="aksrt-native-amount">' . esc_html(sprintf(__('%d 单', 'aksrt'), $orders)) . '</span>';
echo '<div class="aksrt-products-sub">' . esc_html(!empty($meta['show_sales'])
? sprintf(__('前台显示 %d', 'aksrt'), aksrt_get_content_sales_count($post, $meta))
: __('前台不显示销量', 'aksrt')) . '</div></td>';
echo '<td class="column-sales"><span class="jio-native-amount">' . esc_html(sprintf(__('%d 单', 'jio'), $orders)) . '</span>';
echo '<div class="jio-products-sub">' . esc_html(!empty($meta['show_sales'])
? sprintf(__('前台显示 %d', 'jio'), jio_get_content_sales_count($post, $meta))
: __('前台不显示销量', 'jio')) . '</div></td>';
echo '<td class="column-revenue"><span class="aksrt-native-amount">' . esc_html($this->sales_amount($sales['revenue'] ?? '0.00', $currency)) . '</span>';
echo '<td class="column-revenue"><span class="jio-native-amount">' . esc_html($this->sales_amount($sales['revenue'] ?? '0.00', $currency)) . '</span>';
if ($orders > 0) {
echo '<div class="aksrt-products-sub">' . esc_html(sprintf(__('均价 %s', 'aksrt'), $this->sales_amount($sales['avg_amount'] ?? '0.00', $currency))) . '</div>';
echo '<div class="jio-products-sub">' . esc_html(sprintf(__('均价 %s', 'jio'), $this->sales_amount($sales['avg_amount'] ?? '0.00', $currency))) . '</div>';
}
echo '</td>';
$payload = $this->product_payload($post, $meta, $edit_url);
echo '<td class="column-actions">';
echo '<button type="button" class="button button-small button-primary" data-aksrt-product-edit="' . esc_attr((string) wp_json_encode($payload)) . '" aria-haspopup="dialog" aria-controls="aksrt-product-editor">'
. esc_html__('快速设置', 'aksrt') . '</button>';
echo '<div class="aksrt-products-links">';
echo '<button type="button" class="button button-small button-primary" data-jio-product-edit="' . esc_attr((string) wp_json_encode($payload)) . '" aria-haspopup="dialog" aria-controls="jio-product-editor">'
. esc_html__('快速设置', 'jio') . '</button>';
echo '<div class="jio-products-links">';
if ($edit_url !== '') {
echo '<a href="' . esc_url($edit_url) . '">' . esc_html__('编辑文章', 'aksrt') . '</a>';
echo '<a href="' . esc_url($edit_url) . '">' . esc_html__('编辑文章', 'jio') . '</a>';
}
$orders_url = $this->page_url('aksrt_orders', [
$orders_url = $this->page_url('jio_orders', [
'product_type' => DomainModel::ORDER_PRODUCT_RESOURCE,
'product_id' => $post_id,
]);
echo '<a href="' . esc_url($orders_url) . '">' . esc_html__('订单', 'aksrt') . '</a>';
echo '<a href="' . esc_url($orders_url) . '">' . esc_html__('订单', 'jio') . '</a>';
if ($permalink !== '') {
echo '<a href="' . esc_url($permalink) . '" target="_blank" rel="noopener noreferrer">' . esc_html__('查看', 'aksrt') . '</a>';
echo '<a href="' . esc_url($permalink) . '" target="_blank" rel="noopener noreferrer">' . esc_html__('查看', 'jio') . '</a>';
}
echo '</div></td></tr>';
}
/** @param array<string, mixed> $meta */
private function price_cell_html(array $meta, string $currency): string {
$free = __('免费', 'aksrt');
$free = __('免费', 'jio');
if ($meta['pay_modo'] === 'points') {
$fmt = static fn(int $points): string => $points > 0 ? sprintf(__('%d 积分', 'aksrt'), $points) : $free;
$fmt = static fn(int $points): string => $points > 0 ? sprintf(__('%d 积分', 'jio'), $points) : $free;
$main = $fmt((int) $meta['points_price']);
$original = '';
$vip = [$fmt((int) $meta['vip1_points']), $fmt((int) $meta['vip2_points'])];
@@ -267,15 +267,15 @@ trait PayProductsList {
$vip = [$fmt((string) $meta['vip1_price']), $fmt((string) $meta['vip2_price'])];
}
return '<span class="aksrt-native-amount">' . esc_html($main) . '</span>'
. ($original !== '' ? ' <del class="aksrt-native-muted">' . esc_html($original) . '</del>' : '')
. '<div class="aksrt-products-sub">' . esc_html(sprintf(__('VIP1 %1$s · VIP2 %2$s', 'aksrt'), $vip[0], $vip[1])) . '</div>';
return '<span class="jio-native-amount">' . esc_html($main) . '</span>'
. ($original !== '' ? ' <del class="jio-native-muted">' . esc_html($original) . '</del>' : '')
. '<div class="jio-products-sub">' . esc_html(sprintf(__('VIP1 %1$s · VIP2 %2$s', 'jio'), $vip[0], $vip[1])) . '</div>';
}
/** 订单统计里积分订单为 int、现金订单为两位小数字符串。 */
private function sales_amount($amount, string $currency): string {
return is_int($amount)
? sprintf(__('%d 积分', 'aksrt'), $amount)
? sprintf(__('%d 积分', 'jio'), $amount)
: $currency . $this->cash_decimal((string) $amount);
}
}
+43 -43
View File
@@ -6,12 +6,12 @@
* admin-post 处理器与共用的展示助手。页面样式在 assets/css/admin/withdrawals.css,
* 处理弹窗、复制收款账户与防重复提交在 assets/js/admin-withdrawals.js。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Payment\Withdrawal;
use Jio\Payment\Withdrawal;
defined('ABSPATH') || exit;
@@ -20,7 +20,7 @@ class PayWithdrawals {
use PayWithdrawalsList;
public const CAPABILITY = 'manage_options';
public const PAGE_SLUG = 'aksrt_withdraw';
public const PAGE_SLUG = 'jio_withdraw';
/** 后台可发起的目标状态。 */
private const TARGET_STATUSES = [Withdrawal::STATUS_PROCESSING, Withdrawal::STATUS_COMPLETED, Withdrawal::STATUS_REJECTED];
@@ -39,7 +39,7 @@ class PayWithdrawals {
return;
}
add_action('admin_enqueue_scripts', [$this, 'enqueue_assets']);
add_action('admin_post_aksrt_pay_update_withdrawal', [$this, 'handle_update_withdrawal']);
add_action('admin_post_jio_pay_update_withdrawal', [$this, 'handle_update_withdrawal']);
}
public function enqueue_assets(): void {
@@ -49,17 +49,17 @@ class PayWithdrawals {
}
$css = '/assets/css/admin/withdrawals.css';
$js = '/assets/js/admin-withdrawals.js';
wp_enqueue_style('aksrt-admin-withdrawals', AKSRT_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(AKSRT_DIR . $css));
wp_enqueue_script('aksrt-admin-withdrawals', AKSRT_URI . $js, [], (string) filemtime(AKSRT_DIR . $js), true);
wp_enqueue_style('jio-admin-withdrawals', JIO_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(JIO_DIR . $css));
wp_enqueue_script('jio-admin-withdrawals', JIO_URI . $js, [], (string) filemtime(JIO_DIR . $js), true);
}
public function render_withdrawals_page(): void {
$this->ensure_cap();
NativeUi::page_open('aksrt-wd-page');
NativeUi::page_open('jio-wd-page');
if (!class_exists('\AKSRT\Payment\Withdrawal')) {
NativeUi::header(__('提现审核', 'aksrt'), '', 'dashicons-money-alt');
echo '<div class="notice notice-error"><p>' . esc_html__('提现模块未初始化。', 'aksrt') . '</p></div>';
if (!class_exists('\Jio\Payment\Withdrawal')) {
NativeUi::header(__('提现审核', 'jio'), '', 'dashicons-money-alt');
echo '<div class="notice notice-error"><p>' . esc_html__('提现模块未初始化。', 'jio') . '</p></div>';
NativeUi::page_close();
return;
}
@@ -75,39 +75,39 @@ class PayWithdrawals {
public function handle_update_withdrawal(): void {
$this->ensure_cap();
$id = isset($_POST['withdrawal_id']) ? (int) $_POST['withdrawal_id'] : 0;
check_admin_referer('aksrt_pay_update_withdrawal_' . $id);
check_admin_referer('jio_pay_update_withdrawal_' . $id);
$new_status = isset($_POST['new_status']) ? sanitize_key((string) wp_unslash($_POST['new_status'])) : '';
$note = isset($_POST['note']) ? trim(sanitize_textarea_field((string) wp_unslash($_POST['note']))) : '';
$request_key = isset($_POST['idempotency_key']) ? sanitize_text_field((string) wp_unslash($_POST['idempotency_key'])) : '';
if ($id <= 0 || !in_array($new_status, self::TARGET_STATUSES, true) || !wp_is_uuid($request_key)) {
$this->redirect_back(['aksrt_pay_msg' => 'withdraw_failed']);
$this->redirect_back(['jio_pay_msg' => 'withdraw_failed']);
}
// 拒绝原因会随站内信通知用户,必须填写。
if ($new_status === Withdrawal::STATUS_REJECTED && $note === '') {
$this->redirect_back(['aksrt_pay_msg' => 'withdraw_note_required']);
$this->redirect_back(['jio_pay_msg' => 'withdraw_note_required']);
}
$operation_key = \AKSRT\Ledger\OperationKey::deterministic('admin-withdraw-transition', [get_current_user_id(), strtolower($request_key)]);
$operation_key = \Jio\Ledger\OperationKey::deterministic('admin-withdraw-transition', [get_current_user_id(), strtolower($request_key)]);
$result = Withdrawal::get_instance()->process_status_change($id, $new_status, $note, $operation_key);
if (is_array($result)) {
$this->redirect_back([
'aksrt_pay_msg' => 'withdraw_' . $new_status,
'jio_pay_msg' => 'withdraw_' . $new_status,
'number' => (string) ($result['withdrawal_number'] ?? ''),
]);
}
$code = is_wp_error($result) ? $result->get_error_code() : '';
$msg = [
'aksrt_withdraw_transition' => 'withdraw_stale',
'aksrt_withdraw_idempotency_conflict' => 'withdraw_stale',
'aksrt_balance_insufficient' => 'withdraw_refund_failed',
'jio_withdraw_transition' => 'withdraw_stale',
'jio_withdraw_idempotency_conflict' => 'withdraw_stale',
'jio_balance_insufficient' => 'withdraw_refund_failed',
][$code] ?? 'withdraw_failed';
// 可重试的失败带回同一个幂等键:重提同一操作不会重复入账。
$args = ['aksrt_pay_msg' => $msg];
$args = ['jio_pay_msg' => $msg];
if ($msg !== 'withdraw_stale') {
$args['retry_id'] = $id;
$args['retry_key'] = $request_key;
@@ -122,7 +122,7 @@ class PayWithdrawals {
if (is_string($referer) && $referer !== '') {
parse_str((string) wp_parse_url($referer, PHP_URL_QUERY), $parsed);
if (($parsed['page'] ?? '') === self::PAGE_SLUG) {
unset($parsed['page'], $parsed['aksrt_pay_msg'], $parsed['number'], $parsed['retry_id'], $parsed['retry_key']);
unset($parsed['page'], $parsed['jio_pay_msg'], $parsed['number'], $parsed['retry_id'], $parsed['retry_key']);
$query = $parsed;
}
}
@@ -137,10 +137,10 @@ class PayWithdrawals {
/** @return array<string, string> */
private function status_labels(): array {
return [
Withdrawal::STATUS_PENDING => __('待处理', 'aksrt'),
Withdrawal::STATUS_PROCESSING => __('处理中', 'aksrt'),
Withdrawal::STATUS_COMPLETED => __('已完成', 'aksrt'),
Withdrawal::STATUS_REJECTED => __('已拒绝', 'aksrt'),
Withdrawal::STATUS_PENDING => __('待处理', 'jio'),
Withdrawal::STATUS_PROCESSING => __('处理中', 'jio'),
Withdrawal::STATUS_COMPLETED => __('已完成', 'jio'),
Withdrawal::STATUS_REJECTED => __('已拒绝', 'jio'),
];
}
@@ -171,22 +171,22 @@ class PayWithdrawals {
/** @return array<string, string> */
private function source_labels(): array {
return [
Withdrawal::SOURCE_BALANCE => __('余额钱包', 'aksrt'),
Withdrawal::SOURCE_COMMISSION => __('推广佣金', 'aksrt'),
Withdrawal::SOURCE_BALANCE => __('余额钱包', 'jio'),
Withdrawal::SOURCE_COMMISSION => __('推广佣金', 'jio'),
];
}
/** @return array<string, string> */
private function method_labels(): array {
return [
'alipay' => __('支付宝', 'aksrt'),
'wechat' => __('微信', 'aksrt'),
'alipay' => __('支付宝', 'jio'),
'wechat' => __('微信', 'jio'),
];
}
private function format_money(string $decimal): string {
try {
return '¥' . \AKSRT\Ledger\AssetAmount::balance($decimal)->formatted();
return '¥' . \Jio\Ledger\AssetAmount::balance($decimal)->formatted();
} catch (\InvalidArgumentException $e) {
return '¥0.00';
}
@@ -195,11 +195,11 @@ class PayWithdrawals {
/** 手续费 = 申请毛额 − 到账净额,按分计算避免浮点误差。 */
private function fee_decimal(string $gross, string $net): string {
try {
$fee = \AKSRT\Ledger\AssetAmount::balance($gross)->minor() - \AKSRT\Ledger\AssetAmount::balance($net)->minor();
$fee = \Jio\Ledger\AssetAmount::balance($gross)->minor() - \Jio\Ledger\AssetAmount::balance($net)->minor();
} catch (\InvalidArgumentException $e) {
return '0.00';
}
return \AKSRT\Ledger\AssetAmount::decimal_from_minor(max(0, $fee));
return \Jio\Ledger\AssetAmount::decimal_from_minor(max(0, $fee));
}
/** 行内操作的幂等键:上次失败的那一行沿用带回的键,其余行每次渲染新生成。 */
@@ -208,21 +208,21 @@ class PayWithdrawals {
}
private function render_flash(): void {
if (empty($_GET['aksrt_pay_msg'])) {
if (empty($_GET['jio_pay_msg'])) {
return;
}
$code = sanitize_key((string) wp_unslash($_GET['aksrt_pay_msg']));
$code = sanitize_key((string) wp_unslash($_GET['jio_pay_msg']));
$number = isset($_GET['number']) ? sanitize_text_field((string) wp_unslash($_GET['number'])) : '';
$suffix = $number !== '' ? sprintf(__('(订单号 %s)', 'aksrt'), $number) : '';
$suffix = $number !== '' ? sprintf(__('(订单号 %s)', 'jio'), $number) : '';
// 码 => [文案, notice 类型]
$map = [
'withdraw_processing' => [__('已标记为处理中,请尽快完成打款。', 'aksrt') . $suffix, 'success'],
'withdraw_completed' => [__('已确认打款完成,用户已收到通知。', 'aksrt') . $suffix, 'success'],
'withdraw_rejected' => [__('已拒绝提现,冻结金额已退回用户,拒绝原因已通知用户。', 'aksrt') . $suffix, 'success'],
'withdraw_note_required' => [__('拒绝提现必须填写拒绝原因。', 'aksrt'), 'warning'],
'withdraw_stale' => [__('操作未执行:该申请的状态已被更新,请查看最新状态后再处理。', 'aksrt'), 'warning'],
'withdraw_refund_failed' => [__('状态未更新:冻结金额退回失败,请检查用户钱包后重试。', 'aksrt'), 'error'],
'withdraw_failed' => [__('提现状态更新失败,请重试。重试不会重复入账。', 'aksrt'), 'error'],
'withdraw_processing' => [__('已标记为处理中,请尽快完成打款。', 'jio') . $suffix, 'success'],
'withdraw_completed' => [__('已确认打款完成,用户已收到通知。', 'jio') . $suffix, 'success'],
'withdraw_rejected' => [__('已拒绝提现,冻结金额已退回用户,拒绝原因已通知用户。', 'jio') . $suffix, 'success'],
'withdraw_note_required' => [__('拒绝提现必须填写拒绝原因。', 'jio'), 'warning'],
'withdraw_stale' => [__('操作未执行:该申请的状态已被更新,请查看最新状态后再处理。', 'jio'), 'warning'],
'withdraw_refund_failed' => [__('状态未更新:冻结金额退回失败,请检查用户钱包后重试。', 'jio'), 'error'],
'withdraw_failed' => [__('提现状态更新失败,请重试。重试不会重复入账。', 'jio'), 'error'],
];
if (!isset($map[$code])) {
return;
@@ -233,7 +233,7 @@ class PayWithdrawals {
private function ensure_cap(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
wp_die(esc_html__('您没有权限访问此页面。', 'jio'));
}
}
+98 -98
View File
@@ -4,12 +4,12 @@
*
* 作为 flat sibling trait 被 PayWithdrawals 使用(PSR-4:trait 必须与宿主类同目录)。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Payment\Withdrawal;
use Jio\Payment\Withdrawal;
defined('ABSPATH') || exit;
@@ -17,12 +17,12 @@ trait PayWithdrawalsList {
private function render_withdrawals_list_page(): void {
NativeUi::header(
__('提现审核', 'aksrt'),
__('用户发起的余额与推广佣金提现申请。按收款信息线下打款后,在这里确认完成;拒绝会把冻结金额退回原资金池。', 'aksrt'),
__('提现审核', 'jio'),
__('用户发起的余额与推广佣金提现申请。按收款信息线下打款后,在这里确认完成;拒绝会把冻结金额退回原资金池。', 'jio'),
'dashicons-money-alt',
[[
'label' => __('提现策略', 'aksrt'),
'url' => admin_url('admin.php?page=aksrt-settings#/payment/withdrawal'),
'label' => __('提现策略', 'jio'),
'url' => admin_url('admin.php?page=jio-settings#/payment/withdrawal'),
'icon' => 'dashicons-admin-generic',
]]
);
@@ -78,17 +78,17 @@ trait PayWithdrawalsList {
$this->render_status_views($filters['status'], $query_args, $counts);
$this->render_filter_card($filters, $query_args);
NativeUi::table_card_open(__('提现申请', 'aksrt'));
echo '<table class="wp-list-table widefat striped aksrt-wd-table">';
echo '<caption class="screen-reader-text">' . esc_html__('提现申请', 'aksrt') . '</caption><thead><tr>';
NativeUi::table_card_open(__('提现申请', 'jio'));
echo '<table class="wp-list-table widefat striped jio-wd-table">';
echo '<caption class="screen-reader-text">' . esc_html__('提现申请', 'jio') . '</caption><thead><tr>';
foreach ([
'number' => esc_html__('订单号', 'aksrt'),
'user' => esc_html__('用户', 'aksrt'),
'amount' => esc_html__('金额', 'aksrt'),
'payee' => esc_html__('收款信息', 'aksrt'),
'status' => esc_html__('状态', 'aksrt'),
'note' => esc_html__('备注', 'aksrt'),
'actions' => esc_html__('操作', 'aksrt'),
'number' => esc_html__('订单号', 'jio'),
'user' => esc_html__('用户', 'jio'),
'amount' => esc_html__('金额', 'jio'),
'payee' => esc_html__('收款信息', 'jio'),
'status' => esc_html__('状态', 'jio'),
'note' => esc_html__('备注', 'jio'),
'actions' => esc_html__('操作', 'jio'),
] as $key => $label) {
echo '<th scope="col" class="column-' . esc_attr($key) . '">' . $label . '</th>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped above.
}
@@ -96,9 +96,9 @@ trait PayWithdrawalsList {
if (!$items) {
$filtered = array_diff_key($query_args, ['status' => true]) !== [];
echo NativeUi::empty_row(7, __('暂无提现记录', 'aksrt'), $filtered // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
? __('没有符合当前筛选条件的申请,请调整或重置筛选。', 'aksrt')
: __('用户在用户中心发起余额或佣金提现后,申请会出现在这里。', 'aksrt'));
echo NativeUi::empty_row(7, __('暂无提现记录', 'jio'), $filtered // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
? __('没有符合当前筛选条件的申请,请调整或重置筛选。', 'jio')
: __('用户在用户中心发起余额或佣金提现后,申请会出现在这里。', 'jio'));
}
foreach ($items as $row) {
$this->render_withdrawal_row($row, $retry_id, $retry_key);
@@ -110,9 +110,9 @@ trait PayWithdrawalsList {
NativeUi::card_close();
$this->render_process_dialog();
echo '<div class="aksrt-wd-toast" role="status" aria-live="polite" data-aksrt-wd-toast'
. ' data-copied="' . esc_attr__('收款信息已复制', 'aksrt') . '"'
. ' data-copy-failed="' . esc_attr__('自动复制失败,请手动选中后复制。', 'aksrt') . '"></div>';
echo '<div class="jio-wd-toast" role="status" aria-live="polite" data-jio-wd-toast'
. ' data-copied="' . esc_attr__('收款信息已复制', 'jio') . '"'
. ' data-copy-failed="' . esc_attr__('自动复制失败,请手动选中后复制。', 'jio') . '"></div>';
}
private function render_withdrawal_row(array $row, int $retry_id, string $retry_key): void {
@@ -131,44 +131,44 @@ trait PayWithdrawalsList {
$user = $user_id > 0 ? get_userdata($user_id) : false;
echo '<tr' . ($retry_id === $id ? ' class="is-retry"' : '') . '>';
echo '<td class="column-number"><code class="aksrt-native-mono">' . esc_html($number !== '' ? $number : '#' . $id) . '</code>'
. '<div class="aksrt-wd-sub">' . esc_html(sprintf(__('%s 申请', 'aksrt'), (string) ($row['created_at'] ?? ''))) . '</div></td>';
echo '<td class="column-number"><code class="jio-native-mono">' . esc_html($number !== '' ? $number : '#' . $id) . '</code>'
. '<div class="jio-wd-sub">' . esc_html(sprintf(__('%s 申请', 'jio'), (string) ($row['created_at'] ?? ''))) . '</div></td>';
echo '<td class="column-user">' . NativeUi::user_cell($user_id) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
. '<div class="aksrt-wd-sub">' . esc_html(sprintf(__('来源:%s', 'aksrt'), $source_label)) . '</div></td>';
. '<div class="jio-wd-sub">' . esc_html(sprintf(__('来源:%s', 'jio'), $source_label)) . '</div></td>';
echo '<td class="column-amount"><strong class="aksrt-native-amount">' . esc_html($this->format_money($net)) . '</strong>'
. '<div class="aksrt-wd-sub">' . esc_html(sprintf(
echo '<td class="column-amount"><strong class="jio-native-amount">' . esc_html($this->format_money($net)) . '</strong>'
. '<div class="jio-wd-sub">' . esc_html(sprintf(
/* translators: 1 gross 2 fee 3 fee percent */
__('申请 %1$s · 手续费 %2$s(%3$d%%)', 'aksrt'),
__('申请 %1$s · 手续费 %2$s(%3$d%%)', 'jio'),
$this->format_money($gross),
$this->format_money($this->fee_decimal($gross, $net)),
(int) ($row['fee_rate'] ?? 0)
)) . '</div></td>';
echo '<td class="column-payee"><span class="aksrt-wd-method aksrt-wd-method--' . esc_attr(sanitize_html_class($method)) . '">' . esc_html($method_label) . '</span>';
echo '<td class="column-payee"><span class="jio-wd-method jio-wd-method--' . esc_attr(sanitize_html_class($method)) . '">' . esc_html($method_label) . '</span>';
if ($account !== '') {
echo '<button type="button" class="aksrt-wd-account" data-clipboard-text="' . esc_attr($account) . '" aria-label="' . esc_attr(sprintf(__('复制收款信息:%s', 'aksrt'), $account)) . '">'
echo '<button type="button" class="jio-wd-account" data-clipboard-text="' . esc_attr($account) . '" aria-label="' . esc_attr(sprintf(__('复制收款信息:%s', 'jio'), $account)) . '">'
. '<span>' . esc_html($account) . '</span><span class="dashicons dashicons-admin-page" aria-hidden="true"></span></button>';
}
echo '</td>';
$processed = (string) ($row['processed_at'] ?? '');
echo '<td class="column-status">' . NativeUi::status_badge($status, $this->status_badge_map()) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
. ($processed !== '' ? '<div class="aksrt-wd-sub">' . esc_html(sprintf(__('%s 处理', 'aksrt'), $processed)) . '</div>' : '') . '</td>';
. ($processed !== '' ? '<div class="jio-wd-sub">' . esc_html(sprintf(__('%s 处理', 'jio'), $processed)) . '</div>' : '') . '</td>';
echo '<td class="column-note">' . ($note !== ''
? '<div class="aksrt-wd-note">' . nl2br(esc_html($note)) . '</div>'
: '<span class="aksrt-native-muted">—</span>') . '</td>';
? '<div class="jio-wd-note">' . nl2br(esc_html($note)) . '</div>'
: '<span class="jio-native-muted">—</span>') . '</td>';
echo '<td class="column-actions">';
$next = $this->next_statuses($status);
if ($next === []) {
echo '<span class="aksrt-native-muted">—</span></td></tr>';
echo '<span class="jio-native-muted">—</span></td></tr>';
return;
}
$data = [
'id' => (string) $id,
'nonce' => wp_create_nonce('aksrt_pay_update_withdrawal_' . $id),
'nonce' => wp_create_nonce('jio_pay_update_withdrawal_' . $id),
'key' => $this->request_key_for($id, $retry_id, $retry_key),
'number' => $number !== '' ? $number : '#' . $id,
'user' => $user ? $user->display_name . ' #' . $user_id : '#' . $user_id,
@@ -182,38 +182,38 @@ trait PayWithdrawalsList {
$attrs .= ' data-' . $key . '="' . esc_attr($value) . '"';
}
$buttons = [
Withdrawal::STATUS_PROCESSING => [__('开始处理', 'aksrt'), 'button-primary'],
Withdrawal::STATUS_COMPLETED => [__('确认打款', 'aksrt'), 'button-primary'],
Withdrawal::STATUS_REJECTED => [__('拒绝', 'aksrt'), 'is-danger'],
Withdrawal::STATUS_PROCESSING => [__('开始处理', 'jio'), 'button-primary'],
Withdrawal::STATUS_COMPLETED => [__('确认打款', 'jio'), 'button-primary'],
Withdrawal::STATUS_REJECTED => [__('拒绝', 'jio'), 'is-danger'],
];
echo '<div class="aksrt-wd-actions">';
echo '<div class="jio-wd-actions">';
foreach ($next as $target) {
[$label, $class] = $buttons[$target];
echo '<button type="button" class="button button-small ' . esc_attr($class) . '" data-aksrt-wd-open="' . esc_attr($target) . '"' . $attrs // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped above.
. ' aria-haspopup="dialog" aria-controls="aksrt-wd-dialog">' . esc_html($label) . '</button>';
echo '<button type="button" class="button button-small ' . esc_attr($class) . '" data-jio-wd-open="' . esc_attr($target) . '"' . $attrs // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped above.
. ' aria-haspopup="dialog" aria-controls="jio-wd-dialog">' . esc_html($label) . '</button>';
}
echo '</div></td></tr>';
}
/** 处理流程 + 当前提现策略;有待办时用警示色提醒冻结金额。 */
private function render_process_callout(array $stats): void {
$settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_withdrawal_settings() : [];
$settings = class_exists('\Jio\Settings\Settings') ? \Jio\Settings\Settings::get_withdrawal_settings() : [];
$open = (int) ($stats['pending'] ?? 0) + (int) ($stats['processing'] ?? 0);
$policy = sprintf(
/* translators: 1 min amount 2 fee percent 3 daily count 4 daily limit */
__('当前策略:最低 %1$s,手续费 %2$d%%,每日 %3$d 次 / %4$s。', 'aksrt'),
__('当前策略:最低 %1$s,手续费 %2$d%%,每日 %3$d 次 / %4$s。', 'jio'),
$this->format_money((string) ($settings['min_amount'] ?? '0.00')),
(int) ($settings['service_charge_rate'] ?? 0),
(int) ($settings['daily_count'] ?? 0),
$this->format_money((string) ($settings['daily_limit'] ?? '0.00'))
);
$flow = __('处理流程:开始处理 → 按收款信息线下打款 → 确认打款。确认打款与拒绝都不可撤销。', 'aksrt');
$flow = __('处理流程:开始处理 → 按收款信息线下打款 → 确认打款。确认打款与拒绝都不可撤销。', 'jio');
echo '<div class="aksrt-native-callout' . ($open > 0 ? ' is-warning' : '') . '">';
echo '<div class="jio-native-callout' . ($open > 0 ? ' is-warning' : '') . '">';
if ($open > 0) {
echo '<strong>' . esc_html(sprintf(
/* translators: 1 open count 2 frozen amount */
__('%1$d 笔申请待办,冻结中 %2$s。', 'aksrt'),
__('%1$d 笔申请待办,冻结中 %2$s。', 'jio'),
$open,
$this->format_money((string) ($stats['open_gross'] ?? '0.00'))
)) . '</strong> ';
@@ -223,32 +223,32 @@ trait PayWithdrawalsList {
private function render_withdrawal_stats(array $stats): void {
NativeUi::stats([
['label' => __('待处理', 'aksrt'), 'value' => number_format_i18n((int) ($stats['pending'] ?? 0)), 'url' => $this->page_url(['status' => Withdrawal::STATUS_PENDING]), 'icon' => 'dashicons-clock', 'tone' => 'warning'],
['label' => __('处理中', 'aksrt'), 'value' => number_format_i18n((int) ($stats['processing'] ?? 0)), 'url' => $this->page_url(['status' => Withdrawal::STATUS_PROCESSING]), 'icon' => 'dashicons-update', 'tone' => 'info'],
['label' => __('冻结中金额', 'aksrt'), 'value' => $this->format_money((string) ($stats['open_gross'] ?? '0.00')), 'hint' => __('待处理 + 处理中的申请毛额', 'aksrt'), 'icon' => 'dashicons-lock', 'tone' => 'warning'],
['label' => __('已打款', 'aksrt'), 'value' => $this->format_money((string) ($stats['paid_net'] ?? '0.00')), 'url' => $this->page_url(['status' => Withdrawal::STATUS_COMPLETED]), 'hint' => sprintf(
['label' => __('待处理', 'jio'), 'value' => number_format_i18n((int) ($stats['pending'] ?? 0)), 'url' => $this->page_url(['status' => Withdrawal::STATUS_PENDING]), 'icon' => 'dashicons-clock', 'tone' => 'warning'],
['label' => __('处理中', 'jio'), 'value' => number_format_i18n((int) ($stats['processing'] ?? 0)), 'url' => $this->page_url(['status' => Withdrawal::STATUS_PROCESSING]), 'icon' => 'dashicons-update', 'tone' => 'info'],
['label' => __('冻结中金额', 'jio'), 'value' => $this->format_money((string) ($stats['open_gross'] ?? '0.00')), 'hint' => __('待处理 + 处理中的申请毛额', 'jio'), 'icon' => 'dashicons-lock', 'tone' => 'warning'],
['label' => __('已打款', 'jio'), 'value' => $this->format_money((string) ($stats['paid_net'] ?? '0.00')), 'url' => $this->page_url(['status' => Withdrawal::STATUS_COMPLETED]), 'hint' => sprintf(
/* translators: 1 completed count 2 fee total */
__('%1$s 笔 · 手续费 %2$s', 'aksrt'),
__('%1$s 笔 · 手续费 %2$s', 'jio'),
number_format_i18n((int) ($stats['completed'] ?? 0)),
$this->format_money((string) ($stats['paid_fee'] ?? '0.00'))
), 'icon' => 'dashicons-yes-alt', 'tone' => 'success'],
['label' => __('已拒绝', 'aksrt'), 'value' => number_format_i18n((int) ($stats['rejected'] ?? 0)), 'url' => $this->page_url(['status' => Withdrawal::STATUS_REJECTED]), 'icon' => 'dashicons-no-alt', 'tone' => 'danger'],
['label' => __('已拒绝', 'jio'), 'value' => number_format_i18n((int) ($stats['rejected'] ?? 0)), 'url' => $this->page_url(['status' => Withdrawal::STATUS_REJECTED]), 'icon' => 'dashicons-no-alt', 'tone' => 'danger'],
]);
}
/** @param array<string, int> $counts */
private function render_status_views(string $current, array $query_args, array $counts): void {
$base = array_diff_key($query_args, ['status' => true]);
$views = ['' => [__('全部', 'aksrt'), array_sum($counts)]];
$views = ['' => [__('全部', 'jio'), array_sum($counts)]];
foreach ($this->status_labels() as $key => $label) {
$views[$key] = [$label, (int) ($counts[$key] ?? 0)];
}
echo '<nav class="aksrt-native-tabs aksrt-wd-views" aria-label="' . esc_attr__('按处理状态查看', 'aksrt') . '">';
echo '<nav class="jio-native-tabs jio-wd-views" aria-label="' . esc_attr__('按处理状态查看', 'jio') . '">';
foreach ($views as $key => [$label, $count]) {
$url = $this->page_url($base + ($key !== '' ? ['status' => $key] : []));
$active = $current === $key;
echo '<a class="aksrt-native-tabs__link' . ($active ? ' is-active' : '') . '" href="' . esc_url($url) . '"' . ($active ? ' aria-current="page"' : '') . '>'
echo '<a class="jio-native-tabs__link' . ($active ? ' is-active' : '') . '" href="' . esc_url($url) . '"' . ($active ? ' aria-current="page"' : '') . '>'
. esc_html($label) . ' <span class="count">' . esc_html(number_format_i18n($count)) . '</span></a>';
}
echo '</nav>';
@@ -259,28 +259,28 @@ trait PayWithdrawalsList {
* @param array<string, string|int> $query_args
*/
private function render_filter_card(array $filters, array $query_args): void {
NativeUi::card_open('', '', false, 'aksrt-wd-filter-card');
echo '<form method="get" class="aksrt-wd-filters" role="search">';
NativeUi::card_open('', '', false, 'jio-wd-filter-card');
echo '<form method="get" class="jio-wd-filters" role="search">';
echo '<input type="hidden" name="page" value="' . esc_attr(self::PAGE_SLUG) . '" />';
if ($filters['status'] !== '') {
echo '<input type="hidden" name="status" value="' . esc_attr($filters['status']) . '" />';
}
echo '<label class="aksrt-wd-filters__search"><span class="screen-reader-text">' . esc_html__('订单号 / 用户 ID', 'aksrt') . '</span>'
. '<input type="search" name="s" value="' . esc_attr($filters['keyword']) . '" placeholder="' . esc_attr__('搜索订单号 / 用户 ID', 'aksrt') . '" /></label>';
echo $this->filter_select('source', __('提现来源', 'aksrt'), ['' => __('全部来源', 'aksrt')] + $this->source_labels(), $filters['source']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $this->filter_select('method', __('收款方式', 'aksrt'), ['' => __('全部收款方式', 'aksrt')] + $this->method_labels(), $filters['method']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<span class="aksrt-wd-filters__range">'
. '<label><span class="screen-reader-text">' . esc_html__('开始日期', 'aksrt') . '</span><input type="date" name="date_from" value="' . esc_attr($filters['date_from']) . '" /></label>'
echo '<label class="jio-wd-filters__search"><span class="screen-reader-text">' . esc_html__('订单号 / 用户 ID', 'jio') . '</span>'
. '<input type="search" name="s" value="' . esc_attr($filters['keyword']) . '" placeholder="' . esc_attr__('搜索订单号 / 用户 ID', 'jio') . '" /></label>';
echo $this->filter_select('source', __('提现来源', 'jio'), ['' => __('全部来源', 'jio')] + $this->source_labels(), $filters['source']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $this->filter_select('method', __('收款方式', 'jio'), ['' => __('全部收款方式', 'jio')] + $this->method_labels(), $filters['method']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<span class="jio-wd-filters__range">'
. '<label><span class="screen-reader-text">' . esc_html__('开始日期', 'jio') . '</span><input type="date" name="date_from" value="' . esc_attr($filters['date_from']) . '" /></label>'
. '<span aria-hidden="true">–</span>'
. '<label><span class="screen-reader-text">' . esc_html__('结束日期', 'aksrt') . '</span><input type="date" name="date_to" value="' . esc_attr($filters['date_to']) . '" /></label>'
. '<label><span class="screen-reader-text">' . esc_html__('结束日期', 'jio') . '</span><input type="date" name="date_to" value="' . esc_attr($filters['date_to']) . '" /></label>'
. '</span>';
echo '<label><span class="screen-reader-text">' . esc_html__('用户 ID', 'aksrt') . '</span><input type="number" min="1" name="user_id" class="aksrt-wd-filters__id" value="'
. esc_attr($filters['user_id'] > 0 ? (string) $filters['user_id'] : '') . '" placeholder="' . esc_attr__('用户 ID', 'aksrt') . '" /></label>';
echo '<span class="aksrt-wd-filters__submit"><button type="submit" class="button button-primary">' . esc_html__('筛选', 'aksrt') . '</button>';
echo '<label><span class="screen-reader-text">' . esc_html__('用户 ID', 'jio') . '</span><input type="number" min="1" name="user_id" class="jio-wd-filters__id" value="'
. esc_attr($filters['user_id'] > 0 ? (string) $filters['user_id'] : '') . '" placeholder="' . esc_attr__('用户 ID', 'jio') . '" /></label>';
echo '<span class="jio-wd-filters__submit"><button type="submit" class="button button-primary">' . esc_html__('筛选', 'jio') . '</button>';
if (array_diff_key($query_args, ['status' => true]) !== []) {
$reset = $this->page_url($filters['status'] !== '' ? ['status' => $filters['status']] : []);
echo '<a class="button" href="' . esc_url($reset) . '">' . esc_html__('重置', 'aksrt') . '</a>';
echo '<a class="button" href="' . esc_url($reset) . '">' . esc_html__('重置', 'jio') . '</a>';
}
echo '</span></form>';
NativeUi::card_close();
@@ -300,9 +300,9 @@ trait PayWithdrawalsList {
* 三种目标状态各自的说明与备注要求用 data-mode 区分。
*/
private function render_process_dialog(): void {
echo '<dialog class="aksrt-wd-dialog" id="aksrt-wd-dialog" aria-labelledby="aksrt-wd-dialog-title">';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-wd-dialog__frame" data-aksrt-wd-form data-busy-label="' . esc_attr__('正在提交…', 'aksrt') . '">';
echo '<input type="hidden" name="action" value="aksrt_pay_update_withdrawal" />';
echo '<dialog class="jio-wd-dialog" id="jio-wd-dialog" aria-labelledby="jio-wd-dialog-title">';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="jio-wd-dialog__frame" data-jio-wd-form data-busy-label="' . esc_attr__('正在提交…', 'jio') . '">';
echo '<input type="hidden" name="action" value="jio_pay_update_withdrawal" />';
echo '<input type="hidden" name="withdrawal_id" value="" data-field="id" />';
echo '<input type="hidden" name="new_status" value="" data-field="status" />';
echo '<input type="hidden" name="idempotency_key" value="" data-field="key" />';
@@ -310,52 +310,52 @@ trait PayWithdrawalsList {
wp_referer_field();
$titles = [
Withdrawal::STATUS_PROCESSING => __('开始处理提现', 'aksrt'),
Withdrawal::STATUS_COMPLETED => __('确认已打款', 'aksrt'),
Withdrawal::STATUS_REJECTED => __('拒绝提现', 'aksrt'),
Withdrawal::STATUS_PROCESSING => __('开始处理提现', 'jio'),
Withdrawal::STATUS_COMPLETED => __('确认已打款', 'jio'),
Withdrawal::STATUS_REJECTED => __('拒绝提现', 'jio'),
];
echo '<header class="aksrt-wd-dialog__head"><h2 id="aksrt-wd-dialog-title">';
echo '<header class="jio-wd-dialog__head"><h2 id="jio-wd-dialog-title">';
foreach ($titles as $mode => $title) {
echo '<span data-mode="' . esc_attr($mode) . '" hidden>' . esc_html($title) . '</span>';
}
echo '</h2><button type="button" class="aksrt-wd-dialog__close" data-aksrt-wd-close aria-label="' . esc_attr__('关闭弹窗', 'aksrt') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></header>';
echo '</h2><button type="button" class="jio-wd-dialog__close" data-jio-wd-close aria-label="' . esc_attr__('关闭弹窗', 'jio') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></header>';
echo '<div class="aksrt-wd-dialog__body"><dl class="aksrt-wd-summary">';
echo '<div class="jio-wd-dialog__body"><dl class="jio-wd-summary">';
foreach ([
'number' => __('订单号', 'aksrt'),
'user' => __('用户', 'aksrt'),
'net' => __('应打款', 'aksrt'),
'gross' => __('申请金额', 'aksrt'),
'source' => __('来源', 'aksrt'),
'payee' => __('收款信息', 'aksrt'),
'number' => __('订单号', 'jio'),
'user' => __('用户', 'jio'),
'net' => __('应打款', 'jio'),
'gross' => __('申请金额', 'jio'),
'source' => __('来源', 'jio'),
'payee' => __('收款信息', 'jio'),
] as $field => $label) {
echo '<div class="aksrt-wd-summary__item aksrt-wd-summary__item--' . esc_attr($field) . '"><dt>' . esc_html($label) . '</dt><dd data-summary="' . esc_attr($field) . '"></dd></div>';
echo '<div class="jio-wd-summary__item jio-wd-summary__item--' . esc_attr($field) . '"><dt>' . esc_html($label) . '</dt><dd data-summary="' . esc_attr($field) . '"></dd></div>';
}
echo '</dl>';
$hints = [
Withdrawal::STATUS_PROCESSING => [__('标记为处理中后,请按上方收款信息线下打款,完成后回到这里确认打款。', 'aksrt'), ''],
Withdrawal::STATUS_COMPLETED => [__('请确认已向用户实际打出「应打款」金额。确认后状态不可更改,用户会收到完成通知。', 'aksrt'), 'is-warning'],
Withdrawal::STATUS_REJECTED => [__('拒绝后,冻结的申请金额会退回用户的原资金池,拒绝原因会通知用户。此操作不可撤销。', 'aksrt'), 'is-danger'],
Withdrawal::STATUS_PROCESSING => [__('标记为处理中后,请按上方收款信息线下打款,完成后回到这里确认打款。', 'jio'), ''],
Withdrawal::STATUS_COMPLETED => [__('请确认已向用户实际打出「应打款」金额。确认后状态不可更改,用户会收到完成通知。', 'jio'), 'is-warning'],
Withdrawal::STATUS_REJECTED => [__('拒绝后,冻结的申请金额会退回用户的原资金池,拒绝原因会通知用户。此操作不可撤销。', 'jio'), 'is-danger'],
];
foreach ($hints as $mode => [$hint, $tone]) {
echo '<p class="aksrt-native-callout ' . esc_attr($tone) . '" data-mode="' . esc_attr($mode) . '" hidden>' . esc_html($hint) . '</p>';
echo '<p class="jio-native-callout ' . esc_attr($tone) . '" data-mode="' . esc_attr($mode) . '" hidden>' . esc_html($hint) . '</p>';
}
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">'
. '<span data-mode="' . esc_attr(Withdrawal::STATUS_REJECTED) . '" hidden>' . esc_html__('拒绝原因(必填)', 'aksrt') . '</span>'
. '<span data-mode="' . esc_attr(Withdrawal::STATUS_PROCESSING) . ' ' . esc_attr(Withdrawal::STATUS_COMPLETED) . '" hidden>' . esc_html__('备注(选填)', 'aksrt') . '</span>'
. '</span><textarea name="note" rows="3" maxlength="1000" data-aksrt-wd-note placeholder="' . esc_attr__('如打款流水号、拒绝原因等,追加记录在申请上', 'aksrt') . '"></textarea></label>';
echo '<label class="jio-native-field"><span class="jio-native-field__label">'
. '<span data-mode="' . esc_attr(Withdrawal::STATUS_REJECTED) . '" hidden>' . esc_html__('拒绝原因(必填)', 'jio') . '</span>'
. '<span data-mode="' . esc_attr(Withdrawal::STATUS_PROCESSING) . ' ' . esc_attr(Withdrawal::STATUS_COMPLETED) . '" hidden>' . esc_html__('备注(选填)', 'jio') . '</span>'
. '</span><textarea name="note" rows="3" maxlength="1000" data-jio-wd-note placeholder="' . esc_attr__('如打款流水号、拒绝原因等,追加记录在申请上', 'jio') . '"></textarea></label>';
echo '</div>';
$submits = [
Withdrawal::STATUS_PROCESSING => [__('开始处理', 'aksrt'), 'button-primary'],
Withdrawal::STATUS_COMPLETED => [__('确认已打款', 'aksrt'), 'button-primary'],
Withdrawal::STATUS_REJECTED => [__('确认拒绝', 'aksrt'), 'button-primary is-danger'],
Withdrawal::STATUS_PROCESSING => [__('开始处理', 'jio'), 'button-primary'],
Withdrawal::STATUS_COMPLETED => [__('确认已打款', 'jio'), 'button-primary'],
Withdrawal::STATUS_REJECTED => [__('确认拒绝', 'jio'), 'button-primary is-danger'],
];
echo '<footer class="aksrt-wd-dialog__foot"><button type="button" class="button" data-aksrt-wd-close>' . esc_html__('取消', 'aksrt') . '</button>';
echo '<footer class="jio-wd-dialog__foot"><button type="button" class="button" data-jio-wd-close>' . esc_html__('取消', 'jio') . '</button>';
foreach ($submits as $mode => [$label, $class]) {
echo '<button type="submit" class="button ' . esc_attr($class) . '" data-mode="' . esc_attr($mode) . '" data-aksrt-wd-submit hidden>' . esc_html($label) . '</button>';
echo '<button type="submit" class="button ' . esc_attr($class) . '" data-mode="' . esc_attr($mode) . '" data-jio-wd-submit hidden>' . esc_html($label) . '</button>';
}
echo '</footer></form></dialog>';
}
+48 -48
View File
@@ -8,10 +8,10 @@
* DomainModel::default_post_meta() or the custom-filter settings — the
* post-list-quick-edit contract test enforces that pairing.
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
@@ -27,40 +27,40 @@ class PostListEditFields {
[
'id' => 'views',
'type' => 'number',
'title' => __('阅读量', 'aksrt'),
'title' => __('阅读量', 'jio'),
],
[
'id' => 'like',
'type' => 'number',
'title' => __('点赞数', 'aksrt'),
'title' => __('点赞数', 'jio'),
],
[
'id' => 'show_layout',
'type' => 'radio',
'title' => __('显示布局', 'aksrt'),
'title' => __('显示布局', 'jio'),
'default' => 'false',
'options' => [
'false' => __('跟随主题', 'aksrt'),
'no_sidebar' => __('无侧边栏', 'aksrt'),
'sidebar_left' => __('侧边栏靠左', 'aksrt'),
'sidebar_right' => __('侧边栏靠右', 'aksrt'),
'false' => __('跟随主题', 'jio'),
'no_sidebar' => __('无侧边栏', 'jio'),
'sidebar_left' => __('侧边栏靠左', 'jio'),
'sidebar_right' => __('侧边栏靠右', 'jio'),
],
],
[
'id' => 'no_article-navs',
'type' => 'checkbox',
'label' => __('不显示目录树', 'aksrt'),
'label' => __('不显示目录树', 'jio'),
],
[
'id' => 'article_maxheight_xz',
'type' => 'checkbox',
'label' => __('限制内容最大高度', 'aksrt'),
'label' => __('限制内容最大高度', 'jio'),
],
];
}
/**
* Pay parameters (saved into the _aksrt_pay meta bag).
* Pay parameters (saved into the _jio_pay meta bag).
*
* @return array<int,array<string,mixed>>
*/
@@ -69,132 +69,132 @@ class PostListEditFields {
[
'id' => 'pay_type',
'type' => 'radio',
'title' => __('付费模式', 'aksrt'),
'options' => \AKSRT\Core\DomainModel::pay_type_labels(),
'title' => __('付费模式', 'jio'),
'options' => \Jio\Core\DomainModel::pay_type_labels(),
],
[
'id' => 'pay_limit',
'type' => 'radio',
'title' => __('购买权限', 'aksrt'),
'title' => __('购买权限', 'jio'),
'options' => [
'0' => __('所有人可购买', 'aksrt'),
'1' => __('VIP1 及以上会员可购买', 'aksrt'),
'2' => __('仅 VIP2 可购买', 'aksrt'),
'0' => __('所有人可购买', 'jio'),
'1' => __('VIP1 及以上会员可购买', 'jio'),
'2' => __('仅 VIP2 可购买', 'jio'),
],
],
[
'id' => 'pay_modo',
'type' => 'radio',
'title' => __('支付类型', 'aksrt'),
'title' => __('支付类型', 'jio'),
'options' => [
'0' => __('普通商品(金钱购买)', 'aksrt'),
'points' => __('积分商品(积分购买,依赖于用户积分功能)', 'aksrt'),
'0' => __('普通商品(金钱购买)', 'jio'),
'points' => __('积分商品(积分购买,依赖于用户积分功能)', 'jio'),
],
],
[
'dependency' => [['pay_type', '!=', 'no'], ['pay_modo', '==', 'points']],
'id' => 'points_price',
'title' => __('积分售价', 'aksrt'),
'title' => __('积分售价', 'jio'),
'type' => 'number',
'unit' => __('积分', 'aksrt'),
'unit' => __('积分', 'jio'),
],
[
'dependency' => [['pay_type', '!=', 'no'], ['pay_modo', '==', 'points']],
'title' => __('VIP1 积分售价', 'aksrt'),
'title' => __('VIP1 积分售价', 'jio'),
'id' => 'vip1_points',
'class' => 'compact',
'subtitle' => __('填 0 则为 VIP1 免费', 'aksrt'),
'subtitle' => __('填 0 则为 VIP1 免费', 'jio'),
'type' => 'number',
'unit' => __('积分', 'aksrt'),
'unit' => __('积分', 'jio'),
],
[
'dependency' => [['pay_type', '!=', 'no'], ['pay_modo', '==', 'points']],
'title' => __('VIP2 积分售价', 'aksrt'),
'title' => __('VIP2 积分售价', 'jio'),
'id' => 'vip2_points',
'class' => 'compact',
'subtitle' => __('填 0 则为 VIP2 免费', 'aksrt'),
'desc' => __('会员价格不能高于售价', 'aksrt'),
'subtitle' => __('填 0 则为 VIP2 免费', 'jio'),
'desc' => __('会员价格不能高于售价', 'jio'),
'type' => 'number',
'unit' => __('积分', 'aksrt'),
'unit' => __('积分', 'jio'),
],
[
'dependency' => [['pay_type', '!=', 'no'], ['pay_modo', '!=', 'points']],
'id' => 'price',
'title' => __('执行价', 'aksrt'),
'title' => __('执行价', 'jio'),
'type' => 'number',
'unit' => $currency,
],
[
'dependency' => [['pay_type', '!=', 'no'], ['pay_modo', '!=', 'points']],
'id' => 'original_price',
'title' => __('原价', 'aksrt'),
'title' => __('原价', 'jio'),
'class' => 'compact',
'subtitle' => __('显示在执行价格前面,并划掉', 'aksrt'),
'subtitle' => __('显示在执行价格前面,并划掉', 'jio'),
'type' => 'number',
'unit' => $currency,
],
[
'dependency' => [['pay_type', '!=', 'no'], ['pay_modo', '!=', 'points']],
'title' => __('VIP1 价格', 'aksrt'),
'title' => __('VIP1 价格', 'jio'),
'id' => 'vip1_price',
'class' => 'compact',
'subtitle' => __('填 0 则为 VIP1 免费', 'aksrt'),
'subtitle' => __('填 0 则为 VIP1 免费', 'jio'),
'type' => 'number',
'unit' => $currency,
],
[
'dependency' => [['pay_type', '!=', 'no'], ['pay_modo', '!=', 'points']],
'title' => __('VIP2 价格', 'aksrt'),
'title' => __('VIP2 价格', 'jio'),
'id' => 'vip2_price',
'class' => 'compact',
'subtitle' => __('填 0 则为 VIP2 免费', 'aksrt'),
'desc' => __('会员价格不能高于执行价', 'aksrt'),
'subtitle' => __('填 0 则为 VIP2 免费', 'jio'),
'desc' => __('会员价格不能高于执行价', 'jio'),
'type' => 'number',
'unit' => $currency,
],
[
'dependency' => [['pay_type', '!=', 'no']],
'id' => 'pay_count',
'title' => __('销量浮动', 'aksrt'),
'subtitle' => __('为真实销量增加或减少的数量', 'aksrt'),
'title' => __('销量浮动', 'jio'),
'subtitle' => __('为真实销量增加或减少的数量', 'jio'),
'type' => 'number',
],
[
'dependency' => [['pay_type', '!=', 'no']],
'id' => 'show_sales',
'title' => __('显示销量', 'aksrt'),
'title' => __('显示销量', 'jio'),
'default' => true,
'type' => 'switcher',
],
[
'dependency' => [['pay_type', '!=', 'no']],
'id' => 'coupon_s',
'title' => __('允许使用优惠码', 'aksrt'),
'title' => __('允许使用优惠码', 'jio'),
'default' => false,
'type' => 'switcher',
],
[
'dependency' => [['pay_type', '!=', 'no']],
'id' => 'order_expired_s',
'title' => __('订单时效', 'aksrt'),
'title' => __('订单时效', 'jio'),
'default' => false,
'type' => 'switcher',
],
[
'id' => 'order_expired_time',
'title' => __('订单时效有效期数值', 'aksrt'),
'title' => __('订单时效有效期数值', 'jio'),
'default' => 0,
'type' => 'number',
],
[
'title' => __('订单时效有效期单位', 'aksrt'),
'title' => __('订单时效有效期单位', 'jio'),
'type' => 'select',
'class' => 'compact',
'id' => 'order_expired_unit',
'options' => [
'day' => __('天', 'aksrt'),
'hour' => __('小时', 'aksrt'),
'minute' => __('分钟', 'aksrt'),
'day' => __('天', 'jio'),
'hour' => __('小时', 'jio'),
'minute' => __('分钟', 'jio'),
],
'default' => 'day',
],
+93 -93
View File
@@ -9,17 +9,17 @@
* those boxes onto its topics taxonomy column; this project has no topics
* taxonomy, so they hang off the interaction-data column instead.
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
class PostListEnhance {
public const COLUMN_STATS = 'aksrt_stats';
public const COLUMN_SALES = 'aksrt_sales';
public const COLUMN_STATS = 'jio_stats';
public const COLUMN_SALES = 'jio_sales';
private static $instance = null;
@@ -61,17 +61,17 @@ class PostListEnhance {
return;
}
if (function_exists('aksrt_enqueue_fontawesome')) {
aksrt_enqueue_fontawesome();
if (function_exists('jio_enqueue_fontawesome')) {
jio_enqueue_fontawesome();
}
$css = AKSRT_DIR . '/assets/css/admin-main.css';
$css = JIO_DIR . '/assets/css/admin-main.css';
if (is_file($css)) {
wp_enqueue_style('aksrt-admin-main', AKSRT_URI . '/assets/css/admin-main.css', ['aksrt-font-awesome'], (string) filemtime($css));
wp_enqueue_style('jio-admin-main', JIO_URI . '/assets/css/admin-main.css', ['jio-font-awesome'], (string) filemtime($css));
}
$js = AKSRT_DIR . '/assets/js/admin-post-list.js';
$js = JIO_DIR . '/assets/js/admin-post-list.js';
if (is_file($js)) {
wp_enqueue_script('aksrt-post-list', AKSRT_URI . '/assets/js/admin-post-list.js', [], (string) filemtime($js), true);
wp_enqueue_script('jio-post-list', JIO_URI . '/assets/js/admin-post-list.js', [], (string) filemtime($js), true);
}
}
@@ -102,8 +102,8 @@ class PostListEnhance {
if ($key !== 'title') {
continue;
}
$new_columns[self::COLUMN_STATS] = __('互动数据', 'aksrt');
$new_columns[self::COLUMN_SALES] = '<a href="' . esc_url(add_query_arg(['orderby' => 'sales_volume', 'order' => $order])) . '"><span>' . esc_html__('销售数据', 'aksrt') . ($order_by === 'sales_volume' ? $o_icon : '') . '</span></a>';
$new_columns[self::COLUMN_STATS] = __('互动数据', 'jio');
$new_columns[self::COLUMN_SALES] = '<a href="' . esc_url(add_query_arg(['orderby' => 'sales_volume', 'order' => $order])) . '"><span>' . esc_html__('销售数据', 'jio') . ($order_by === 'sales_volume' ? $o_icon : '') . '</span></a>';
}
return $new_columns;
@@ -124,13 +124,13 @@ class PostListEnhance {
}
private function render_stats_column(int $post_id): void {
$views = function_exists('aksrt_get_post_views') ? (int) aksrt_get_post_views($post_id) : (int) get_post_meta($post_id, \AKSRT\Core\DomainModel::POST_VIEW_COUNT_KEY, true);
$likes = function_exists('aksrt_get_post_likes') ? (int) aksrt_get_post_likes($post_id) : (int) get_post_meta($post_id, '_aksrt_like_count', true);
$favorites = function_exists('aksrt_get_post_favorites') ? (int) aksrt_get_post_favorites($post_id) : (int) get_post_meta($post_id, '_aksrt_favorite_count', true);
$views = function_exists('jio_get_post_views') ? (int) jio_get_post_views($post_id) : (int) get_post_meta($post_id, \Jio\Core\DomainModel::POST_VIEW_COUNT_KEY, true);
$likes = function_exists('jio_get_post_likes') ? (int) jio_get_post_likes($post_id) : (int) get_post_meta($post_id, '_jio_like_count', true);
$favorites = function_exists('jio_get_post_favorites') ? (int) jio_get_post_favorites($post_id) : (int) get_post_meta($post_id, '_jio_favorite_count', true);
$format = static function (int $n): string {
if (function_exists('aksrt_format_number')) {
return (string) aksrt_format_number($n);
if (function_exists('jio_format_number')) {
return (string) jio_format_number($n);
}
if ($n >= 10000) {
return round($n / 10000, 1) . 'W+';
@@ -142,45 +142,45 @@ class PostListEnhance {
};
$parts = [];
$parts[] = '<span title="' . esc_attr__('浏览', 'aksrt') . '" style="margin-right:4px;"><i class="fa fas fa-eye" style="color:#8a909c;font-size:12px;"></i> ' . esc_html($format($views)) . '</span>';
$parts[] = '<span title="' . esc_attr__('点赞', 'aksrt') . '" style="margin-right:4px;"><i class="fa fas fa-thumbs-up" style="color:#1890ff;font-size:12px;"></i> ' . esc_html($format($likes)) . '</span>';
$parts[] = '<span title="' . esc_attr__('收藏', 'aksrt') . '" style="margin-right:4px;"><i class="fa fas fa-star" style="color:#f39c12;font-size:12px;"></i> ' . esc_html($format($favorites)) . '</span>';
$parts[] = '<span title="' . esc_attr__('浏览', 'jio') . '" style="margin-right:4px;"><i class="fa fas fa-eye" style="color:#8a909c;font-size:12px;"></i> ' . esc_html($format($views)) . '</span>';
$parts[] = '<span title="' . esc_attr__('点赞', 'jio') . '" style="margin-right:4px;"><i class="fa fas fa-thumbs-up" style="color:#1890ff;font-size:12px;"></i> ' . esc_html($format($likes)) . '</span>';
$parts[] = '<span title="' . esc_attr__('收藏', 'jio') . '" style="margin-right:4px;"><i class="fa fas fa-star" style="color:#f39c12;font-size:12px;"></i> ' . esc_html($format($favorites)) . '</span>';
echo implode(' &middot; ', $parts);
}
private function render_sales_column(int $post_id): void {
$pay_meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($post_id) : [];
$pay_meta = function_exists('jio_get_pay_meta') ? jio_get_pay_meta($post_id) : [];
$pay_type = (string) ($pay_meta['pay_type'] ?? '');
if ($pay_type === '' || $pay_type === \AKSRT\Core\DomainModel::PAY_TYPE_NO) {
echo '<div style="font-size: 12px;">' . esc_html__('未开启付费', 'aksrt') . '</div>';
if ($pay_type === '' || $pay_type === \Jio\Core\DomainModel::PAY_TYPE_NO) {
echo '<div style="font-size: 12px;">' . esc_html__('未开启付费', 'jio') . '</div>';
return;
}
$pay_modo = (string) ($pay_meta['pay_modo'] ?? '0');
$type_labels = \AKSRT\Core\DomainModel::pay_type_labels();
$type_labels = \Jio\Core\DomainModel::pay_type_labels();
$type_label = $type_labels[$pay_type] ?? $pay_type;
$type_color = $this->pay_type_color($pay_type);
if ($pay_modo === 'points') {
$price_text = sprintf(__('积分:%s', 'aksrt'), (string) max(0, (int) ($pay_meta['points_price'] ?? 0)));
$price_text = sprintf(__('积分:%s', 'jio'), (string) max(0, (int) ($pay_meta['points_price'] ?? 0)));
} else {
$price_text = $this->currency_symbol() . ':' . (string) ($pay_meta['price'] ?? '0.00');
}
// 列表列与前台同一口径:真实订单 + 后台「销量浮动」(pay_count)。
// 真实销量取订单表统计,_aksrt_sales_volume 只是订单流水累加的副本。
// 真实销量取订单表统计,_jio_sales_volume 只是订单流水累加的副本。
$real_sales = $this->real_sales_count($post_id);
$meta_sales = max(0, (int) get_post_meta($post_id, \AKSRT\Core\DomainModel::RESOURCE_SALES_VOLUME_KEY, true));
$meta_sales = max(0, (int) get_post_meta($post_id, \Jio\Core\DomainModel::RESOURCE_SALES_VOLUME_KEY, true));
$sales_volume = max($real_sales, $meta_sales) + max(0, (int) ($pay_meta['pay_count'] ?? 0));
$real_html = $real_sales > 0
? ' <a title="' . esc_attr__('点击查看销售明细', 'aksrt') . '" href="' . esc_url($this->orders_url($post_id)) . '">[' . esc_html(sprintf(__('真实销量%s', 'aksrt'), $real_sales)) . ']</a>'
? ' <a title="' . esc_attr__('点击查看销售明细', 'jio') . '" href="' . esc_url($this->orders_url($post_id)) . '">[' . esc_html(sprintf(__('真实销量%s', 'jio'), $real_sales)) . ']</a>'
: '';
$link = add_query_arg(['aksrt_pay_type' => $pay_type]);
$link = add_query_arg(['jio_pay_type' => $pay_type]);
echo '<div>';
echo '<a style="color: ' . esc_attr($type_color) . ';" href="' . esc_url($link) . '">' . esc_html($type_label) . '</a>';
echo '<div style="font-size: 12px;">' . esc_html($price_text) . ' · ' . esc_html__('销量', 'aksrt') . esc_html((string) $sales_volume) . wp_kses_post($real_html) . '</div>';
echo '<div style="font-size: 12px;">' . esc_html($price_text) . ' · ' . esc_html__('销量', 'jio') . esc_html((string) $sales_volume) . wp_kses_post($real_html) . '</div>';
echo '</div>';
}
@@ -189,11 +189,11 @@ class PostListEnhance {
*/
private function pay_type_color(string $pay_type): string {
$palette = [
\AKSRT\Core\DomainModel::PAY_TYPE_READING => '#ff4747',
\AKSRT\Core\DomainModel::PAY_TYPE_DOWNLOAD => '#ee5307',
\AKSRT\Core\DomainModel::PAY_TYPE_GALLERY => '#1e8608',
\AKSRT\Core\DomainModel::PAY_TYPE_VIDEO => '#086ae8',
\AKSRT\Core\DomainModel::PAY_TYPE_VIP => '#3353fd',
\Jio\Core\DomainModel::PAY_TYPE_READING => '#ff4747',
\Jio\Core\DomainModel::PAY_TYPE_DOWNLOAD => '#ee5307',
\Jio\Core\DomainModel::PAY_TYPE_GALLERY => '#1e8608',
\Jio\Core\DomainModel::PAY_TYPE_VIDEO => '#086ae8',
\Jio\Core\DomainModel::PAY_TYPE_VIP => '#3353fd',
];
return $palette[$pay_type] ?? '#464646';
}
@@ -207,12 +207,12 @@ class PostListEnhance {
return;
}
$current = isset($_GET['aksrt_pay_type']) ? sanitize_key((string) wp_unslash($_GET['aksrt_pay_type'])) : '';
$labels = \AKSRT\Core\DomainModel::pay_type_labels();
unset($labels[\AKSRT\Core\DomainModel::PAY_TYPE_NO]);
$current = isset($_GET['jio_pay_type']) ? sanitize_key((string) wp_unslash($_GET['jio_pay_type'])) : '';
$labels = \Jio\Core\DomainModel::pay_type_labels();
unset($labels[\Jio\Core\DomainModel::PAY_TYPE_NO]);
echo '<select name="aksrt_pay_type">';
echo '<option value="">' . esc_html__('商品类型', 'aksrt') . '</option>';
echo '<select name="jio_pay_type">';
echo '<option value="">' . esc_html__('商品类型', 'jio') . '</option>';
foreach ($labels as $key => $label) {
printf('<option %s value="%s">%s</option>', selected($current, $key, false), esc_attr($key), esc_html($label));
}
@@ -232,25 +232,25 @@ class PostListEnhance {
$orderby = isset($_GET['orderby']) ? sanitize_key((string) wp_unslash($_GET['orderby'])) : '';
if ($orderby === 'sales_volume') {
$query->set('meta_key', \AKSRT\Core\DomainModel::RESOURCE_SALES_VOLUME_KEY);
$query->set('meta_key', \Jio\Core\DomainModel::RESOURCE_SALES_VOLUME_KEY);
$query->set('orderby', 'meta_value_num');
} elseif ($orderby === 'views' || $orderby === 'like' || $orderby === 'favorite') {
$query->set('meta_key', $this->stats_meta_key($orderby));
$query->set('orderby', 'meta_value_num');
}
$pay_type = isset($_GET['aksrt_pay_type']) ? sanitize_key((string) wp_unslash($_GET['aksrt_pay_type'])) : '';
if ($pay_type !== '' && isset(\AKSRT\Core\DomainModel::pay_type_labels()[$pay_type])) {
$pay_type = isset($_GET['jio_pay_type']) ? sanitize_key((string) wp_unslash($_GET['jio_pay_type'])) : '';
if ($pay_type !== '' && isset(\Jio\Core\DomainModel::pay_type_labels()[$pay_type])) {
$meta_query = $query->get('meta_query');
$meta_query = is_array($meta_query) ? $meta_query : [];
$meta_query[] = [
'relation' => 'OR',
[
'key' => \AKSRT\Core\DomainModel::PAY_TYPE_KEY,
'key' => \Jio\Core\DomainModel::PAY_TYPE_KEY,
'value' => $pay_type,
],
[
'key' => \AKSRT\Core\DomainModel::PAY_META_KEY,
'key' => \Jio\Core\DomainModel::PAY_META_KEY,
'value' => '"pay_type";s:' . strlen($pay_type) . ':"' . $pay_type . '"',
'compare' => 'LIKE',
],
@@ -264,11 +264,11 @@ class PostListEnhance {
*/
private function stats_meta_key(string $orderby): string {
$map = [
'views' => \AKSRT\Core\DomainModel::POST_VIEW_COUNT_KEY,
'like' => '_aksrt_like_count',
'favorite' => '_aksrt_favorite_count',
'views' => \Jio\Core\DomainModel::POST_VIEW_COUNT_KEY,
'like' => '_jio_like_count',
'favorite' => '_jio_favorite_count',
];
return $map[$orderby] ?? \AKSRT\Core\DomainModel::POST_VIEW_COUNT_KEY;
return $map[$orderby] ?? \Jio\Core\DomainModel::POST_VIEW_COUNT_KEY;
}
// ============================================================
@@ -291,7 +291,7 @@ class PostListEnhance {
private function edit_boxes_html(): string {
$html = $this->fields_html(PostListEditFields::extended(), 'extended');
$html .= $this->fields_html(PostListEditFields::pay($this->currency_symbol()), 'pay', __('付费参数', 'aksrt'), '<div class="c-yellow">' . esc_html__('批量设置付费参数,请仔细检查配置内容,更新后无法撤回。请注意配置逻辑,例如会员价不能高于普通价,执行价不能高于原价等', 'aksrt') . '</div>');
$html .= $this->fields_html(PostListEditFields::pay($this->currency_symbol()), 'pay', __('付费参数', 'jio'), '<div class="c-yellow">' . esc_html__('批量设置付费参数,请仔细检查配置内容,更新后无法撤回。请注意配置逻辑,例如会员价不能高于普通价,执行价不能高于原价等', 'jio') . '</div>');
$html .= $this->filter_fields_html();
return $html;
}
@@ -302,8 +302,8 @@ class PostListEnhance {
*/
private function filter_fields_html(): string {
$dimensions = [];
if (class_exists('\AKSRT\Settings\Settings')) {
$settings = \AKSRT\Settings\Settings::get_post_filter_settings();
if (class_exists('\Jio\Settings\Settings')) {
$settings = \Jio\Settings\Settings::get_post_filter_settings();
$dimensions = is_array($settings['custom_filter'] ?? null) ? $settings['custom_filter'] : [];
}
if (!$dimensions) {
@@ -332,7 +332,7 @@ class PostListEnhance {
'title' => (string) ($dimension['name'] ?? $key),
'type' => 'checkbox',
'options' => $options,
'placeholder' => sprintf(__('请选择%s', 'aksrt'), (string) ($dimension['name'] ?? $key)),
'placeholder' => sprintf(__('请选择%s', 'jio'), (string) ($dimension['name'] ?? $key)),
];
}
@@ -340,7 +340,7 @@ class PostListEnhance {
return '';
}
return $this->fields_html($fields, 'filter', __('高级筛选', 'aksrt'), '<div class="px12 aksrt-bulk-edit-desc">' . esc_html__('让文章实现更加精细化的分类,同时方便用户进行文章筛选', 'aksrt') . '</div>');
return $this->fields_html($fields, 'filter', __('高级筛选', 'jio'), '<div class="px12 jio-bulk-edit-desc">' . esc_html__('让文章实现更加精细化的分类,同时方便用户进行文章筛选', 'jio') . '</div>');
}
/**
@@ -357,15 +357,15 @@ class PostListEnhance {
$options_html = ' ';
switch ($field['type']) {
case 'number':
$name = 'aksrt_bulk_edit[' . $id . '][' . $field['id'] . ']';
$name = 'jio_bulk_edit[' . $id . '][' . $field['id'] . ']';
$options_html = '<div class="flex ac hh">'
. '<select name="' . esc_attr($name) . '[operation]">'
. '<option value="ignore" selected="selected">' . esc_html__('不更改', 'aksrt') . '</option>'
. '<option value="set">' . esc_html__('设置为', 'aksrt') . '</option>'
. '<option value="plus">' . esc_html__('加', 'aksrt') . '</option>'
. '<option value="subtract">' . esc_html__('减', 'aksrt') . '</option>'
. '<option value="multiply">' . esc_html__('乘', 'aksrt') . '</option>'
. '<option value="division">' . esc_html__('除', 'aksrt') . '</option>'
. '<option value="ignore" selected="selected">' . esc_html__('不更改', 'jio') . '</option>'
. '<option value="set">' . esc_html__('设置为', 'jio') . '</option>'
. '<option value="plus">' . esc_html__('加', 'jio') . '</option>'
. '<option value="subtract">' . esc_html__('减', 'jio') . '</option>'
. '<option value="multiply">' . esc_html__('乘', 'jio') . '</option>'
. '<option value="division">' . esc_html__('除', 'jio') . '</option>'
. '</select>'
. '<input type="text" name="' . esc_attr($name) . '[val]" value="">'
. '</div>';
@@ -374,32 +374,32 @@ class PostListEnhance {
case 'checkbox':
$select_options = !empty($field['options']) ? $field['options'] : [];
if ($select_options) {
$name = 'aksrt_bulk_edit[' . $id . '][' . $field['id'] . ']';
$name = 'jio_bulk_edit[' . $id . '][' . $field['id'] . ']';
$options_html = $this->checkbox_options_html($name, $select_options);
break;
}
$field['options'] = [
'ignore' => __('不更改', 'aksrt'),
'1' => __('开启', 'aksrt'),
'0' => __('关闭', 'aksrt'),
'ignore' => __('不更改', 'jio'),
'1' => __('开启', 'jio'),
'0' => __('关闭', 'jio'),
];
// Falls through to the switcher renderer below.
case 'switcher':
$field['options'] = [
'ignore' => __('不更改', 'aksrt'),
'1' => __('开启', 'aksrt'),
'0' => __('关闭', 'aksrt'),
'ignore' => __('不更改', 'jio'),
'1' => __('开启', 'jio'),
'0' => __('关闭', 'jio'),
];
// Falls through to the select renderer below.
case 'radio':
case 'select':
$options = isset($field['options']) && is_array($field['options']) ? $field['options'] : [];
$name = 'aksrt_bulk_edit[' . $id . '][' . $field['id'] . ']';
$name = 'jio_bulk_edit[' . $id . '][' . $field['id'] . ']';
$options_html = '<div class="flex ac hh">'
. '<select name="' . esc_attr($name) . '">'
. '<option selected="selected" value="ignore">--' . esc_html__('不更改', 'aksrt') . '--</option>';
. '<option selected="selected" value="ignore">--' . esc_html__('不更改', 'jio') . '--</option>';
foreach ($options as $key => $value) {
$options_html .= '<option value="' . esc_attr((string) $key) . '">' . esc_html((string) $value) . '</option>';
}
@@ -412,11 +412,11 @@ class PostListEnhance {
}
$title_html = !empty($field['title']) ? (string) $field['title'] : (!empty($field['label']) ? (string) $field['label'] : '');
$desc_html = !empty($field['desc']) ? '<div class="px12 aksrt-bulk-edit-desc">' . esc_html((string) $field['desc']) . '</div>' : '';
$subtitle = !empty($field['subtitle']) ? '<div class="px12 aksrt-bulk-edit-desc">' . esc_html((string) $field['subtitle']) . '</div>' : '';
$desc_html = !empty($field['desc']) ? '<div class="px12 jio-bulk-edit-desc">' . esc_html((string) $field['desc']) . '</div>' : '';
$subtitle = !empty($field['subtitle']) ? '<div class="px12 jio-bulk-edit-desc">' . esc_html((string) $field['subtitle']) . '</div>' : '';
$dependency = !empty($field['dependency']) ? ' data-dependency="' . esc_attr(wp_json_encode($field['dependency'])) . '"' : '';
$item_html .= '<div class="aksrt-bulk-edit-box"' . $dependency . '>'
$item_html .= '<div class="jio-bulk-edit-box"' . $dependency . '>'
. '<div>' . esc_html($title_html) . '</div>'
. $options_html . $subtitle . $desc_html
. '</div>';
@@ -438,8 +438,8 @@ class PostListEnhance {
*/
private function checkbox_options_html(string $name, array $options): string {
$html = '<select class="mr10" name="' . esc_attr($name) . '[operation]">'
. '<option value="ignore" selected="selected">' . esc_html__('不更改', 'aksrt') . '</option>'
. '<option value="set">' . esc_html__('设置为', 'aksrt') . '</option>'
. '<option value="ignore" selected="selected">' . esc_html__('不更改', 'jio') . '</option>'
. '<option value="set">' . esc_html__('设置为', 'jio') . '</option>'
. '</select>';
foreach ($options as $key => $value) {
@@ -476,7 +476,7 @@ class PostListEnhance {
if ($screen !== 'edit-post') {
return;
}
$submitted = isset($_POST['aksrt_bulk_edit']) && is_array($_POST['aksrt_bulk_edit']) ? wp_unslash($_POST['aksrt_bulk_edit']) : [];
$submitted = isset($_POST['jio_bulk_edit']) && is_array($_POST['jio_bulk_edit']) ? wp_unslash($_POST['jio_bulk_edit']) : [];
if (!$submitted) {
return;
}
@@ -538,9 +538,9 @@ class PostListEnhance {
* @param array<string,mixed> $fields
*/
private function save_pay(int $post_id, array $fields): void {
$pay_meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($post_id) : [];
$pay_meta = function_exists('jio_get_pay_meta') ? jio_get_pay_meta($post_id) : [];
if (!is_array($pay_meta) || $pay_meta === []) {
$pay_meta = \AKSRT\Core\DomainModel::default_pay_meta();
$pay_meta = \Jio\Core\DomainModel::default_pay_meta();
}
foreach ($fields as $field_id => $field_value) {
@@ -600,19 +600,19 @@ class PostListEnhance {
}
$current = (string) ($pay_meta[$field_id] ?? '0.00');
try {
$current_minor = (float) \AKSRT\Ledger\AssetAmount::balance_minor($current);
$current_minor = (float) \Jio\Ledger\AssetAmount::balance_minor($current);
} catch (\InvalidArgumentException $e) {
$current_minor = 0.0;
}
$new = $this->apply_operation($current_minor, (float) $field_value['val'], $operation);
if ($new !== null) {
$pay_meta[$field_id] = \AKSRT\Ledger\AssetAmount::decimal_from_minor(max(0, (int) round($new)));
$pay_meta[$field_id] = \Jio\Ledger\AssetAmount::decimal_from_minor(max(0, (int) round($new)));
}
break;
}
}
\AKSRT\Core\DomainModel::persist_pay_meta($post_id, $pay_meta);
\Jio\Core\DomainModel::persist_pay_meta($post_id, $pay_meta);
}
/**
@@ -620,8 +620,8 @@ class PostListEnhance {
*/
private function save_filters(int $post_id, array $fields): void {
$dimensions = [];
if (class_exists('\AKSRT\Settings\Settings')) {
$settings = \AKSRT\Settings\Settings::get_post_filter_settings();
if (class_exists('\Jio\Settings\Settings')) {
$settings = \Jio\Settings\Settings::get_post_filter_settings();
$dimensions = is_array($settings['custom_filter'] ?? null) ? $settings['custom_filter'] : [];
}
if (!$dimensions) {
@@ -652,9 +652,9 @@ class PostListEnhance {
: [];
$values = array_values(array_intersect($allowed_dimensions[$field_id], $values));
if ($values) {
update_post_meta($post_id, '_aksrt_cf_' . $field_id, $values);
update_post_meta($post_id, '_jio_cf_' . $field_id, $values);
} else {
delete_post_meta($post_id, '_aksrt_cf_' . $field_id);
delete_post_meta($post_id, '_jio_cf_' . $field_id);
}
}
}
@@ -664,7 +664,7 @@ class PostListEnhance {
* meta box.
*/
private function update_post_meta_bag(int $post_id, string $key, string $value): void {
$bag_key = \AKSRT\Core\PostMetaBox::META_KEY;
$bag_key = \Jio\Core\PostMetaBox::META_KEY;
$bag = get_post_meta($post_id, $bag_key, true);
$bag = is_array($bag) ? $bag : [];
$bag[$key] = $value;
@@ -698,8 +698,8 @@ class PostListEnhance {
* Configured cash symbol, shared by the sales column and the pay fields.
*/
private function currency_symbol(): string {
if (class_exists('\AKSRT\Settings\Settings')) {
$paid = \AKSRT\Settings\Settings::get_paid_content_settings();
if (class_exists('\Jio\Settings\Settings')) {
$paid = \Jio\Settings\Settings::get_paid_content_settings();
return (string) ($paid['display']['currency_symbol'] ?? '¥');
}
return '¥';
@@ -732,7 +732,7 @@ class PostListEnhance {
if (!$wp_query instanceof \WP_Query || empty($wp_query->posts)) {
return [];
}
if (!class_exists('\AKSRT\Payment\OrderRepository')) {
if (!class_exists('\Jio\Payment\OrderRepository')) {
return [];
}
@@ -746,9 +746,9 @@ class PostListEnhance {
return [];
}
$map = \AKSRT\Payment\OrderRepository::get_instance()->get_content_sales_map(
$map = \Jio\Payment\OrderRepository::get_instance()->get_content_sales_map(
$ids,
\AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE
\Jio\Core\DomainModel::ORDER_PRODUCT_RESOURCE
);
$out = [];
foreach ($map as $pid => $row) {
@@ -760,8 +760,8 @@ class PostListEnhance {
private function orders_url(int $post_id): string {
return add_query_arg(
[
'page' => 'aksrt_orders',
'product_type' => \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE,
'page' => 'jio_orders',
'product_type' => \Jio\Core\DomainModel::ORDER_PRODUCT_RESOURCE,
'product_id' => $post_id,
],
admin_url('admin.php')
+74 -74
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
@@ -35,13 +35,13 @@ class UsersMenus {
NativeUi::boot();
add_action('admin_menu', [$this, 'register_menus']);
add_action('admin_notices', [$this, 'pending_notices']);
add_action('admin_post_aksrt_users_send_message', [$this, 'handle_send_message']);
add_action('admin_post_aksrt_users_bulk_messages', [$this, 'handle_bulk_messages']);
add_action('admin_post_aksrt_users_review_verify', [$this, 'handle_review_verify']);
add_action('admin_post_aksrt_users_delete_verify', [$this, 'handle_delete_verify']);
add_action('admin_post_aksrt_users_update_report', [$this, 'handle_update_report']);
add_action('admin_post_aksrt_users_process_appeal', [$this, 'handle_process_appeal']);
add_action('admin_post_aksrt_users_mutate_vip', [$this, 'handle_mutate_vip']);
add_action('admin_post_jio_users_send_message', [$this, 'handle_send_message']);
add_action('admin_post_jio_users_bulk_messages', [$this, 'handle_bulk_messages']);
add_action('admin_post_jio_users_review_verify', [$this, 'handle_review_verify']);
add_action('admin_post_jio_users_delete_verify', [$this, 'handle_delete_verify']);
add_action('admin_post_jio_users_update_report', [$this, 'handle_update_report']);
add_action('admin_post_jio_users_process_appeal', [$this, 'handle_process_appeal']);
add_action('admin_post_jio_users_mutate_vip', [$this, 'handle_mutate_vip']);
add_action('admin_enqueue_scripts', [$this, 'enqueue_ticket_assets']);
add_action('admin_enqueue_scripts', [$this, 'enqueue_message_assets']);
add_action('admin_enqueue_scripts', [$this, 'enqueue_invite_assets']);
@@ -53,10 +53,10 @@ class UsersMenus {
public function register_menus(): void {
add_submenu_page(
'users.php',
__('邀请码管理', 'aksrt'),
__('邀请码管理', 'aksrt'),
__('邀请码管理', 'jio'),
__('邀请码管理', 'jio'),
'edit_users',
'aksrt_invite_codes',
'jio_invite_codes',
[$this, 'render_invite_codes_page']
);
@@ -66,42 +66,42 @@ class UsersMenus {
add_submenu_page(
'users.php',
__('消息管理', 'aksrt'),
__('消息管理', 'aksrt'),
__('消息管理', 'jio'),
__('消息管理', 'jio'),
self::CAPABILITY,
'aksrt_messages',
'jio_messages',
[$this, 'render_messages_page']
);
add_submenu_page(
'users.php',
__('用户会员', 'aksrt'),
__('用户会员', 'aksrt'),
__('用户会员', 'jio'),
__('用户会员', 'jio'),
self::CAPABILITY,
'aksrt_user_vip',
'jio_user_vip',
[$this, 'render_vip_page']
);
add_submenu_page(
'users.php',
__('身份认证', 'aksrt'),
__('身份认证', 'aksrt'),
__('身份认证', 'jio'),
__('身份认证', 'jio'),
self::CAPABILITY,
'aksrt_user_auth',
'jio_user_auth',
[$this, 'render_verify_page']
);
add_submenu_page(
'users.php',
__('处理举报及禁封申诉', 'aksrt'),
__('举报&禁封申诉', 'aksrt'),
__('处理举报及禁封申诉', 'jio'),
__('举报&禁封申诉', 'jio'),
self::CAPABILITY,
'aksrt_user_ban',
'jio_user_ban',
[$this, 'render_ban_page']
);
add_submenu_page(
'users.php',
__('工单管理', 'aksrt'),
__('工单管理', 'aksrt'),
__('工单管理', 'jio'),
__('工单管理', 'jio'),
self::CAPABILITY,
'aksrt_tickets',
'jio_tickets',
[$this, 'render_tickets_page']
);
}
@@ -112,115 +112,115 @@ class UsersMenus {
}
$page = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : '';
if ($page !== 'aksrt_user_auth') {
if ($page !== 'jio_user_auth') {
$pending_verify = $this->count_verify_status('pending');
if ($pending_verify > 0) {
$url = admin_url('users.php?page=aksrt_user_auth&status=pending');
echo '<div class="notice notice-info is-dismissible"><h3>' . esc_html__('用户身份认证申请待处理', 'aksrt') . '</h3>';
$url = admin_url('users.php?page=jio_user_auth&status=pending');
echo '<div class="notice notice-info is-dismissible"><h3>' . esc_html__('用户身份认证申请待处理', 'jio') . '</h3>';
echo '<p>' . esc_html(sprintf(
/* translators: %d count */
__('您有 %d 个用户身份认证申请待处理', 'aksrt'),
__('您有 %d 个用户身份认证申请待处理', 'jio'),
$pending_verify
)) . '</p>';
echo '<p><a class="button button-primary" href="' . esc_url($url) . '">' . esc_html__('立即处理', 'aksrt') . '</a></p></div>';
echo '<p><a class="button button-primary" href="' . esc_url($url) . '">' . esc_html__('立即处理', 'jio') . '</a></p></div>';
}
}
if ($page !== 'aksrt_user_ban' && class_exists('\AKSRT\Messages\ReportCenter')) {
$pending_reports = (int) \AKSRT\Messages\ReportCenter::get_instance()->get_pending_count();
if ($page !== 'jio_user_ban' && class_exists('\Jio\Messages\ReportCenter')) {
$pending_reports = (int) \Jio\Messages\ReportCenter::get_instance()->get_pending_count();
if ($pending_reports > 0) {
$url = admin_url('users.php?page=aksrt_user_ban&status=pending');
echo '<div class="notice notice-info is-dismissible"><h3>' . esc_html__('举报待处理', 'aksrt') . '</h3>';
$url = admin_url('users.php?page=jio_user_ban&status=pending');
echo '<div class="notice notice-info is-dismissible"><h3>' . esc_html__('举报待处理', 'jio') . '</h3>';
echo '<p>' . esc_html(sprintf(
/* translators: %d count */
__('您有 %d 条举报待处理', 'aksrt'),
__('您有 %d 条举报待处理', 'jio'),
$pending_reports
)) . '</p>';
echo '<p><a class="button button-primary" href="' . esc_url($url) . '">' . esc_html__('立即处理', 'aksrt') . '</a></p></div>';
echo '<p><a class="button button-primary" href="' . esc_url($url) . '">' . esc_html__('立即处理', 'jio') . '</a></p></div>';
}
}
if ($page !== 'aksrt_user_ban' && class_exists('\AKSRT\User\BanAppeal')) {
if ($page !== 'jio_user_ban' && class_exists('\Jio\User\BanAppeal')) {
$pending_appeals = count(get_users([
'meta_key' => \AKSRT\User\BanAppeal::META_STATUS,
'meta_key' => \Jio\User\BanAppeal::META_STATUS,
'meta_value' => 'pending',
'number' => 50,
'fields' => 'ID',
]));
if ($pending_appeals > 0) {
$url = admin_url('users.php?page=aksrt_user_ban&type=appeal&status=pending');
echo '<div class="notice notice-warning is-dismissible"><h3>' . esc_html__('禁封申诉待处理', 'aksrt') . '</h3>';
echo '<p>' . esc_html(sprintf(__('您有 %d 条禁封申诉待处理', 'aksrt'), $pending_appeals)) . '</p>';
echo '<p><a class="button button-primary" href="' . esc_url($url) . '">' . esc_html__('立即处理', 'aksrt') . '</a></p></div>';
$url = admin_url('users.php?page=jio_user_ban&type=appeal&status=pending');
echo '<div class="notice notice-warning is-dismissible"><h3>' . esc_html__('禁封申诉待处理', 'jio') . '</h3>';
echo '<p>' . esc_html(sprintf(__('您有 %d 条禁封申诉待处理', 'jio'), $pending_appeals)) . '</p>';
echo '<p><a class="button button-primary" href="' . esc_url($url) . '">' . esc_html__('立即处理', 'jio') . '</a></p></div>';
}
}
if ($page !== 'aksrt_tickets' && class_exists('\AKSRT\Ticket\TicketSettings') && \AKSRT\Ticket\TicketSettings::enabled()) {
if ($page !== 'jio_tickets' && class_exists('\Jio\Ticket\TicketSettings') && \Jio\Ticket\TicketSettings::enabled()) {
$pending_tickets = $this->count_pending_tickets();
if ($pending_tickets > 0) {
$url = admin_url('users.php?page=aksrt_tickets&status=' . \AKSRT\Ticket\TicketStatusService::KEY_PENDING);
echo '<div class="notice notice-info is-dismissible"><h3>' . esc_html__('工单待接单', 'aksrt') . '</h3>';
$url = admin_url('users.php?page=jio_tickets&status=' . \Jio\Ticket\TicketStatusService::KEY_PENDING);
echo '<div class="notice notice-info is-dismissible"><h3>' . esc_html__('工单待接单', 'jio') . '</h3>';
echo '<p>' . esc_html(sprintf(
/* translators: %d count */
__('您有 %d 个工单待接单', 'aksrt'),
__('您有 %d 个工单待接单', 'jio'),
$pending_tickets
)) . '</p>';
echo '<p><a class="button button-primary" href="' . esc_url($url) . '">' . esc_html__('立即处理', 'aksrt') . '</a></p></div>';
echo '<p><a class="button button-primary" href="' . esc_url($url) . '">' . esc_html__('立即处理', 'jio') . '</a></p></div>';
}
}
}
private function ensure_cap(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
wp_die(esc_html__('您没有权限访问此页面。', 'jio'));
}
}
private function flash(): void {
if (empty($_GET['aksrt_users_msg'])) {
if (empty($_GET['jio_users_msg'])) {
return;
}
$code = sanitize_key((string) wp_unslash($_GET['aksrt_users_msg']));
$code = sanitize_key((string) wp_unslash($_GET['jio_users_msg']));
$map = [
'verify_updated' => __('认证状态已更新。', 'aksrt'),
'verify_deleted' => __('认证记录已删除。', 'aksrt'),
'verify_failed' => __('认证审核失败。', 'aksrt'),
'report_updated' => __('举报状态已更新。', 'aksrt'),
'report_failed' => __('举报更新失败。', 'aksrt'),
'appeal_approved' => __('申诉已通过,限制已解除。', 'aksrt'),
'appeal_rejected' => __('申诉已驳回。', 'aksrt'),
'appeal_failed' => __('申诉处理失败。', 'aksrt'),
'vip_updated' => __('会员状态已更新。', 'aksrt'),
'vip_partial' => __('部分用户的会员状态已更新。', 'aksrt'),
'vip_failed' => __('会员操作失败。', 'aksrt'),
'verify_updated' => __('认证状态已更新。', 'jio'),
'verify_deleted' => __('认证记录已删除。', 'jio'),
'verify_failed' => __('认证审核失败。', 'jio'),
'report_updated' => __('举报状态已更新。', 'jio'),
'report_failed' => __('举报更新失败。', 'jio'),
'appeal_approved' => __('申诉已通过,限制已解除。', 'jio'),
'appeal_rejected' => __('申诉已驳回。', 'jio'),
'appeal_failed' => __('申诉处理失败。', 'jio'),
'vip_updated' => __('会员状态已更新。', 'jio'),
'vip_partial' => __('部分用户的会员状态已更新。', 'jio'),
'vip_failed' => __('会员操作失败。', 'jio'),
];
if (!isset($map[$code])) {
return;
}
$class = str_contains($code, 'failed') ? 'notice-error' : 'notice-success';
$message = $map[$code];
if ($code === 'verify_failed' && isset($_GET['aksrt_verify_error'])) {
$message = sanitize_text_field((string) wp_unslash($_GET['aksrt_verify_error']));
if ($code === 'verify_failed' && isset($_GET['jio_verify_error'])) {
$message = sanitize_text_field((string) wp_unslash($_GET['jio_verify_error']));
}
if (in_array($code, ['report_failed', 'appeal_failed'], true) && isset($_GET['aksrt_ban_error'])) {
$message = sanitize_text_field((string) wp_unslash($_GET['aksrt_ban_error']));
if (in_array($code, ['report_failed', 'appeal_failed'], true) && isset($_GET['jio_ban_error'])) {
$message = sanitize_text_field((string) wp_unslash($_GET['jio_ban_error']));
}
if ($code === 'vip_failed' && isset($_GET['aksrt_vip_error'])) {
$message = sanitize_text_field((string) wp_unslash($_GET['aksrt_vip_error']));
if ($code === 'vip_failed' && isset($_GET['jio_vip_error'])) {
$message = sanitize_text_field((string) wp_unslash($_GET['jio_vip_error']));
}
if (in_array($code, ['vip_updated', 'vip_partial'], true)) {
$message .= ' ' . sprintf(
__('成功:%1$d;失败:%2$d;跳过:%3$d。', 'aksrt'),
absint($_GET['aksrt_vip_ok'] ?? 0),
absint($_GET['aksrt_vip_failed_count'] ?? 0),
absint($_GET['aksrt_vip_skipped'] ?? 0)
__('成功:%1$d;失败:%2$d;跳过:%3$d。', 'jio'),
absint($_GET['jio_vip_ok'] ?? 0),
absint($_GET['jio_vip_failed_count'] ?? 0),
absint($_GET['jio_vip_skipped'] ?? 0)
);
}
echo '<div class="notice ' . esc_attr($class) . ' is-dismissible"><p>' . esc_html($message) . '</p></div>';
}
private function redirect(string $page, string $msg, array $extra = []): void {
wp_safe_redirect(add_query_arg(array_merge(['page' => $page, 'aksrt_users_msg' => $msg], $extra), admin_url('users.php')));
wp_safe_redirect(add_query_arg(array_merge(['page' => $page, 'jio_users_msg' => $msg], $extra), admin_url('users.php')));
exit;
}
+51 -51
View File
@@ -1,9 +1,9 @@
<?php
/** Native report and ban appeal queue. @package AKSRT */
/** Native report and ban appeal queue. @package Jio */
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\User\Restriction;
use Jio\User\Restriction;
defined('ABSPATH') || exit;
@@ -15,11 +15,11 @@ trait UsersMenusBan {
private array $ban_restrictions = [];
public function enqueue_ban_assets(string $hook): void {
if ($hook !== 'users_page_aksrt_user_ban') return;
if ($hook !== 'users_page_jio_user_ban') return;
$css = '/assets/css/admin/user-ban.css';
$js = '/assets/js/admin-user-ban.js';
wp_enqueue_style('aksrt-admin-user-ban', AKSRT_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(AKSRT_DIR . $css));
wp_enqueue_script('aksrt-admin-user-ban', AKSRT_URI . $js, [], (string) filemtime(AKSRT_DIR . $js), true);
wp_enqueue_style('jio-admin-user-ban', JIO_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(JIO_DIR . $css));
wp_enqueue_script('jio-admin-user-ban', JIO_URI . $js, [], (string) filemtime(JIO_DIR . $js), true);
}
public function render_ban_page(): void {
@@ -28,71 +28,71 @@ trait UsersMenusBan {
$data = $this->query_ban_requests($filters, max(1, (int) ($_GET['paged'] ?? 1)));
$this->cache_ban_targets($data['items']);
$this->load_ban_restrictions($data['items']);
NativeUi::page_open('aksrt-ban-page');
NativeUi::page_open('jio-ban-page');
NativeUi::header(
__('举报与禁封申诉', 'aksrt'),
__('核对举报内容与申诉资料,在审核弹窗中记录处理结果。用户禁封操作仍在前台用户主页完成。', 'aksrt'),
__('举报与禁封申诉', 'jio'),
__('核对举报内容与申诉资料,在审核弹窗中记录处理结果。用户禁封操作仍在前台用户主页完成。', 'jio'),
'dashicons-shield'
);
$this->flash();
NativeUi::card_open('', '', true, 'aksrt-ban-list-card');
NativeUi::card_open('', '', true, 'jio-ban-list-card');
$this->render_ban_filters($filters, $data['counts']);
echo '<div class="aksrt-native-table-wrap">';
echo '<div class="jio-native-table-wrap">';
$this->render_ban_table($data['items'], $filters, $data['page']);
NativeUi::table_wrap_close();
AdminListPagination::render('aksrt_user_ban', $data['total'], $data['page'], self::BAN_PER_PAGE, $filters, 'users.php');
AdminListPagination::render('jio_user_ban', $data['total'], $data['page'], self::BAN_PER_PAGE, $filters, 'users.php');
NativeUi::card_close();
echo '<dialog id="aksrt-ban-dialog" class="aksrt-ban-dialog" aria-labelledby="aksrt-ban-dialog-title" aria-describedby="aksrt-ban-dialog-description"></dialog>';
echo '<noscript><p class="aksrt-native-callout">' . esc_html__('请启用 JavaScript 后查看详情和处理审核。', 'aksrt') . '</p></noscript>';
echo '<dialog id="jio-ban-dialog" class="jio-ban-dialog" aria-labelledby="jio-ban-dialog-title" aria-describedby="jio-ban-dialog-description"></dialog>';
echo '<noscript><p class="jio-native-callout">' . esc_html__('请启用 JavaScript 后查看详情和处理审核。', 'jio') . '</p></noscript>';
NativeUi::page_close();
}
private function ban_page_url(array $args = []): string {
return add_query_arg(array_merge(['page' => 'aksrt_user_ban'], $args), admin_url('users.php'));
return add_query_arg(array_merge(['page' => 'jio_user_ban'], $args), admin_url('users.php'));
}
private function render_ban_filters(array $filters, array $counts): void {
echo '<div class="aksrt-ban-toolbar"><div class="aksrt-ban-toolbar__top"><h2 class="aksrt-native-card__title">' . esc_html__('处理记录', 'aksrt') . '</h2>';
echo '<nav class="aksrt-ban-types" aria-label="' . esc_attr__('记录类型', 'aksrt') . '">';
foreach (['' => __('全部类型', 'aksrt'), 'report' => __('用户举报', 'aksrt'), 'appeal' => __('禁封申诉', 'aksrt')] as $key => $label) {
echo '<div class="jio-ban-toolbar"><div class="jio-ban-toolbar__top"><h2 class="jio-native-card__title">' . esc_html__('处理记录', 'jio') . '</h2>';
echo '<nav class="jio-ban-types" aria-label="' . esc_attr__('记录类型', 'jio') . '">';
foreach (['' => __('全部类型', 'jio'), 'report' => __('用户举报', 'jio'), 'appeal' => __('禁封申诉', 'jio')] as $key => $label) {
echo '<a href="' . esc_url($this->ban_page_url(array_merge($filters, ['type' => $key]))) . '"' . ($key === $filters['type'] ? ' aria-current="page"' : '') . '>' . esc_html($label) . '</a>';
}
echo '</nav></div><div class="aksrt-ban-toolbar__bottom"><ul class="subsubsub" aria-label="' . esc_attr__('处理状态筛选', 'aksrt') . '">';
foreach (['pending' => __('待处理', 'aksrt'), 'resolved' => __('已处理 / 已通过', 'aksrt'), 'dismissed' => __('已驳回', 'aksrt'), '' => __('全部状态', 'aksrt')] as $key => $label) {
if ($filters['type'] === 'report' && $key === 'resolved') $label = __('已处理', 'aksrt');
if ($filters['type'] === 'appeal' && $key === 'resolved') $label = __('已通过', 'aksrt');
echo '</nav></div><div class="jio-ban-toolbar__bottom"><ul class="subsubsub" aria-label="' . esc_attr__('处理状态筛选', 'jio') . '">';
foreach (['pending' => __('待处理', 'jio'), 'resolved' => __('已处理 / 已通过', 'jio'), 'dismissed' => __('已驳回', 'jio'), '' => __('全部状态', 'jio')] as $key => $label) {
if ($filters['type'] === 'report' && $key === 'resolved') $label = __('已处理', 'jio');
if ($filters['type'] === 'appeal' && $key === 'resolved') $label = __('已通过', 'jio');
echo '<li><a href="' . esc_url($this->ban_page_url(array_merge($filters, ['status' => $key]))) . '"' . ($key === $filters['status'] ? ' class="current" aria-current="page"' : '') . '>' . esc_html($label)
. ' <span class="count">' . esc_html(number_format_i18n($counts[$key === '' ? 'all' : $key])) . '</span></a></li>';
}
echo '</ul><form method="get" action="' . esc_url(admin_url('users.php')) . '" class="aksrt-native-filters">';
echo '<input type="hidden" name="page" value="aksrt_user_ban">';
echo '</ul><form method="get" action="' . esc_url(admin_url('users.php')) . '" class="jio-native-filters">';
echo '<input type="hidden" name="page" value="jio_user_ban">';
foreach (['type', 'status', 'send_user', 'orderby', 'order'] as $key) {
echo '<input type="hidden" name="' . esc_attr($key) . '" value="' . esc_attr((string) $filters[$key]) . '">';
}
echo '<label class="screen-reader-text" for="aksrt-ban-search">' . esc_html__('搜索用户或记录内容', 'aksrt') . '</label>';
echo '<input type="search" id="aksrt-ban-search" name="s" value="' . esc_attr($filters['s']) . '" placeholder="' . esc_attr__('用户 / 目标 ID / 原因 / 申诉资料', 'aksrt') . '">';
echo '<button type="submit" class="button">' . esc_html__('搜索', 'aksrt') . '</button>';
echo '<label class="screen-reader-text" for="jio-ban-search">' . esc_html__('搜索用户或记录内容', 'jio') . '</label>';
echo '<input type="search" id="jio-ban-search" name="s" value="' . esc_attr($filters['s']) . '" placeholder="' . esc_attr__('用户 / 目标 ID / 原因 / 申诉资料', 'jio') . '">';
echo '<button type="submit" class="button">' . esc_html__('搜索', 'jio') . '</button>';
if ($filters['s'] !== '' || $filters['send_user'] > 0) {
echo '<a class="button-link" href="' . esc_url($this->ban_page_url(array_merge($filters, ['s' => '', 'send_user' => 0]))) . '">' . esc_html__('清除筛选', 'aksrt') . '</a>';
echo '<a class="button-link" href="' . esc_url($this->ban_page_url(array_merge($filters, ['s' => '', 'send_user' => 0]))) . '">' . esc_html__('清除筛选', 'jio') . '</a>';
}
echo '</form></div>';
if ($filters['send_user'] > 0) {
$user = get_userdata($filters['send_user']);
echo '<p class="aksrt-ban-filter-hint">' . esc_html(sprintf(__('当前仅显示 %s 提交的记录。', 'aksrt'), $user ? $user->display_name : '#' . $filters['send_user'])) . '</p>';
echo '<p class="jio-ban-filter-hint">' . esc_html(sprintf(__('当前仅显示 %s 提交的记录。', 'jio'), $user ? $user->display_name : '#' . $filters['send_user'])) . '</p>';
}
echo '</div>';
}
private function render_ban_table(array $items, array $filters, int $page): void {
echo '<table class="widefat fixed striped aksrt-ban-table"><caption class="screen-reader-text">' . esc_html__('举报与禁封申诉处理记录', 'aksrt') . '</caption><thead><tr>';
foreach (['type' => __('类型 / 编号', 'aksrt'), 'user' => __('提交用户', 'aksrt'), 'summary' => __('事项摘要', 'aksrt'), 'status' => __('状态', 'aksrt'), 'time' => __('提交时间', 'aksrt'), 'updated' => __('更新时间', 'aksrt'), 'actions' => __('操作', 'aksrt')] as $key => $label) {
echo '<table class="widefat fixed striped jio-ban-table"><caption class="screen-reader-text">' . esc_html__('举报与禁封申诉处理记录', 'jio') . '</caption><thead><tr>';
foreach (['type' => __('类型 / 编号', 'jio'), 'user' => __('提交用户', 'jio'), 'summary' => __('事项摘要', 'jio'), 'status' => __('状态', 'jio'), 'time' => __('提交时间', 'jio'), 'updated' => __('更新时间', 'jio'), 'actions' => __('操作', 'jio')] as $key => $label) {
$sortable = in_array($key, ['type', 'status', 'time', 'updated'], true);
$active = $sortable && $filters['orderby'] === $key;
echo '<th scope="col" class="column-' . esc_attr($key) . '"' . ($active ? ' aria-sort="' . ($filters['order'] === 'ASC' ? 'ascending' : 'descending') . '"' : '') . '>';
if ($sortable) {
$order = $active && $filters['order'] === 'DESC' ? 'ASC' : 'DESC';
$title = sprintf($order === 'ASC' ? __('按%s升序排列', 'aksrt') : __('按%s降序排列', 'aksrt'), $label);
echo '<a class="aksrt-ban-sort" href="' . esc_url($this->ban_page_url(array_merge($filters, ['orderby' => $key, 'order' => $order]))) . '" aria-label="' . esc_attr($title) . '">' . esc_html($label) . '<span aria-hidden="true">' . ($active ? ($filters['order'] === 'ASC' ? '↑' : '↓') : '↕') . '</span></a>';
$title = sprintf($order === 'ASC' ? __('按%s升序排列', 'jio') : __('按%s降序排列', 'jio'), $label);
echo '<a class="jio-ban-sort" href="' . esc_url($this->ban_page_url(array_merge($filters, ['orderby' => $key, 'order' => $order]))) . '" aria-label="' . esc_attr($title) . '">' . esc_html($label) . '<span aria-hidden="true">' . ($active ? ($filters['order'] === 'ASC' ? '↑' : '↓') : '↕') . '</span></a>';
} else {
echo esc_html($label);
}
@@ -102,8 +102,8 @@ trait UsersMenusBan {
if ($items === []) {
$filtered = $filters['s'] !== '' || $filters['send_user'] > 0;
echo NativeUi::empty_row(7,
!$filtered && $filters['status'] === 'pending' ? __('暂无待处理记录', 'aksrt') : __('没有符合条件的记录', 'aksrt'),
$filtered ? __('请调整关键词、清除用户筛选或切换类型和状态。', 'aksrt') : __('可切换状态查看处理记录,新举报和申诉将在这里显示。', 'aksrt')
!$filtered && $filters['status'] === 'pending' ? __('暂无待处理记录', 'jio') : __('没有符合条件的记录', 'jio'),
$filtered ? __('请调整关键词、清除用户筛选或切换类型和状态。', 'jio') : __('可切换状态查看处理记录,新举报和申诉将在这里显示。', 'jio')
);
}
foreach ($items as $row) $this->render_ban_row($row, $filters, $page);
@@ -115,31 +115,31 @@ trait UsersMenusBan {
$payload = $row['payload'];
$key = $row['kind'] . '-' . $row['id'];
$user_id = (int) $row['user_id'];
echo '<tr><td class="column-type">' . NativeUi::badge($report ? __('用户举报', 'aksrt') : __('禁封申诉', 'aksrt'), $report ? 'info' : 'neutral', false);
echo '<div class="aksrt-ban-sub">#' . (int) $row['id'] . '</div></td><td class="column-user">' . NativeUi::user_cell($user_id, __('用户已删除', 'aksrt'));
echo '<tr><td class="column-type">' . NativeUi::badge($report ? __('用户举报', 'jio') : __('禁封申诉', 'jio'), $report ? 'info' : 'neutral', false);
echo '<div class="jio-ban-sub">#' . (int) $row['id'] . '</div></td><td class="column-user">' . NativeUi::user_cell($user_id, __('用户已删除', 'jio'));
if ($user_id > 0) {
echo '<a class="aksrt-ban-user-filter" href="' . esc_url($this->ban_page_url(array_merge($filters, ['send_user' => $user_id]))) . '">' . esc_html__('筛选此用户', 'aksrt') . '</a>';
echo '<a class="jio-ban-user-filter" href="' . esc_url($this->ban_page_url(array_merge($filters, ['send_user' => $user_id]))) . '">' . esc_html__('筛选此用户', 'jio') . '</a>';
}
echo '</td><td class="column-summary">';
if ($report) {
echo '<strong>' . $this->ban_target_link((string) ($payload['target_type'] ?? ''), (int) ($payload['target_id'] ?? 0)) . '</strong>';
echo '<div class="aksrt-ban-summary">' . esc_html((string) ($payload['category'] ?? '')) . '</div>';
echo '<div class="jio-ban-summary">' . esc_html((string) ($payload['category'] ?? '')) . '</div>';
} else {
echo '<strong>' . esc_html__('申请解除禁封', 'aksrt') . '</strong>';
echo '<strong>' . esc_html__('申请解除禁封', 'jio') . '</strong>';
}
if ((string) ($payload['reason'] ?? '') !== '') echo '<div class="aksrt-ban-summary">' . esc_html((string) $payload['reason']) . '</div>';
if ((string) ($payload['reason'] ?? '') !== '') echo '<div class="jio-ban-summary">' . esc_html((string) $payload['reason']) . '</div>';
$this->render_ban_detail_template($row, $filters, $page);
echo '</td><td class="column-status">' . $this->ban_status_badge($row) . '</td>';
echo '<td class="column-time">' . esc_html($this->ban_time((string) $row['time'])) . '</td>';
echo '<td class="column-updated">' . esc_html($this->ban_time((string) $row['updated'])) . '</td>';
echo '<td class="column-actions"><button type="button" class="button' . ($row['status'] === 'pending' ? ' button-primary' : '') . '" data-aksrt-ban-view="' . esc_attr($key) . '" aria-haspopup="dialog" aria-controls="aksrt-ban-dialog">'
. esc_html($row['status'] === 'pending' ? __('审核', 'aksrt') : __('查看详情', 'aksrt')) . '</button></td></tr>';
echo '<td class="column-actions"><button type="button" class="button' . ($row['status'] === 'pending' ? ' button-primary' : '') . '" data-jio-ban-view="' . esc_attr($key) . '" aria-haspopup="dialog" aria-controls="jio-ban-dialog">'
. esc_html($row['status'] === 'pending' ? __('审核', 'jio') : __('查看详情', 'jio')) . '</button></td></tr>';
}
private function ban_status_badge(array $row): string {
if ($row['status'] === 'pending') return NativeUi::badge($row['raw_status'] === 'reviewing' ? __('审核中', 'aksrt') : __('待处理', 'aksrt'), 'warning');
if ($row['status'] === 'resolved') return NativeUi::badge($row['kind'] === 'report' ? __('已处理', 'aksrt') : __('已通过', 'aksrt'), 'success');
return NativeUi::badge(__('已驳回', 'aksrt'), 'danger');
if ($row['status'] === 'pending') return NativeUi::badge($row['raw_status'] === 'reviewing' ? __('审核中', 'jio') : __('待处理', 'jio'), 'warning');
if ($row['status'] === 'resolved') return NativeUi::badge($row['kind'] === 'report' ? __('已处理', 'jio') : __('已通过', 'jio'), 'success');
return NativeUi::badge(__('已驳回', 'jio'), 'danger');
}
private function ban_time(string $time): string {
@@ -147,12 +147,12 @@ trait UsersMenusBan {
}
private function ban_user_home(int $user_id): string {
return function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($user_id) : get_author_posts_url($user_id);
return function_exists('jio_get_user_home_url') ? jio_get_user_home_url($user_id) : get_author_posts_url($user_id);
}
private function ban_target_link(string $type, int $id): string {
$labels = ['post' => __('文章', 'aksrt'), 'comment' => __('评论', 'aksrt'), 'user' => __('用户', 'aksrt')];
$label = ($labels[$type] ?? __('目标', 'aksrt')) . ' #' . $id;
$labels = ['post' => __('文章', 'jio'), 'comment' => __('评论', 'jio'), 'user' => __('用户', 'jio')];
$label = ($labels[$type] ?? __('目标', 'jio')) . ' #' . $id;
$url = '';
if ($type === 'post' && get_post($id)) {
$label = get_the_title($id) ?: $label;
@@ -163,7 +163,7 @@ trait UsersMenusBan {
$label = $user->display_name . ' #' . $id;
$url = $this->ban_user_home($id);
}
return $url !== '' ? '<a href="' . esc_url($url) . '" target="_blank" rel="noopener noreferrer">' . esc_html($label) . '</a>' : esc_html($label . ' ' . __('(目标已不可用)', 'aksrt'));
return $url !== '' ? '<a href="' . esc_url($url) . '" target="_blank" rel="noopener noreferrer">' . esc_html($label) . '</a>' : esc_html($label . ' ' . __('(目标已不可用)', 'jio'));
}
private function cache_ban_targets(array $items): void {
@@ -194,7 +194,7 @@ trait UsersMenusBan {
global $wpdb;
$placeholders = implode(',', array_fill(0, count($ids), '%d'));
$rows = $wpdb->get_results($wpdb->prepare(
"SELECT user_id, type, scope, reason, created_at, expires_at FROM {$wpdb->prefix}aksrt_user_restriction WHERE user_id IN ({$placeholders}) AND (expires_at IS NULL OR expires_at > %s) ORDER BY created_at DESC",
"SELECT user_id, type, scope, reason, created_at, expires_at FROM {$wpdb->prefix}jio_user_restriction WHERE user_id IN ({$placeholders}) AND (expires_at IS NULL OR expires_at > %s) ORDER BY created_at DESC",
array_merge($ids, [current_time('mysql', true)])
), ARRAY_A);
foreach ((array) $rows as $row) $this->ban_restrictions[(int) $row['user_id']][] = $row;
+49 -49
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
@@ -11,87 +11,87 @@ trait UsersMenusBanActions {
public function handle_update_report(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
wp_die(esc_html__('权限不足', 'jio'));
}
$report_id = absint($this->ban_post_value('report_id'));
$new_status = sanitize_key($this->ban_post_value('new_status'));
$note = trim(sanitize_textarea_field($this->ban_post_value('note')));
check_admin_referer('aksrt_users_update_report_' . $report_id);
check_admin_referer('jio_users_update_report_' . $report_id);
if ($report_id <= 0 || !in_array($new_status, ['resolved', 'dismissed'], true)) {
$this->redirect_ban_action('report_failed', __('举报处理请求无效。', 'aksrt'));
$this->redirect_ban_action('report_failed', __('举报处理请求无效。', 'jio'));
}
if ($new_status === 'dismissed' && $note === '') {
$this->redirect_ban_action('report_failed', __('驳回举报时必须填写处理说明。', 'aksrt'));
$this->redirect_ban_action('report_failed', __('驳回举报时必须填写处理说明。', 'jio'));
}
$row = $this->find_ban_request('report', $report_id);
if (!$this->ban_request_is_current($row, 'pending')) {
$this->redirect_ban_action('report_failed', __('举报记录已变化或已处理,请刷新后重试。', 'aksrt'));
$this->redirect_ban_action('report_failed', __('举报记录已变化或已处理,请刷新后重试。', 'jio'));
}
if (!class_exists('\\AKSRT\\Messages\\ReportCenter')) {
$this->redirect_ban_action('report_failed', __('举报处理服务不可用。', 'aksrt'));
if (!class_exists('\\Jio\\Messages\\ReportCenter')) {
$this->redirect_ban_action('report_failed', __('举报处理服务不可用。', 'jio'));
}
$ok = \AKSRT\Messages\ReportCenter::get_instance()->update_status($report_id, $new_status, $note);
$ok = \Jio\Messages\ReportCenter::get_instance()->update_status($report_id, $new_status, $note);
$this->redirect_ban_action(
$ok ? 'report_updated' : 'report_failed',
$ok ? '' : __('举报状态更新失败,请稍后重试。', 'aksrt')
$ok ? '' : __('举报状态更新失败,请稍后重试。', 'jio')
);
}
public function handle_process_appeal(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
wp_die(esc_html__('权限不足', 'jio'));
}
$user_id = absint($this->ban_post_value('user_id'));
$decision = sanitize_key($this->ban_post_value('decision'));
$note = trim(sanitize_textarea_field($this->ban_post_value('note')));
check_admin_referer('aksrt_users_process_appeal_' . $user_id);
check_admin_referer('jio_users_process_appeal_' . $user_id);
if ($user_id <= 0 || !in_array($decision, ['approve', 'reject'], true)) {
$this->redirect_ban_action('appeal_failed', __('申诉处理请求无效。', 'aksrt'));
$this->redirect_ban_action('appeal_failed', __('申诉处理请求无效。', 'jio'));
}
if ($decision === 'reject' && $note === '') {
$this->redirect_ban_action('appeal_failed', __('驳回申诉时必须填写拒绝原因。', 'aksrt'));
$this->redirect_ban_action('appeal_failed', __('驳回申诉时必须填写拒绝原因。', 'jio'));
}
$row = $this->find_ban_request('appeal', $user_id);
if (!$this->ban_request_is_current($row, 'pending')) {
$this->redirect_ban_action('appeal_failed', __('申诉记录已变化或已处理,请刷新后重试。', 'aksrt'));
$this->redirect_ban_action('appeal_failed', __('申诉记录已变化或已处理,请刷新后重试。', 'jio'));
}
$user = get_userdata($user_id);
if (!($user instanceof \WP_User)) {
$this->redirect_ban_action('appeal_failed', __('申诉用户不存在,无法处理。', 'aksrt'));
$this->redirect_ban_action('appeal_failed', __('申诉用户不存在,无法处理。', 'jio'));
}
if ($decision === 'approve') {
if (!class_exists('\\AKSRT\\User\\Restriction')) {
$this->redirect_ban_action('appeal_failed', __('用户限制服务不可用,无法通过申诉。', 'aksrt'));
if (!class_exists('\\Jio\\User\\Restriction')) {
$this->redirect_ban_action('appeal_failed', __('用户限制服务不可用,无法通过申诉。', 'jio'));
}
global $wpdb;
$restriction_service = \AKSRT\User\Restriction::get_instance();
$restriction_service = \Jio\User\Restriction::get_instance();
$restriction_service->lift_user_restriction($user_id);
$lift_error = (string) $wpdb->last_error;
if ($lift_error !== '') {
$this->redirect_ban_action('appeal_failed', __('解除限制时发生数据库错误,申诉未通过。', 'aksrt'));
$this->redirect_ban_action('appeal_failed', __('解除限制时发生数据库错误,申诉未通过。', 'jio'));
}
$active_restrictions = $restriction_service->get_user_restrictions($user_id, true);
$check_error = (string) $wpdb->last_error;
if ($check_error !== '') {
$this->redirect_ban_action('appeal_failed', __('无法核实限制是否解除,限制可能已解除,请检查用户当前状态。', 'aksrt'));
$this->redirect_ban_action('appeal_failed', __('无法核实限制是否解除,限制可能已解除,请检查用户当前状态。', 'jio'));
}
foreach ($active_restrictions as $restriction) {
if (in_array($restriction['type'] ?? '', [
\AKSRT\User\Restriction::TYPE_BAN,
\AKSRT\User\Restriction::TYPE_SUSPEND,
\AKSRT\User\Restriction::TYPE_MUTE,
\Jio\User\Restriction::TYPE_BAN,
\Jio\User\Restriction::TYPE_SUSPEND,
\Jio\User\Restriction::TYPE_MUTE,
], true)) {
$this->redirect_ban_action('appeal_failed', __('仍有生效限制,申诉未通过。', 'aksrt'));
$this->redirect_ban_action('appeal_failed', __('仍有生效限制,申诉未通过。', 'jio'));
}
}
}
@@ -103,58 +103,58 @@ trait UsersMenusBanActions {
$payload['handled_by'] = get_current_user_id();
$payload['decision'] = $raw_status;
$history = get_user_meta($user_id, \AKSRT\User\BanAppeal::META_HISTORY, true);
$history = get_user_meta($user_id, \Jio\User\BanAppeal::META_HISTORY, true);
$history = is_array($history) ? $history : [];
$history[] = $payload;
$history = array_slice($history, -30);
$old_payload = $row['payload'];
$old_history = get_user_meta($user_id, \AKSRT\User\BanAppeal::META_HISTORY, true);
$payload_updated = update_user_meta($user_id, \AKSRT\User\BanAppeal::META_PAYLOAD, $payload, $old_payload);
$old_history = get_user_meta($user_id, \Jio\User\BanAppeal::META_HISTORY, true);
$payload_updated = update_user_meta($user_id, \Jio\User\BanAppeal::META_PAYLOAD, $payload, $old_payload);
if ($payload_updated === false) {
$this->redirect_ban_action('appeal_failed', $this->appeal_metadata_error(__('申诉处理记录保存失败。', 'aksrt'), $decision));
$this->redirect_ban_action('appeal_failed', $this->appeal_metadata_error(__('申诉处理记录保存失败。', 'jio'), $decision));
}
$status_updated = update_user_meta($user_id, \AKSRT\User\BanAppeal::META_STATUS, $raw_status, 'pending');
$status_updated = update_user_meta($user_id, \Jio\User\BanAppeal::META_STATUS, $raw_status, 'pending');
if ($status_updated === false) {
update_user_meta($user_id, \AKSRT\User\BanAppeal::META_PAYLOAD, $old_payload);
$this->redirect_ban_action('appeal_failed', $this->appeal_metadata_error(__('申诉状态保存失败。', 'aksrt'), $decision));
update_user_meta($user_id, \Jio\User\BanAppeal::META_PAYLOAD, $old_payload);
$this->redirect_ban_action('appeal_failed', $this->appeal_metadata_error(__('申诉状态保存失败。', 'jio'), $decision));
}
$history_updated = update_user_meta($user_id, \AKSRT\User\BanAppeal::META_HISTORY, $history);
$history_updated = update_user_meta($user_id, \Jio\User\BanAppeal::META_HISTORY, $history);
if ($history_updated === false) {
update_user_meta($user_id, \AKSRT\User\BanAppeal::META_STATUS, 'pending');
update_user_meta($user_id, \AKSRT\User\BanAppeal::META_PAYLOAD, $old_payload);
update_user_meta($user_id, \Jio\User\BanAppeal::META_STATUS, 'pending');
update_user_meta($user_id, \Jio\User\BanAppeal::META_PAYLOAD, $old_payload);
if ($old_history === '' || $old_history === null) {
delete_user_meta($user_id, \AKSRT\User\BanAppeal::META_HISTORY);
delete_user_meta($user_id, \Jio\User\BanAppeal::META_HISTORY);
} else {
update_user_meta($user_id, \AKSRT\User\BanAppeal::META_HISTORY, $old_history);
update_user_meta($user_id, \Jio\User\BanAppeal::META_HISTORY, $old_history);
}
$this->redirect_ban_action('appeal_failed', $this->appeal_metadata_error(__('申诉历史保存失败。', 'aksrt'), $decision));
$this->redirect_ban_action('appeal_failed', $this->appeal_metadata_error(__('申诉历史保存失败。', 'jio'), $decision));
}
if (class_exists('\\AKSRT\\Messages\\Message')) {
if (class_exists('\\Jio\\Messages\\Message')) {
$message = $decision === 'approve'
? __('你的禁封申诉已通过,限制已解除。', 'aksrt')
: __('你的禁封申诉未通过,可修改后重新提交。', 'aksrt');
? __('你的禁封申诉已通过,限制已解除。', 'jio')
: __('你的禁封申诉未通过,可修改后重新提交。', 'jio');
if ($note !== '') {
$message .= ' ' . sprintf(__('备注:%s', 'aksrt'), $note);
$message .= ' ' . sprintf(__('备注:%s', 'jio'), $note);
}
\AKSRT\Messages\Message::get_instance()->send(
\Jio\Messages\Message::get_instance()->send(
$user_id,
'ban_appeal',
$message,
['title' => $decision === 'approve' ? __('申诉通过', 'aksrt') : __('申诉驳回', 'aksrt')]
['title' => $decision === 'approve' ? __('申诉通过', 'jio') : __('申诉驳回', 'jio')]
);
}
do_action('aksrt_ban_appeal_processed', $user_id, $decision, $note);
do_action('jio_ban_appeal_processed', $user_id, $decision, $note);
$this->redirect_ban_action($decision === 'approve' ? 'appeal_approved' : 'appeal_rejected');
}
private function appeal_metadata_error(string $message, string $decision): string {
if ($decision === 'approve') {
return $message . ' ' . __('限制可能已解除,请核对用户当前状态。', 'aksrt');
return $message . ' ' . __('限制可能已解除,请核对用户当前状态。', 'jio');
}
return $message;
}
@@ -193,8 +193,8 @@ trait UsersMenusBanActions {
'paged' => max(1, absint($this->ban_post_value('paged', '1'))),
];
if ($error !== '') {
$extra['aksrt_ban_error'] = $error;
$extra['jio_ban_error'] = $error;
}
$this->redirect('aksrt_user_ban', $message, $extra);
$this->redirect('jio_user_ban', $message, $extra);
}
}
+55 -55
View File
@@ -1,9 +1,9 @@
<?php
/** Report and ban appeal detail/review dialog templates. @package AKSRT */
/** Report and ban appeal detail/review dialog templates. @package Jio */
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\User\Restriction;
use Jio\User\Restriction;
defined('ABSPATH') || exit;
@@ -13,24 +13,24 @@ trait UsersMenusBanDialogs {
$pending = $row['status'] === 'pending';
$report = $row['kind'] === 'report';
$key = $row['kind'] . '-' . $row['id'];
echo '<template id="aksrt-ban-detail-' . esc_attr($key) . '">';
echo '<template id="jio-ban-detail-' . esc_attr($key) . '">';
if ($pending) {
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-ban-dialog__frame" data-aksrt-ban-review>';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="jio-ban-dialog__frame" data-jio-ban-review>';
$this->render_ban_context($row, $filters, $page);
} else {
echo '<div class="aksrt-ban-dialog__frame">';
echo '<div class="jio-ban-dialog__frame">';
}
$title = $report
? ($pending ? __('审核用户举报', 'aksrt') : __('用户举报详情', 'aksrt'))
: ($pending ? __('审核禁封申诉', 'aksrt') : __('禁封申诉详情', 'aksrt'));
echo '<div class="aksrt-ban-dialog__head"><div><h2 id="aksrt-ban-dialog-title" tabindex="-1" autofocus>' . esc_html($title) . '</h2>';
echo '<p id="aksrt-ban-dialog-description">' . esc_html($pending ? __('先核对原始资料,再选择处理结果;提交后将通知用户。', 'aksrt') : __('该记录已处理,以下资料和结果仅供查看。', 'aksrt')) . '</p></div>';
echo '<button type="button" class="aksrt-ban-dialog__close" data-aksrt-dialog-close aria-label="' . esc_attr__('关闭弹窗', 'aksrt') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></div>';
echo '<div class="aksrt-ban-dialog__body"><div class="aksrt-ban-applicant">' . NativeUi::user_cell((int) $row['user_id'], __('用户已删除', 'aksrt')) . $this->ban_status_badge($row) . '</div>';
echo '<dl class="aksrt-ban-meta">';
$this->render_ban_meta_item(__('记录编号', 'aksrt'), ($report ? __('举报', 'aksrt') : __('申诉', 'aksrt')) . ' #' . $row['id']);
$this->render_ban_meta_item(__('提交时间', 'aksrt'), $this->ban_time((string) $row['time']));
$this->render_ban_meta_item(__('更新时间', 'aksrt'), $this->ban_time((string) $row['updated']));
? ($pending ? __('审核用户举报', 'jio') : __('用户举报详情', 'jio'))
: ($pending ? __('审核禁封申诉', 'jio') : __('禁封申诉详情', 'jio'));
echo '<div class="jio-ban-dialog__head"><div><h2 id="jio-ban-dialog-title" tabindex="-1" autofocus>' . esc_html($title) . '</h2>';
echo '<p id="jio-ban-dialog-description">' . esc_html($pending ? __('先核对原始资料,再选择处理结果;提交后将通知用户。', 'jio') : __('该记录已处理,以下资料和结果仅供查看。', 'jio')) . '</p></div>';
echo '<button type="button" class="jio-ban-dialog__close" data-jio-dialog-close aria-label="' . esc_attr__('关闭弹窗', 'jio') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></div>';
echo '<div class="jio-ban-dialog__body"><div class="jio-ban-applicant">' . NativeUi::user_cell((int) $row['user_id'], __('用户已删除', 'jio')) . $this->ban_status_badge($row) . '</div>';
echo '<dl class="jio-ban-meta">';
$this->render_ban_meta_item(__('记录编号', 'jio'), ($report ? __('举报', 'jio') : __('申诉', 'jio')) . ' #' . $row['id']);
$this->render_ban_meta_item(__('提交时间', 'jio'), $this->ban_time((string) $row['time']));
$this->render_ban_meta_item(__('更新时间', 'jio'), $this->ban_time((string) $row['updated']));
echo '</dl>';
if ($report) {
$this->render_ban_report_details($row);
@@ -39,20 +39,20 @@ trait UsersMenusBanDialogs {
}
$this->render_ban_review_history($row);
if ($pending) $this->render_ban_review_fields($report);
echo '</div><div class="aksrt-ban-dialog__foot"><button type="button" class="button" data-aksrt-dialog-close>' . esc_html($pending ? __('取消', 'aksrt') : __('关闭', 'aksrt')) . '</button>';
echo '</div><div class="jio-ban-dialog__foot"><button type="button" class="button" data-jio-dialog-close>' . esc_html($pending ? __('取消', 'jio') : __('关闭', 'jio')) . '</button>';
if ($pending) {
echo '<button type="submit" class="button button-primary" data-aksrt-ban-submit disabled'
. ' data-idle-label="' . esc_attr__('请选择处理结果', 'aksrt') . '"'
. ' data-approve-label="' . esc_attr($report ? __('确认标记已处理', 'aksrt') : __('确认通过并解除限制', 'aksrt')) . '"'
. ' data-reject-label="' . esc_attr__('确认驳回', 'aksrt') . '"'
. ' data-busy-label="' . esc_attr__('正在提交…', 'aksrt') . '">' . esc_html__('请选择处理结果', 'aksrt') . '</button>';
echo '<button type="submit" class="button button-primary" data-jio-ban-submit disabled'
. ' data-idle-label="' . esc_attr__('请选择处理结果', 'jio') . '"'
. ' data-approve-label="' . esc_attr($report ? __('确认标记已处理', 'jio') : __('确认通过并解除限制', 'jio')) . '"'
. ' data-reject-label="' . esc_attr__('确认驳回', 'jio') . '"'
. ' data-busy-label="' . esc_attr__('正在提交…', 'jio') . '">' . esc_html__('请选择处理结果', 'jio') . '</button>';
}
echo '</div>' . ($pending ? '</form>' : '</div>') . '</template>';
}
private function render_ban_context(array $row, array $filters, int $page): void {
$report = $row['kind'] === 'report';
$action = $report ? 'aksrt_users_update_report' : 'aksrt_users_process_appeal';
$action = $report ? 'jio_users_update_report' : 'jio_users_process_appeal';
$fields = [
'action' => $action,
$report ? 'report_id' : 'user_id' => (int) $row['id'],
@@ -76,7 +76,7 @@ trait UsersMenusBanDialogs {
}
private function render_ban_text_section(string $title, string $text): void {
echo '<section class="aksrt-ban-section"><h3>' . esc_html($title) . '</h3><div class="aksrt-ban-copy">';
echo '<section class="jio-ban-section"><h3>' . esc_html($title) . '</h3><div class="jio-ban-copy">';
// Evidence URLs remain links: opening a case must not load reporter-supplied remote images.
$parts = preg_split('~(https?://[^\s<>"\x27]+)~u', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
foreach ($parts ?: [$text] as $index => $part) {
@@ -84,7 +84,7 @@ trait UsersMenusBanDialogs {
? '<a href="' . esc_url($part, ['http', 'https']) . '" target="_blank" rel="noopener noreferrer">' . esc_html($part) . '</a>'
: esc_html($part !== '' ? $part : '');
}
if ($text === '') echo esc_html__('未填写', 'aksrt');
if ($text === '') echo esc_html__('未填写', 'jio');
echo '</div></section>';
}
@@ -92,59 +92,59 @@ trait UsersMenusBanDialogs {
$payload = $row['payload'];
$type = (string) ($payload['target_type'] ?? '');
$id = (int) ($payload['target_id'] ?? 0);
echo '<section class="aksrt-ban-section"><h3>' . esc_html__('举报对象', 'aksrt') . '</h3><div class="aksrt-ban-target">' . $this->ban_target_link($type, $id) . '</div></section>';
if ((string) ($payload['category'] ?? '') !== '') $this->render_ban_text_section(__('举报分类', 'aksrt'), (string) $payload['category']);
$this->render_ban_text_section(__('举报内容与举证', 'aksrt'), (string) ($payload['reason'] ?? ''));
echo '<section class="jio-ban-section"><h3>' . esc_html__('举报对象', 'jio') . '</h3><div class="jio-ban-target">' . $this->ban_target_link($type, $id) . '</div></section>';
if ((string) ($payload['category'] ?? '') !== '') $this->render_ban_text_section(__('举报分类', 'jio'), (string) $payload['category']);
$this->render_ban_text_section(__('举报内容与举证', 'jio'), (string) ($payload['reason'] ?? ''));
if ($type === 'user') {
$this->render_ban_restriction_section($id);
if (get_userdata($id)) {
echo '<p class="aksrt-ban-profile-link"><a href="' . esc_url($this->ban_user_home($id)) . '" target="_blank" rel="noopener noreferrer">' . esc_html__('查看用户主页并管理禁封(新窗口)', 'aksrt') . '</a></p>';
echo '<p class="jio-ban-profile-link"><a href="' . esc_url($this->ban_user_home($id)) . '" target="_blank" rel="noopener noreferrer">' . esc_html__('查看用户主页并管理禁封(新窗口)', 'jio') . '</a></p>';
}
}
if ($row['status'] === 'pending') {
echo '<p class="aksrt-native-callout">' . esc_html__('本次审核仅记录举报处理结果,不会自动禁封用户或删除内容。请先核实举证并完成必要处置。', 'aksrt') . '</p>';
echo '<p class="jio-native-callout">' . esc_html__('本次审核仅记录举报处理结果,不会自动禁封用户或删除内容。请先核实举证并完成必要处置。', 'jio') . '</p>';
}
}
private function render_ban_appeal_details(array $row): void {
$payload = $row['payload'];
$this->render_ban_restriction_section((int) $row['user_id']);
if (!empty($payload['reason_key'])) $this->render_ban_text_section(__('申诉类型', 'aksrt'), (string) $payload['reason_key']);
if (!empty($payload['reason_key'])) $this->render_ban_text_section(__('申诉类型', 'jio'), (string) $payload['reason_key']);
$data = isset($payload['data']) && is_array($payload['data']) ? $payload['data'] : [];
if ($data !== []) {
echo '<section class="aksrt-ban-section"><h3>' . esc_html__('申诉资料', 'aksrt') . '</h3><dl class="aksrt-ban-materials">';
echo '<section class="jio-ban-section"><h3>' . esc_html__('申诉资料', 'jio') . '</h3><dl class="jio-ban-materials">';
foreach ($data as $label => $value) {
if (is_scalar($value)) $this->render_ban_meta_item((string) $label, (string) $value);
}
echo '</dl></section>';
}
$reason = (string) ($payload['reason'] ?? '');
if ($data === [] || !in_array($reason, $data, true)) $this->render_ban_text_section(__('申诉说明', 'aksrt'), $reason);
if ($data === [] || !in_array($reason, $data, true)) $this->render_ban_text_section(__('申诉说明', 'jio'), $reason);
if (get_userdata((int) $row['user_id'])) {
echo '<p class="aksrt-ban-profile-link"><a href="' . esc_url($this->ban_user_home((int) $row['user_id'])) . '" target="_blank" rel="noopener noreferrer">' . esc_html__('查看申诉用户主页(新窗口)', 'aksrt') . '</a></p>';
echo '<p class="jio-ban-profile-link"><a href="' . esc_url($this->ban_user_home((int) $row['user_id'])) . '" target="_blank" rel="noopener noreferrer">' . esc_html__('查看申诉用户主页(新窗口)', 'jio') . '</a></p>';
}
}
private function render_ban_restriction_section(int $user_id): void {
echo '<section class="aksrt-ban-section"><h3>' . esc_html__('当前限制', 'aksrt') . '</h3>';
echo '<section class="jio-ban-section"><h3>' . esc_html__('当前限制', 'jio') . '</h3>';
if (!class_exists(Restriction::class)) {
echo '<p class="aksrt-native-callout is-warning">' . esc_html__('限制服务不可用,暂时无法核对当前状态。', 'aksrt') . '</p></section>';
echo '<p class="jio-native-callout is-warning">' . esc_html__('限制服务不可用,暂时无法核对当前状态。', 'jio') . '</p></section>';
return;
}
$rows = $this->ban_restrictions[$user_id] ?? [];
if ($rows === []) {
echo '<p class="aksrt-ban-normal">' . esc_html__('当前无生效限制。此处仅展示当前状态,不会自动更改处理结果。', 'aksrt') . '</p></section>';
echo '<p class="jio-ban-normal">' . esc_html__('当前无生效限制。此处仅展示当前状态,不会自动更改处理结果。', 'jio') . '</p></section>';
return;
}
$labels = [Restriction::TYPE_WARN => __('警告', 'aksrt'), Restriction::TYPE_MUTE => __('禁言', 'aksrt'), Restriction::TYPE_SUSPEND => __('停权', 'aksrt'), Restriction::TYPE_BAN => __('封禁', 'aksrt')];
echo '<ul class="aksrt-ban-restrictions">';
$labels = [Restriction::TYPE_WARN => __('警告', 'jio'), Restriction::TYPE_MUTE => __('禁言', 'jio'), Restriction::TYPE_SUSPEND => __('停权', 'jio'), Restriction::TYPE_BAN => __('封禁', 'jio')];
echo '<ul class="jio-ban-restrictions">';
foreach ($rows as $restriction) {
echo '<li><div class="aksrt-ban-restriction__title">' . NativeUi::badge($labels[$restriction['type']] ?? (string) $restriction['type'], 'warning', false)
. '<span>' . esc_html(($restriction['scope'] ?? '') === Restriction::SCOPE_COMMENT ? __('评论范围', 'aksrt') : __('全站范围', 'aksrt')) . '</span></div>';
echo '<div class="aksrt-ban-copy">' . esc_html((string) ($restriction['reason'] ?? '')) . '</div>';
echo '<li><div class="jio-ban-restriction__title">' . NativeUi::badge($labels[$restriction['type']] ?? (string) $restriction['type'], 'warning', false)
. '<span>' . esc_html(($restriction['scope'] ?? '') === Restriction::SCOPE_COMMENT ? __('评论范围', 'jio') : __('全站范围', 'jio')) . '</span></div>';
echo '<div class="jio-ban-copy">' . esc_html((string) ($restriction['reason'] ?? '')) . '</div>';
$start = !empty($restriction['created_at']) ? get_date_from_gmt((string) $restriction['created_at'], 'Y-m-d H:i') : '—';
$end = !empty($restriction['expires_at']) ? get_date_from_gmt((string) $restriction['expires_at'], 'Y-m-d H:i') : __('永久', 'aksrt');
echo '<div class="aksrt-ban-sub">' . esc_html(sprintf(__('开始:%1$s;结束:%2$s', 'aksrt'), $start, $end)) . '</div></li>';
$end = !empty($restriction['expires_at']) ? get_date_from_gmt((string) $restriction['expires_at'], 'Y-m-d H:i') : __('永久', 'jio');
echo '<div class="jio-ban-sub">' . esc_html(sprintf(__('开始:%1$s;结束:%2$s', 'jio'), $start, $end)) . '</div></li>';
}
echo '</ul></section>';
}
@@ -156,30 +156,30 @@ trait UsersMenusBanDialogs {
$notes[] = ['note' => $payload['admin_note'] ?? '', 'operator' => $payload['handled_by'] ?? 0, 'timestamp' => $payload['handled_at'], 'status' => $payload['decision'] ?? $row['raw_status']];
}
if ($notes === []) return;
$labels = ['pending' => __('待处理', 'aksrt'), 'reviewing' => __('审核中', 'aksrt'), 'resolved' => __('已处理', 'aksrt'), 'dismissed' => __('已驳回', 'aksrt'), 'approved' => __('已通过', 'aksrt'), 'rejected' => __('已驳回', 'aksrt')];
echo '<section class="aksrt-ban-section"><h3>' . esc_html__('处理记录', 'aksrt') . '</h3><ol class="aksrt-ban-history">';
$labels = ['pending' => __('待处理', 'jio'), 'reviewing' => __('审核中', 'jio'), 'resolved' => __('已处理', 'jio'), 'dismissed' => __('已驳回', 'jio'), 'approved' => __('已通过', 'jio'), 'rejected' => __('已驳回', 'jio')];
echo '<section class="jio-ban-section"><h3>' . esc_html__('处理记录', 'jio') . '</h3><ol class="jio-ban-history">';
foreach ($notes as $note) {
if (!is_array($note)) continue;
$operator = get_userdata((int) ($note['operator'] ?? 0));
echo '<li><div class="aksrt-ban-history__meta"><strong>' . esc_html($labels[$note['status'] ?? ''] ?? (string) ($note['status'] ?? '')) . '</strong><span>'
. esc_html($operator ? $operator->display_name : __('管理员', 'aksrt')) . '</span><time>' . esc_html((string) ($note['timestamp'] ?? '')) . '</time></div>';
echo '<div class="aksrt-ban-copy">' . esc_html((string) ($note['note'] ?? '') !== '' ? (string) $note['note'] : __('未填写处理留言', 'aksrt')) . '</div></li>';
echo '<li><div class="jio-ban-history__meta"><strong>' . esc_html($labels[$note['status'] ?? ''] ?? (string) ($note['status'] ?? '')) . '</strong><span>'
. esc_html($operator ? $operator->display_name : __('管理员', 'jio')) . '</span><time>' . esc_html((string) ($note['timestamp'] ?? '')) . '</time></div>';
echo '<div class="jio-ban-copy">' . esc_html((string) ($note['note'] ?? '') !== '' ? (string) $note['note'] : __('未填写处理留言', 'jio')) . '</div></li>';
}
echo '</ol></section>';
}
private function render_ban_review_fields(bool $report): void {
echo '<section class="aksrt-ban-review"><h3>' . esc_html__('审核处理', 'aksrt') . '</h3><fieldset class="aksrt-ban-decisions"><legend>' . esc_html__('处理结果(必选)', 'aksrt') . '</legend>';
$choices = $report ? ['resolved' => __('标记已处理', 'aksrt'), 'dismissed' => __('驳回举报', 'aksrt')] : ['approve' => __('通过申诉', 'aksrt'), 'reject' => __('驳回申诉', 'aksrt')];
echo '<section class="jio-ban-review"><h3>' . esc_html__('审核处理', 'jio') . '</h3><fieldset class="jio-ban-decisions"><legend>' . esc_html__('处理结果(必选)', 'jio') . '</legend>';
$choices = $report ? ['resolved' => __('标记已处理', 'jio'), 'dismissed' => __('驳回举报', 'jio')] : ['approve' => __('通过申诉', 'jio'), 'reject' => __('驳回申诉', 'jio')];
foreach ($choices as $value => $label) {
echo '<label><input type="radio" name="' . ($report ? 'new_status' : 'decision') . '" value="' . esc_attr($value) . '" required><span>' . esc_html($label) . '</span></label>';
}
echo '</fieldset>';
if (!$report) {
echo '<p class="aksrt-native-callout is-warning">' . esc_html__('通过申诉将解除该用户当前生效的封禁、停权和禁言,保留警告记录;驳回不会改变限制。', 'aksrt') . '</p>';
echo '<p class="jio-native-callout is-warning">' . esc_html__('通过申诉将解除该用户当前生效的封禁、停权和禁言,保留警告记录;驳回不会改变限制。', 'jio') . '</p>';
}
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label" data-aksrt-ban-note-label data-optional="' . esc_attr__('处理说明(选填)', 'aksrt') . '" data-required="' . esc_attr__('驳回原因(必填)', 'aksrt') . '">' . esc_html__('处理说明(选填)', 'aksrt') . '</span>';
echo '<textarea name="note" rows="3" data-required-error="' . esc_attr__('请填写驳回原因,不能只包含空格', 'aksrt') . '" aria-describedby="aksrt-ban-note-hint"></textarea></label>';
echo '<p id="aksrt-ban-note-hint" class="aksrt-native-field__hint">' . esc_html($report ? __('处理结果及说明会反馈给举报人。驳回时请填写具体原因。', 'aksrt') : __('处理结果及说明会反馈给申诉用户。驳回时请说明原因,便于用户补充资料。', 'aksrt')) . '</p></section>';
echo '<label class="jio-native-field"><span class="jio-native-field__label" data-jio-ban-note-label data-optional="' . esc_attr__('处理说明(选填)', 'jio') . '" data-required="' . esc_attr__('驳回原因(必填)', 'jio') . '">' . esc_html__('处理说明(选填)', 'jio') . '</span>';
echo '<textarea name="note" rows="3" data-required-error="' . esc_attr__('请填写驳回原因,不能只包含空格', 'jio') . '" aria-describedby="jio-ban-note-hint"></textarea></label>';
echo '<p id="jio-ban-note-hint" class="jio-native-field__hint">' . esc_html($report ? __('处理结果及说明会反馈给举报人。驳回时请填写具体原因。', 'jio') : __('处理结果及说明会反馈给申诉用户。驳回时请说明原因,便于用户补充资料。', 'jio')) . '</p></section>';
}
}
+13 -13
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
@@ -105,10 +105,10 @@ trait UsersMenusBanQuery {
private function query_ban_report_rows(array $filters): array {
global $wpdb;
if (!class_exists('\\AKSRT\\Core\\DBMigrator')) {
if (!class_exists('\\Jio\\Core\\DBMigrator')) {
return [];
}
$table = \AKSRT\Core\DBMigrator::table('report');
$table = \Jio\Core\DBMigrator::table('report');
if ($table === '') {
return [];
}
@@ -147,13 +147,13 @@ trait UsersMenusBanQuery {
/** @return array<int,array<string,mixed>> */
private function query_ban_appeal_rows(array $filters): array {
if (!class_exists('\\AKSRT\\User\\BanAppeal')) {
if (!class_exists('\\Jio\\User\\BanAppeal')) {
return [];
}
$args = [
'meta_query' => [[
'key' => \AKSRT\User\BanAppeal::META_STATUS,
'key' => \Jio\User\BanAppeal::META_STATUS,
'value' => ['pending', 'approved', 'rejected'],
'compare' => 'IN',
]],
@@ -216,12 +216,12 @@ trait UsersMenusBanQuery {
}
}
$report_ids = array_values(array_unique($report_ids));
if ($report_ids === [] || !class_exists('\\AKSRT\\Core\\DBMigrator')) {
if ($report_ids === [] || !class_exists('\\Jio\\Core\\DBMigrator')) {
return;
}
global $wpdb;
$table = \AKSRT\Core\DBMigrator::table('report');
$table = \Jio\Core\DBMigrator::table('report');
if ($table === '') {
return;
}
@@ -281,12 +281,12 @@ trait UsersMenusBanQuery {
if ($kind === 'appeal') {
return $this->get_ban_appeal_row($id);
}
if ($kind !== 'report' || !class_exists('\\AKSRT\\Core\\DBMigrator')) {
if ($kind !== 'report' || !class_exists('\\Jio\\Core\\DBMigrator')) {
return null;
}
global $wpdb;
$table = \AKSRT\Core\DBMigrator::table('report');
$table = \Jio\Core\DBMigrator::table('report');
if ($table === '') {
return null;
}
@@ -333,17 +333,17 @@ trait UsersMenusBanQuery {
}
private function get_ban_appeal_row(int $user_id): ?array {
if ($user_id <= 0 || !class_exists('\\AKSRT\\User\\BanAppeal')) {
if ($user_id <= 0 || !class_exists('\\Jio\\User\\BanAppeal')) {
return null;
}
$raw_status = \AKSRT\User\BanAppeal::get_status($user_id);
$raw_status = \Jio\User\BanAppeal::get_status($user_id);
$status = $raw_status === 'approved' ? 'resolved' : ($raw_status === 'rejected' ? 'dismissed' : $raw_status);
if (!in_array($status, ['pending', 'resolved', 'dismissed'], true)) {
return null;
}
$payload = \AKSRT\User\BanAppeal::get_payload($user_id);
$payload = \Jio\User\BanAppeal::get_payload($user_id);
return [
'kind' => 'appeal',
'id' => $user_id,
+59 -59
View File
@@ -2,10 +2,10 @@
/**
* Native invite-code list and page assets.
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
@@ -13,44 +13,44 @@ trait UsersMenusInviteCode {
use UsersMenusInviteForms;
public function enqueue_invite_assets(string $hook): void {
if ($hook !== 'users_page_aksrt_invite_codes') {
if ($hook !== 'users_page_jio_invite_codes') {
return;
}
$css = '/assets/css/admin/invite-codes.css';
$js = '/assets/js/admin-invite-codes.js';
wp_enqueue_style('aksrt-admin-invite-codes', AKSRT_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(AKSRT_DIR . $css));
wp_enqueue_script('aksrt-admin-invite-codes', AKSRT_URI . $js, [], (string) filemtime(AKSRT_DIR . $js), true);
wp_enqueue_style('jio-admin-invite-codes', JIO_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(JIO_DIR . $css));
wp_enqueue_script('jio-admin-invite-codes', JIO_URI . $js, [], (string) filemtime(JIO_DIR . $js), true);
}
public function render_invite_codes_page(): void {
if (!current_user_can('edit_users')) {
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
wp_die(esc_html__('您没有权限访问此页面。', 'jio'));
}
$service = \AKSRT\Payment\InviteCode::get_instance();
$this_url = admin_url('users.php?page=aksrt_invite_codes');
$nonce = wp_create_nonce('aksrt_invite_admin');
NativeUi::page_open('aksrt-inv-page');
$service = \Jio\Payment\InviteCode::get_instance();
$this_url = admin_url('users.php?page=jio_invite_codes');
$nonce = wp_create_nonce('jio_invite_admin');
NativeUi::page_open('jio-inv-page');
NativeUi::header(
__('邀请码管理', 'aksrt'),
__('管理注册邀请码与使用奖励,点击邀请码即可复制。', 'aksrt'),
__('邀请码管理', 'jio'),
__('管理注册邀请码与使用奖励,点击邀请码即可复制。', 'jio'),
'dashicons-tickets-alt',
[
['label' => __('添加邀请码', 'aksrt'), 'dialog' => 'aksrt-inv-add', 'primary' => true, 'icon' => 'dashicons-plus-alt2'],
['label' => __('导出邀请码', 'aksrt'), 'dialog' => 'aksrt-inv-export', 'icon' => 'dashicons-download'],
['label' => __('添加邀请码', 'jio'), 'dialog' => 'jio-inv-add', 'primary' => true, 'icon' => 'dashicons-plus-alt2'],
['label' => __('导出邀请码', 'jio'), 'dialog' => 'jio-inv-export', 'icon' => 'dashicons-download'],
]
);
$settings = $service->registration_settings();
if (empty($settings['enabled'])) {
$settings_url = admin_url('admin.php?page=aksrt-settings#/users/auth');
echo '<div class="notice notice-warning"><p>' . esc_html__('邀请码注册尚未启用,已添加的邀请码暂不能用于注册。', 'aksrt')
. ' <a href="' . esc_url($settings_url) . '">' . esc_html__('前往登录注册设置', 'aksrt') . '</a></p></div>';
$settings_url = admin_url('admin.php?page=jio-settings#/users/auth');
echo '<div class="notice notice-warning"><p>' . esc_html__('邀请码注册尚未启用,已添加的邀请码暂不能用于注册。', 'jio')
. ' <a href="' . esc_url($settings_url) . '">' . esc_html__('前往登录注册设置', 'jio') . '</a></p></div>';
}
$title = isset($_GET['aksrt_inv_title']) ? sanitize_text_field(rawurldecode((string) wp_unslash($_GET['aksrt_inv_title']))) : '';
$message = isset($_GET['aksrt_inv_msg']) ? sanitize_text_field(rawurldecode((string) wp_unslash($_GET['aksrt_inv_msg']))) : '';
$type = ($_GET['aksrt_inv_type'] ?? '') === 'error' ? 'error' : 'success';
$title = isset($_GET['jio_inv_title']) ? sanitize_text_field(rawurldecode((string) wp_unslash($_GET['jio_inv_title']))) : '';
$message = isset($_GET['jio_inv_msg']) ? sanitize_text_field(rawurldecode((string) wp_unslash($_GET['jio_inv_msg']))) : '';
$type = ($_GET['jio_inv_type'] ?? '') === 'error' ? 'error' : 'success';
if ($title !== '') {
echo '<div class="notice notice-' . esc_attr($type) . ' is-dismissible"><p><strong>' . esc_html($title) . '</strong>'
. ($message !== '' ? ' ' . esc_html($message) : '') . '</p></div>';
@@ -59,14 +59,14 @@ trait UsersMenusInviteCode {
$this->render_invite_codes_list($service, $this_url, $nonce);
$this->render_invite_codes_add($nonce);
$this->render_invite_codes_export($nonce);
echo '<div class="aksrt-inv-toast" role="status" aria-live="polite" data-aksrt-inv-toast'
. ' data-copied="' . esc_attr__('邀请码已复制', 'aksrt') . '"'
. ' data-copy-failed="' . esc_attr__('自动复制失败,请选中邀请码后手动复制。', 'aksrt') . '"'
. ' data-exported="' . esc_attr__('下载请求已提交,请查看浏览器下载列表。', 'aksrt') . '"></div>';
echo '<div class="jio-inv-toast" role="status" aria-live="polite" data-jio-inv-toast'
. ' data-copied="' . esc_attr__('邀请码已复制', 'jio') . '"'
. ' data-copy-failed="' . esc_attr__('自动复制失败,请选中邀请码后手动复制。', 'jio') . '"'
. ' data-exported="' . esc_attr__('下载请求已提交,请查看浏览器下载列表。', 'jio') . '"></div>';
NativeUi::page_close();
}
private function render_invite_codes_list(\AKSRT\Payment\InviteCode $service, string $this_url, string $nonce): void {
private function render_invite_codes_list(\Jio\Payment\InviteCode $service, string $this_url, string $nonce): void {
$status = in_array(($_GET['status'] ?? 'all'), ['all', 'unused', 'used'], true) ? (string) $_GET['status'] : 'all';
$s = isset($_GET['s']) ? sanitize_text_field((string) wp_unslash($_GET['s'])) : '';
$remark = isset($_GET['remark']) ? sanitize_text_field((string) wp_unslash($_GET['remark'])) : '';
@@ -81,44 +81,44 @@ trait UsersMenusInviteCode {
$filters = ['status' => $status, 's' => $s, 'remark' => $remark, 'orderby' => $data['orderby'], 'desc' => $data['desc']];
$filter_url = add_query_arg($filters, $this_url);
NativeUi::card_open('', '', true, 'aksrt-inv-list-card');
echo '<div class="aksrt-inv-toolbar"><div class="aksrt-native-toolbar"><div class="aksrt-inv-toolbar__views">';
echo '<h2 class="aksrt-native-card__title">' . esc_html__('邀请码列表', 'aksrt') . '</h2>';
echo '<ul class="subsubsub" aria-label="' . esc_attr__('使用状态筛选', 'aksrt') . '">';
foreach (['all' => __('全部', 'aksrt'), 'unused' => __('未使用', 'aksrt'), 'used' => __('已使用', 'aksrt')] as $key => $label) {
NativeUi::card_open('', '', true, 'jio-inv-list-card');
echo '<div class="jio-inv-toolbar"><div class="jio-native-toolbar"><div class="jio-inv-toolbar__views">';
echo '<h2 class="jio-native-card__title">' . esc_html__('邀请码列表', 'jio') . '</h2>';
echo '<ul class="subsubsub" aria-label="' . esc_attr__('使用状态筛选', 'jio') . '">';
foreach (['all' => __('全部', 'jio'), 'unused' => __('未使用', 'jio'), 'used' => __('已使用', 'jio')] as $key => $label) {
$url = add_query_arg('status', $key, $filter_url);
echo '<li><a href="' . esc_url($url) . '"' . ($key === $status ? ' class="current" aria-current="page"' : '') . '>' . esc_html($label) . '</a></li>';
}
echo '</ul></div><form method="get" action="' . esc_url(admin_url('users.php')) . '" class="aksrt-native-filters">';
echo '<input type="hidden" name="page" value="aksrt_invite_codes">';
echo '</ul></div><form method="get" action="' . esc_url(admin_url('users.php')) . '" class="jio-native-filters">';
echo '<input type="hidden" name="page" value="jio_invite_codes">';
foreach (array_diff_key($filters, ['s' => '']) as $key => $value) {
echo '<input type="hidden" name="' . esc_attr($key) . '" value="' . esc_attr($value) . '">';
}
echo '<label class="screen-reader-text" for="aksrt-inv-search">' . esc_html__('搜索邀请码或标识', 'aksrt') . '</label>';
echo '<input type="search" id="aksrt-inv-search" name="s" value="' . esc_attr($s) . '" placeholder="' . esc_attr__('搜索邀请码或标识', 'aksrt') . '">';
echo '<button type="submit" class="button">' . esc_html__('搜索', 'aksrt') . '</button>';
echo '<label class="screen-reader-text" for="jio-inv-search">' . esc_html__('搜索邀请码或标识', 'jio') . '</label>';
echo '<input type="search" id="jio-inv-search" name="s" value="' . esc_attr($s) . '" placeholder="' . esc_attr__('搜索邀请码或标识', 'jio') . '">';
echo '<button type="submit" class="button">' . esc_html__('搜索', 'jio') . '</button>';
if ($remark !== '') {
echo '<span class="aksrt-inv-filter-remark">' . esc_html(sprintf(__('标识:%s', 'aksrt'), $remark)) . '</span>';
echo '<span class="jio-inv-filter-remark">' . esc_html(sprintf(__('标识:%s', 'jio'), $remark)) . '</span>';
}
if ($s !== '' || $remark !== '' || $status !== 'all') {
echo '<a class="button-link" href="' . esc_url($this_url) . '">' . esc_html__('清除筛选', 'aksrt') . '</a>';
echo '<a class="button-link" href="' . esc_url($this_url) . '">' . esc_html__('清除筛选', 'jio') . '</a>';
}
echo '</form></div></div>';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" data-aksrt-inv-list'
. ' data-confirm="' . esc_attr__('确定删除选中的 %d 个邀请码及其使用记录?此操作不可撤销。', 'aksrt') . '">';
echo '<input type="hidden" name="action" value="aksrt_invite_delete"><input type="hidden" name="aksrt_nonce" value="' . esc_attr($nonce) . '">';
echo '<div class="aksrt-native-bulk aksrt-inv-bulk"><span class="aksrt-inv-selection" data-aksrt-inv-selection data-label="' . esc_attr__('已选 %d 项', 'aksrt') . '" aria-live="polite"></span>'
. '<label class="screen-reader-text" for="bulk-action-selector-top">' . esc_html__('批量操作', 'aksrt') . '</label>'
. '<select name="bulk_action" id="bulk-action-selector-top"><option value="-1">' . esc_html__('批量操作', 'aksrt') . '</option><option value="delete">' . esc_html__('删除', 'aksrt') . '</option></select>'
. '<button type="submit" class="button" data-aksrt-inv-bulk disabled>' . esc_html__('应用', 'aksrt') . '</button></div>';
echo '<div class="aksrt-native-table-wrap">';
echo '<table class="widefat fixed striped aksrt-inv-table"><thead><tr><td class="manage-column column-cb check-column">';
echo '<input type="checkbox" data-aksrt-inv-check-all aria-label="' . esc_attr__('全选本页邀请码', 'aksrt') . '"></td>';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" data-jio-inv-list'
. ' data-confirm="' . esc_attr__('确定删除选中的 %d 个邀请码及其使用记录?此操作不可撤销。', 'jio') . '">';
echo '<input type="hidden" name="action" value="jio_invite_delete"><input type="hidden" name="jio_nonce" value="' . esc_attr($nonce) . '">';
echo '<div class="jio-native-bulk jio-inv-bulk"><span class="jio-inv-selection" data-jio-inv-selection data-label="' . esc_attr__('已选 %d 项', 'jio') . '" aria-live="polite"></span>'
. '<label class="screen-reader-text" for="bulk-action-selector-top">' . esc_html__('批量操作', 'jio') . '</label>'
. '<select name="bulk_action" id="bulk-action-selector-top"><option value="-1">' . esc_html__('批量操作', 'jio') . '</option><option value="delete">' . esc_html__('删除', 'jio') . '</option></select>'
. '<button type="submit" class="button" data-jio-inv-bulk disabled>' . esc_html__('应用', 'jio') . '</button></div>';
echo '<div class="jio-native-table-wrap">';
echo '<table class="widefat fixed striped jio-inv-table"><thead><tr><td class="manage-column column-cb check-column">';
echo '<input type="checkbox" data-jio-inv-check-all aria-label="' . esc_attr__('全选本页邀请码', 'jio') . '"></td>';
$columns = [
'code' => __('邀请码', 'aksrt'), 'rewards' => __('使用奖励', 'aksrt'),
'created_at' => __('创建时间', 'aksrt'), 'updated_at' => __('更新时间', 'aksrt'),
'used_count' => __('使用状态', 'aksrt'), 'remark' => __('标识', 'aksrt'),
'code' => __('邀请码', 'jio'), 'rewards' => __('使用奖励', 'jio'),
'created_at' => __('创建时间', 'jio'), 'updated_at' => __('更新时间', 'jio'),
'used_count' => __('使用状态', 'jio'), 'remark' => __('标识', 'jio'),
];
foreach ($columns as $key => $label) {
$active = $key === $data['orderby'];
@@ -130,38 +130,38 @@ trait UsersMenusInviteCode {
$next = $active && $data['desc'] === 'ASC' ? 'DESC' : 'ASC';
$url = add_query_arg(['orderby' => $key, 'desc' => $next], $filter_url);
$icon = $active && $data['desc'] === 'ASC' ? 'dashicons-arrow-up-alt2' : 'dashicons-arrow-down-alt2';
echo '<a class="orderby-but' . ($active ? ' is-active' : '') . '" href="' . esc_url($url) . '" title="' . esc_attr($next === 'ASC' ? __('按此列升序排列', 'aksrt') : __('按此列降序排列', 'aksrt')) . '">'
echo '<a class="orderby-but' . ($active ? ' is-active' : '') . '" href="' . esc_url($url) . '" title="' . esc_attr($next === 'ASC' ? __('按此列升序排列', 'jio') : __('按此列降序排列', 'jio')) . '">'
. esc_html($label) . '<span class="dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span></a>';
}
echo '</th>';
}
echo '</tr></thead><tbody>';
if ($data['items'] === []) {
echo NativeUi::empty_row(7, __('暂无符合条件的邀请码', 'aksrt'), __('调整筛选条件,或点击“添加邀请码”生成、导入新的邀请码。', 'aksrt'));
echo NativeUi::empty_row(7, __('暂无符合条件的邀请码', 'jio'), __('调整筛选条件,或点击“添加邀请码”生成、导入新的邀请码。', 'jio'));
}
foreach ($data['items'] as $item) {
$used = (int) $item['used_count'];
$max_uses = (int) $item['max_uses'];
echo '<tr><th scope="row" class="check-column"><input type="checkbox" name="action_id[]" value="' . esc_attr((string) $item['id']) . '" aria-label="' . esc_attr(sprintf(__('选择邀请码 %s', 'aksrt'), $item['code'])) . '"></th>';
echo '<td><button type="button" class="aksrt-inv-code aksrt-native-mono" data-clipboard-text="' . esc_attr($item['code']) . '" aria-label="' . esc_attr(sprintf(__('复制邀请码 %s', 'aksrt'), $item['code'])) . '">' . esc_html($item['code']) . '<span class="dashicons dashicons-admin-page" aria-hidden="true"></span></button></td>';
echo '<tr><th scope="row" class="check-column"><input type="checkbox" name="action_id[]" value="' . esc_attr((string) $item['id']) . '" aria-label="' . esc_attr(sprintf(__('选择邀请码 %s', 'jio'), $item['code'])) . '"></th>';
echo '<td><button type="button" class="jio-inv-code jio-native-mono" data-clipboard-text="' . esc_attr($item['code']) . '" aria-label="' . esc_attr(sprintf(__('复制邀请码 %s', 'jio'), $item['code'])) . '">' . esc_html($item['code']) . '<span class="dashicons dashicons-admin-page" aria-hidden="true"></span></button></td>';
echo '<td>' . wp_kses((string) $item['reward_text'], ['br' => []]) . '</td>';
echo '<td class="aksrt-inv-date">' . esc_html($item['created_at']) . '</td><td class="aksrt-inv-date">' . esc_html($item['updated_at']) . '</td><td>';
$label = $used > 0 && $max_uses > 0 && $used < $max_uses ? sprintf(__('已使用 %1$d/%2$d', 'aksrt'), $used, $max_uses) : ($used > 0 ? __('已使用', 'aksrt') : __('未使用', 'aksrt'));
echo '<td class="jio-inv-date">' . esc_html($item['created_at']) . '</td><td class="jio-inv-date">' . esc_html($item['updated_at']) . '</td><td>';
$label = $used > 0 && $max_uses > 0 && $used < $max_uses ? sprintf(__('已使用 %1$d/%2$d', 'jio'), $used, $max_uses) : ($used > 0 ? __('已使用', 'jio') : __('未使用', 'jio'));
echo NativeUi::badge($label, $used > 0 ? 'neutral' : 'success');
foreach ((array) $item['recent_users'] as $user) {
echo '<div class="aksrt-inv-user"><a href="' . esc_url(get_edit_user_link((int) $user['user_id'])) . '">' . esc_html($user['name']) . '</a></div>';
echo '<div class="jio-inv-user"><a href="' . esc_url(get_edit_user_link((int) $user['user_id'])) . '">' . esc_html($user['name']) . '</a></div>';
}
echo '</td><td>';
if ($item['remark'] !== '') {
echo '<a href="' . esc_url(add_query_arg('remark', $item['remark'], $this_url)) . '">' . esc_html($item['remark']) . '</a>';
} else {
echo '<span class="aksrt-native-muted">—</span>';
echo '<span class="jio-native-muted">—</span>';
}
echo '</td></tr>';
}
echo '</tbody></table>';
NativeUi::table_wrap_close();
echo '<div class="tablenav bottom"><div class="tablenav-pages"><span class="displaying-num">' . esc_html(sprintf(__('共 %s 项', 'aksrt'), number_format_i18n((int) $data['total']))) . '</span>';
echo '<div class="tablenav bottom"><div class="tablenav-pages"><span class="displaying-num">' . esc_html(sprintf(__('共 %s 项', 'jio'), number_format_i18n((int) $data['total']))) . '</span>';
if ((int) $data['max_pages'] > 1) {
echo paginate_links([
'base' => add_query_arg('paged', '%#%', $filter_url), 'format' => '',
+49 -49
View File
@@ -2,91 +2,91 @@
/**
* Invite-code generation, import and export dialogs.
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
trait UsersMenusInviteForms {
private function render_invite_codes_add(string $nonce): void {
$this->invite_dialog_open('aksrt-inv-add', __('添加邀请码', 'aksrt'), __('批量生成新邀请码,或导入已有的邀请码数据。', 'aksrt'), 'post', 'aksrt_invite_add', $nonce);
echo '<fieldset class="aksrt-inv-modes"><legend class="screen-reader-text">' . esc_html__('添加方式', 'aksrt') . '</legend>';
foreach (['auto' => __('系统自动生成', 'aksrt'), 'import' => __('导入邀请码', 'aksrt')] as $value => $label) {
$this->invite_dialog_open('jio-inv-add', __('添加邀请码', 'jio'), __('批量生成新邀请码,或导入已有的邀请码数据。', 'jio'), 'post', 'jio_invite_add', $nonce);
echo '<fieldset class="jio-inv-modes"><legend class="screen-reader-text">' . esc_html__('添加方式', 'jio') . '</legend>';
foreach (['auto' => __('系统自动生成', 'jio'), 'import' => __('导入邀请码', 'jio')] as $value => $label) {
echo '<label><input type="radio" name="add_type" value="' . esc_attr($value) . '"' . checked($value, 'auto', false) . '><span>' . esc_html($label) . '</span></label>';
}
echo '</fieldset><fieldset class="aksrt-inv-pane" data-aksrt-inv-dep="add_type=auto"><legend class="screen-reader-text">' . esc_html__('自动生成设置', 'aksrt') . '</legend>';
echo '<div class="aksrt-native-form-grid">';
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html__('生成数量', 'aksrt') . '</span>'
echo '</fieldset><fieldset class="jio-inv-pane" data-jio-inv-dep="add_type=auto"><legend class="screen-reader-text">' . esc_html__('自动生成设置', 'jio') . '</legend>';
echo '<div class="jio-native-form-grid">';
echo '<label class="jio-native-field"><span class="jio-native-field__label">' . esc_html__('生成数量', 'jio') . '</span>'
. '<input type="number" name="auto_num" value="20" min="1" max="1000" step="1" required autofocus>'
. '<small class="aksrt-native-field__hint">' . esc_html__('单次可生成 1–1000 个。', 'aksrt') . '</small></label>';
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html__('标识', 'aksrt') . '</span>'
. '<small class="jio-native-field__hint">' . esc_html__('单次可生成 1–1000 个。', 'jio') . '</small></label>';
echo '<label class="jio-native-field"><span class="jio-native-field__label">' . esc_html__('标识', 'jio') . '</span>'
. '<input type="text" name="auto_remarks" value="' . esc_attr('invita_' . current_time('YmdHis')) . '" maxlength="191">'
. '<small class="aksrt-native-field__hint">' . esc_html__('为本批邀请码命名,方便查找与管理。', 'aksrt') . '</small></label></div>';
. '<small class="jio-native-field__hint">' . esc_html__('为本批邀请码命名,方便查找与管理。', 'jio') . '</small></label></div>';
$this->render_invite_reward_fields();
echo '<label class="aksrt-inv-advanced"><input type="checkbox" name="auto_top_s" value="1">' . esc_html__('自定义邀请码位数', 'aksrt') . '</label>';
echo '<fieldset class="aksrt-inv-pane aksrt-inv-length" data-aksrt-inv-dep="auto_top_s=1" hidden disabled><legend class="screen-reader-text">' . esc_html__('自定义位数', 'aksrt') . '</legend>';
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html__('邀请码位数', 'aksrt') . '</span>'
echo '<label class="jio-inv-advanced"><input type="checkbox" name="auto_top_s" value="1">' . esc_html__('自定义邀请码位数', 'jio') . '</label>';
echo '<fieldset class="jio-inv-pane jio-inv-length" data-jio-inv-dep="auto_top_s=1" hidden disabled><legend class="screen-reader-text">' . esc_html__('自定义位数', 'jio') . '</legend>';
echo '<label class="jio-native-field"><span class="jio-native-field__label">' . esc_html__('邀请码位数', 'jio') . '</span>'
. '<input type="number" name="auto_rand_password_limit" value="10" min="4" max="50" step="1" required>'
. '<small class="aksrt-native-field__hint">' . esc_html__('4–50 位;默认 10 位。位数过短容易重复。', 'aksrt') . '</small></label></fieldset></fieldset>';
. '<small class="jio-native-field__hint">' . esc_html__('4–50 位;默认 10 位。位数过短容易重复。', 'jio') . '</small></label></fieldset></fieldset>';
echo '<fieldset class="aksrt-inv-pane" data-aksrt-inv-dep="add_type=import" hidden disabled><legend class="screen-reader-text">' . esc_html__('导入设置', 'aksrt') . '</legend>';
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html__('邀请码数据', 'aksrt') . '</span>'
. '<textarea name="import_data" rows="7" class="code" required data-limit-error="' . esc_attr__('单次最多导入 1000 行,请拆分后重试。', 'aksrt') . '" placeholder="93FW0XAY 无 signup&#10;P86NPWKI points=10 signup" aria-describedby="aksrt-inv-import-hint"></textarea>'
. '<small class="aksrt-native-field__hint" id="aksrt-inv-import-hint">' . esc_html__('每行一个,格式为“邀请码 奖励 标识”,无奖励填“无”。单次最多 1000 条,空行会忽略,重复的邀请码不会导入。', 'aksrt') . '</small></label>';
echo '<label class="aksrt-native-field aksrt-inv-separator"><span class="aksrt-native-field__label">' . esc_html__('分隔符', 'aksrt') . '</span>'
echo '<fieldset class="jio-inv-pane" data-jio-inv-dep="add_type=import" hidden disabled><legend class="screen-reader-text">' . esc_html__('导入设置', 'jio') . '</legend>';
echo '<label class="jio-native-field"><span class="jio-native-field__label">' . esc_html__('邀请码数据', 'jio') . '</span>'
. '<textarea name="import_data" rows="7" class="code" required data-limit-error="' . esc_attr__('单次最多导入 1000 行,请拆分后重试。', 'jio') . '" placeholder="93FW0XAY 无 signup&#10;P86NPWKI points=10 signup" aria-describedby="jio-inv-import-hint"></textarea>'
. '<small class="jio-native-field__hint" id="jio-inv-import-hint">' . esc_html__('每行一个,格式为“邀请码 奖励 标识”,无奖励填“无”。单次最多 1000 条,空行会忽略,重复的邀请码不会导入。', 'jio') . '</small></label>';
echo '<label class="jio-native-field jio-inv-separator"><span class="jio-native-field__label">' . esc_html__('分隔符', 'jio') . '</span>'
. '<input type="text" name="import_division" value=" " maxlength="4">'
. '<small class="aksrt-native-field__hint">' . esc_html__('默认为空格,也可填写其他分隔符,例如 |。', 'aksrt') . '</small></label>';
echo '<details class="aksrt-inv-help"><summary>' . esc_html__('查看奖励格式与示例', 'aksrt') . '</summary><div>'
. '<p>' . esc_html__('奖励参数用 & 连接:level_integral 为经验值,points 为积分,balance 为余额(元),vip 为会员等级(1 或 2),vip_time 为会员天数。', 'aksrt') . '</p>'
. '<small class="jio-native-field__hint">' . esc_html__('默认为空格,也可填写其他分隔符,例如 |。', 'jio') . '</small></label>';
echo '<details class="jio-inv-help"><summary>' . esc_html__('查看奖励格式与示例', 'jio') . '</summary><div>'
. '<p>' . esc_html__('奖励参数用 & 连接:level_integral 为经验值,points 为积分,balance 为余额(元),vip 为会员等级(1 或 2),vip_time 为会员天数。', 'jio') . '</p>'
. '<pre>93FW0XAY 无 signup&#10;P86NPWKI points=10 signup&#10;AUF9D2B4 balance=30&amp;vip=2&amp;vip_time=5 signup</pre>'
. '<p>' . esc_html__('第三行示例:奖励 30 元余额和 5 天二级会员。标识可省略;导出的文本文件也可按相同分隔符重新导入。', 'aksrt') . '</p></div></details></fieldset>';
$this->invite_dialog_close(__('生成邀请码', 'aksrt'), ' data-aksrt-inv-add-submit data-auto-label="' . esc_attr__('生成邀请码', 'aksrt') . '" data-import-label="' . esc_attr__('导入邀请码', 'aksrt') . '" data-busy-label="' . esc_attr__('正在添加…', 'aksrt') . '"');
. '<p>' . esc_html__('第三行示例:奖励 30 元余额和 5 天二级会员。标识可省略;导出的文本文件也可按相同分隔符重新导入。', 'jio') . '</p></div></details></fieldset>';
$this->invite_dialog_close(__('生成邀请码', 'jio'), ' data-jio-inv-add-submit data-auto-label="' . esc_attr__('生成邀请码', 'jio') . '" data-import-label="' . esc_attr__('导入邀请码', 'jio') . '" data-busy-label="' . esc_attr__('正在添加…', 'jio') . '"');
}
private function render_invite_reward_fields(): void {
echo '<fieldset class="aksrt-inv-rewards"><legend>' . esc_html__('使用奖励', 'aksrt') . '</legend><p class="aksrt-inv-hint">' . esc_html__('用户使用邀请码注册后获得以下奖励;数值为 0 表示不赠送。', 'aksrt') . '</p>';
echo '<div class="aksrt-native-form-grid aksrt-inv-reward-grid">';
foreach (['level_integral' => __('经验值', 'aksrt'), 'points' => __('积分', 'aksrt'), 'balance' => __('余额(元)', 'aksrt')] as $name => $label) {
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html($label) . '</span><input type="number" name="' . esc_attr($name) . '" value="0" min="0" step="' . ($name === 'balance' ? '0.01' : '1') . '"></label>';
echo '<fieldset class="jio-inv-rewards"><legend>' . esc_html__('使用奖励', 'jio') . '</legend><p class="jio-inv-hint">' . esc_html__('用户使用邀请码注册后获得以下奖励;数值为 0 表示不赠送。', 'jio') . '</p>';
echo '<div class="jio-native-form-grid jio-inv-reward-grid">';
foreach (['level_integral' => __('经验值', 'jio'), 'points' => __('积分', 'jio'), 'balance' => __('余额(元)', 'jio')] as $name => $label) {
echo '<label class="jio-native-field"><span class="jio-native-field__label">' . esc_html($label) . '</span><input type="number" name="' . esc_attr($name) . '" value="0" min="0" step="' . ($name === 'balance' ? '0.01' : '1') . '"></label>';
}
echo '</div><fieldset class="aksrt-inv-vip"><legend>' . esc_html__('VIP 会员', 'aksrt') . '</legend><div class="aksrt-inv-radios">';
foreach (['0' => __('不赠送', 'aksrt'), '1' => __('一级会员', 'aksrt'), '2' => __('二级会员', 'aksrt')] as $value => $label) {
echo '</div><fieldset class="jio-inv-vip"><legend>' . esc_html__('VIP 会员', 'jio') . '</legend><div class="jio-inv-radios">';
foreach (['0' => __('不赠送', 'jio'), '1' => __('一级会员', 'jio'), '2' => __('二级会员', 'jio')] as $value => $label) {
echo '<label><input type="radio" name="vip" value="' . esc_attr($value) . '"' . checked((string) $value, '0', false) . '>' . esc_html($label) . '</label>';
}
echo '</div></fieldset><fieldset class="aksrt-inv-pane aksrt-inv-vip-time" data-aksrt-inv-dep="vip=1|2" hidden disabled><legend class="screen-reader-text">' . esc_html__('会员时长', 'aksrt') . '</legend>';
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html__('会员赠送时长(天)', 'aksrt') . '</span><input type="number" name="vip_time" value="30" min="1" step="1" required></label></fieldset></fieldset>';
echo '</div></fieldset><fieldset class="jio-inv-pane jio-inv-vip-time" data-jio-inv-dep="vip=1|2" hidden disabled><legend class="screen-reader-text">' . esc_html__('会员时长', 'jio') . '</legend>';
echo '<label class="jio-native-field"><span class="jio-native-field__label">' . esc_html__('会员赠送时长(天)', 'jio') . '</span><input type="number" name="vip_time" value="30" min="1" step="1" required></label></fieldset></fieldset>';
}
private function render_invite_codes_export(string $nonce): void {
$status = in_array(($_GET['status'] ?? 'all'), ['all', 'unused', 'used'], true) ? (string) $_GET['status'] : 'all';
$this->invite_dialog_open('aksrt-inv-export', __('导出邀请码', 'aksrt'), __('选择使用状态与文件格式,导出后可在本地保存或分发。', 'aksrt'), 'get', 'aksrt_invite_export', $nonce);
echo '<fieldset class="aksrt-inv-options"><legend>' . esc_html__('使用状态', 'aksrt') . '</legend><div class="aksrt-inv-radios">';
foreach (['all' => __('全部', 'aksrt'), 'unused' => __('未使用', 'aksrt'), 'used' => __('已使用', 'aksrt')] as $value => $label) {
$this->invite_dialog_open('jio-inv-export', __('导出邀请码', 'jio'), __('选择使用状态与文件格式,导出后可在本地保存或分发。', 'jio'), 'get', 'jio_invite_export', $nonce);
echo '<fieldset class="jio-inv-options"><legend>' . esc_html__('使用状态', 'jio') . '</legend><div class="jio-inv-radios">';
foreach (['all' => __('全部', 'jio'), 'unused' => __('未使用', 'jio'), 'used' => __('已使用', 'jio')] as $value => $label) {
echo '<label><input type="radio" name="status" value="' . esc_attr($value) . '"' . checked($value, $status, false) . '>' . esc_html($label) . '</label>';
}
echo '</div></fieldset><fieldset class="aksrt-inv-options"><legend>' . esc_html__('导出格式', 'aksrt') . '</legend><div class="aksrt-inv-formats">';
foreach (['csv' => [__('Excel 表格(.csv)', 'aksrt'), __('包含奖励、时间、使用状态与标识,适合整理统计。', 'aksrt')], 'text' => [__('文本文档(.txt)', 'aksrt'), __('每行一个邀请码,包含奖励与标识,可再次导入。', 'aksrt')]] as $value => [$label, $hint]) {
echo '</div></fieldset><fieldset class="jio-inv-options"><legend>' . esc_html__('导出格式', 'jio') . '</legend><div class="jio-inv-formats">';
foreach (['csv' => [__('Excel 表格(.csv)', 'jio'), __('包含奖励、时间、使用状态与标识,适合整理统计。', 'jio')], 'text' => [__('文本文档(.txt)', 'jio'), __('每行一个邀请码,包含奖励与标识,可再次导入。', 'jio')]] as $value => [$label, $hint]) {
echo '<label><input type="radio" name="export_format" value="' . esc_attr($value) . '"' . checked($value, 'csv', false) . '><span><strong>' . esc_html($label) . '</strong><small>' . esc_html($hint) . '</small></span></label>';
}
echo '</div></fieldset><fieldset class="aksrt-inv-pane" data-aksrt-inv-dep="export_format=text" hidden disabled><legend class="screen-reader-text">' . esc_html__('文本设置', 'aksrt') . '</legend>';
echo '<label class="aksrt-native-field aksrt-inv-separator"><span class="aksrt-native-field__label">' . esc_html__('分隔符', 'aksrt') . '</span><input type="text" name="text_division" value=" " maxlength="4">'
. '<small class="aksrt-native-field__hint">' . esc_html__('默认为空格;重新导入时请使用相同分隔符。', 'aksrt') . '</small></label></fieldset>';
echo '<p class="aksrt-native-callout aksrt-inv-export-note">' . esc_html__('每次最多导出最新的 10,000 条记录。导出仅按上方使用状态筛选,不受列表关键词、标识或勾选项影响。', 'aksrt') . '</p>';
$this->invite_dialog_close(__('导出并下载', 'aksrt'));
echo '</div></fieldset><fieldset class="jio-inv-pane" data-jio-inv-dep="export_format=text" hidden disabled><legend class="screen-reader-text">' . esc_html__('文本设置', 'jio') . '</legend>';
echo '<label class="jio-native-field jio-inv-separator"><span class="jio-native-field__label">' . esc_html__('分隔符', 'jio') . '</span><input type="text" name="text_division" value=" " maxlength="4">'
. '<small class="jio-native-field__hint">' . esc_html__('默认为空格;重新导入时请使用相同分隔符。', 'jio') . '</small></label></fieldset>';
echo '<p class="jio-native-callout jio-inv-export-note">' . esc_html__('每次最多导出最新的 10,000 条记录。导出仅按上方使用状态筛选,不受列表关键词、标识或勾选项影响。', 'jio') . '</p>';
$this->invite_dialog_close(__('导出并下载', 'jio'));
}
private function invite_dialog_open(string $id, string $title, string $description, string $method, string $action, string $nonce): void {
echo '<dialog class="aksrt-inv-dialog" id="' . esc_attr($id) . '" aria-labelledby="' . esc_attr($id) . '-title" aria-describedby="' . esc_attr($id) . '-description">';
echo '<form method="' . esc_attr($method) . '" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-inv-dialog__frame">';
echo '<input type="hidden" name="action" value="' . esc_attr($action) . '"><input type="hidden" name="aksrt_nonce" value="' . esc_attr($nonce) . '">';
echo '<header class="aksrt-inv-dialog__head"><div><h2 id="' . esc_attr($id) . '-title">' . esc_html($title) . '</h2><p id="' . esc_attr($id) . '-description">' . esc_html($description) . '</p></div>';
echo '<button type="button" class="aksrt-inv-dialog__close" data-aksrt-dialog-close aria-label="' . esc_attr__('关闭弹窗', 'aksrt') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></header><div class="aksrt-inv-dialog__body">';
echo '<dialog class="jio-inv-dialog" id="' . esc_attr($id) . '" aria-labelledby="' . esc_attr($id) . '-title" aria-describedby="' . esc_attr($id) . '-description">';
echo '<form method="' . esc_attr($method) . '" action="' . esc_url(admin_url('admin-post.php')) . '" class="jio-inv-dialog__frame">';
echo '<input type="hidden" name="action" value="' . esc_attr($action) . '"><input type="hidden" name="jio_nonce" value="' . esc_attr($nonce) . '">';
echo '<header class="jio-inv-dialog__head"><div><h2 id="' . esc_attr($id) . '-title">' . esc_html($title) . '</h2><p id="' . esc_attr($id) . '-description">' . esc_html($description) . '</p></div>';
echo '<button type="button" class="jio-inv-dialog__close" data-jio-dialog-close aria-label="' . esc_attr__('关闭弹窗', 'jio') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></header><div class="jio-inv-dialog__body">';
}
private function invite_dialog_close(string $label, string $attributes = ''): void {
echo '</div><footer class="aksrt-inv-dialog__foot"><button type="button" class="button" data-aksrt-dialog-close>' . esc_html__('取消', 'aksrt') . '</button>';
echo '</div><footer class="jio-inv-dialog__foot"><button type="button" class="button" data-jio-dialog-close>' . esc_html__('取消', 'jio') . '</button>';
echo '<button type="submit" class="button button-primary"' . $attributes . '>' . esc_html($label) . '</button></footer></form></dialog>';
}
}
+47 -47
View File
@@ -3,13 +3,13 @@
* 消息管理后台页:页面编排、带权限 / nonce 的写操作、筛选与反馈。
* 展示由同目录的 MessagesList / MessagesCompose traits 承载;查询与投递下沉消息服务。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Messages\AdminBroadcast;
use AKSRT\Messages\NotificationAdminQuery;
use Jio\Messages\AdminBroadcast;
use Jio\Messages\NotificationAdminQuery;
defined('ABSPATH') || exit;
@@ -18,8 +18,8 @@ trait UsersMenusMessages {
use UsersMenusMessagesList;
use UsersMenusMessagesCompose;
private const MESSAGE_PAGE_SLUG = 'aksrt_messages';
private const MESSAGE_COMPOSE_ID = 'aksrt-msg-compose';
private const MESSAGE_PAGE_SLUG = 'jio_messages';
private const MESSAGE_COMPOSE_ID = 'jio-msg-compose';
public function enqueue_message_assets(string $hook): void {
if ($hook !== 'users_page_' . self::MESSAGE_PAGE_SLUG) {
@@ -27,11 +27,11 @@ trait UsersMenusMessages {
}
$css = '/assets/css/admin/messages.css';
$js = '/assets/js/admin-messages.js';
if (is_file(AKSRT_DIR . $css)) {
wp_enqueue_style('aksrt-admin-messages', AKSRT_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(AKSRT_DIR . $css));
if (is_file(JIO_DIR . $css)) {
wp_enqueue_style('jio-admin-messages', JIO_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(JIO_DIR . $css));
}
if (is_file(AKSRT_DIR . $js)) {
wp_enqueue_script('aksrt-admin-messages', AKSRT_URI . $js, [], (string) filemtime(AKSRT_DIR . $js), true);
if (is_file(JIO_DIR . $js)) {
wp_enqueue_script('jio-admin-messages', JIO_URI . $js, [], (string) filemtime(JIO_DIR . $js), true);
}
}
@@ -40,21 +40,21 @@ trait UsersMenusMessages {
$filters = $this->message_filters();
$paged = max(1, absint($_GET['paged'] ?? 1));
NativeUi::page_open('aksrt-messages-admin');
NativeUi::page_open('jio-messages-admin');
NativeUi::header(
__('消息管理', 'aksrt'),
__('消息管理', 'jio'),
'',
'dashicons-email-alt',
[
[
'label' => __('发送系统消息', 'aksrt'),
'label' => __('发送系统消息', 'jio'),
'dialog' => self::MESSAGE_COMPOSE_ID,
'icon' => 'dashicons-megaphone',
'primary' => true,
],
[
'label' => __('通知配置', 'aksrt'),
'url' => admin_url('admin.php?page=aksrt-settings#/users/notifications'),
'label' => __('通知配置', 'jio'),
'url' => admin_url('admin.php?page=jio-settings#/users/notifications'),
'icon' => 'dashicons-admin-generic',
],
]
@@ -62,7 +62,7 @@ trait UsersMenusMessages {
$this->render_message_notices();
if (NotificationAdminQuery::table() === '') {
echo '<div class="notice notice-warning"><p>' . esc_html__('通知表不可用。', 'aksrt') . '</p></div>';
echo '<div class="notice notice-warning"><p>' . esc_html__('通知表不可用。', 'jio') . '</p></div>';
NativeUi::page_close();
return;
}
@@ -71,8 +71,8 @@ trait UsersMenusMessages {
$result = NotificationAdminQuery::paginate($query, $paged);
$this->render_message_overview();
NativeUi::card_open(__('站内通知', 'aksrt'), '', true, 'aksrt-messages-admin__card');
echo '<div class="aksrt-messages-admin__toolbar">';
NativeUi::card_open(__('站内通知', 'jio'), '', true, 'jio-messages-admin__card');
echo '<div class="jio-messages-admin__toolbar">';
echo $this->message_views_html($filters, NotificationAdminQuery::status_counts($query)); // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
$this->render_message_filter_form($filters);
echo '</div>';
@@ -94,7 +94,7 @@ trait UsersMenusMessages {
public function handle_send_message(): void {
$this->ensure_cap();
check_admin_referer('aksrt_users_send_message');
check_admin_referer('jio_users_send_message');
$mode = sanitize_key((string) wp_unslash($_POST['recipient_mode'] ?? 'users'));
$title = trim(sanitize_text_field((string) wp_unslash($_POST['title'] ?? '')));
@@ -102,16 +102,16 @@ trait UsersMenusMessages {
$target_url = esc_url_raw(trim((string) wp_unslash($_POST['target_url'] ?? '')));
if (!in_array($mode, ['users', 'role'], true)) {
$this->message_fail(__('请选择有效的收件人模式。', 'aksrt'));
$this->message_fail(__('请选择有效的收件人模式。', 'jio'));
}
if ($content === '') {
$this->message_fail(__('请填写消息内容。', 'aksrt'));
$this->message_fail(__('请填写消息内容。', 'jio'));
}
if (mb_strlen($content) > AdminBroadcast::CONTENT_MAX) {
$this->message_fail(sprintf(__('消息内容不能超过 %d 字。', 'aksrt'), AdminBroadcast::CONTENT_MAX));
$this->message_fail(sprintf(__('消息内容不能超过 %d 字。', 'jio'), AdminBroadcast::CONTENT_MAX));
}
if (mb_strlen($title) > AdminBroadcast::TITLE_MAX) {
$this->message_fail(sprintf(__('消息标题不能超过 %d 字。', 'aksrt'), AdminBroadcast::TITLE_MAX));
$this->message_fail(sprintf(__('消息标题不能超过 %d 字。', 'jio'), AdminBroadcast::TITLE_MAX));
}
$title = mb_substr($title !== '' ? $title : $content, 0, AdminBroadcast::TITLE_MAX);
@@ -128,28 +128,28 @@ trait UsersMenusMessages {
$this->message_fail($ids->get_error_message());
}
if ($ids === []) {
$this->message_fail(__('没有匹配到任何有效收件人。', 'aksrt'));
$this->message_fail(__('没有匹配到任何有效收件人。', 'jio'));
}
// 确认绑定角色与人数;页面加载后人数变化时拒绝投递,避免按过期预览群发。
if ($mode === 'role' && (
sanitize_key((string) wp_unslash($_POST['confirmed_role'] ?? '')) !== $role
|| absint($_POST['confirmed_count'] ?? 0) !== count($ids)
)) {
$this->message_fail(__('收件人范围尚未确认或人数已变化,请重新确认后发送。', 'aksrt'));
$this->message_fail(__('收件人范围尚未确认或人数已变化,请重新确认后发送。', 'jio'));
}
$sent = AdminBroadcast::send($ids, $title, $content, $target_url);
$this->message_redirect([
'aksrt_msg' => $sent['sent'] > 0 ? 'sent' : 'send_failed',
'aksrt_msg_n' => $sent['sent'],
'aksrt_msg_f' => $sent['failed'],
'aksrt_msg_u' => $unknown,
'jio_msg' => $sent['sent'] > 0 ? 'sent' : 'send_failed',
'jio_msg_n' => $sent['sent'],
'jio_msg_f' => $sent['failed'],
'jio_msg_u' => $unknown,
]);
}
public function handle_bulk_messages(): void {
$this->ensure_cap();
check_admin_referer('aksrt_users_bulk_messages');
check_admin_referer('jio_users_bulk_messages');
$op = sanitize_key((string) wp_unslash($_POST['bulk_action'] ?? ''));
$single_id = absint($_POST['message_id'] ?? 0);
@@ -161,46 +161,46 @@ trait UsersMenusMessages {
}
if (!in_array($op, ['delete', 'mark_read'], true) || array_filter($ids) === []) {
$this->message_redirect(array_merge($back, ['aksrt_msg' => 'bulk_none']));
$this->message_redirect(array_merge($back, ['jio_msg' => 'bulk_none']));
}
$count = $op === 'delete' ? NotificationAdminQuery::delete($ids) : NotificationAdminQuery::mark_read($ids);
$this->message_redirect(array_merge($back, [
'aksrt_msg' => $op === 'delete' ? 'deleted' : 'marked',
'aksrt_msg_n' => $count,
'jio_msg' => $op === 'delete' ? 'deleted' : 'marked',
'jio_msg_n' => $count,
]));
}
private function render_message_notices(): void {
$code = sanitize_key((string) wp_unslash($_GET['aksrt_msg'] ?? ''));
$n = absint($_GET['aksrt_msg_n'] ?? 0);
$code = sanitize_key((string) wp_unslash($_GET['jio_msg'] ?? ''));
$n = absint($_GET['jio_msg_n'] ?? 0);
$error = false;
switch ($code) {
case 'sent':
$text = sprintf(__('已发送 %d 条消息。', 'aksrt'), $n);
$failed = absint($_GET['aksrt_msg_f'] ?? 0);
$unknown = absint($_GET['aksrt_msg_u'] ?? 0);
$text = sprintf(__('已发送 %d 条消息。', 'jio'), $n);
$failed = absint($_GET['jio_msg_f'] ?? 0);
$unknown = absint($_GET['jio_msg_u'] ?? 0);
if ($failed > 0) {
$text .= ' ' . sprintf(__('投递失败 %d 条。', 'aksrt'), $failed);
$text .= ' ' . sprintf(__('投递失败 %d 条。', 'jio'), $failed);
}
if ($unknown > 0) {
$text .= ' ' . sprintf(__('%d 个收件人未找到,已跳过。', 'aksrt'), $unknown);
$text .= ' ' . sprintf(__('%d 个收件人未找到,已跳过。', 'jio'), $unknown);
}
break;
case 'send_failed':
$error = true;
$text = isset($_GET['aksrt_msg_error'])
? sanitize_text_field((string) wp_unslash($_GET['aksrt_msg_error']))
: __('消息发送失败。', 'aksrt');
$text = isset($_GET['jio_msg_error'])
? sanitize_text_field((string) wp_unslash($_GET['jio_msg_error']))
: __('消息发送失败。', 'jio');
break;
case 'deleted':
$text = sprintf(__('已删除 %d 条消息。', 'aksrt'), $n);
$text = sprintf(__('已删除 %d 条消息。', 'jio'), $n);
break;
case 'marked':
$text = sprintf(__('已将 %d 条消息标记为已读。', 'aksrt'), $n);
$text = sprintf(__('已将 %d 条消息标记为已读。', 'jio'), $n);
break;
case 'bulk_none':
$error = true;
$text = __('请先勾选消息并选择操作。', 'aksrt');
$text = __('请先勾选消息并选择操作。', 'jio');
break;
default:
return;
@@ -258,7 +258,7 @@ trait UsersMenusMessages {
}
private function message_fail(string $error): void {
$this->message_redirect(['aksrt_msg' => 'send_failed', 'aksrt_msg_error' => $error]);
$this->message_redirect(['jio_msg' => 'send_failed', 'jio_msg_error' => $error]);
}
private function message_redirect(array $args): void {
+54 -54
View File
@@ -2,108 +2,108 @@
/**
* 消息管理弹窗:发送表单、只读详情、删除与角色群发共用的二次确认。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Messages\AdminBroadcast;
use Jio\Messages\AdminBroadcast;
defined('ABSPATH') || exit;
trait UsersMenusMessagesCompose {
private function render_message_compose(): void {
$open = sanitize_key((string) wp_unslash($_GET['aksrt_msg'] ?? '')) === 'send_failed';
$open = sanitize_key((string) wp_unslash($_GET['jio_msg'] ?? '')) === 'send_failed';
printf(
'<dialog class="aksrt-msg-dialog" id="%s" aria-labelledby="%s-title"%s>',
esc_attr(self::MESSAGE_COMPOSE_ID), esc_attr(self::MESSAGE_COMPOSE_ID), $open ? ' data-aksrt-dialog-autoopen' : ''
'<dialog class="jio-msg-dialog" id="%s" aria-labelledby="%s-title"%s>',
esc_attr(self::MESSAGE_COMPOSE_ID), esc_attr(self::MESSAGE_COMPOSE_ID), $open ? ' data-jio-dialog-autoopen' : ''
);
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-msg-dialog__frame aksrt-messages-admin__compose" data-aksrt-msg-compose';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="jio-msg-dialog__frame jio-messages-admin__compose" data-jio-msg-compose';
$strings = [
'count-error' => __('最多输入 {count} 字。', 'aksrt'),
'role-hint' => __('将发送给 {count} 位用户;提交前需再次确认。', 'aksrt'),
'role-title' => __('确认按角色群发', 'aksrt'),
'role-confirm' => __('将向「{role}」的 {count} 位用户发送系统消息,并按通知配置触发提醒。请确认收件人范围与消息内容。', 'aksrt'),
'role-send' => __('确认发送', 'aksrt'),
'count-error' => __('最多输入 {count} 字。', 'jio'),
'role-hint' => __('将发送给 {count} 位用户;提交前需再次确认。', 'jio'),
'role-title' => __('确认按角色群发', 'jio'),
'role-confirm' => __('将向「{role}」的 {count} 位用户发送系统消息,并按通知配置触发提醒。请确认收件人范围与消息内容。', 'jio'),
'role-send' => __('确认发送', 'jio'),
];
foreach ($strings as $key => $text) {
echo ' data-i18n-' . esc_attr($key) . '="' . esc_attr($text) . '"';
}
echo '>';
echo '<input type="hidden" name="action" value="aksrt_users_send_message" />';
echo '<input type="hidden" name="action" value="jio_users_send_message" />';
echo '<input type="hidden" name="confirmed_role" value="" /><input type="hidden" name="confirmed_count" value="" />';
wp_nonce_field('aksrt_users_send_message');
$this->render_message_dialog_head(self::MESSAGE_COMPOSE_ID, __('发送系统消息', 'aksrt'), 'dashicons-megaphone');
echo '<div class="aksrt-msg-dialog__body">';
wp_nonce_field('jio_users_send_message');
$this->render_message_dialog_head(self::MESSAGE_COMPOSE_ID, __('发送系统消息', 'jio'), 'dashicons-megaphone');
echo '<div class="jio-msg-dialog__body">';
if ($open) {
$error = sanitize_text_field((string) wp_unslash($_GET['aksrt_msg_error'] ?? __('消息发送失败,请检查后重试。', 'aksrt')));
echo '<p class="aksrt-native-callout is-danger">' . esc_html($error) . '</p>';
$error = sanitize_text_field((string) wp_unslash($_GET['jio_msg_error'] ?? __('消息发送失败,请检查后重试。', 'jio')));
echo '<p class="jio-native-callout is-danger">' . esc_html($error) . '</p>';
}
echo '<fieldset class="aksrt-msg-modes"><legend class="aksrt-native-field__label">' . esc_html__('收件人', 'aksrt') . '</legend>';
foreach (['users' => [__('指定用户', 'aksrt'), 'dashicons-admin-users'], 'role' => [__('按角色群发', 'aksrt'), 'dashicons-groups']] as $value => [$label, $icon]) {
echo '<label class="aksrt-msg-mode"><input type="radio" name="recipient_mode" value="' . esc_attr($value) . '"' . checked($value, 'users', false) . ' />';
echo '<fieldset class="jio-msg-modes"><legend class="jio-native-field__label">' . esc_html__('收件人', 'jio') . '</legend>';
foreach (['users' => [__('指定用户', 'jio'), 'dashicons-admin-users'], 'role' => [__('按角色群发', 'jio'), 'dashicons-groups']] as $value => [$label, $icon]) {
echo '<label class="jio-msg-mode"><input type="radio" name="recipient_mode" value="' . esc_attr($value) . '"' . checked($value, 'users', false) . ' />';
echo '<span><span class="dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span>' . esc_html($label) . '</span></label>';
}
echo '</fieldset><div class="aksrt-native-form-grid">';
echo '</fieldset><div class="jio-native-form-grid">';
printf(
'<label class="aksrt-native-field is-wide" data-aksrt-msg-pane="users"><span class="aksrt-native-field__label">%s</span><textarea name="users" rows="2" required placeholder="%s"></textarea><small class="aksrt-native-field__hint">%s</small></label>',
esc_html__('用户', 'aksrt'), esc_attr__('用户 ID / 用户名 / 邮箱,逗号或换行分隔', 'aksrt'), esc_html(sprintf(__('单次最多 %d 位。', 'aksrt'), AdminBroadcast::MAX_USERS))
'<label class="jio-native-field is-wide" data-jio-msg-pane="users"><span class="jio-native-field__label">%s</span><textarea name="users" rows="2" required placeholder="%s"></textarea><small class="jio-native-field__hint">%s</small></label>',
esc_html__('用户', 'jio'), esc_attr__('用户 ID / 用户名 / 邮箱,逗号或换行分隔', 'jio'), esc_html(sprintf(__('单次最多 %d 位。', 'jio'), AdminBroadcast::MAX_USERS))
);
echo '<label class="aksrt-native-field is-wide" data-aksrt-msg-pane="role" hidden><span class="aksrt-native-field__label">' . esc_html__('角色', 'aksrt') . '</span>';
echo '<select name="role" required disabled><option value="" selected disabled>' . esc_html__('请选择角色', 'aksrt') . '</option>';
echo '<label class="jio-native-field is-wide" data-jio-msg-pane="role" hidden><span class="jio-native-field__label">' . esc_html__('角色', 'jio') . '</span>';
echo '<select name="role" required disabled><option value="" selected disabled>' . esc_html__('请选择角色', 'jio') . '</option>';
$counts = count_users('time');
foreach (wp_roles()->get_names() as $role => $name) {
$count = (int) ($counts['avail_roles'][$role] ?? 0);
$label = translate_user_role($name);
$disabled = $count === 0 || $count > AdminBroadcast::MAX_ROLE_USERS;
$suffix = $count > AdminBroadcast::MAX_ROLE_USERS ? __('(超过发送上限)', 'aksrt') : ($count === 0 ? __('(暂无用户)', 'aksrt') : '');
$suffix = $count > AdminBroadcast::MAX_ROLE_USERS ? __('(超过发送上限)', 'jio') : ($count === 0 ? __('(暂无用户)', 'jio') : '');
printf(
'<option value="%s" data-count="%d" data-label="%s"%s>%s</option>',
esc_attr($role), $count, esc_attr($label), $disabled ? ' disabled' : '',
esc_html(sprintf(__('%1$s · %2$s 人', 'aksrt'), $label, number_format_i18n($count)) . $suffix)
esc_html(sprintf(__('%1$s · %2$s 人', 'jio'), $label, number_format_i18n($count)) . $suffix)
);
}
echo '</select><small class="aksrt-native-field__hint">' . esc_html(sprintf(__('单个角色最多 %d 人;空角色和超限角色不可选。', 'aksrt'), AdminBroadcast::MAX_ROLE_USERS)) . '</small>';
echo '<small class="aksrt-native-field__hint" data-aksrt-msg-role-hint aria-live="polite"></small></label>';
echo '</select><small class="jio-native-field__hint">' . esc_html(sprintf(__('单个角色最多 %d 人;空角色和超限角色不可选。', 'jio'), AdminBroadcast::MAX_ROLE_USERS)) . '</small>';
echo '<small class="jio-native-field__hint" data-jio-msg-role-hint aria-live="polite"></small></label>';
printf(
'<label class="aksrt-native-field"><span class="aksrt-native-field__label">%s</span><input type="text" name="title" data-count-limit="%d" aria-describedby="aksrt-msg-title-count" placeholder="%s" /><small class="aksrt-msg-counter" id="aksrt-msg-title-count" data-count-for="title"></small></label>',
esc_html__('标题', 'aksrt'), AdminBroadcast::TITLE_MAX, esc_attr__('留空则取内容开头', 'aksrt')
'<label class="jio-native-field"><span class="jio-native-field__label">%s</span><input type="text" name="title" data-count-limit="%d" aria-describedby="jio-msg-title-count" placeholder="%s" /><small class="jio-msg-counter" id="jio-msg-title-count" data-count-for="title"></small></label>',
esc_html__('标题', 'jio'), AdminBroadcast::TITLE_MAX, esc_attr__('留空则取内容开头', 'jio')
);
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html__('跳转链接(可选)', 'aksrt') . '</span><input type="url" name="target_url" placeholder="https://" /></label>';
echo '<label class="jio-native-field"><span class="jio-native-field__label">' . esc_html__('跳转链接(可选)', 'jio') . '</span><input type="url" name="target_url" placeholder="https://" /></label>';
printf(
'<label class="aksrt-native-field is-wide"><span class="aksrt-native-field__label">%s</span><textarea name="content" rows="5" data-count-limit="%d" required aria-describedby="aksrt-msg-content-count"></textarea><small class="aksrt-msg-counter" id="aksrt-msg-content-count" data-count-for="content"></small></label>',
esc_html__('内容', 'aksrt'), AdminBroadcast::CONTENT_MAX
'<label class="jio-native-field is-wide"><span class="jio-native-field__label">%s</span><textarea name="content" rows="5" data-count-limit="%d" required aria-describedby="jio-msg-content-count"></textarea><small class="jio-msg-counter" id="jio-msg-content-count" data-count-for="content"></small></label>',
esc_html__('内容', 'jio'), AdminBroadcast::CONTENT_MAX
);
echo '</div><p class="aksrt-native-muted aksrt-msg-dialog__note">' . esc_html__('以「系统通知」类型逐人投递;邮件 / 公众号提醒按通知配置同步触发。', 'aksrt') . '</p></div>';
echo '<footer class="aksrt-msg-dialog__foot"><button type="button" class="button" data-aksrt-dialog-close>' . esc_html__('取消', 'aksrt') . '</button>';
echo '<button type="submit" class="button button-primary" data-aksrt-msg-send>' . esc_html__('发送', 'aksrt') . '</button></footer></form></dialog>';
echo '</div><p class="jio-native-muted jio-msg-dialog__note">' . esc_html__('以「系统通知」类型逐人投递;邮件 / 公众号提醒按通知配置同步触发。', 'jio') . '</p></div>';
echo '<footer class="jio-msg-dialog__foot"><button type="button" class="button" data-jio-dialog-close>' . esc_html__('取消', 'jio') . '</button>';
echo '<button type="submit" class="button button-primary" data-jio-msg-send>' . esc_html__('发送', 'jio') . '</button></footer></form></dialog>';
}
private function render_message_dialogs(): void {
echo '<dialog class="aksrt-msg-dialog" id="aksrt-msg-detail" aria-labelledby="aksrt-msg-detail-title"><div class="aksrt-msg-dialog__frame">';
$this->render_message_dialog_head('aksrt-msg-detail', __('消息详情', 'aksrt'), 'dashicons-email-alt');
echo '<div class="aksrt-msg-dialog__body" data-aksrt-msg-detail-body></div>';
echo '<footer class="aksrt-msg-dialog__foot"><button type="button" class="button" data-aksrt-dialog-close>' . esc_html__('关闭', 'aksrt') . '</button></footer></div></dialog>';
echo '<dialog class="jio-msg-dialog" id="jio-msg-detail" aria-labelledby="jio-msg-detail-title"><div class="jio-msg-dialog__frame">';
$this->render_message_dialog_head('jio-msg-detail', __('消息详情', 'jio'), 'dashicons-email-alt');
echo '<div class="jio-msg-dialog__body" data-jio-msg-detail-body></div>';
echo '<footer class="jio-msg-dialog__foot"><button type="button" class="button" data-jio-dialog-close>' . esc_html__('关闭', 'jio') . '</button></footer></div></dialog>';
echo '<dialog class="aksrt-msg-dialog aksrt-msg-dialog--confirm" id="aksrt-msg-confirm" aria-labelledby="aksrt-msg-confirm-title" aria-describedby="aksrt-msg-confirm-text"';
echo '<dialog class="jio-msg-dialog jio-msg-dialog--confirm" id="jio-msg-confirm" aria-labelledby="jio-msg-confirm-title" aria-describedby="jio-msg-confirm-text"';
foreach ([
'delete-title' => __('删除消息', 'aksrt'),
'delete-text' => __('确定删除 {count} 条消息?删除后用户收件箱中也将不再显示,此操作不可撤销。', 'aksrt'),
'delete-send' => __('确认删除', 'aksrt'),
'delete-title' => __('删除消息', 'jio'),
'delete-text' => __('确定删除 {count} 条消息?删除后用户收件箱中也将不再显示,此操作不可撤销。', 'jio'),
'delete-send' => __('确认删除', 'jio'),
] as $key => $text) {
echo ' data-i18n-' . esc_attr($key) . '="' . esc_attr($text) . '"';
}
echo '><div class="aksrt-msg-dialog__frame">';
$this->render_message_dialog_head('aksrt-msg-confirm', __('确认操作', 'aksrt'), 'dashicons-warning');
echo '<div class="aksrt-msg-dialog__body"><p id="aksrt-msg-confirm-text"></p></div>';
echo '<footer class="aksrt-msg-dialog__foot"><button type="button" class="button" data-aksrt-dialog-close autofocus>' . esc_html__('取消', 'aksrt') . '</button>';
echo '<button type="button" class="button button-primary" data-aksrt-msg-confirm>' . esc_html__('确认', 'aksrt') . '</button></footer></div></dialog>';
echo '><div class="jio-msg-dialog__frame">';
$this->render_message_dialog_head('jio-msg-confirm', __('确认操作', 'jio'), 'dashicons-warning');
echo '<div class="jio-msg-dialog__body"><p id="jio-msg-confirm-text"></p></div>';
echo '<footer class="jio-msg-dialog__foot"><button type="button" class="button" data-jio-dialog-close autofocus>' . esc_html__('取消', 'jio') . '</button>';
echo '<button type="button" class="button button-primary" data-jio-msg-confirm>' . esc_html__('确认', 'jio') . '</button></footer></div></dialog>';
}
private function render_message_dialog_head(string $id, string $title, string $icon): void {
echo '<header class="aksrt-msg-dialog__head"><h2 id="' . esc_attr($id) . '-title" class="aksrt-msg-dialog__title">';
echo '<span class="dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span><span data-aksrt-dialog-title>' . esc_html($title) . '</span></h2>';
echo '<button type="button" class="aksrt-msg-dialog__close" data-aksrt-dialog-close aria-label="' . esc_attr__('关闭', 'aksrt') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></header>';
echo '<header class="jio-msg-dialog__head"><h2 id="' . esc_attr($id) . '-title" class="jio-msg-dialog__title">';
echo '<span class="dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span><span data-jio-dialog-title>' . esc_html($title) . '</span></h2>';
echo '<button type="button" class="jio-msg-dialog__close" data-jio-dialog-close aria-label="' . esc_attr__('关闭', 'jio') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></header>';
}
}
+67 -67
View File
@@ -2,13 +2,13 @@
/**
* 消息管理展示:概览、筛选、列表与按行预渲染的只读详情。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Messages\MessageTypes;
use AKSRT\Messages\NotificationAdminQuery;
use Jio\Messages\MessageTypes;
use Jio\Messages\NotificationAdminQuery;
defined('ABSPATH') || exit;
@@ -17,22 +17,22 @@ trait UsersMenusMessagesList {
private function render_message_overview(): void {
$stats = NotificationAdminQuery::overview();
NativeUi::stats([
['label' => __('消息总数', 'aksrt'), 'value' => number_format_i18n($stats['total']), 'icon' => 'dashicons-email-alt', 'tone' => 'info', 'url' => $this->message_page_url()],
['label' => __('未读', 'aksrt'), 'value' => number_format_i18n($stats['unread']), 'icon' => 'dashicons-marker', 'tone' => 'danger', 'url' => $this->message_page_url(['status' => 'unread'])],
['label' => __('今日新增', 'aksrt'), 'value' => number_format_i18n($stats['today']), 'icon' => 'dashicons-calendar-alt', 'tone' => 'success', 'url' => $this->message_page_url(['period' => 'today'])],
['label' => __('涉及用户', 'aksrt'), 'value' => number_format_i18n($stats['recipients']), 'icon' => 'dashicons-groups', 'tone' => 'primary', 'url' => admin_url('users.php'), 'hint' => __('前往用户管理', 'aksrt')],
['label' => __('消息总数', 'jio'), 'value' => number_format_i18n($stats['total']), 'icon' => 'dashicons-email-alt', 'tone' => 'info', 'url' => $this->message_page_url()],
['label' => __('未读', 'jio'), 'value' => number_format_i18n($stats['unread']), 'icon' => 'dashicons-marker', 'tone' => 'danger', 'url' => $this->message_page_url(['status' => 'unread'])],
['label' => __('今日新增', 'jio'), 'value' => number_format_i18n($stats['today']), 'icon' => 'dashicons-calendar-alt', 'tone' => 'success', 'url' => $this->message_page_url(['period' => 'today'])],
['label' => __('涉及用户', 'jio'), 'value' => number_format_i18n($stats['recipients']), 'icon' => 'dashicons-groups', 'tone' => 'primary', 'url' => admin_url('users.php'), 'hint' => __('前往用户管理', 'jio')],
]);
}
private function message_views_html(array $filters, array $counts): string {
$views = [
'' => [__('全部', 'aksrt'), $counts['all']],
'unread' => [__('未读', 'aksrt'), $counts['unread']],
'read' => [__('已读', 'aksrt'), $counts['read']],
'' => [__('全部', 'jio'), $counts['all']],
'unread' => [__('未读', 'jio'), $counts['unread']],
'read' => [__('已读', 'jio'), $counts['read']],
];
$args = $this->message_filter_args($filters);
unset($args['status']);
$html = '<ul class="subsubsub" aria-label="' . esc_attr__('消息状态', 'aksrt') . '">';
$html = '<ul class="subsubsub" aria-label="' . esc_attr__('消息状态', 'jio') . '">';
foreach ($views as $key => [$label, $count]) {
$html .= sprintf(
'<li><a href="%s"%s>%s <span class="count">(%s)</span></a></li>',
@@ -46,33 +46,33 @@ trait UsersMenusMessagesList {
}
private function render_message_filter_form(array $filters): void {
echo '<form method="get" action="' . esc_url(admin_url('users.php')) . '" class="aksrt-native-filters">';
echo '<form method="get" action="' . esc_url(admin_url('users.php')) . '" class="jio-native-filters">';
echo '<input type="hidden" name="page" value="' . esc_attr(self::MESSAGE_PAGE_SLUG) . '" />';
if ($filters['status'] !== '') {
echo '<input type="hidden" name="status" value="' . esc_attr($filters['status']) . '" />';
}
echo '<label class="screen-reader-text" for="aksrt-msg-filter-type">' . esc_html__('按类型筛选', 'aksrt') . '</label>';
echo '<select name="type" id="aksrt-msg-filter-type"><option value="">' . esc_html__('全部类型', 'aksrt') . '</option>';
echo '<label class="screen-reader-text" for="jio-msg-filter-type">' . esc_html__('按类型筛选', 'jio') . '</label>';
echo '<select name="type" id="jio-msg-filter-type"><option value="">' . esc_html__('全部类型', 'jio') . '</option>';
foreach (NotificationAdminQuery::types() as $type) {
printf('<option value="%s"%s>%s</option>', esc_attr($type), selected($filters['type'], $type, false), esc_html(MessageTypes::label($type)));
}
echo '</select>';
echo '<label class="screen-reader-text" for="aksrt-msg-filter-period">' . esc_html__('按发送时间筛选', 'aksrt') . '</label>';
echo '<select name="period" id="aksrt-msg-filter-period"><option value="">' . esc_html__('全部时间', 'aksrt') . '</option>';
echo '<option value="today"' . selected($filters['period'], 'today', false) . '>' . esc_html__('今日新增', 'aksrt') . '</option></select>';
echo '<label class="screen-reader-text" for="jio-msg-filter-period">' . esc_html__('按发送时间筛选', 'jio') . '</label>';
echo '<select name="period" id="jio-msg-filter-period"><option value="">' . esc_html__('全部时间', 'jio') . '</option>';
echo '<option value="today"' . selected($filters['period'], 'today', false) . '>' . esc_html__('今日新增', 'jio') . '</option></select>';
printf(
'<label class="screen-reader-text" for="aksrt-msg-filter-user">%s</label><input type="text" id="aksrt-msg-filter-user" name="user" value="%s" placeholder="%s" />',
esc_html__('按收件人筛选', 'aksrt'), esc_attr($filters['user']), esc_attr__('收件人 ID / 用户名 / 邮箱', 'aksrt')
'<label class="screen-reader-text" for="jio-msg-filter-user">%s</label><input type="text" id="jio-msg-filter-user" name="user" value="%s" placeholder="%s" />',
esc_html__('按收件人筛选', 'jio'), esc_attr($filters['user']), esc_attr__('收件人 ID / 用户名 / 邮箱', 'jio')
);
printf(
'<label class="screen-reader-text" for="aksrt-msg-filter-s">%s</label><input type="search" id="aksrt-msg-filter-s" name="s" value="%s" placeholder="%s" />',
esc_html__('搜索消息', 'aksrt'), esc_attr($filters['s']), esc_attr__('标题 / 内容', 'aksrt')
'<label class="screen-reader-text" for="jio-msg-filter-s">%s</label><input type="search" id="jio-msg-filter-s" name="s" value="%s" placeholder="%s" />',
esc_html__('搜索消息', 'jio'), esc_attr($filters['s']), esc_attr__('标题 / 内容', 'jio')
);
submit_button(__('筛选', 'aksrt'), '', '', false);
submit_button(__('筛选', 'jio'), '', '', false);
$other = $filters;
unset($other['status']);
if ($this->message_filter_args($other) !== []) {
echo '<a class="button-link" href="' . esc_url($this->message_page_url($filters['status'] === '' ? [] : ['status' => $filters['status']])) . '">' . esc_html__('清除筛选', 'aksrt') . '</a>';
echo '<a class="button-link" href="' . esc_url($this->message_page_url($filters['status'] === '' ? [] : ['status' => $filters['status']])) . '">' . esc_html__('清除筛选', 'jio') . '</a>';
}
echo '</form>';
}
@@ -82,29 +82,29 @@ trait UsersMenusMessagesList {
if ($user_ids !== []) {
cache_users($user_ids);
}
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-messages-admin__list" data-aksrt-msg-list>';
echo '<input type="hidden" name="action" value="aksrt_users_bulk_messages" />';
wp_nonce_field('aksrt_users_bulk_messages');
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="jio-messages-admin__list" data-jio-msg-list>';
echo '<input type="hidden" name="action" value="jio_users_bulk_messages" />';
wp_nonce_field('jio_users_bulk_messages');
foreach ($this->message_filter_args($filters) as $key => $value) {
printf('<input type="hidden" name="%s" value="%s" />', esc_attr($key), esc_attr((string) $value));
}
echo '<input type="hidden" name="paged" value="' . esc_attr((string) $page) . '" />';
echo '<input type="hidden" name="bulk_action" value="" /><input type="hidden" name="message_id" value="" />';
echo '<div class="aksrt-messages-admin__bulk">';
echo '<span class="aksrt-messages-admin__selection" data-aksrt-msg-selection role="status" aria-live="polite" data-label="' . esc_attr__('已选 %d 条', 'aksrt') . '"></span>';
echo '<button type="button" class="button" data-aksrt-msg-action="mark_read" disabled>' . esc_html__('标记已读', 'aksrt') . '</button>';
echo '<button type="button" class="button aksrt-messages-admin__danger" data-aksrt-msg-action="delete" disabled>' . esc_html__('删除', 'aksrt') . '</button>';
echo '</div><div class="aksrt-native-table-wrap">';
echo '<table class="wp-list-table widefat fixed striped aksrt-messages-admin__table"><thead><tr>';
echo '<td class="manage-column column-cb check-column"><label class="screen-reader-text" for="aksrt-msg-check-all">' . esc_html__('全选本页消息', 'aksrt') . '</label><input id="aksrt-msg-check-all" type="checkbox" data-aksrt-msg-check-all /></td>';
foreach (['title' => __('消息', 'aksrt'), 'type' => __('类型', 'aksrt'), 'user' => __('收件人', 'aksrt'), 'read' => __('状态', 'aksrt'), 'time' => __('发送时间', 'aksrt'), 'actions' => __('操作', 'aksrt')] as $column => $label) {
echo '<div class="jio-messages-admin__bulk">';
echo '<span class="jio-messages-admin__selection" data-jio-msg-selection role="status" aria-live="polite" data-label="' . esc_attr__('已选 %d 条', 'jio') . '"></span>';
echo '<button type="button" class="button" data-jio-msg-action="mark_read" disabled>' . esc_html__('标记已读', 'jio') . '</button>';
echo '<button type="button" class="button jio-messages-admin__danger" data-jio-msg-action="delete" disabled>' . esc_html__('删除', 'jio') . '</button>';
echo '</div><div class="jio-native-table-wrap">';
echo '<table class="wp-list-table widefat fixed striped jio-messages-admin__table"><thead><tr>';
echo '<td class="manage-column column-cb check-column"><label class="screen-reader-text" for="jio-msg-check-all">' . esc_html__('全选本页消息', 'jio') . '</label><input id="jio-msg-check-all" type="checkbox" data-jio-msg-check-all /></td>';
foreach (['title' => __('消息', 'jio'), 'type' => __('类型', 'jio'), 'user' => __('收件人', 'jio'), 'read' => __('状态', 'jio'), 'time' => __('发送时间', 'jio'), 'actions' => __('操作', 'jio')] as $column => $label) {
echo '<th scope="col" class="column-' . esc_attr($column) . '">' . esc_html($label) . '</th>';
}
echo '</tr></thead><tbody>';
if ($items === []) {
$hint = $user_missing ? __('没有找到该收件人。', 'aksrt')
: ($this->message_filter_args($filters) === [] ? __('站内还没有任何通知。', 'aksrt') : __('当前筛选条件下没有消息。', 'aksrt'));
echo NativeUi::empty_row(7, __('暂无消息', 'aksrt'), $hint); // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
$hint = $user_missing ? __('没有找到该收件人。', 'jio')
: ($this->message_filter_args($filters) === [] ? __('站内还没有任何通知。', 'jio') : __('当前筛选条件下没有消息。', 'jio'));
echo NativeUi::empty_row(7, __('暂无消息', 'jio'), $hint); // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
}
foreach ($items as $row) {
$this->render_message_row($row, $filters);
@@ -120,77 +120,77 @@ trait UsersMenusMessagesList {
$unread = empty($row['is_read']);
echo '<tr class="' . ($unread ? 'is-unread' : 'is-read') . '">';
printf(
'<th scope="row" class="check-column"><label class="screen-reader-text" for="aksrt-msg-%1$d">%2$s</label><input id="aksrt-msg-%1$d" type="checkbox" name="ids[]" value="%1$d" data-aksrt-msg-check /></th>',
$id, esc_html(sprintf(__('选择消息 #%d', 'aksrt'), $id))
'<th scope="row" class="check-column"><label class="screen-reader-text" for="jio-msg-%1$d">%2$s</label><input id="jio-msg-%1$d" type="checkbox" name="ids[]" value="%1$d" data-jio-msg-check /></th>',
$id, esc_html(sprintf(__('选择消息 #%d', 'jio'), $id))
);
echo '<td class="column-title"><strong class="row-title">' . esc_html($title) . '</strong>';
if ($desc !== '' && $desc !== $title) {
echo '<div class="aksrt-messages-admin__desc">' . esc_html(mb_strimwidth($desc, 0, 160, '…')) . '</div>';
echo '<div class="jio-messages-admin__desc">' . esc_html(mb_strimwidth($desc, 0, 160, '…')) . '</div>';
}
echo '<div class="aksrt-messages-admin__sub"><code>#' . esc_html((string) $id) . '</code></div>';
echo '<div class="jio-messages-admin__sub"><code>#' . esc_html((string) $id) . '</code></div>';
$this->render_message_detail_template($row);
echo '</td>';
printf(
'<td class="column-type"><a class="aksrt-msg-badge is-%s" href="%s">%s</a></td>',
'<td class="column-type"><a class="jio-msg-badge is-%s" href="%s">%s</a></td>',
esc_attr(MessageTypes::tone($type)), esc_url($this->message_page_url(array_merge($this->message_filter_args($filters), ['type' => $type]))), esc_html(MessageTypes::label($type))
);
echo '<td class="column-user">' . $this->message_user_cell((int) $row['user_id'], $filters) . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
$read_at = (string) ($row['read_at'] ?? '');
printf(
'<td class="column-read"><span class="aksrt-msg-state %s"%s>%s</span></td>',
'<td class="column-read"><span class="jio-msg-state %s"%s>%s</span></td>',
$unread ? 'is-unread' : 'is-read',
!$unread && $read_at !== '' ? ' title="' . esc_attr(sprintf(__('阅读于 %s', 'aksrt'), $this->message_time($read_at))) . '"' : '',
$unread ? esc_html__('未读', 'aksrt') : esc_html__('已读', 'aksrt')
!$unread && $read_at !== '' ? ' title="' . esc_attr(sprintf(__('阅读于 %s', 'jio'), $this->message_time($read_at))) . '"' : '',
$unread ? esc_html__('未读', 'jio') : esc_html__('已读', 'jio')
);
$created = (string) $row['created_at'];
$ts = $created !== '' ? strtotime($created . ' UTC') : false;
printf(
'<td class="column-time"><time datetime="%s">%s</time>%s</td>',
esc_attr($ts ? gmdate('c', $ts) : ''), esc_html($this->message_time($created)),
$ts ? '<div class="aksrt-messages-admin__sub">' . esc_html(sprintf(__('%s前', 'aksrt'), human_time_diff($ts))) . '</div>' : ''
$ts ? '<div class="jio-messages-admin__sub">' . esc_html(sprintf(__('%s前', 'jio'), human_time_diff($ts))) . '</div>' : ''
);
echo '<td class="column-actions"><div class="aksrt-msg-row-actions">';
printf('<button type="button" class="button-link" data-aksrt-msg-view="%d" aria-haspopup="dialog" aria-controls="aksrt-msg-detail">%s</button>', $id, esc_html__('查看', 'aksrt'));
echo '<td class="column-actions"><div class="jio-msg-row-actions">';
printf('<button type="button" class="button-link" data-jio-msg-view="%d" aria-haspopup="dialog" aria-controls="jio-msg-detail">%s</button>', $id, esc_html__('查看', 'jio'));
if ($unread) {
printf('<button type="button" class="button-link" data-aksrt-msg-action="mark_read" data-id="%d">%s</button>', $id, esc_html__('标记已读', 'aksrt'));
printf('<button type="button" class="button-link" data-jio-msg-action="mark_read" data-id="%d">%s</button>', $id, esc_html__('标记已读', 'jio'));
}
printf('<button type="button" class="button-link button-link-delete" data-aksrt-msg-action="delete" data-id="%d">%s</button>', $id, esc_html__('删除', 'aksrt'));
printf('<button type="button" class="button-link button-link-delete" data-jio-msg-action="delete" data-id="%d">%s</button>', $id, esc_html__('删除', 'jio'));
echo '</div></td></tr>';
}
private function render_message_detail_template(array $row): void {
$user = get_userdata((int) $row['user_id']);
$recipient = $user ? $user->display_name : __('已删除用户', 'aksrt');
echo '<template id="aksrt-msg-detail-' . esc_attr((string) $row['id']) . '">';
echo '<h3 class="aksrt-msg-detail__title">' . esc_html((string) $row['title']) . '</h3><dl class="aksrt-msg-detail__meta">';
$recipient = $user ? $user->display_name : __('已删除用户', 'jio');
echo '<template id="jio-msg-detail-' . esc_attr((string) $row['id']) . '">';
echo '<h3 class="jio-msg-detail__title">' . esc_html((string) $row['title']) . '</h3><dl class="jio-msg-detail__meta">';
$meta = [
__('消息编号', 'aksrt') => '#' . $row['id'],
__('收件人', 'aksrt') => $recipient . ' · #' . $row['user_id'],
__('消息类型', 'aksrt') => MessageTypes::label((string) $row['type']),
__('发送时间', 'aksrt') => $this->message_time((string) $row['created_at']),
__('阅读状态', 'aksrt') => empty($row['is_read']) ? __('未读', 'aksrt') : __('已读', 'aksrt'),
__('阅读时间', 'aksrt') => empty($row['is_read']) || empty($row['read_at']) ? '—' : $this->message_time((string) $row['read_at']),
__('消息编号', 'jio') => '#' . $row['id'],
__('收件人', 'jio') => $recipient . ' · #' . $row['user_id'],
__('消息类型', 'jio') => MessageTypes::label((string) $row['type']),
__('发送时间', 'jio') => $this->message_time((string) $row['created_at']),
__('阅读状态', 'jio') => empty($row['is_read']) ? __('未读', 'jio') : __('已读', 'jio'),
__('阅读时间', 'jio') => empty($row['is_read']) || empty($row['read_at']) ? '—' : $this->message_time((string) $row['read_at']),
];
foreach ($meta as $label => $value) {
echo '<div><dt>' . esc_html($label) . '</dt><dd>' . esc_html($value) . '</dd></div>';
}
echo '</dl><h4>' . esc_html__('完整内容', 'aksrt') . '</h4><div class="aksrt-msg-detail__content">' . esc_html((string) $row['description']) . '</div>';
echo '</dl><h4>' . esc_html__('完整内容', 'jio') . '</h4><div class="jio-msg-detail__content">' . esc_html((string) $row['description']) . '</div>';
if (!empty($row['target_url'])) {
printf('<a class="aksrt-msg-detail__link" href="%s" target="_blank" rel="noopener noreferrer">%s<span class="screen-reader-text">%s</span></a>', esc_url((string) $row['target_url']), esc_html((string) $row['target_url']), esc_html__('(在新窗口打开)', 'aksrt'));
printf('<a class="jio-msg-detail__link" href="%s" target="_blank" rel="noopener noreferrer">%s<span class="screen-reader-text">%s</span></a>', esc_url((string) $row['target_url']), esc_html((string) $row['target_url']), esc_html__('(在新窗口打开)', 'jio'));
}
echo '<p class="aksrt-native-field__hint">' . esc_html__('查看详情不会改变收件人的已读状态。', 'aksrt') . '</p></template>';
echo '<p class="jio-native-field__hint">' . esc_html__('查看详情不会改变收件人的已读状态。', 'jio') . '</p></template>';
}
private function message_user_cell(int $user_id, array $filters): string {
$user = $user_id > 0 ? get_userdata($user_id) : false;
if (!$user) {
return '<span class="aksrt-messages-admin__muted">' . esc_html(sprintf(__('已删除用户 #%d', 'aksrt'), $user_id)) . '</span>';
return '<span class="jio-messages-admin__muted">' . esc_html(sprintf(__('已删除用户 #%d', 'jio'), $user_id)) . '</span>';
}
$avatar = function_exists('aksrt_get_avatar_url') ? aksrt_get_avatar_url($user_id, 48) : get_avatar_url($user_id, ['size' => 48]);
$avatar = function_exists('jio_get_avatar_url') ? jio_get_avatar_url($user_id, 48) : get_avatar_url($user_id, ['size' => 48]);
$filter_url = $this->message_page_url(array_merge($this->message_filter_args($filters), ['user' => (string) $user_id]));
return '<a class="aksrt-msg-user" href="' . esc_url($filter_url) . '" title="' . esc_attr__('只看该用户', 'aksrt') . '">'
return '<a class="jio-msg-user" href="' . esc_url($filter_url) . '" title="' . esc_attr__('只看该用户', 'jio') . '">'
. '<img src="' . esc_url($avatar) . '" width="24" height="24" alt="" />'
. '<span>' . esc_html($user->display_name) . '</span></a>'
. '<div class="aksrt-messages-admin__sub">#' . esc_html((string) $user_id) . ' · <a href="' . esc_url(get_edit_user_link($user_id)) . '">' . esc_html__('编辑', 'aksrt') . '</a></div>';
. '<div class="jio-messages-admin__sub">#' . esc_html((string) $user_id) . ' · <a href="' . esc_url(get_edit_user_link($user_id)) . '">' . esc_html__('编辑', 'jio') . '</a></div>';
}
}
+27 -27
View File
@@ -3,13 +3,13 @@
* 工单管理:列表(含代客提交弹窗)与处理工作区(?id=)两个页内视图。
* 写操作统一使用 TicketRestAdmin;此处只负责服务端渲染。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Ticket\TicketRepository;
use AKSRT\Ticket\TicketStatusService;
use Jio\Ticket\TicketRepository;
use Jio\Ticket\TicketStatusService;
defined('ABSPATH') || exit;
@@ -19,7 +19,7 @@ trait UsersMenusTicketsCenter {
use UsersMenusTicketsDetail;
use UsersMenusTicketsCreate;
private const TICKET_PAGE_SLUG = 'aksrt_tickets';
private const TICKET_PAGE_SLUG = 'jio_tickets';
private const TICKET_PER_PAGE = 20;
private function count_pending_tickets(): int {
@@ -35,18 +35,18 @@ trait UsersMenusTicketsCenter {
}
$css = '/assets/css/admin/tickets.css';
$js = '/assets/js/admin-tickets.js';
wp_enqueue_style('aksrt-admin-tickets', AKSRT_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(AKSRT_DIR . $css));
wp_enqueue_script('aksrt-admin-tickets', AKSRT_URI . $js, [], (string) filemtime(AKSRT_DIR . $js), true);
wp_localize_script('aksrt-admin-tickets', 'AKSRTAdminTickets', [
wp_enqueue_style('jio-admin-tickets', JIO_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(JIO_DIR . $css));
wp_enqueue_script('jio-admin-tickets', JIO_URI . $js, [], (string) filemtime(JIO_DIR . $js), true);
wp_localize_script('jio-admin-tickets', 'JioAdminTickets', [
'restNonce' => wp_create_nonce('wp_rest'),
'currentUser' => get_current_user_id(),
'strings' => [
'saving' => __('正在保存…', 'aksrt'),
'saveFailed' => __('保存失败,请检查填写内容后重试。', 'aksrt'),
'uncertain' => __('连接中断,提交结果尚未确认。请先重新加载核对,避免重复提交。', 'aksrt'),
'required' => __('请填写内容,不能只包含空格。', 'aksrt'),
'tooLong' => __('插入后将超过字数限制,请先精简回复内容。', 'aksrt'),
'discard' => __('有尚未保存的内容。关闭后将丢弃,是否继续?', 'aksrt'),
'saving' => __('正在保存…', 'jio'),
'saveFailed' => __('保存失败,请检查填写内容后重试。', 'jio'),
'uncertain' => __('连接中断,提交结果尚未确认。请先重新加载核对,避免重复提交。', 'jio'),
'required' => __('请填写内容,不能只包含空格。', 'jio'),
'tooLong' => __('插入后将超过字数限制,请先精简回复内容。', 'jio'),
'discard' => __('有尚未保存的内容。关闭后将丢弃,是否继续?', 'jio'),
],
]);
}
@@ -56,7 +56,7 @@ trait UsersMenusTicketsCenter {
$filters = $this->ticket_filters($_GET);
$paged = max(1, absint($_GET['paged'] ?? 1));
$id = absint($_GET['id'] ?? 0);
NativeUi::page_open('aksrt-tickets-admin');
NativeUi::page_open('jio-tickets-admin');
if ($id > 0) {
$this->render_ticket_detail_page($id, $this->ticket_context($filters, $paged));
} else {
@@ -85,12 +85,12 @@ trait UsersMenusTicketsCenter {
/** 写操作成功后脚本跳转回工作区并带上结果码,这里转成原生通知。 */
private function render_ticket_notice(): void {
$code = isset($_GET['aksrt_ticket_msg']) && is_scalar($_GET['aksrt_ticket_msg']) ? sanitize_key((string) wp_unslash($_GET['aksrt_ticket_msg'])) : '';
$code = isset($_GET['jio_ticket_msg']) && is_scalar($_GET['jio_ticket_msg']) ? sanitize_key((string) wp_unslash($_GET['jio_ticket_msg'])) : '';
$map = [
'created' => __('工单已创建,用户将收到通知。', 'aksrt'),
'reply' => __('回复已发送,用户将收到通知。', 'aksrt'),
'status' => __('工单信息已保存。', 'aksrt'),
'note' => __('内部备注已添加,仅管理员可见。', 'aksrt'),
'created' => __('工单已创建,用户将收到通知。', 'jio'),
'reply' => __('回复已发送,用户将收到通知。', 'jio'),
'status' => __('工单信息已保存。', 'jio'),
'note' => __('内部备注已添加,仅管理员可见。', 'jio'),
];
if (isset($map[$code])) {
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html($map[$code]) . '</p></div>';
@@ -99,21 +99,21 @@ trait UsersMenusTicketsCenter {
/** 提交中与失败提示;失败且结果不确定时显示「重新加载」。 */
private function ticket_feedback(): string {
return '<div class="aksrt-ticket-feedback" hidden data-aksrt-ticket-feedback><p role="status" aria-live="polite" aria-atomic="true"></p>'
. '<button type="button" class="button" data-aksrt-ticket-reload hidden>' . esc_html__('重新加载', 'aksrt') . '</button></div>';
return '<div class="jio-ticket-feedback" hidden data-jio-ticket-feedback><p role="status" aria-live="polite" aria-atomic="true"></p>'
. '<button type="button" class="button" data-jio-ticket-reload hidden>' . esc_html__('重新加载', 'jio') . '</button></div>';
}
/** 代客提交与内部备注弹窗共用的头部:标题、说明与关闭按钮。 */
private function ticket_dialog_head(string $key, string $title, string $description): string {
return '<header class="aksrt-ticket-dialog__head"><div><h2 id="aksrt-ticket-' . esc_attr($key) . '-title" tabindex="-1">' . esc_html($title) . '</h2>'
. '<p id="aksrt-ticket-' . esc_attr($key) . '-description">' . esc_html($description) . '</p></div>'
. '<button type="button" class="aksrt-ticket-dialog__close" data-aksrt-dialog-close aria-label="' . esc_attr__('关闭弹窗', 'aksrt') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></header>';
return '<header class="jio-ticket-dialog__head"><div><h2 id="jio-ticket-' . esc_attr($key) . '-title" tabindex="-1">' . esc_html($title) . '</h2>'
. '<p id="jio-ticket-' . esc_attr($key) . '-description">' . esc_html($description) . '</p></div>'
. '<button type="button" class="jio-ticket-dialog__close" data-jio-dialog-close aria-label="' . esc_attr__('关闭弹窗', 'jio') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></header>';
}
/** 所有表单只提交到现有 REST 写路由,不保留 admin_post 分支。 */
private function ticket_form_open(string $action, int $id, string $class = ''): string {
$path = $action === 'create' ? '' : '/' . $id . '/' . ($action === 'note' ? 'notes' : $action);
return '<form method="post" action="' . esc_url(rest_url('aksrt/v1/admin/tickets' . $path)) . '"'
. ' class="' . esc_attr($class) . '" data-aksrt-ticket-form="' . esc_attr($action) . '">';
return '<form method="post" action="' . esc_url(rest_url('jio/v1/admin/tickets' . $path)) . '"'
. ' class="' . esc_attr($class) . '" data-jio-ticket-form="' . esc_attr($action) . '">';
}
}
+22 -22
View File
@@ -1,48 +1,48 @@
<?php
/** 代客提交工单:列表页上的原生 <dialog>,创建成功后跳转到新工单的工作区。@package AKSRT */
/** 代客提交工单:列表页上的原生 <dialog>,创建成功后跳转到新工单的工作区。@package Jio */
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Ticket\TicketSettings;
use AKSRT\Ticket\TicketTypeService;
use Jio\Ticket\TicketSettings;
use Jio\Ticket\TicketTypeService;
defined('ABSPATH') || exit;
trait UsersMenusTicketsCreate {
private const TICKET_CREATE_DIALOG = 'aksrt-ticket-create-dialog';
private const TICKET_CREATE_DIALOG = 'jio-ticket-create-dialog';
private function render_ticket_create_dialog(): void {
$types = TicketTypeService::all();
echo '<dialog id="' . esc_attr(self::TICKET_CREATE_DIALOG) . '" class="aksrt-ticket-dialog" aria-labelledby="aksrt-ticket-create-title" aria-describedby="aksrt-ticket-create-description">';
echo $types === [] ? '<div class="aksrt-ticket-dialog__frame">' : $this->ticket_form_open('create', 0, 'aksrt-ticket-dialog__frame'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $this->ticket_dialog_head('create', __('代客提交工单', 'aksrt'), __('为指定用户创建工单,创建后由你跟进,用户会收到通知。', 'aksrt')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="aksrt-ticket-dialog__body">' . $this->ticket_feedback(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<dialog id="' . esc_attr(self::TICKET_CREATE_DIALOG) . '" class="jio-ticket-dialog" aria-labelledby="jio-ticket-create-title" aria-describedby="jio-ticket-create-description">';
echo $types === [] ? '<div class="jio-ticket-dialog__frame">' : $this->ticket_form_open('create', 0, 'jio-ticket-dialog__frame'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $this->ticket_dialog_head('create', __('代客提交工单', 'jio'), __('为指定用户创建工单,创建后由你跟进,用户会收到通知。', 'jio')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="jio-ticket-dialog__body">' . $this->ticket_feedback(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ($types === []) {
echo NativeUi::empty_state(__('尚未配置工单部门', 'aksrt'), __('请先添加部门,再为用户创建工单。', 'aksrt')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<p><a class="button" href="' . esc_url(admin_url('admin.php?page=aksrt-settings#/users/ticket')) . '">' . esc_html__('前往工单配置', 'aksrt') . '</a></p>';
echo NativeUi::empty_state(__('尚未配置工单部门', 'jio'), __('请先添加部门,再为用户创建工单。', 'jio')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<p><a class="button" href="' . esc_url(admin_url('admin.php?page=jio-settings#/users/ticket')) . '">' . esc_html__('前往工单配置', 'jio') . '</a></p>';
} else {
echo '<div class="aksrt-native-form-grid">';
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html__('用户 ID', 'aksrt') . '</span><input type="number" name="user_id" min="1" step="1" required placeholder="' . esc_attr__('填写工单归属用户的 ID', 'aksrt') . '"></label>';
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html__('工单部门', 'aksrt') . '</span><select name="ticket_type_id" required><option value="">' . esc_html__('请选择部门', 'aksrt') . '</option>';
echo '<div class="jio-native-form-grid">';
echo '<label class="jio-native-field"><span class="jio-native-field__label">' . esc_html__('用户 ID', 'jio') . '</span><input type="number" name="user_id" min="1" step="1" required placeholder="' . esc_attr__('填写工单归属用户的 ID', 'jio') . '"></label>';
echo '<label class="jio-native-field"><span class="jio-native-field__label">' . esc_html__('工单部门', 'jio') . '</span><select name="ticket_type_id" required><option value="">' . esc_html__('请选择部门', 'jio') . '</option>';
foreach ($types as $type) {
echo '<option value="' . (int) $type['id'] . '">' . esc_html((string) $type['name']) . '</option>';
}
echo '</select></label>';
echo '<label class="aksrt-native-field is-wide"><span class="aksrt-native-field__label">' . esc_html__('工单标题', 'aksrt') . '</span><input type="text" name="title" maxlength="' . TicketSettings::title_max() . '" required placeholder="' . esc_attr__('简要说明用户遇到的问题', 'aksrt') . '"></label>';
echo '<label class="jio-native-field is-wide"><span class="jio-native-field__label">' . esc_html__('工单标题', 'jio') . '</span><input type="text" name="title" maxlength="' . TicketSettings::title_max() . '" required placeholder="' . esc_attr__('简要说明用户遇到的问题', 'jio') . '"></label>';
if (TicketSettings::order_link_enabled()) {
echo '<label class="aksrt-native-field is-wide"><span class="aksrt-native-field__label">' . esc_html__('关联订单(可选)', 'aksrt') . '</span><input type="text" name="order_number" placeholder="' . esc_attr__('填写订单号', 'aksrt') . '"></label>';
echo '<label class="jio-native-field is-wide"><span class="jio-native-field__label">' . esc_html__('关联订单(可选)', 'jio') . '</span><input type="text" name="order_number" placeholder="' . esc_attr__('填写订单号', 'jio') . '"></label>';
}
echo '<label class="aksrt-native-field is-wide"><span class="aksrt-native-field__label">' . esc_html__('问题描述', 'aksrt') . '</span><textarea id="aksrt-ticket-create-content" name="content" rows="7" maxlength="' . TicketSettings::content_max() . '" required data-aksrt-ticket-editor aria-describedby="aksrt-ticket-create-count" placeholder="' . esc_attr__('描述问题、发生时间和已尝试的处理方式', 'aksrt') . '"></textarea>';
echo '<span id="aksrt-ticket-create-count" class="aksrt-ticket-counter" data-aksrt-ticket-counter="aksrt-ticket-create-content">0 / ' . TicketSettings::content_max() . '</span></label></div>';
echo '<label class="jio-native-field is-wide"><span class="jio-native-field__label">' . esc_html__('问题描述', 'jio') . '</span><textarea id="jio-ticket-create-content" name="content" rows="7" maxlength="' . TicketSettings::content_max() . '" required data-jio-ticket-editor aria-describedby="jio-ticket-create-count" placeholder="' . esc_attr__('描述问题、发生时间和已尝试的处理方式', 'jio') . '"></textarea>';
echo '<span id="jio-ticket-create-count" class="jio-ticket-counter" data-jio-ticket-counter="jio-ticket-create-content">0 / ' . TicketSettings::content_max() . '</span></label></div>';
}
echo '</div><footer class="aksrt-ticket-dialog__foot">';
echo '</div><footer class="jio-ticket-dialog__foot">';
if ($types !== []) {
echo '<span class="aksrt-ticket-hint">' . esc_html__('新工单状态为「已回复」', 'aksrt') . '</span>';
echo '<span class="jio-ticket-hint">' . esc_html__('新工单状态为「已回复」', 'jio') . '</span>';
}
echo '<button type="button" class="button" data-aksrt-dialog-close>' . esc_html__('取消', 'aksrt') . '</button>';
echo '<button type="button" class="button" data-jio-dialog-close>' . esc_html__('取消', 'jio') . '</button>';
if ($types !== []) {
echo '<button type="submit" class="button button-primary">' . esc_html__('创建工单并通知用户', 'aksrt') . '</button>';
echo '<button type="submit" class="button button-primary">' . esc_html__('创建工单并通知用户', 'jio') . '</button>';
}
echo '</footer>' . ($types === [] ? '</div>' : '</form>') . '</dialog>';
}
+83 -83
View File
@@ -1,34 +1,34 @@
<?php
/** 工单处理工作区(?id=):左侧会话面板(含底部回复框),右侧处理信息与内部备注(弹窗添加)。@package AKSRT */
/** 工单处理工作区(?id=):左侧会话面板(含底部回复框),右侧处理信息与内部备注(弹窗添加)。@package Jio */
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Ticket\TicketNoteService;
use AKSRT\Ticket\TicketPrereplyService;
use AKSRT\Ticket\TicketRepository;
use AKSRT\Ticket\TicketSettings;
use AKSRT\Ticket\TicketStatusService;
use AKSRT\Ticket\TicketTypeService;
use Jio\Ticket\TicketNoteService;
use Jio\Ticket\TicketPrereplyService;
use Jio\Ticket\TicketRepository;
use Jio\Ticket\TicketSettings;
use Jio\Ticket\TicketStatusService;
use Jio\Ticket\TicketTypeService;
defined('ABSPATH') || exit;
trait UsersMenusTicketsDetail {
private const TICKET_NOTE_DIALOG = 'aksrt-ticket-note-dialog';
private const TICKET_NOTE_DIALOG = 'jio-ticket-note-dialog';
/** 详情按 ID 查询,不受列表筛选影响;$context 只用于「返回工单列表」。 */
private function render_ticket_detail_page(int $id, array $context): void {
$back = NativeUi::button_link(__('返回工单列表', 'aksrt'), $this->ticket_page_url($context), false, 'dashicons-arrow-left-alt');
$back = NativeUi::button_link(__('返回工单列表', 'jio'), $this->ticket_page_url($context), false, 'dashicons-arrow-left-alt');
$ticket = TicketRepository::get_instance()->find($id);
echo '<div class="aksrt-native-header aksrt-tickets-admin__detail-header"><div class="aksrt-native-header__main"><h1 class="aksrt-native-header__title">';
echo '<div class="jio-native-header jio-tickets-admin__detail-header"><div class="jio-native-header__main"><h1 class="jio-native-header__title">';
if ($ticket === null) {
echo esc_html__('工单详情', 'aksrt') . '</h1></div><div class="aksrt-native-header__actions">' . $back . '</div></div><hr class="wp-header-end">'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo NativeUi::empty_state(__('工单不存在', 'aksrt'), __('工单可能已被删除,或链接有误。', 'aksrt'), 'dashicons-search'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo esc_html__('工单详情', 'jio') . '</h1></div><div class="jio-native-header__actions">' . $back . '</div></div><hr class="wp-header-end">'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo NativeUi::empty_state(__('工单不存在', 'jio'), __('工单可能已被删除,或链接有误。', 'jio'), 'dashicons-search'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
}
echo '<span class="aksrt-native-header__icon dashicons dashicons-tickets-alt" aria-hidden="true"></span>' . esc_html((string) $ticket['title']) . '</h1>';
echo '<p class="aksrt-native-header__desc"><span>' . esc_html((string) $ticket['ticket_num']) . '</span> ' . $this->ticket_status_badge($ticket) . $this->ticket_urge_badge($ticket) . '</p>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div><div class="aksrt-native-header__actions">' . $back . '</div></div><hr class="wp-header-end">'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<span class="jio-native-header__icon dashicons dashicons-tickets-alt" aria-hidden="true"></span>' . esc_html((string) $ticket['title']) . '</h1>';
echo '<p class="jio-native-header__desc"><span>' . esc_html((string) $ticket['ticket_num']) . '</span> ' . $this->ticket_status_badge($ticket) . $this->ticket_urge_badge($ticket) . '</p>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div><div class="jio-native-header__actions">' . $back . '</div></div><hr class="wp-header-end">'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$this->render_ticket_notice();
echo $this->ticket_feedback(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $this->render_ticket_detail_panel($ticket); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
@@ -38,11 +38,11 @@ trait UsersMenusTicketsDetail {
private function render_ticket_detail_panel(array $ticket): string {
$id = (int) $ticket['id'];
$footer = !empty($ticket['is_closed'])
? '<p class="aksrt-ticket-detail__closed"><span class="dashicons dashicons-lock" aria-hidden="true"></span>' . esc_html__('工单已关闭,不能继续回复或变更处理信息;仍可添加内部备注。', 'aksrt') . '</p>'
? '<p class="jio-ticket-detail__closed"><span class="dashicons dashicons-lock" aria-hidden="true"></span>' . esc_html__('工单已关闭,不能继续回复或变更处理信息;仍可添加内部备注。', 'jio') . '</p>'
: $this->ticket_reply_form($id);
$html = '<div class="aksrt-ticket-detail" data-aksrt-ticket-detail="' . $id . '"><div class="aksrt-ticket-detail__main">';
$html = '<div class="jio-ticket-detail" data-jio-ticket-detail="' . $id . '"><div class="jio-ticket-detail__main">';
$html .= $this->ticket_conversation($ticket, TicketRepository::get_instance()->replies($id), $footer);
$html .= '</div><aside class="aksrt-ticket-detail__side" aria-label="' . esc_attr__('工单信息与内部备注', 'aksrt') . '">';
$html .= '</div><aside class="jio-ticket-detail__side" aria-label="' . esc_attr__('工单信息与内部备注', 'jio') . '">';
return $html . $this->ticket_info_card($ticket) . $this->ticket_notes_card($id) . '</aside></div>';
}
@@ -52,48 +52,48 @@ trait UsersMenusTicketsDetail {
* 原始问题与回复同属一条时间线:用户在左、客服在右、系统事件居中。
*/
private function ticket_conversation(array $ticket, array $replies, string $footer): string {
$html = '<section class="aksrt-ticket-conversation" aria-label="' . esc_attr__('问题与沟通记录', 'aksrt') . '">';
$html .= '<div class="aksrt-ticket-section-head"><h3>' . esc_html__('沟通记录', 'aksrt') . ' <span class="aksrt-ticket-pill">' . esc_html(sprintf(__('%d 条记录', 'aksrt'), count($replies))) . '</span></h3>';
$html = '<section class="jio-ticket-conversation" aria-label="' . esc_attr__('问题与沟通记录', 'jio') . '">';
$html .= '<div class="jio-ticket-section-head"><h3>' . esc_html__('沟通记录', 'jio') . ' <span class="jio-ticket-pill">' . esc_html(sprintf(__('%d 条记录', 'jio'), count($replies))) . '</span></h3>';
if ($replies !== []) {
$html .= '<button type="button" class="button-link" data-aksrt-ticket-latest hidden>' . esc_html__('查看最新记录', 'aksrt') . '</button>';
$html .= '<button type="button" class="button-link" data-jio-ticket-latest hidden>' . esc_html__('查看最新记录', 'jio') . '</button>';
}
$html .= '</div><div class="aksrt-ticket-conversation__scroll" tabindex="0" role="region" aria-label="' . esc_attr__('可滚动的沟通记录', 'aksrt') . '"><ol class="aksrt-ticket-detail__thread">';
$html .= $this->ticket_message('origin', (int) $ticket['user_id'], (string) $ticket['user_name'], !empty($ticket['post_admin_id']) ? __('代客提交', 'aksrt') : __('提交了工单', 'aksrt'), (string) $ticket['post_time'], (string) $ticket['content'], (array) $ticket['attachment']);
$html .= '</div><div class="jio-ticket-conversation__scroll" tabindex="0" role="region" aria-label="' . esc_attr__('可滚动的沟通记录', 'jio') . '"><ol class="jio-ticket-detail__thread">';
$html .= $this->ticket_message('origin', (int) $ticket['user_id'], (string) $ticket['user_name'], !empty($ticket['post_admin_id']) ? __('代客提交', 'jio') : __('提交了工单', 'jio'), (string) $ticket['post_time'], (string) $ticket['content'], (array) $ticket['attachment']);
if ($replies === []) {
$html .= '<li class="aksrt-ticket-detail__empty">' . esc_html__('暂无回复,请核对问题后联系用户。', 'aksrt') . '</li>';
$html .= '<li class="jio-ticket-detail__empty">' . esc_html__('暂无回复,请核对问题后联系用户。', 'jio') . '</li>';
}
foreach ($replies as $reply) {
if ((string) $reply['author_type'] === 'system') {
$html .= '<li class="aksrt-ticket-detail__event"><span>' . esc_html((string) $reply['content']) . '</span><time>' . esc_html((string) $reply['created_at']) . '</time></li>';
$html .= '<li class="jio-ticket-detail__event"><span>' . esc_html((string) $reply['content']) . '</span><time>' . esc_html((string) $reply['created_at']) . '</time></li>';
continue;
}
$admin = (string) $reply['author_type'] === 'admin';
$quote_html = '';
if (is_array($reply['quote'] ?? null)) {
$quote = $reply['quote'];
$quote_html = '<blockquote class="aksrt-ticket-detail__quote">' . esc_html(!empty($quote['is_deleted']) ? __('原回复已删除', 'aksrt') : (string) $quote['content']) . '</blockquote>';
$quote_html = '<blockquote class="jio-ticket-detail__quote">' . esc_html(!empty($quote['is_deleted']) ? __('原回复已删除', 'jio') : (string) $quote['content']) . '</blockquote>';
}
$html .= $this->ticket_message($admin ? 'admin' : 'user', (int) $reply['author_id'], (string) $reply['author_name'], $admin ? __('客服', 'aksrt') : __('用户', 'aksrt'), (string) $reply['created_at'], (string) $reply['content'], (array) $reply['attachment'], $quote_html);
$html .= $this->ticket_message($admin ? 'admin' : 'user', (int) $reply['author_id'], (string) $reply['author_name'], $admin ? __('客服', 'jio') : __('用户', 'jio'), (string) $reply['created_at'], (string) $reply['content'], (array) $reply['attachment'], $quote_html);
}
return $html . '</ol></div>' . $footer . '</section>';
}
private function ticket_message(string $variant, int $user_id, string $name, string $role, string $time, string $content, array $attachments, string $quote_html = ''): string {
$avatar = function_exists('aksrt_get_avatar_url') ? aksrt_get_avatar_url($user_id, 64) : '';
return '<li class="aksrt-ticket-detail__message is-' . esc_attr($variant) . '">'
. ($avatar !== '' ? '<img class="aksrt-ticket-detail__avatar" src="' . esc_url($avatar) . '" alt="" width="32" height="32">' : '<span class="aksrt-ticket-detail__avatar" aria-hidden="true"></span>')
. '<div class="aksrt-ticket-detail__bubble"><header class="aksrt-ticket-detail__meta"><strong>' . esc_html($name !== '' ? $name : '#' . $user_id) . '</strong>'
. '<span class="aksrt-ticket-detail__role">' . esc_html($role) . '</span><time>' . esc_html($time) . '</time></header>'
. $quote_html . '<div class="aksrt-ticket-detail__body">' . wpautop(esc_html($content)) . '</div>'
$avatar = function_exists('jio_get_avatar_url') ? jio_get_avatar_url($user_id, 64) : '';
return '<li class="jio-ticket-detail__message is-' . esc_attr($variant) . '">'
. ($avatar !== '' ? '<img class="jio-ticket-detail__avatar" src="' . esc_url($avatar) . '" alt="" width="32" height="32">' : '<span class="jio-ticket-detail__avatar" aria-hidden="true"></span>')
. '<div class="jio-ticket-detail__bubble"><header class="jio-ticket-detail__meta"><strong>' . esc_html($name !== '' ? $name : '#' . $user_id) . '</strong>'
. '<span class="jio-ticket-detail__role">' . esc_html($role) . '</span><time>' . esc_html($time) . '</time></header>'
. $quote_html . '<div class="jio-ticket-detail__body">' . wpautop(esc_html($content)) . '</div>'
. $this->ticket_attachments_html($attachments) . '</div></li>';
}
private function ticket_reply_form(int $id): string {
$field = 'aksrt-ticket-reply-' . $id;
$field = 'jio-ticket-reply-' . $id;
$tools = '';
$prereplies = TicketPrereplyService::all();
if ($prereplies !== []) {
$tools = '<label class="screen-reader-text" for="' . $field . '-pre">' . esc_html__('插入预设回复', 'aksrt') . '</label><select id="' . $field . '-pre" data-aksrt-ticket-prereply="' . $field . '"><option value="">' . esc_html__('插入预设回复…', 'aksrt') . '</option>';
$tools = '<label class="screen-reader-text" for="' . $field . '-pre">' . esc_html__('插入预设回复', 'jio') . '</label><select id="' . $field . '-pre" data-jio-ticket-prereply="' . $field . '"><option value="">' . esc_html__('插入预设回复…', 'jio') . '</option>';
foreach ($prereplies as $row) {
$tools .= '<option value="' . esc_attr((string) $row['content']) . '">' . esc_html(mb_strimwidth((string) $row['content'], 0, 40, '…')) . '</option>';
}
@@ -101,13 +101,13 @@ trait UsersMenusTicketsDetail {
}
$max = TicketSettings::reply_max();
// 输入框与工具栏合为一个整体:预设回复、字数与发送按钮同排;Ctrl/⌘ + Enter 发送。
return $this->ticket_form_open('reply', $id, 'aksrt-ticket-composer')
. '<label class="screen-reader-text" for="' . esc_attr($field) . '">' . esc_html__('回复用户', 'aksrt') . '</label>'
. '<div class="aksrt-ticket-composer__box"><textarea id="' . esc_attr($field) . '" name="content" rows="3" maxlength="' . $max . '" required data-aksrt-ticket-editor data-aksrt-ticket-send-shortcut aria-describedby="' . esc_attr($field) . '-count ' . esc_attr($field) . '-hint" placeholder="' . esc_attr__('回复用户,发送后用户将收到通知…', 'aksrt') . '"></textarea>'
. '<div class="aksrt-ticket-composer__bar">' . $tools
. '<span id="' . esc_attr($field) . '-hint" class="aksrt-ticket-hint aksrt-ticket-composer__shortcut">' . esc_html__('Ctrl + Enter 发送', 'aksrt') . '</span>'
. '<span id="' . esc_attr($field) . '-count" class="aksrt-ticket-counter" data-aksrt-ticket-counter="' . esc_attr($field) . '">0 / ' . $max . '</span>'
. '<button type="submit" class="button button-primary"><span class="dashicons dashicons-email-alt" aria-hidden="true"></span>' . esc_html__('发送回复', 'aksrt') . '</button></div></div></form>';
return $this->ticket_form_open('reply', $id, 'jio-ticket-composer')
. '<label class="screen-reader-text" for="' . esc_attr($field) . '">' . esc_html__('回复用户', 'jio') . '</label>'
. '<div class="jio-ticket-composer__box"><textarea id="' . esc_attr($field) . '" name="content" rows="3" maxlength="' . $max . '" required data-jio-ticket-editor data-jio-ticket-send-shortcut aria-describedby="' . esc_attr($field) . '-count ' . esc_attr($field) . '-hint" placeholder="' . esc_attr__('回复用户,发送后用户将收到通知…', 'jio') . '"></textarea>'
. '<div class="jio-ticket-composer__bar">' . $tools
. '<span id="' . esc_attr($field) . '-hint" class="jio-ticket-hint jio-ticket-composer__shortcut">' . esc_html__('Ctrl + Enter 发送', 'jio') . '</span>'
. '<span id="' . esc_attr($field) . '-count" class="jio-ticket-counter" data-jio-ticket-counter="' . esc_attr($field) . '">0 / ' . $max . '</span>'
. '<button type="submit" class="button button-primary"><span class="dashicons dashicons-email-alt" aria-hidden="true"></span>' . esc_html__('发送回复', 'jio') . '</button></div></div></form>';
}
private function ticket_info_card(array $ticket): string {
@@ -115,40 +115,40 @@ trait UsersMenusTicketsDetail {
$editable = empty($ticket['is_closed']);
$controls = $editable ? $this->ticket_info_controls($ticket) : [];
$rows = [
[__('工单号', 'aksrt'), esc_html((string) $ticket['ticket_num'])],
[__('状态', 'aksrt'), $controls['status'] ?? $this->ticket_status_badge($ticket), 'status'],
[__('用户', 'aksrt'), $this->ticket_user_chip((int) $ticket['user_id'], (string) $ticket['user_name'])],
[__('部门', 'aksrt'), $controls['ticket_type_id'] ?? esc_html((string) $ticket['ticket_type_name'] !== '' ? (string) $ticket['ticket_type_name'] : '—'), 'ticket_type_id'],
[__('跟进人', 'aksrt'), $controls['admin_id'] ?? esc_html((int) $ticket['admin_id'] > 0 ? (string) $ticket['admin_name'] : __('未指派', 'aksrt')), 'admin_id'],
[__('工单号', 'jio'), esc_html((string) $ticket['ticket_num'])],
[__('状态', 'jio'), $controls['status'] ?? $this->ticket_status_badge($ticket), 'status'],
[__('用户', 'jio'), $this->ticket_user_chip((int) $ticket['user_id'], (string) $ticket['user_name'])],
[__('部门', 'jio'), $controls['ticket_type_id'] ?? esc_html((string) $ticket['ticket_type_name'] !== '' ? (string) $ticket['ticket_type_name'] : '—'), 'ticket_type_id'],
[__('跟进人', 'jio'), $controls['admin_id'] ?? esc_html((int) $ticket['admin_id'] > 0 ? (string) $ticket['admin_name'] : __('未指派', 'jio')), 'admin_id'],
];
if ((string) $ticket['order_number'] !== '') {
$rows[] = [__('关联订单', 'aksrt'), esc_html((string) $ticket['order_number'])];
$rows[] = [__('关联订单', 'jio'), esc_html((string) $ticket['order_number'])];
}
$rows[] = [__('提交时间', 'aksrt'), esc_html((string) $ticket['post_time'])];
$rows[] = [__('提交时间', 'jio'), esc_html((string) $ticket['post_time'])];
if ((int) $ticket['urge_count'] > 0) {
$rows[] = [__('催单', 'aksrt'), '<span class="aksrt-ticket-urge">' . esc_html(sprintf(__('%1$d 次,最近 %2$s', 'aksrt'), (int) $ticket['urge_count'], (string) $ticket['last_urge_time'])) . '</span>'];
$rows[] = [__('催单', 'jio'), '<span class="jio-ticket-urge">' . esc_html(sprintf(__('%1$d 次,最近 %2$s', 'jio'), (int) $ticket['urge_count'], (string) $ticket['last_urge_time'])) . '</span>'];
}
$html = '<section class="aksrt-ticket-detail__card"><div class="aksrt-ticket-section-head"><h3>' . esc_html__('工单信息', 'aksrt') . '</h3>';
$html = '<section class="jio-ticket-detail__card"><div class="jio-ticket-section-head"><h3>' . esc_html__('工单信息', 'jio') . '</h3>';
if ($editable) {
$html .= '<button type="button" class="button-link" data-aksrt-ticket-assign>' . esc_html__('由我跟进', 'aksrt') . '</button>';
$html .= '<button type="button" class="button-link" data-jio-ticket-assign>' . esc_html__('由我跟进', 'jio') . '</button>';
}
$html .= '</div>';
if ($editable) {
$html .= $this->ticket_form_open('status', $id, 'aksrt-ticket-detail__status-form');
$html .= $this->ticket_form_open('status', $id, 'jio-ticket-detail__status-form');
}
$html .= '<dl class="aksrt-ticket-detail__info">';
$html .= '<dl class="jio-ticket-detail__info">';
foreach ($rows as $row) {
[$label, $value] = $row;
$label_html = esc_html($label);
if ($editable && isset($row[2])) {
$label_html = '<label for="aksrt-ticket-' . esc_attr($row[2]) . '-' . $id . '">' . $label_html . '</label>';
$label_html = '<label for="jio-ticket-' . esc_attr($row[2]) . '-' . $id . '">' . $label_html . '</label>';
}
$html .= '<dt>' . $label_html . '</dt><dd>' . $value . '</dd>';
}
$html .= '</dl>';
if ($editable) {
$html .= '<div class="aksrt-ticket-close-warning" data-aksrt-ticket-close-warning hidden><label><input type="checkbox" data-aksrt-ticket-close-confirm> <span>' . esc_html__('确认关闭工单:关闭后无法再次回复,尚未发送的回复不会保存。', 'aksrt') . '</span></label></div>';
$html .= '<button type="submit" class="button">' . esc_html__('保存工单信息', 'aksrt') . '</button></form>';
$html .= '<div class="jio-ticket-close-warning" data-jio-ticket-close-warning hidden><label><input type="checkbox" data-jio-ticket-close-confirm> <span>' . esc_html__('确认关闭工单:关闭后无法再次回复,尚未发送的回复不会保存。', 'jio') . '</span></label></div>';
$html .= '<button type="submit" class="button">' . esc_html__('保存工单信息', 'jio') . '</button></form>';
}
return $html . '</section>';
}
@@ -157,7 +157,7 @@ trait UsersMenusTicketsDetail {
private function ticket_info_controls(array $ticket): array {
$id = (int) $ticket['id'];
$from = (string) $ticket['status'];
$controls = ['status' => '<select id="aksrt-ticket-status-' . $id . '" name="status" data-aksrt-ticket-status>'];
$controls = ['status' => '<select id="jio-ticket-status-' . $id . '" name="status" data-jio-ticket-status>'];
foreach (TicketStatusService::map() as $row) {
$key = (string) $row['status_key'];
if (TicketStatusService::can_transition($from, $key)) {
@@ -165,19 +165,19 @@ trait UsersMenusTicketsDetail {
}
}
$controls['status'] .= '</select>';
$controls['ticket_type_id'] = '<select id="aksrt-ticket-ticket_type_id-' . $id . '" name="ticket_type_id" required>';
$controls['ticket_type_id'] = '<select id="jio-ticket-ticket_type_id-' . $id . '" name="ticket_type_id" required>';
foreach (TicketTypeService::all() as $row) {
$controls['ticket_type_id'] .= '<option value="' . (int) $row['id'] . '"' . selected((int) $ticket['ticket_type_id'], (int) $row['id'], false) . '>' . esc_html((string) $row['name']) . '</option>';
}
$controls['ticket_type_id'] .= '</select>';
$controls['admin_id'] = '<select id="aksrt-ticket-admin_id-' . $id . '" name="admin_id"><option value="0">' . esc_html__('未指派', 'aksrt') . '</option>';
$controls['admin_id'] = '<select id="jio-ticket-admin_id-' . $id . '" name="admin_id"><option value="0">' . esc_html__('未指派', 'jio') . '</option>';
$current = (int) $ticket['admin_id'];
$admins = $this->ticket_admin_options();
if ($current > 0 && !isset($admins[$current])) {
$admins[$current] = (string) $ticket['admin_name'];
}
foreach ($admins as $admin_id => $name) {
$controls['admin_id'] .= '<option value="' . $admin_id . '"' . selected($current, $admin_id, false) . '>' . esc_html($admin_id === get_current_user_id() ? sprintf(__('%s(我)', 'aksrt'), $name) : $name) . '</option>';
$controls['admin_id'] .= '<option value="' . $admin_id . '"' . selected($current, $admin_id, false) . '>' . esc_html($admin_id === get_current_user_id() ? sprintf(__('%s(我)', 'jio'), $name) : $name) . '</option>';
}
$controls['admin_id'] .= '</select>';
return $controls;
@@ -203,42 +203,42 @@ trait UsersMenusTicketsDetail {
private function ticket_notes_card(int $id): string {
$notes = TicketNoteService::for_ticket($id);
$visible = 3;
$html = '<section class="aksrt-ticket-detail__card aksrt-ticket-detail__private"><div class="aksrt-ticket-section-head"><h3>' . esc_html__('内部备注', 'aksrt');
$html = '<section class="jio-ticket-detail__card jio-ticket-detail__private"><div class="jio-ticket-section-head"><h3>' . esc_html__('内部备注', 'jio');
if ($notes !== []) {
$html .= ' <span class="aksrt-ticket-pill">' . count($notes) . '</span>';
$html .= ' <span class="jio-ticket-pill">' . count($notes) . '</span>';
}
$html .= '</h3><button type="button" class="button button-small" data-aksrt-dialog-open="' . esc_attr(self::TICKET_NOTE_DIALOG) . '" aria-haspopup="dialog" aria-controls="' . esc_attr(self::TICKET_NOTE_DIALOG) . '">'
. '<span class="dashicons dashicons-plus-alt2" aria-hidden="true"></span>' . esc_html__('添加备注', 'aksrt') . '</button></div>';
$html .= '<p class="aksrt-ticket-hint">' . esc_html__('仅管理员可见,不会发送给用户。', 'aksrt') . '</p>';
$html .= '</h3><button type="button" class="button button-small" data-jio-dialog-open="' . esc_attr(self::TICKET_NOTE_DIALOG) . '" aria-haspopup="dialog" aria-controls="' . esc_attr(self::TICKET_NOTE_DIALOG) . '">'
. '<span class="dashicons dashicons-plus-alt2" aria-hidden="true"></span>' . esc_html__('添加备注', 'jio') . '</button></div>';
$html .= '<p class="jio-ticket-hint">' . esc_html__('仅管理员可见,不会发送给用户。', 'jio') . '</p>';
if ($notes === []) {
return $html . '<p class="aksrt-ticket-detail__notes-empty">' . esc_html__('暂无备注', 'aksrt') . '</p></section>';
return $html . '<p class="jio-ticket-detail__notes-empty">' . esc_html__('暂无备注', 'jio') . '</p></section>';
}
$older = array_slice($notes, 0, -$visible);
$html .= '<div class="aksrt-ticket-detail__notes">';
$html .= '<div class="jio-ticket-detail__notes">';
if ($older !== []) {
$html .= '<details class="aksrt-ticket-detail__notes-older"><summary>' . esc_html(sprintf(__('更早的 %d 条备注', 'aksrt'), count($older))) . '</summary>' . $this->ticket_notes_list($older) . '</details>';
$html .= '<details class="jio-ticket-detail__notes-older"><summary>' . esc_html(sprintf(__('更早的 %d 条备注', 'jio'), count($older))) . '</summary>' . $this->ticket_notes_list($older) . '</details>';
}
return $html . $this->ticket_notes_list(array_slice($notes, -$visible)) . '</div></section>';
}
private function ticket_note_dialog(int $id): string {
$field = 'aksrt-ticket-note-' . $id;
$field = 'jio-ticket-note-' . $id;
$max = TicketSettings::reply_max();
return '<dialog id="' . esc_attr(self::TICKET_NOTE_DIALOG) . '" class="aksrt-ticket-dialog aksrt-ticket-dialog--note" aria-labelledby="aksrt-ticket-note-title" aria-describedby="aksrt-ticket-note-description">'
. $this->ticket_form_open('note', $id, 'aksrt-ticket-dialog__frame')
. $this->ticket_dialog_head('note', __('添加内部备注', 'aksrt'), __('仅管理员可见,不会发送给用户。', 'aksrt'))
. '<div class="aksrt-ticket-dialog__body">' . $this->ticket_feedback()
. '<label class="screen-reader-text" for="' . esc_attr($field) . '">' . esc_html__('备注内容', 'aksrt') . '</label>'
. '<textarea id="' . esc_attr($field) . '" name="content" rows="6" maxlength="' . $max . '" required autofocus data-aksrt-ticket-editor aria-describedby="' . esc_attr($field) . '-count" placeholder="' . esc_attr__('记录处理进展、排查结论…', 'aksrt') . '"></textarea>'
. '<span id="' . esc_attr($field) . '-count" class="aksrt-ticket-counter aksrt-ticket-dialog__counter" data-aksrt-ticket-counter="' . esc_attr($field) . '">0 / ' . $max . '</span></div>'
. '<footer class="aksrt-ticket-dialog__foot"><button type="button" class="button" data-aksrt-dialog-close>' . esc_html__('取消', 'aksrt') . '</button>'
. '<button type="submit" class="button button-primary">' . esc_html__('保存备注', 'aksrt') . '</button></footer></form></dialog>';
return '<dialog id="' . esc_attr(self::TICKET_NOTE_DIALOG) . '" class="jio-ticket-dialog jio-ticket-dialog--note" aria-labelledby="jio-ticket-note-title" aria-describedby="jio-ticket-note-description">'
. $this->ticket_form_open('note', $id, 'jio-ticket-dialog__frame')
. $this->ticket_dialog_head('note', __('添加内部备注', 'jio'), __('仅管理员可见,不会发送给用户。', 'jio'))
. '<div class="jio-ticket-dialog__body">' . $this->ticket_feedback()
. '<label class="screen-reader-text" for="' . esc_attr($field) . '">' . esc_html__('备注内容', 'jio') . '</label>'
. '<textarea id="' . esc_attr($field) . '" name="content" rows="6" maxlength="' . $max . '" required autofocus data-jio-ticket-editor aria-describedby="' . esc_attr($field) . '-count" placeholder="' . esc_attr__('记录处理进展、排查结论…', 'jio') . '"></textarea>'
. '<span id="' . esc_attr($field) . '-count" class="jio-ticket-counter jio-ticket-dialog__counter" data-jio-ticket-counter="' . esc_attr($field) . '">0 / ' . $max . '</span></div>'
. '<footer class="jio-ticket-dialog__foot"><button type="button" class="button" data-jio-dialog-close>' . esc_html__('取消', 'jio') . '</button>'
. '<button type="submit" class="button button-primary">' . esc_html__('保存备注', 'jio') . '</button></footer></form></dialog>';
}
private function ticket_notes_list(array $notes): string {
$html = '<ol>';
foreach ($notes as $note) {
$html .= '<li><div class="aksrt-ticket-detail__note-head"><strong>' . esc_html((string) $note['admin_name']) . '</strong><time>' . esc_html((string) $note['created_at']) . '</time></div><div>' . nl2br(esc_html((string) $note['content'])) . '</div></li>';
$html .= '<li><div class="jio-ticket-detail__note-head"><strong>' . esc_html((string) $note['admin_name']) . '</strong><time>' . esc_html((string) $note['created_at']) . '</time></div><div>' . nl2br(esc_html((string) $note['content'])) . '</div></li>';
}
return $html . '</ol>';
}
@@ -247,9 +247,9 @@ trait UsersMenusTicketsDetail {
if ($urls === []) {
return '';
}
$html = '<ul class="aksrt-ticket-detail__attachments">';
$html = '<ul class="jio-ticket-detail__attachments">';
foreach ($urls as $index => $url) {
$html .= '<li><a href="' . esc_url((string) $url) . '" target="_blank" rel="noopener noreferrer"><img src="' . esc_url((string) $url) . '" alt="' . esc_attr(sprintf(__('查看附件 %d(新窗口)', 'aksrt'), $index + 1)) . '"></a></li>';
$html .= '<li><a href="' . esc_url((string) $url) . '" target="_blank" rel="noopener noreferrer"><img src="' . esc_url((string) $url) . '" alt="' . esc_attr(sprintf(__('查看附件 %d(新窗口)', 'jio'), $index + 1)) . '"></a></li>';
}
return $html . '</ul>';
}
+43 -43
View File
@@ -1,11 +1,11 @@
<?php
/** 工单筛选与列表;标题与操作直接链接到处理工作区(?id=)。@package AKSRT */
/** 工单筛选与列表;标题与操作直接链接到处理工作区(?id=)。@package Jio */
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Ticket\TicketRepository;
use AKSRT\Ticket\TicketStatusService;
use AKSRT\Ticket\TicketTypeService;
use Jio\Ticket\TicketRepository;
use Jio\Ticket\TicketStatusService;
use Jio\Ticket\TicketTypeService;
defined('ABSPATH') || exit;
@@ -15,19 +15,19 @@ trait UsersMenusTicketsList {
// 先查询并夹取页码,创建成功后跳转的工作区链接才能带上实际页码。
[$result, $paged] = $this->ticket_list_result($filters, $paged);
NativeUi::header(
__('工单管理', 'aksrt'),
__('查看用户问题、跟进沟通并记录处理结果。', 'aksrt'),
__('工单管理', 'jio'),
__('查看用户问题、跟进沟通并记录处理结果。', 'jio'),
'dashicons-tickets-alt',
[
[
'label' => __('代客提交工单', 'aksrt'),
'label' => __('代客提交工单', 'jio'),
'dialog' => self::TICKET_CREATE_DIALOG,
'primary' => true,
'icon' => 'dashicons-plus-alt2',
],
[
'label' => __('工单配置', 'aksrt'),
'url' => admin_url('admin.php?page=aksrt-settings#/users/ticket'),
'label' => __('工单配置', 'jio'),
'url' => admin_url('admin.php?page=jio-settings#/users/ticket'),
'icon' => 'dashicons-admin-generic',
],
]
@@ -57,27 +57,27 @@ trait UsersMenusTicketsList {
}
private function render_ticket_list(array $filters, int $paged, array $result): void {
NativeUi::card_open('', '', true, 'aksrt-tickets-admin__list-card');
NativeUi::card_open('', '', true, 'jio-tickets-admin__list-card');
$this->render_ticket_toolbar($filters);
echo '<div class="aksrt-native-table-wrap"><table class="widefat striped aksrt-tickets-admin__table">';
echo '<caption class="screen-reader-text">' . esc_html__('工单处理列表', 'aksrt') . '</caption><thead><tr>';
echo '<div class="jio-native-table-wrap"><table class="widefat striped jio-tickets-admin__table">';
echo '<caption class="screen-reader-text">' . esc_html__('工单处理列表', 'jio') . '</caption><thead><tr>';
foreach ([
'title' => __('工单 / 问题', 'aksrt'),
'status' => __('状态', 'aksrt'),
'type' => __('部门', 'aksrt'),
'user' => __('用户', 'aksrt'),
'admin' => __('跟进人', 'aksrt'),
'time' => __('最后活动', 'aksrt'),
'actions' => __('操作', 'aksrt'),
'title' => __('工单 / 问题', 'jio'),
'status' => __('状态', 'jio'),
'type' => __('部门', 'jio'),
'user' => __('用户', 'jio'),
'admin' => __('跟进人', 'jio'),
'time' => __('最后活动', 'jio'),
'actions' => __('操作', 'jio'),
] as $key => $label) {
echo '<th scope="col" class="column-' . esc_attr($key) . '">' . esc_html($label) . '</th>';
}
echo '</tr></thead><tbody>';
if ($result['items'] === []) {
$filtered = $filters['status'] !== '' || $filters['ticket_type_id'] > 0 || $filters['s'] !== '';
echo NativeUi::empty_row(7, __('暂无工单', 'aksrt'), $filtered // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
? __('请调整筛选条件,或使用标题右侧的「代客提交工单」。', 'aksrt')
: __('暂无未关闭工单。可切换状态查看历史记录,或代用户创建工单。', 'aksrt'));
echo NativeUi::empty_row(7, __('暂无工单', 'jio'), $filtered // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
? __('请调整筛选条件,或使用标题右侧的「代客提交工单」。', 'jio')
: __('暂无未关闭工单。可切换状态查看历史记录,或代用户创建工单。', 'jio'));
}
$context = $this->ticket_context($filters, $paged);
foreach ($result['items'] as $ticket) {
@@ -106,13 +106,13 @@ trait UsersMenusTicketsList {
}
$views[] = [$key, (string) $row['name'], $count, (string) $row['color']];
}
array_unshift($views, ['', __('未关闭', 'aksrt'), $open_total, '']);
echo '<div class="aksrt-tickets-admin__toolbar"><h2 class="aksrt-native-card__title">' . esc_html__('工单列表', 'aksrt') . '</h2>';
echo '<nav aria-label="' . esc_attr__('按工单状态筛选', 'aksrt') . '"><ul class="subsubsub">';
array_unshift($views, ['', __('未关闭', 'jio'), $open_total, '']);
echo '<div class="jio-tickets-admin__toolbar"><h2 class="jio-native-card__title">' . esc_html__('工单列表', 'jio') . '</h2>';
echo '<nav aria-label="' . esc_attr__('按工单状态筛选', 'jio') . '"><ul class="subsubsub">';
foreach ($views as [$key, $label, $count, $color]) {
echo '<li><a href="' . esc_url($this->ticket_page_url(array_merge($filters, ['status' => $key]))) . '"' . ($filters['status'] === $key ? ' class="current" aria-current="page"' : '') . '>';
if ($color !== '') {
echo '<span class="aksrt-ticket-dot" aria-hidden="true" style="--aksrt-ticket-color:' . esc_attr($color) . '"></span>';
echo '<span class="jio-ticket-dot" aria-hidden="true" style="--jio-ticket-color:' . esc_attr($color) . '"></span>';
}
echo esc_html($label) . ' <span class="count">' . esc_html(number_format_i18n($count)) . '</span></a></li>';
}
@@ -122,17 +122,17 @@ trait UsersMenusTicketsList {
}
private function render_ticket_filter_form(array $filters): void {
echo '<form method="get" action="' . esc_url(admin_url('users.php')) . '" class="aksrt-native-filters">';
echo '<form method="get" action="' . esc_url(admin_url('users.php')) . '" class="jio-native-filters">';
echo '<input type="hidden" name="page" value="' . esc_attr(self::TICKET_PAGE_SLUG) . '"><input type="hidden" name="status" value="' . esc_attr($filters['status']) . '">';
echo '<label class="screen-reader-text" for="aksrt-ticket-filter-type">' . esc_html__('按部门筛选', 'aksrt') . '</label><select name="ticket_type_id" id="aksrt-ticket-filter-type"><option value="">' . esc_html__('全部部门', 'aksrt') . '</option>';
echo '<label class="screen-reader-text" for="jio-ticket-filter-type">' . esc_html__('按部门筛选', 'jio') . '</label><select name="ticket_type_id" id="jio-ticket-filter-type"><option value="">' . esc_html__('全部部门', 'jio') . '</option>';
foreach (TicketTypeService::all() as $row) {
echo '<option value="' . (int) $row['id'] . '"' . selected($filters['ticket_type_id'], (int) $row['id'], false) . '>' . esc_html((string) $row['name']) . '</option>';
}
echo '</select><label class="screen-reader-text" for="aksrt-ticket-filter-s">' . esc_html__('搜索工单', 'aksrt') . '</label>';
echo '<input type="search" id="aksrt-ticket-filter-s" name="s" value="' . esc_attr($filters['s']) . '" placeholder="' . esc_attr__('工单号 / 标题', 'aksrt') . '">';
echo '<button type="submit" class="button">' . esc_html__('筛选', 'aksrt') . '</button>';
echo '</select><label class="screen-reader-text" for="jio-ticket-filter-s">' . esc_html__('搜索工单', 'jio') . '</label>';
echo '<input type="search" id="jio-ticket-filter-s" name="s" value="' . esc_attr($filters['s']) . '" placeholder="' . esc_attr__('工单号 / 标题', 'jio') . '">';
echo '<button type="submit" class="button">' . esc_html__('筛选', 'jio') . '</button>';
if ($filters['ticket_type_id'] > 0 || $filters['s'] !== '') {
echo '<a class="button-link" href="' . esc_url($this->ticket_page_url(['status' => $filters['status']])) . '">' . esc_html__('清除筛选', 'aksrt') . '</a>';
echo '<a class="button-link" href="' . esc_url($this->ticket_page_url(['status' => $filters['status']])) . '">' . esc_html__('清除筛选', 'jio') . '</a>';
}
echo '</form>';
}
@@ -142,22 +142,22 @@ trait UsersMenusTicketsList {
$url = esc_url($this->ticket_page_url(['id' => $id] + $context));
$last = (string) $ticket['last_reply_time'] !== '' ? (string) $ticket['last_reply_time'] : (string) $ticket['post_time'];
$awaiting = in_array((string) $ticket['status'], [TicketStatusService::KEY_PENDING, TicketStatusService::KEY_USER_REPLIED], true);
echo '<tr class="aksrt-tickets-admin__row' . ($awaiting ? ' is-awaiting' : '') . '">';
echo '<tr class="jio-tickets-admin__row' . ($awaiting ? ' is-awaiting' : '') . '">';
echo '<td class="column-title"><a class="row-title" href="' . $url . '">' . esc_html((string) $ticket['title']) . '</a>' . $this->ticket_urge_badge($ticket); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="aksrt-tickets-admin__sub">' . esc_html((string) $ticket['ticket_num']) . '</div>';
echo '<div class="jio-tickets-admin__sub">' . esc_html((string) $ticket['ticket_num']) . '</div>';
if ((string) $ticket['order_number'] !== '') {
echo '<div class="aksrt-tickets-admin__sub">' . esc_html__('订单:', 'aksrt') . esc_html((string) $ticket['order_number']) . '</div>';
echo '<div class="jio-tickets-admin__sub">' . esc_html__('订单:', 'jio') . esc_html((string) $ticket['order_number']) . '</div>';
}
echo '</td><td class="column-status">' . $this->ticket_status_badge($ticket) . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<td class="column-type">' . esc_html((string) $ticket['ticket_type_name'] !== '' ? (string) $ticket['ticket_type_name'] : '—') . '</td>';
echo '<td class="column-user">' . $this->ticket_user_chip((int) $ticket['user_id'], (string) $ticket['user_name']) . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<td class="column-admin">' . ((int) $ticket['admin_id'] > 0 ? esc_html((string) $ticket['admin_name']) : '<span class="aksrt-ticket-hint">' . esc_html__('未指派', 'aksrt') . '</span>') . '</td>';
echo '<td class="column-time"><time>' . esc_html($last) . '</time><div class="aksrt-tickets-admin__sub">' . esc_html(sprintf(__('提交于 %s', 'aksrt'), (string) $ticket['post_time'])) . '</div></td>';
echo '<td class="column-actions"><a class="button' . ($awaiting ? ' button-primary' : '') . '" href="' . $url . '">' . esc_html(!empty($ticket['is_closed']) ? __('查看记录', 'aksrt') : __('处理工单', 'aksrt')) . '</a></td></tr>';
echo '<td class="column-admin">' . ((int) $ticket['admin_id'] > 0 ? esc_html((string) $ticket['admin_name']) : '<span class="jio-ticket-hint">' . esc_html__('未指派', 'jio') . '</span>') . '</td>';
echo '<td class="column-time"><time>' . esc_html($last) . '</time><div class="jio-tickets-admin__sub">' . esc_html(sprintf(__('提交于 %s', 'jio'), (string) $ticket['post_time'])) . '</div></td>';
echo '<td class="column-actions"><a class="button' . ($awaiting ? ' button-primary' : '') . '" href="' . $url . '">' . esc_html(!empty($ticket['is_closed']) ? __('查看记录', 'jio') : __('处理工单', 'jio')) . '</a></td></tr>';
}
private function ticket_status_badge(array $ticket): string {
return '<span class="aksrt-ticket-badge" style="--aksrt-ticket-color:' . esc_attr((string) $ticket['status_color']) . '">' . esc_html((string) $ticket['status_label']) . '</span>';
return '<span class="jio-ticket-badge" style="--jio-ticket-color:' . esc_attr((string) $ticket['status_color']) . '">' . esc_html((string) $ticket['status_label']) . '</span>';
}
private function ticket_urge_badge(array $ticket): string {
@@ -165,13 +165,13 @@ trait UsersMenusTicketsList {
if ($count <= 0 || !empty($ticket['is_closed'])) {
return '';
}
return ' <span class="aksrt-ticket-urge" title="' . esc_attr(sprintf(__('最近催单:%s', 'aksrt'), (string) $ticket['last_urge_time'])) . '">' . esc_html(sprintf(__('催单 ×%d', 'aksrt'), $count)) . '</span>';
return ' <span class="jio-ticket-urge" title="' . esc_attr(sprintf(__('最近催单:%s', 'jio'), (string) $ticket['last_urge_time'])) . '">' . esc_html(sprintf(__('催单 ×%d', 'jio'), $count)) . '</span>';
}
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) : '';
$avatar = function_exists('jio_get_avatar_url') ? jio_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)) . '" target="_blank" rel="noopener noreferrer" aria-label="' . esc_attr(sprintf(__('%s 的用户资料(新窗口)', 'aksrt'), $label)) . '">'
return '<a class="jio-ticket-user" href="' . esc_url(get_edit_user_link($user_id)) . '" target="_blank" rel="noopener noreferrer" aria-label="' . esc_attr(sprintf(__('%s 的用户资料(新窗口)', 'jio'), $label)) . '">'
. ($avatar !== '' ? '<img src="' . esc_url($avatar) . '" alt="" width="24" height="24">' : '')
. '<span>' . esc_html($label) . '</span></a>';
}
+54 -54
View File
@@ -1,12 +1,12 @@
<?php
/** Native WordPress identity application review. @package AKSRT */
/** Native WordPress identity application review. @package Jio */
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
use AKSRT\Identity\ApplicationRepository;
use AKSRT\Identity\EvidenceService;
use AKSRT\Identity\EventRepository;
use AKSRT\User\AuthApply;
use Jio\Identity\ApplicationRepository;
use Jio\Identity\EvidenceService;
use Jio\Identity\EventRepository;
use Jio\User\AuthApply;
defined('ABSPATH') || exit;
@@ -17,16 +17,16 @@ trait UsersMenusVerify {
private const VERIFY_STATUSES = ['pending', 'approved', 'rejected', ''];
public function enqueue_verify_assets(string $hook): void {
if ($hook !== 'users_page_aksrt_user_auth') return;
if ($hook !== 'users_page_jio_user_auth') return;
$css = '/assets/css/admin/user-auth.css';
$js = '/assets/js/admin-user-auth.js';
wp_enqueue_style('aksrt-admin-user-auth', AKSRT_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(AKSRT_DIR . $css));
wp_enqueue_script('aksrt-admin-user-auth', AKSRT_URI . $js, [], (string) filemtime(AKSRT_DIR . $js), true);
wp_enqueue_style('jio-admin-user-auth', JIO_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(JIO_DIR . $css));
wp_enqueue_script('jio-admin-user-auth', JIO_URI . $js, [], (string) filemtime(JIO_DIR . $js), true);
}
private function count_verify_status(string $status): int {
global $wpdb;
$table = \AKSRT\Core\DBMigrator::table('identity_application');
$table = \Jio\Core\DBMigrator::table('identity_application');
return (int) $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$table} WHERE type = %s AND channel = %s AND status = %s",
'general', 'manual', $status === 'pending' ? 'manual_pending' : $status
@@ -38,23 +38,23 @@ trait UsersMenusVerify {
$filters = $this->get_verify_filters($_GET);
$data = $this->query_verify_requests($filters, max(1, (int) ($_GET['paged'] ?? 1)));
$counts = $this->get_verify_status_counts($filters);
NativeUi::page_open('aksrt-verify-page');
NativeUi::page_open('jio-verify-page');
NativeUi::header(
__('身份认证', 'aksrt'),
__('查看申请资料与证明材料,在审核弹窗中确认处理结果。', 'aksrt'),
__('身份认证', 'jio'),
__('查看申请资料与证明材料,在审核弹窗中确认处理结果。', 'jio'),
'dashicons-id',
[['label' => __('认证设置', 'aksrt'), 'url' => admin_url('admin.php?page=aksrt-settings#/users/verify'), 'icon' => 'dashicons-admin-settings']]
[['label' => __('认证设置', 'jio'), 'url' => admin_url('admin.php?page=jio-settings#/users/verify'), 'icon' => 'dashicons-admin-settings']]
);
$this->flash();
NativeUi::card_open('', '', true, 'aksrt-verify-list-card');
NativeUi::card_open('', '', true, 'jio-verify-list-card');
$this->render_verify_filters($filters, $counts);
echo '<div class="aksrt-native-table-wrap">';
echo '<div class="jio-native-table-wrap">';
$this->render_verify_table($data['items'], $filters, $data['page']);
NativeUi::table_wrap_close();
AdminListPagination::render('aksrt_user_auth', $data['total'], $data['page'], self::VERIFY_PER_PAGE, $filters, 'users.php');
AdminListPagination::render('jio_user_auth', $data['total'], $data['page'], self::VERIFY_PER_PAGE, $filters, 'users.php');
NativeUi::card_close();
echo '<dialog id="aksrt-verify-dialog" class="aksrt-verify-dialog" aria-labelledby="aksrt-verify-dialog-title" aria-describedby="aksrt-verify-dialog-description"></dialog>';
echo '<noscript><p class="aksrt-native-callout">' . esc_html__('请启用 JavaScript 后查看申请资料和处理审核。', 'aksrt') . '</p></noscript>';
echo '<dialog id="jio-verify-dialog" class="jio-verify-dialog" aria-labelledby="jio-verify-dialog-title" aria-describedby="jio-verify-dialog-description"></dialog>';
echo '<noscript><p class="jio-native-callout">' . esc_html__('请启用 JavaScript 后查看申请资料和处理审核。', 'jio') . '</p></noscript>';
NativeUi::page_close();
}
@@ -70,7 +70,7 @@ trait UsersMenusVerify {
/** @return array{0:string,1:array} */
private function verify_query_base(array $filters): array {
global $wpdb;
$table = \AKSRT\Core\DBMigrator::table('identity_application');
$table = \Jio\Core\DBMigrator::table('identity_application');
$where = ['a.type = %s', 'a.channel = %s'];
$params = ['general', 'manual'];
if ($filters['status'] !== '') {
@@ -128,38 +128,38 @@ trait UsersMenusVerify {
}
private function verify_page_url(array $args = []): string {
return add_query_arg(array_merge(['page' => 'aksrt_user_auth'], $args), admin_url('users.php'));
return add_query_arg(array_merge(['page' => 'jio_user_auth'], $args), admin_url('users.php'));
}
private function render_verify_filters(array $filters, array $counts): void {
echo '<div class="aksrt-verify-toolbar"><div class="aksrt-verify-toolbar__views">';
echo '<h2 class="aksrt-native-card__title">' . esc_html__('认证申请', 'aksrt') . '</h2>';
echo '<ul class="subsubsub" aria-label="' . esc_attr__('认证状态筛选', 'aksrt') . '">';
foreach (['pending' => __('待审核', 'aksrt'), 'approved' => __('已通过', 'aksrt'), 'rejected' => __('已拒绝', 'aksrt'), '' => __('全部', 'aksrt')] as $key => $label) {
echo '<div class="jio-verify-toolbar"><div class="jio-verify-toolbar__views">';
echo '<h2 class="jio-native-card__title">' . esc_html__('认证申请', 'jio') . '</h2>';
echo '<ul class="subsubsub" aria-label="' . esc_attr__('认证状态筛选', 'jio') . '">';
foreach (['pending' => __('待审核', 'jio'), 'approved' => __('已通过', 'jio'), 'rejected' => __('已拒绝', 'jio'), '' => __('全部', 'jio')] as $key => $label) {
$url = $this->verify_page_url(['status' => $key, 'keyword' => $filters['keyword']]);
echo '<li><a href="' . esc_url($url) . '"' . ($key === $filters['status'] ? ' class="current" aria-current="page"' : '') . '>' . esc_html($label)
. ' <span class="count">' . esc_html(number_format_i18n($counts[$key === '' ? 'all' : $key])) . '</span></a></li>';
}
echo '</ul></div><form method="get" action="' . esc_url(admin_url('users.php')) . '" class="aksrt-native-filters">';
echo '<input type="hidden" name="page" value="aksrt_user_auth"><input type="hidden" name="status" value="' . esc_attr($filters['status']) . '">';
echo '<label class="screen-reader-text" for="aksrt-verify-search">' . esc_html__('搜索用户或认证资料', 'aksrt') . '</label>';
echo '<input type="search" id="aksrt-verify-search" name="keyword" value="' . esc_attr($filters['keyword']) . '" placeholder="' . esc_attr__('用户 ID / 昵称 / 邮箱 / 认证名称', 'aksrt') . '">';
echo '<button type="submit" class="button">' . esc_html__('搜索', 'aksrt') . '</button>';
echo '</ul></div><form method="get" action="' . esc_url(admin_url('users.php')) . '" class="jio-native-filters">';
echo '<input type="hidden" name="page" value="jio_user_auth"><input type="hidden" name="status" value="' . esc_attr($filters['status']) . '">';
echo '<label class="screen-reader-text" for="jio-verify-search">' . esc_html__('搜索用户或认证资料', 'jio') . '</label>';
echo '<input type="search" id="jio-verify-search" name="keyword" value="' . esc_attr($filters['keyword']) . '" placeholder="' . esc_attr__('用户 ID / 昵称 / 邮箱 / 认证名称', 'jio') . '">';
echo '<button type="submit" class="button">' . esc_html__('搜索', 'jio') . '</button>';
if ($filters['keyword'] !== '') {
echo '<a class="button-link" href="' . esc_url($this->verify_page_url(['status' => $filters['status']])) . '">' . esc_html__('清除搜索', 'aksrt') . '</a>';
echo '<a class="button-link" href="' . esc_url($this->verify_page_url(['status' => $filters['status']])) . '">' . esc_html__('清除搜索', 'jio') . '</a>';
}
echo '</form></div>';
}
private function render_verify_table(array $items, array $filters, int $page): void {
echo '<table class="widefat fixed striped aksrt-verify-table"><thead><tr>';
foreach (['user' => __('申请用户', 'aksrt'), 'profile' => __('认证资料', 'aksrt'), 'evidence' => __('证明材料', 'aksrt'), 'status' => __('状态', 'aksrt'), 'time' => __('申请时间', 'aksrt'), 'actions' => __('操作', 'aksrt')] as $key => $label) {
echo '<table class="widefat fixed striped jio-verify-table"><thead><tr>';
foreach (['user' => __('申请用户', 'jio'), 'profile' => __('认证资料', 'jio'), 'evidence' => __('证明材料', 'jio'), 'status' => __('状态', 'jio'), 'time' => __('申请时间', 'jio'), 'actions' => __('操作', 'jio')] as $key => $label) {
echo '<th scope="col" class="column-' . esc_attr($key) . '">' . esc_html($label) . '</th>';
}
echo '</tr></thead><tbody>';
if ($items === []) {
$title = $filters['status'] === 'pending' && $filters['keyword'] === '' ? __('暂无待审核申请', 'aksrt') : __('没有符合条件的认证申请', 'aksrt');
$hint = $filters['keyword'] !== '' ? __('请调整搜索关键词或切换状态。', 'aksrt') : __('可切换状态查看其他申请,新申请将在这里显示。', 'aksrt');
$title = $filters['status'] === 'pending' && $filters['keyword'] === '' ? __('暂无待审核申请', 'jio') : __('没有符合条件的认证申请', 'jio');
$hint = $filters['keyword'] !== '' ? __('请调整搜索关键词或切换状态。', 'jio') : __('可切换状态查看其他申请,新申请将在这里显示。', 'jio');
echo NativeUi::empty_row(6, $title, $hint); // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
}
foreach ($items as $row) $this->render_verify_row($row, $filters, $page);
@@ -171,23 +171,23 @@ trait UsersMenusVerify {
$pending = $row['status'] === 'manual_pending';
$evidence = (new EvidenceService())->items($row, true);
echo '<tr><td class="column-user">' . NativeUi::user_cell((int) $row['user_id'], (string) $row['display_name']); // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
echo '<div class="aksrt-verify-sub">' . esc_html((string) $row['user_email']) . '</div></td>';
echo '<td class="column-profile"><strong>' . esc_html((string) $row['public_name']) . '</strong><div class="aksrt-verify-summary">' . esc_html((string) $row['description']) . '</div>';
echo '<div class="jio-verify-sub">' . esc_html((string) $row['user_email']) . '</div></td>';
echo '<td class="column-profile"><strong>' . esc_html((string) $row['public_name']) . '</strong><div class="jio-verify-summary">' . esc_html((string) $row['description']) . '</div>';
$this->render_verify_detail_template($row, $evidence, $filters, $page);
$this->render_verify_delete_template($row, $filters, $page);
echo '</td><td class="column-evidence">' . esc_html($evidence === [] ? __('未提供材料', 'aksrt') : sprintf(__('%d 份材料', 'aksrt'), count($evidence))) . '</td>';
echo '</td><td class="column-evidence">' . esc_html($evidence === [] ? __('未提供材料', 'jio') : sprintf(__('%d 份材料', 'jio'), count($evidence))) . '</td>';
echo '<td class="column-status">' . $this->verify_status_badge((string) $row['status']) . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
echo '<td class="column-time">' . esc_html($this->verify_time((string) $row['created_at'])) . '</td>';
echo '<td class="column-actions"><div class="aksrt-verify-row-actions">';
echo '<button type="button" class="button' . ($pending ? ' button-primary' : '') . '" data-aksrt-verify-view="' . $id . '" aria-haspopup="dialog" aria-controls="aksrt-verify-dialog">' . esc_html($pending ? __('审核', 'aksrt') : __('查看详情', 'aksrt')) . '</button>';
echo '<button type="button" class="button-link button-link-delete" data-aksrt-verify-delete="' . $id . '" aria-haspopup="dialog" aria-controls="aksrt-verify-dialog">' . esc_html__('删除记录', 'aksrt') . '</button></div></td></tr>';
echo '<td class="column-actions"><div class="jio-verify-row-actions">';
echo '<button type="button" class="button' . ($pending ? ' button-primary' : '') . '" data-jio-verify-view="' . $id . '" aria-haspopup="dialog" aria-controls="jio-verify-dialog">' . esc_html($pending ? __('审核', 'jio') : __('查看详情', 'jio')) . '</button>';
echo '<button type="button" class="button-link button-link-delete" data-jio-verify-delete="' . $id . '" aria-haspopup="dialog" aria-controls="jio-verify-dialog">' . esc_html__('删除记录', 'jio') . '</button></div></td></tr>';
}
private function verify_status_badge(string $status): string {
return NativeUi::status_badge($status, [
'manual_pending' => [__('待审核', 'aksrt'), 'warning'],
'verified' => [__('已通过', 'aksrt'), 'success'],
'rejected' => [__('已拒绝', 'aksrt'), 'danger'],
'manual_pending' => [__('待审核', 'jio'), 'warning'],
'verified' => [__('已通过', 'jio'), 'success'],
'rejected' => [__('已拒绝', 'jio'), 'danger'],
]);
}
@@ -203,9 +203,9 @@ trait UsersMenusVerify {
}
public function handle_review_verify(): void {
if (!current_user_can('aksrt_review_identity') && !current_user_can(self::CAPABILITY)) wp_die(esc_html__('权限不足', 'aksrt'));
if (!current_user_can('jio_review_identity') && !current_user_can(self::CAPABILITY)) wp_die(esc_html__('权限不足', 'jio'));
$uuid = sanitize_text_field((string) wp_unslash($_POST['application_uuid'] ?? ''));
check_admin_referer('aksrt_users_review_verify_' . $uuid);
check_admin_referer('jio_users_review_verify_' . $uuid);
$user_id = absint($_POST['user_id'] ?? 0);
$status = sanitize_key((string) wp_unslash($_POST['status'] ?? ''));
$note = sanitize_textarea_field((string) wp_unslash($_POST['note'] ?? ''));
@@ -215,27 +215,27 @@ trait UsersMenusVerify {
];
$result = AuthApply::review($user_id, $uuid, $status, $note, get_current_user_id(), $fields);
$extra = $this->verify_return_args();
if (is_wp_error($result)) $extra['aksrt_verify_error'] = $result->get_error_message();
$this->redirect('aksrt_user_auth', is_wp_error($result) ? 'verify_failed' : 'verify_updated', $extra);
if (is_wp_error($result)) $extra['jio_verify_error'] = $result->get_error_message();
$this->redirect('jio_user_auth', is_wp_error($result) ? 'verify_failed' : 'verify_updated', $extra);
}
public function handle_delete_verify(): void {
if (!current_user_can('aksrt_review_identity') && !current_user_can(self::CAPABILITY)) wp_die(esc_html__('权限不足', 'aksrt'));
if (!current_user_can('jio_review_identity') && !current_user_can(self::CAPABILITY)) wp_die(esc_html__('权限不足', 'jio'));
$uuid = sanitize_text_field((string) wp_unslash($_POST['application_uuid'] ?? ''));
check_admin_referer('aksrt_users_delete_verify_' . $uuid);
check_admin_referer('jio_users_delete_verify_' . $uuid);
$extra = $this->verify_return_args();
$repo = new ApplicationRepository();
$application = $repo->find_by_uuid($uuid);
if (!$application) {
$this->redirect('aksrt_user_auth', 'verify_failed', array_merge($extra, ['aksrt_verify_error' => __('认证记录不存在', 'aksrt')]));
$this->redirect('jio_user_auth', 'verify_failed', array_merge($extra, ['jio_verify_error' => __('认证记录不存在', 'jio')]));
}
if (!$repo->delete((int) $application['id'])) {
$this->redirect('aksrt_user_auth', 'verify_failed', array_merge($extra, ['aksrt_verify_error' => __('认证记录删除失败', 'aksrt')]));
$this->redirect('jio_user_auth', 'verify_failed', array_merge($extra, ['jio_verify_error' => __('认证记录删除失败', 'jio')]));
}
(new EvidenceService())->delete((array) $application['evidence_ids']);
if (!(new EventRepository())->delete_for_application((int) $application['id'])) {
$this->redirect('aksrt_user_auth', 'verify_failed', array_merge($extra, ['aksrt_verify_error' => __('认证申请记录已删除,但审核事件清理失败。', 'aksrt')]));
$this->redirect('jio_user_auth', 'verify_failed', array_merge($extra, ['jio_verify_error' => __('认证申请记录已删除,但审核事件清理失败。', 'jio')]));
}
$this->redirect('aksrt_user_auth', 'verify_deleted', $extra);
$this->redirect('jio_user_auth', 'verify_deleted', $extra);
}
}
+55 -55
View File
@@ -1,16 +1,16 @@
<?php
/** Identity application detail, review and deletion dialogs. @package AKSRT */
/** Identity application detail, review and deletion dialogs. @package Jio */
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
trait UsersMenusVerifyDialogs {
private function render_verify_dialog_head(string $title, string $description): void {
echo '<div class="aksrt-verify-dialog__head"><div><h2 id="aksrt-verify-dialog-title">' . esc_html($title) . '</h2>';
echo '<p id="aksrt-verify-dialog-description">' . esc_html($description) . '</p></div>';
echo '<button type="button" class="aksrt-verify-dialog__close" data-aksrt-dialog-close aria-label="' . esc_attr__('关闭弹窗', 'aksrt') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></div>';
echo '<div class="jio-verify-dialog__head"><div><h2 id="jio-verify-dialog-title">' . esc_html($title) . '</h2>';
echo '<p id="jio-verify-dialog-description">' . esc_html($description) . '</p></div>';
echo '<button type="button" class="jio-verify-dialog__close" data-jio-dialog-close aria-label="' . esc_attr__('关闭弹窗', 'jio') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></div>';
}
private function render_verify_context(array $row, array $filters, int $page, string $action): void {
@@ -24,70 +24,70 @@ trait UsersMenusVerifyDialogs {
private function render_verify_detail_template(array $row, array $evidence, array $filters, int $page): void {
$pending = $row['status'] === 'manual_pending';
echo '<template id="aksrt-verify-detail-' . (int) $row['id'] . '">';
echo '<template id="jio-verify-detail-' . (int) $row['id'] . '">';
if ($pending) {
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-verify-dialog__frame" data-aksrt-verify-review>';
$this->render_verify_context($row, $filters, $page, 'aksrt_users_review_verify');
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="jio-verify-dialog__frame" data-jio-verify-review>';
$this->render_verify_context($row, $filters, $page, 'jio_users_review_verify');
echo '<input type="hidden" name="user_id" value="' . (int) $row['user_id'] . '">';
} else {
echo '<div class="aksrt-verify-dialog__frame">';
echo '<div class="jio-verify-dialog__frame">';
}
$this->render_verify_dialog_head(
$pending ? __('审核认证申请', 'aksrt') : __('认证申请详情', 'aksrt'),
$pending ? __('先核对申请资料与证明材料,再选择审核结果。', 'aksrt') : __('该申请已处理,以下为申请资料与审核结果。', 'aksrt')
$pending ? __('审核认证申请', 'jio') : __('认证申请详情', 'jio'),
$pending ? __('先核对申请资料与证明材料,再选择审核结果。', 'jio') : __('该申请已处理,以下为申请资料与审核结果。', 'jio')
);
echo '<div class="aksrt-verify-dialog__body">';
echo '<div class="aksrt-verify-applicant">' . NativeUi::user_cell((int) $row['user_id'], (string) $row['display_name']); // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
echo '<div class="jio-verify-dialog__body">';
echo '<div class="jio-verify-applicant">' . NativeUi::user_cell((int) $row['user_id'], (string) $row['display_name']); // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
echo $this->verify_status_badge((string) $row['status']) . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
$meta = [
__('用户账号', 'aksrt') => (string) $row['user_login'],
__('邮箱', 'aksrt') => (string) $row['user_email'],
__('申请时间', 'aksrt') => $this->verify_time((string) $row['created_at']),
__('用户账号', 'jio') => (string) $row['user_login'],
__('邮箱', 'jio') => (string) $row['user_email'],
__('申请时间', 'jio') => $this->verify_time((string) $row['created_at']),
];
if (!$pending) {
$reviewer = get_userdata((int) $row['reviewer_id']);
$meta[__('审核人', 'aksrt')] = $reviewer ? (string) $reviewer->display_name : '—';
$meta[__('审核时间', 'aksrt')] = $this->verify_time((string) ($row['completed_at'] ?? ''));
$meta[__('审核人', 'jio')] = $reviewer ? (string) $reviewer->display_name : '—';
$meta[__('审核时间', 'jio')] = $this->verify_time((string) ($row['completed_at'] ?? ''));
}
echo '<dl class="aksrt-verify-meta">';
echo '<dl class="jio-verify-meta">';
foreach ($meta as $label => $value) {
echo '<div><dt>' . esc_html($label) . '</dt><dd>' . esc_html($value !== '' ? $value : '—') . '</dd></div>';
}
echo '</dl>';
$this->render_verify_text_section(__('认证名称', 'aksrt'), (string) $row['public_name']);
$this->render_verify_text_section(__('认证简介', 'aksrt'), (string) $row['description']);
$this->render_verify_text_section(__('申请说明', 'aksrt'), (string) $row['manual_reason']);
$this->render_verify_text_section(__('认证名称', 'jio'), (string) $row['public_name']);
$this->render_verify_text_section(__('认证简介', 'jio'), (string) $row['description']);
$this->render_verify_text_section(__('申请说明', 'jio'), (string) $row['manual_reason']);
$this->render_verify_evidence($evidence);
if ($pending) {
$this->render_verify_review_fields($row);
} else {
$this->render_verify_text_section($row['status'] === 'rejected' ? __('拒绝原因', 'aksrt') : __('处理留言', 'aksrt'), (string) $row['review_note']);
$this->render_verify_text_section($row['status'] === 'rejected' ? __('拒绝原因', 'jio') : __('处理留言', 'jio'), (string) $row['review_note']);
}
echo '</div><div class="aksrt-verify-dialog__foot">';
echo '<button type="button" class="button" data-aksrt-dialog-close>' . esc_html($pending ? __('取消', 'aksrt') : __('关闭', 'aksrt')) . '</button>';
echo '</div><div class="jio-verify-dialog__foot">';
echo '<button type="button" class="button" data-jio-dialog-close>' . esc_html($pending ? __('取消', 'jio') : __('关闭', 'jio')) . '</button>';
if ($pending) {
echo '<button type="submit" class="button button-primary" data-aksrt-verify-submit disabled'
. ' data-idle-label="' . esc_attr__('请选择审核结果', 'aksrt') . '"'
. ' data-approve-label="' . esc_attr__('确认通过', 'aksrt') . '"'
. ' data-reject-label="' . esc_attr__('确认拒绝', 'aksrt') . '"'
. ' data-busy-label="' . esc_attr__('正在提交…', 'aksrt') . '">' . esc_html__('请选择审核结果', 'aksrt') . '</button>';
echo '<button type="submit" class="button button-primary" data-jio-verify-submit disabled'
. ' data-idle-label="' . esc_attr__('请选择审核结果', 'jio') . '"'
. ' data-approve-label="' . esc_attr__('确认通过', 'jio') . '"'
. ' data-reject-label="' . esc_attr__('确认拒绝', 'jio') . '"'
. ' data-busy-label="' . esc_attr__('正在提交…', 'jio') . '">' . esc_html__('请选择审核结果', 'jio') . '</button>';
}
echo '</div>' . ($pending ? '</form>' : '</div>') . '</template>';
}
private function render_verify_text_section(string $title, string $text): void {
echo '<section class="aksrt-verify-section"><h3>' . esc_html($title) . '</h3><div class="aksrt-verify-copy">' . esc_html($text !== '' ? $text : __('未填写', 'aksrt')) . '</div></section>';
echo '<section class="jio-verify-section"><h3>' . esc_html($title) . '</h3><div class="jio-verify-copy">' . esc_html($text !== '' ? $text : __('未填写', 'jio')) . '</div></section>';
}
private function render_verify_evidence(array $evidence): void {
echo '<section class="aksrt-verify-section"><h3>' . esc_html__('证明材料', 'aksrt') . '</h3>';
echo '<section class="jio-verify-section"><h3>' . esc_html__('证明材料', 'jio') . '</h3>';
if ($evidence === []) {
echo '<p class="aksrt-verify-evidence-empty">' . esc_html__('未提供可查看的证明材料,请结合申请说明判断。', 'aksrt') . '</p>';
echo '<p class="jio-verify-evidence-empty">' . esc_html__('未提供可查看的证明材料,请结合申请说明判断。', 'jio') . '</p>';
} else {
echo '<ul class="aksrt-verify-evidence">';
echo '<ul class="jio-verify-evidence">';
foreach ($evidence as $index => $item) {
$label = sprintf(__('材料 %d(新窗口查看原图)', 'aksrt'), $index + 1);
echo '<li><a href="' . esc_url($item['url']) . '" target="_blank" rel="noopener noreferrer"><img src="' . esc_url($item['thumbnail_url']) . '" alt="' . esc_attr(sprintf(__('证明材料 %d', 'aksrt'), $index + 1)) . '"><span>' . esc_html($label) . '</span></a></li>';
$label = sprintf(__('材料 %d(新窗口查看原图)', 'jio'), $index + 1);
echo '<li><a href="' . esc_url($item['url']) . '" target="_blank" rel="noopener noreferrer"><img src="' . esc_url($item['thumbnail_url']) . '" alt="' . esc_attr(sprintf(__('证明材料 %d', 'jio'), $index + 1)) . '"><span>' . esc_html($label) . '</span></a></li>';
}
echo '</ul>';
}
@@ -95,30 +95,30 @@ trait UsersMenusVerifyDialogs {
}
private function render_verify_review_fields(array $row): void {
echo '<section class="aksrt-verify-review"><h3>' . esc_html__('审核处理', 'aksrt') . '</h3>';
echo '<fieldset class="aksrt-verify-decisions"><legend>' . esc_html__('审核结果(必选)', 'aksrt') . '</legend>';
foreach (['verified' => __('通过申请', 'aksrt'), 'rejected' => __('拒绝申请', 'aksrt')] as $status => $label) {
echo '<section class="jio-verify-review"><h3>' . esc_html__('审核处理', 'jio') . '</h3>';
echo '<fieldset class="jio-verify-decisions"><legend>' . esc_html__('审核结果(必选)', 'jio') . '</legend>';
foreach (['verified' => __('通过申请', 'jio'), 'rejected' => __('拒绝申请', 'jio')] as $status => $label) {
echo '<label><input type="radio" name="status" value="' . esc_attr($status) . '" required><span>' . esc_html($label) . '</span></label>';
}
echo '</fieldset><fieldset class="aksrt-verify-profile" data-aksrt-verify-profile hidden disabled><legend>' . esc_html__('通过后公开展示的认证资料', 'aksrt') . '</legend>';
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html__('认证名称(必填)', 'aksrt') . '</span>';
echo '<input type="text" name="name" maxlength="64" value="' . esc_attr((string) $row['public_name']) . '" required data-required-error="' . esc_attr__('请填写认证名称', 'aksrt') . '"></label>';
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html__('认证简介', 'aksrt') . '</span><textarea name="description" rows="3">' . esc_textarea((string) $row['description']) . '</textarea></label>';
echo '<p class="aksrt-native-field__hint">' . esc_html__('通过审核前可修订名称与简介,原申请说明和证明材料不会被修改。', 'aksrt') . '</p></fieldset>';
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label" data-aksrt-verify-note-label data-optional="' . esc_attr__('处理留言(选填)', 'aksrt') . '" data-required="' . esc_attr__('拒绝原因(必填)', 'aksrt') . '">' . esc_html__('处理留言(选填)', 'aksrt') . '</span>';
echo '<textarea name="note" rows="3" data-required-error="' . esc_attr__('请填写拒绝原因', 'aksrt') . '" aria-describedby="aksrt-verify-note-hint"></textarea></label>';
echo '<p id="aksrt-verify-note-hint" class="aksrt-native-field__hint">' . esc_html__('审核结果会通知申请用户;拒绝时请说明原因,便于用户修改后重新申请。', 'aksrt') . '</p></section>';
echo '</fieldset><fieldset class="jio-verify-profile" data-jio-verify-profile hidden disabled><legend>' . esc_html__('通过后公开展示的认证资料', 'jio') . '</legend>';
echo '<label class="jio-native-field"><span class="jio-native-field__label">' . esc_html__('认证名称(必填)', 'jio') . '</span>';
echo '<input type="text" name="name" maxlength="64" value="' . esc_attr((string) $row['public_name']) . '" required data-required-error="' . esc_attr__('请填写认证名称', 'jio') . '"></label>';
echo '<label class="jio-native-field"><span class="jio-native-field__label">' . esc_html__('认证简介', 'jio') . '</span><textarea name="description" rows="3">' . esc_textarea((string) $row['description']) . '</textarea></label>';
echo '<p class="jio-native-field__hint">' . esc_html__('通过审核前可修订名称与简介,原申请说明和证明材料不会被修改。', 'jio') . '</p></fieldset>';
echo '<label class="jio-native-field"><span class="jio-native-field__label" data-jio-verify-note-label data-optional="' . esc_attr__('处理留言(选填)', 'jio') . '" data-required="' . esc_attr__('拒绝原因(必填)', 'jio') . '">' . esc_html__('处理留言(选填)', 'jio') . '</span>';
echo '<textarea name="note" rows="3" data-required-error="' . esc_attr__('请填写拒绝原因', 'jio') . '" aria-describedby="jio-verify-note-hint"></textarea></label>';
echo '<p id="jio-verify-note-hint" class="jio-native-field__hint">' . esc_html__('审核结果会通知申请用户;拒绝时请说明原因,便于用户修改后重新申请。', 'jio') . '</p></section>';
}
private function render_verify_delete_template(array $row, array $filters, int $page): void {
echo '<template id="aksrt-verify-delete-' . (int) $row['id'] . '"><form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-verify-dialog__frame" data-aksrt-verify-delete-form>';
$this->render_verify_context($row, $filters, $page, 'aksrt_users_delete_verify');
$this->render_verify_dialog_head(__('删除认证申请记录', 'aksrt'), __('请核对申请信息。此操作不可撤销。', 'aksrt'));
echo '<div class="aksrt-verify-dialog__body"><div class="aksrt-verify-applicant">' . NativeUi::user_cell((int) $row['user_id'], (string) $row['display_name']); // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
echo '<template id="jio-verify-delete-' . (int) $row['id'] . '"><form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="jio-verify-dialog__frame" data-jio-verify-delete-form>';
$this->render_verify_context($row, $filters, $page, 'jio_users_delete_verify');
$this->render_verify_dialog_head(__('删除认证申请记录', 'jio'), __('请核对申请信息。此操作不可撤销。', 'jio'));
echo '<div class="jio-verify-dialog__body"><div class="jio-verify-applicant">' . NativeUi::user_cell((int) $row['user_id'], (string) $row['display_name']); // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
echo $this->verify_status_badge((string) $row['status']) . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
$this->render_verify_text_section(__('认证名称', 'aksrt'), (string) $row['public_name']);
echo '<p class="aksrt-native-callout is-danger">' . esc_html__('将永久删除该申请记录、证明材料及审核事件。删除记录不会撤销用户已获得的认证。', 'aksrt') . '</p>';
echo '</div><div class="aksrt-verify-dialog__foot"><button type="button" class="button" data-aksrt-dialog-close autofocus>' . esc_html__('取消', 'aksrt') . '</button>';
echo '<button type="submit" class="button is-danger" data-aksrt-verify-submit data-idle-label="' . esc_attr__('确认删除', 'aksrt') . '" data-busy-label="' . esc_attr__('正在提交…', 'aksrt') . '">' . esc_html__('确认删除', 'aksrt') . '</button></div></form></template>';
$this->render_verify_text_section(__('认证名称', 'jio'), (string) $row['public_name']);
echo '<p class="jio-native-callout is-danger">' . esc_html__('将永久删除该申请记录、证明材料及审核事件。删除记录不会撤销用户已获得的认证。', 'jio') . '</p>';
echo '</div><div class="jio-verify-dialog__foot"><button type="button" class="button" data-jio-dialog-close autofocus>' . esc_html__('取消', 'jio') . '</button>';
echo '<button type="submit" class="button is-danger" data-jio-verify-submit data-idle-label="' . esc_attr__('确认删除', 'jio') . '" data-busy-label="' . esc_attr__('正在提交…', 'jio') . '">' . esc_html__('确认删除', 'jio') . '</button></div></form></template>';
}
}
+35 -35
View File
@@ -1,15 +1,15 @@
<?php
/**
* 用户会员后台页(users.php → aksrt_user_vip):页面编排、写操作与查询。
* 用户会员后台页(users.php → jio_user_vip):页面编排、写操作与查询。
*
* 列表展示由 UsersMenusVipView 输出;所有会员变更都通过同一个弹窗表单提交到
* admin_post_aksrt_users_mutate_vip(单人调整与批量调整共用),交互脚本见
* admin_post_jio_users_mutate_vip(单人调整与批量调整共用),交互脚本见
* assets/js/admin-vip.js。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
@@ -17,7 +17,7 @@ trait UsersMenusVip {
use UsersMenusVipView;
private const VIP_PAGE_SLUG = 'aksrt_user_vip';
private const VIP_PAGE_SLUG = 'jio_user_vip';
private const VIP_PER_PAGE = 20;
private const VIP_FILTER_KEYS = ['user_id', 'level', 'state', 'date_from', 'date_to', 'orderby', 'order'];
@@ -27,11 +27,11 @@ trait UsersMenusVip {
}
$css = '/assets/css/admin/vip.css';
$js = '/assets/js/admin-vip.js';
if (is_file(AKSRT_DIR . $css)) {
wp_enqueue_style('aksrt-admin-vip', AKSRT_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(AKSRT_DIR . $css));
if (is_file(JIO_DIR . $css)) {
wp_enqueue_style('jio-admin-vip', JIO_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(JIO_DIR . $css));
}
if (is_file(AKSRT_DIR . $js)) {
wp_enqueue_script('aksrt-admin-vip', AKSRT_URI . $js, [], (string) filemtime(AKSRT_DIR . $js), true);
if (is_file(JIO_DIR . $js)) {
wp_enqueue_script('jio-admin-vip', JIO_URI . $js, [], (string) filemtime(JIO_DIR . $js), true);
}
}
@@ -39,14 +39,14 @@ trait UsersMenusVip {
$this->ensure_cap();
$tab = $this->vip_tab();
$page = max(1, absint($_GET['paged'] ?? 1));
NativeUi::page_open('aksrt-vip');
NativeUi::page_open('jio-vip');
NativeUi::header(
__('用户会员', 'aksrt'),
__('查看会员档位与有效期,单人或批量调整会员;每次变更都会记录原因与结果。', 'aksrt'),
__('用户会员', 'jio'),
__('查看会员档位与有效期,单人或批量调整会员;每次变更都会记录原因与结果。', 'jio'),
'dashicons-awards',
[[
'label' => __('会员配置', 'aksrt'),
'url' => admin_url('admin.php?page=aksrt-settings#/payment/vip'),
'label' => __('会员配置', 'jio'),
'url' => admin_url('admin.php?page=jio-settings#/payment/vip'),
'icon' => 'dashicons-admin-generic',
]]
);
@@ -67,18 +67,18 @@ trait UsersMenusVip {
public function handle_mutate_vip(): void {
$this->ensure_cap();
check_admin_referer('aksrt_users_mutate_vip');
check_admin_referer('jio_users_mutate_vip');
$ids = isset($_POST['user_ids']) ? wp_unslash($_POST['user_ids']) : [];
$ids = is_array($ids) ? array_values(array_unique(array_filter(array_map('absint', $ids)))) : [];
if ($ids === [] || count($ids) > 100) {
$this->redirect_vip_result('vip_failed', ['aksrt_vip_error' => __('请选择 1 至 100 名用户。', 'aksrt')]);
$this->redirect_vip_result('vip_failed', ['jio_vip_error' => __('请选择 1 至 100 名用户。', 'jio')]);
}
$operation = sanitize_key((string) wp_unslash($_POST['vip_operation'] ?? ''));
$reason = sanitize_text_field((string) wp_unslash($_POST['reason'] ?? ''));
$payload = $this->vip_mutation_payload($operation);
if (is_wp_error($payload)) {
$this->redirect_vip_result('vip_failed', ['aksrt_vip_error' => $payload->get_error_message()]);
$this->redirect_vip_result('vip_failed', ['jio_vip_error' => $payload->get_error_message()]);
}
$batch = count($ids) > 1 ? wp_generate_uuid4() : '';
@@ -89,14 +89,14 @@ trait UsersMenusVip {
$skipped++;
continue;
}
$result = class_exists('\AKSRT\Content\VipService')
? \AKSRT\Content\VipService::get_instance()->mutate_membership($user_id, $operation, $payload, [
$result = class_exists('\Jio\Content\VipService')
? \Jio\Content\VipService::get_instance()->mutate_membership($user_id, $operation, $payload, [
'actor_user_id' => get_current_user_id(),
'source' => 'admin',
'reason' => $reason,
'batch_uuid' => $batch,
])
: new \WP_Error('aksrt_vip_unavailable', __('会员服务不可用。', 'aksrt'));
: new \WP_Error('jio_vip_unavailable', __('会员服务不可用。', 'jio'));
if (is_wp_error($result)) {
$failed++;
$last_error = $result->get_error_message();
@@ -105,12 +105,12 @@ trait UsersMenusVip {
}
}
if ($ok === 0) {
$this->redirect_vip_result('vip_failed', $last_error !== '' ? ['aksrt_vip_error' => $last_error] : []);
$this->redirect_vip_result('vip_failed', $last_error !== '' ? ['jio_vip_error' => $last_error] : []);
}
$this->redirect_vip_result($failed > 0 || $skipped > 0 ? 'vip_partial' : 'vip_updated', [
'aksrt_vip_ok' => $ok,
'aksrt_vip_failed_count' => $failed,
'aksrt_vip_skipped' => $skipped,
'jio_vip_ok' => $ok,
'jio_vip_failed_count' => $failed,
'jio_vip_skipped' => $skipped,
]);
}
@@ -164,13 +164,13 @@ trait UsersMenusVip {
private function vip_sql_parts(): array {
global $wpdb;
$meta = "(SELECT user_id,
MAX(CASE WHEN meta_key = 'aksrt_vip_level' THEN meta_value END) level,
MAX(CASE WHEN meta_key = 'aksrt_vip_expires' THEN meta_value END) expires,
MAX(CASE WHEN meta_key = 'aksrt_vip_base_level' THEN meta_value END) base_level,
MAX(CASE WHEN meta_key = 'aksrt_vip_base_expires' THEN meta_value END) base_expires,
MAX(CASE WHEN meta_key = 'aksrt_vip_level_expired' THEN meta_value END) expired_level
MAX(CASE WHEN meta_key = 'jio_vip_level' THEN meta_value END) level,
MAX(CASE WHEN meta_key = 'jio_vip_expires' THEN meta_value END) expires,
MAX(CASE WHEN meta_key = 'jio_vip_base_level' THEN meta_value END) base_level,
MAX(CASE WHEN meta_key = 'jio_vip_base_expires' THEN meta_value END) base_expires,
MAX(CASE WHEN meta_key = 'jio_vip_level_expired' THEN meta_value END) expired_level
FROM {$wpdb->usermeta}
WHERE meta_key IN ('aksrt_vip_level', 'aksrt_vip_expires', 'aksrt_vip_base_level', 'aksrt_vip_base_expires', 'aksrt_vip_level_expired')
WHERE meta_key IN ('jio_vip_level', 'jio_vip_expires', 'jio_vip_base_level', 'jio_vip_base_expires', 'jio_vip_level_expired')
GROUP BY user_id) m";
$raw_level = 'CAST(COALESCE(m.level, 0) AS UNSIGNED)';
$raw_expires = 'CAST(COALESCE(m.expires, 0) AS UNSIGNED)';
@@ -274,7 +274,7 @@ trait UsersMenusVip {
/** @return array<string,mixed>|\WP_Error */
private function vip_mutation_payload(string $operation): array|\WP_Error {
if (!in_array($operation, ['set', 'extend', 'disable', 'restore'], true)) {
return new \WP_Error('aksrt_vip_operation_invalid', __('会员操作无效。', 'aksrt'));
return new \WP_Error('jio_vip_operation_invalid', __('会员操作无效。', 'jio'));
}
if ($operation === 'disable' || $operation === 'restore') {
return [];
@@ -290,7 +290,7 @@ trait UsersMenusVip {
}
$date = $this->vip_date($_POST['expires_date'] ?? '');
if ($date === '') {
return new \WP_Error('aksrt_vip_expiry_required', __('请设置永久会员或有效的到期日期。', 'aksrt'));
return new \WP_Error('jio_vip_expiry_required', __('请设置永久会员或有效的到期日期。', 'jio'));
}
$expires = (new \DateTimeImmutable($date . ' 23:59:59', wp_timezone()))->getTimestamp();
return ['level' => absint($_POST['level'] ?? 0), 'expires' => $expires];
@@ -298,10 +298,10 @@ trait UsersMenusVip {
/** @return array<int,string> */
private function vip_level_names(): array {
$levels = class_exists('\AKSRT\Settings\Settings') ? (array) (\AKSRT\Settings\Settings::get_payment_vip_settings()['levels'] ?? []) : [];
$levels = class_exists('\Jio\Settings\Settings') ? (array) (\Jio\Settings\Settings::get_payment_vip_settings()['levels'] ?? []) : [];
return [
1 => (string) ($levels[1]['name'] ?? __('一级会员', 'aksrt')),
2 => (string) ($levels[2]['name'] ?? __('二级会员', 'aksrt')),
1 => (string) ($levels[1]['name'] ?? __('一级会员', 'jio')),
2 => (string) ($levels[2]['name'] ?? __('二级会员', 'jio')),
];
}
+113 -113
View File
@@ -5,10 +5,10 @@
* 弹窗是页面上唯一的写表单:行内「调整」与工具栏「批量调整」都只负责把目标用户
* 填进弹窗(assets/js/admin-vip.js),提交由 UsersMenusVip::handle_mutate_vip() 处理。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Native;
namespace Jio\Admin\Native;
defined('ABSPATH') || exit;
@@ -21,29 +21,29 @@ trait UsersMenusVipView {
$base = admin_url('users.php?page=' . self::VIP_PAGE_SLUG);
NativeUi::stats([
[
'label' => __('限时会员', 'aksrt'),
'label' => __('限时会员', 'jio'),
'value' => $stats['active'],
'url' => add_query_arg('state', 'active', $base),
'icon' => 'dashicons-clock',
'hint' => __('有效期内', 'aksrt'),
'hint' => __('有效期内', 'jio'),
],
[
'label' => __('永久会员', 'aksrt'),
'label' => __('永久会员', 'jio'),
'value' => $stats['permanent'],
'url' => add_query_arg('state', 'permanent', $base),
'tone' => 'primary',
'icon' => 'dashicons-awards',
],
[
'label' => __('7 天内到期', 'aksrt'),
'label' => __('7 天内到期', 'jio'),
'value' => $stats['expiring'],
'url' => add_query_arg(['state' => 'active', 'date_from' => $window['from'], 'date_to' => $window['to'], 'orderby' => 'expires', 'order' => 'ASC'], $base),
'tone' => $stats['expiring'] > 0 ? 'warning' : 'neutral',
'icon' => 'dashicons-warning',
'hint' => __('建议提醒续费', 'aksrt'),
'hint' => __('建议提醒续费', 'jio'),
],
[
'label' => __('已过期', 'aksrt'),
'label' => __('已过期', 'jio'),
'value' => $stats['expired'],
'url' => add_query_arg('state', 'expired', $base),
'tone' => 'neutral',
@@ -54,14 +54,14 @@ trait UsersMenusVipView {
'value' => $stats['level_1'],
'tone' => 'info',
'icon' => 'dashicons-star-half',
'hint' => __('当前有效人数', 'aksrt'),
'hint' => __('当前有效人数', 'jio'),
],
[
'label' => $names[2],
'value' => $stats['level_2'],
'tone' => 'info',
'icon' => 'dashicons-star-filled',
'hint' => __('当前有效人数', 'aksrt'),
'hint' => __('当前有效人数', 'jio'),
],
]);
}
@@ -69,13 +69,13 @@ trait UsersMenusVipView {
private function render_vip_tabs(string $tab): void {
$base = admin_url('users.php?page=' . self::VIP_PAGE_SLUG);
$tabs = [
'members' => [__('会员列表', 'aksrt'), $base, 'dashicons-groups'],
'history' => [__('变更记录', 'aksrt'), add_query_arg('tab', 'history', $base), 'dashicons-backup'],
'members' => [__('会员列表', 'jio'), $base, 'dashicons-groups'],
'history' => [__('变更记录', 'jio'), add_query_arg('tab', 'history', $base), 'dashicons-backup'],
];
echo '<nav class="aksrt-native-tabs" aria-label="' . esc_attr__('会员视图', 'aksrt') . '">';
echo '<nav class="jio-native-tabs" aria-label="' . esc_attr__('会员视图', 'jio') . '">';
foreach ($tabs as $key => [$label, $url, $icon]) {
$current = $key === $tab;
echo '<a class="aksrt-native-tabs__link' . ($current ? ' is-active' : '') . '" href="' . esc_url($url) . '"' . ($current ? ' aria-current="page"' : '') . '>'
echo '<a class="jio-native-tabs__link' . ($current ? ' is-active' : '') . '" href="' . esc_url($url) . '"' . ($current ? ' aria-current="page"' : '') . '>'
. '<span class="dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span>' . esc_html($label) . '</a>';
}
echo '</nav>';
@@ -83,25 +83,25 @@ trait UsersMenusVipView {
private function render_vip_filters(array $filters): void {
$names = $this->vip_level_names();
NativeUi::card_open('', '', false, 'aksrt-native-toolbar aksrt-vip-filters');
echo '<form method="get" class="aksrt-native-filters">';
NativeUi::card_open('', '', false, 'jio-native-toolbar jio-vip-filters');
echo '<form method="get" class="jio-native-filters">';
echo '<input type="hidden" name="page" value="' . esc_attr(self::VIP_PAGE_SLUG) . '">';
echo '<input type="hidden" name="orderby" value="' . esc_attr($filters['orderby']) . '"><input type="hidden" name="order" value="' . esc_attr($filters['order']) . '">';
echo '<label>UID<input type="number" min="1" name="user_id" class="small-text" value="' . esc_attr($filters['user_id'] > 0 ? (string) $filters['user_id'] : '') . '" placeholder="' . esc_attr__('全部', 'aksrt') . '"></label>';
echo '<label>' . esc_html__('档位', 'aksrt') . '<select name="level">';
foreach (['all' => __('全部', 'aksrt'), '0' => __('普通用户', 'aksrt'), '1' => $names[1], '2' => $names[2]] as $key => $label) {
echo '<label>UID<input type="number" min="1" name="user_id" class="small-text" value="' . esc_attr($filters['user_id'] > 0 ? (string) $filters['user_id'] : '') . '" placeholder="' . esc_attr__('全部', 'jio') . '"></label>';
echo '<label>' . esc_html__('档位', 'jio') . '<select name="level">';
foreach (['all' => __('全部', 'jio'), '0' => __('普通用户', 'jio'), '1' => $names[1], '2' => $names[2]] as $key => $label) {
printf('<option value="%s"%s>%s</option>', esc_attr((string) $key), selected($filters['level'], (string) $key, false), esc_html($label));
}
echo '</select></label><label>' . esc_html__('状态', 'aksrt') . '<select name="state">';
echo '</select></label><label>' . esc_html__('状态', 'jio') . '<select name="state">';
foreach ($this->vip_state_labels() as $key => $label) {
printf('<option value="%s"%s>%s</option>', esc_attr($key), selected($filters['state'], $key, false), esc_html($label));
}
echo '</select></label>';
echo '<label>' . esc_html__('到期日', 'aksrt') . '<input type="date" name="date_from" value="' . esc_attr($filters['date_from']) . '" aria-label="' . esc_attr__('到期日起', 'aksrt') . '">'
. '<span aria-hidden="true">—</span><input type="date" name="date_to" value="' . esc_attr($filters['date_to']) . '" aria-label="' . esc_attr__('到期日止', 'aksrt') . '"></label>';
echo '<button type="submit" class="button button-primary">' . esc_html__('筛选', 'aksrt') . '</button>';
echo '<label>' . esc_html__('到期日', 'jio') . '<input type="date" name="date_from" value="' . esc_attr($filters['date_from']) . '" aria-label="' . esc_attr__('到期日起', 'jio') . '">'
. '<span aria-hidden="true">—</span><input type="date" name="date_to" value="' . esc_attr($filters['date_to']) . '" aria-label="' . esc_attr__('到期日止', 'jio') . '"></label>';
echo '<button type="submit" class="button button-primary">' . esc_html__('筛选', 'jio') . '</button>';
if ($this->vip_filters_active($filters)) {
echo '<a class="button" href="' . esc_url(admin_url('users.php?page=' . self::VIP_PAGE_SLUG)) . '">' . esc_html__('重置', 'aksrt') . '</a>';
echo '<a class="button" href="' . esc_url(admin_url('users.php?page=' . self::VIP_PAGE_SLUG)) . '">' . esc_html__('重置', 'jio') . '</a>';
}
echo '</form>';
NativeUi::card_close();
@@ -110,28 +110,28 @@ trait UsersMenusVipView {
/** @return array<string,string> */
private function vip_state_labels(): array {
return [
'all' => __('全部', 'aksrt'),
'active' => __('限时有效', 'aksrt'),
'permanent' => __('永久', 'aksrt'),
'expired' => __('已过期', 'aksrt'),
'none' => __('无会员', 'aksrt'),
'all' => __('全部', 'jio'),
'active' => __('限时有效', 'jio'),
'permanent' => __('永久', 'jio'),
'expired' => __('已过期', 'jio'),
'none' => __('无会员', 'jio'),
];
}
private function render_vip_table(array $items, array $filters, int $pages, int $page): void {
$actions = '<span class="aksrt-vip-selection" data-aksrt-vip-selection hidden></span>'
. '<button type="button" class="button button-primary" data-aksrt-vip-bulk disabled>'
. '<span class="dashicons dashicons-edit" aria-hidden="true"></span>' . esc_html__('批量调整', 'aksrt') . '</button>';
NativeUi::table_card_open(__('会员列表', 'aksrt'), $actions);
echo '<table class="wp-list-table widefat fixed striped aksrt-vip-table"><thead><tr>';
echo '<td class="manage-column column-cb check-column"><input type="checkbox" data-aksrt-vip-check-all aria-label="' . esc_attr__('选择本页全部用户', 'aksrt') . '"></td>';
echo '<th class="column-user">' . esc_html__('用户', 'aksrt') . '</th>';
echo '<th>' . esc_html__('当前会员', 'aksrt') . '</th>';
$actions = '<span class="jio-vip-selection" data-jio-vip-selection hidden></span>'
. '<button type="button" class="button button-primary" data-jio-vip-bulk disabled>'
. '<span class="dashicons dashicons-edit" aria-hidden="true"></span>' . esc_html__('批量调整', 'jio') . '</button>';
NativeUi::table_card_open(__('会员列表', 'jio'), $actions);
echo '<table class="wp-list-table widefat fixed striped jio-vip-table"><thead><tr>';
echo '<td class="manage-column column-cb check-column"><input type="checkbox" data-jio-vip-check-all aria-label="' . esc_attr__('选择本页全部用户', 'jio') . '"></td>';
echo '<th class="column-user">' . esc_html__('用户', 'jio') . '</th>';
echo '<th>' . esc_html__('当前会员', 'jio') . '</th>';
echo '<th>' . $this->vip_sort_link($filters) . '</th>'; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
echo '<th class="column-actions">' . esc_html__('操作', 'aksrt') . '</th></tr></thead><tbody>';
echo '<th class="column-actions">' . esc_html__('操作', 'jio') . '</th></tr></thead><tbody>';
if ($items === []) {
$hint = $this->vip_filters_active($filters) ? __('试试调整或重置筛选条件。', 'aksrt') : '';
echo NativeUi::empty_row(5, __('没有符合条件的用户', 'aksrt'), $hint); // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
$hint = $this->vip_filters_active($filters) ? __('试试调整或重置筛选条件。', 'jio') : '';
echo NativeUi::empty_row(5, __('没有符合条件的用户', 'jio'), $hint); // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
}
foreach ($items as $row) {
$this->render_vip_row($row);
@@ -146,8 +146,8 @@ trait UsersMenusVipView {
$active = $filters['orderby'] === 'expires';
$next = $active && $filters['order'] === 'ASC' ? 'DESC' : 'ASC';
$state = $active ? ($filters['order'] === 'ASC' ? ' is-asc' : ' is-desc') : '';
return '<a class="aksrt-vip-sort' . $state . '" href="' . esc_url($this->vip_list_url($filters, ['orderby' => 'expires', 'order' => $next])) . '">'
. esc_html__('到期时间', 'aksrt') . '<span class="aksrt-vip-sort__icon" aria-hidden="true"><i></i><i></i></span></a>';
return '<a class="jio-vip-sort' . $state . '" href="' . esc_url($this->vip_list_url($filters, ['orderby' => 'expires', 'order' => $next])) . '">'
. esc_html__('到期时间', 'jio') . '<span class="jio-vip-sort__icon" aria-hidden="true"><i></i><i></i></span></a>';
}
private function render_vip_row(array $row): void {
@@ -160,27 +160,27 @@ trait UsersMenusVipView {
$fell_back = $level > 0 && ((int) $row['raw_level'] !== $level || (int) $row['raw_expires'] !== $expires);
$base_level = (int) $row['base_level'];
$base_expires = (int) $row['base_expires'];
$label = $level > 0 ? ($names[$level] ?? ('VIP' . $level)) : __('普通用户', 'aksrt');
$label = $level > 0 ? ($names[$level] ?? ('VIP' . $level)) : __('普通用户', 'jio');
if ($expired) {
$lapsed = max((int) $row['raw_level'], (int) $row['expired_level']);
$badge = NativeUi::badge(sprintf(__('%s · 已过期', 'aksrt'), $names[$lapsed] ?? __('会员', 'aksrt')), 'neutral');
$badge = NativeUi::badge(sprintf(__('%s · 已过期', 'jio'), $names[$lapsed] ?? __('会员', 'jio')), 'neutral');
} else {
$badge = NativeUi::badge($label, $level > 0 ? ($fell_back ? 'warning' : ($expires === 0 ? 'primary' : 'success')) : 'neutral', $level > 0);
}
$note = '';
if ($fell_back) {
$note = __('限时档位已到期,按基础档位计', 'aksrt');
$note = __('限时档位已到期,按基础档位计', 'jio');
} elseif ($level > 0 && $base_level > 0) {
$note = sprintf(__('到期后回退:%s', 'aksrt'), $names[$base_level] ?? ('VIP' . $base_level));
$note = sprintf(__('到期后回退:%s', 'jio'), $names[$base_level] ?? ('VIP' . $base_level));
}
$expiry_label = $this->vip_expiry_label($level, $expires, $expired);
$expiry_html = esc_html($expiry_label);
if ($level > 0 && $expires > 0 && !$expired) {
$days = (int) ceil(($expires - $now) / DAY_IN_SECONDS);
$expiry_html .= '<span class="aksrt-vip-remaining ' . ($days <= 7 ? 'aksrt-native-text--warning' : 'aksrt-native-muted') . '">'
. esc_html(sprintf(__('剩余 %s', 'aksrt'), human_time_diff($now, $expires))) . '</span>';
$expiry_html .= '<span class="jio-vip-remaining ' . ($days <= 7 ? 'jio-native-text--warning' : 'jio-native-muted') . '">'
. esc_html(sprintf(__('剩余 %s', 'jio'), human_time_diff($now, $expires))) . '</span>';
}
$can_extend = $level > 0 && $expires > 0 && !$expired;
@@ -189,7 +189,7 @@ trait UsersMenusVipView {
$data = [
'user-id' => (string) $id,
'user-name' => $user_name,
'current' => $expired ? __('已过期', 'aksrt') : $label . ($level > 0 ? ' · ' . $expiry_label : ''),
'current' => $expired ? __('已过期', 'jio') : $label . ($level > 0 ? ' · ' . $expiry_label : ''),
'level' => (string) max(1, $level),
'can-extend' => $can_extend ? '1' : '0',
'can-restore' => $can_restore ? '1' : '0',
@@ -200,130 +200,130 @@ trait UsersMenusVipView {
}
echo '<tr>';
echo '<th scope="row" class="check-column"><input type="checkbox" class="aksrt-vip-check" value="' . esc_attr((string) $id) . '"' . $attrs . ' aria-label="' . esc_attr(sprintf(__('选择 %s', 'aksrt'), $user_name)) . '"></th>'; // phpcs:ignore WordPress.Security.EscapeOutput -- attrs escaped above.
echo '<td class="column-user">' . NativeUi::user_cell($id, $user_name) . '<span class="aksrt-native-muted aksrt-vip-login">@' . esc_html((string) $row['user_login']) . '</span></td>'; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
echo '<td>' . $badge . ($note !== '' ? '<span class="aksrt-vip-note aksrt-native-muted">' . esc_html($note) . '</span>' : '') . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
echo '<td class="aksrt-vip-expiry">' . $expiry_html . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput -- escaped above.
echo '<td class="column-actions"><button type="button" class="button button-small" data-aksrt-vip-edit' . $attrs . '>' // phpcs:ignore WordPress.Security.EscapeOutput -- attrs escaped above.
. '<span class="dashicons dashicons-edit" aria-hidden="true"></span>' . esc_html__('调整', 'aksrt') . '</button></td>';
echo '<th scope="row" class="check-column"><input type="checkbox" class="jio-vip-check" value="' . esc_attr((string) $id) . '"' . $attrs . ' aria-label="' . esc_attr(sprintf(__('选择 %s', 'jio'), $user_name)) . '"></th>'; // phpcs:ignore WordPress.Security.EscapeOutput -- attrs escaped above.
echo '<td class="column-user">' . NativeUi::user_cell($id, $user_name) . '<span class="jio-native-muted jio-vip-login">@' . esc_html((string) $row['user_login']) . '</span></td>'; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
echo '<td>' . $badge . ($note !== '' ? '<span class="jio-vip-note jio-native-muted">' . esc_html($note) . '</span>' : '') . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
echo '<td class="jio-vip-expiry">' . $expiry_html . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput -- escaped above.
echo '<td class="column-actions"><button type="button" class="button button-small" data-jio-vip-edit' . $attrs . '>' // phpcs:ignore WordPress.Security.EscapeOutput -- attrs escaped above.
. '<span class="dashicons dashicons-edit" aria-hidden="true"></span>' . esc_html__('调整', 'jio') . '</button></td>';
echo '</tr>';
}
private function vip_expiry_label(int $level, int $expires, bool $expired): string {
if ($expired) {
return __('已过期', 'aksrt');
return __('已过期', 'jio');
}
if ($level <= 0) {
return '—';
}
return $expires === 0 ? __('永久有效', 'aksrt') : wp_date('Y-m-d H:i', $expires, wp_timezone());
return $expires === 0 ? __('永久有效', 'jio') : wp_date('Y-m-d H:i', $expires, wp_timezone());
}
private function render_vip_dialog(array $filters): void {
$names = $this->vip_level_names();
$today = (new \DateTimeImmutable('today', wp_timezone()))->format('Y-m-d');
$strings = [
'single' => __('调整会员:%s', 'aksrt'),
'bulk' => __('批量调整会员(%d 人)', 'aksrt'),
'selected' => __('已选 %d 人', 'aksrt'),
'bulk-note' => __('不满足条件的用户会被跳过并计入失败,结果会在提交后汇总显示。', 'aksrt'),
'extend-off' => __('该用户不是限时有效会员,无法延长。', 'aksrt'),
'restore-off' => __('该用户没有可恢复的回退会员。', 'aksrt'),
'single' => __('调整会员:%s', 'jio'),
'bulk' => __('批量调整会员(%d 人)', 'jio'),
'selected' => __('已选 %d 人', 'jio'),
'bulk-note' => __('不满足条件的用户会被跳过并计入失败,结果会在提交后汇总显示。', 'jio'),
'extend-off' => __('该用户不是限时有效会员,无法延长。', 'jio'),
'restore-off' => __('该用户没有可恢复的回退会员。', 'jio'),
];
$attrs = '';
foreach ($strings as $key => $value) {
$attrs .= ' data-i18n-' . $key . '="' . esc_attr($value) . '"';
}
echo '<dialog class="aksrt-vip-dialog" id="aksrt-vip-dialog" aria-labelledby="aksrt-vip-dialog-title"' . $attrs . '>'; // phpcs:ignore WordPress.Security.EscapeOutput -- attrs escaped above.
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-vip-dialog__form">';
echo '<input type="hidden" name="action" value="aksrt_users_mutate_vip">';
wp_nonce_field('aksrt_users_mutate_vip');
echo '<dialog class="jio-vip-dialog" id="jio-vip-dialog" aria-labelledby="jio-vip-dialog-title"' . $attrs . '>'; // phpcs:ignore WordPress.Security.EscapeOutput -- attrs escaped above.
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="jio-vip-dialog__form">';
echo '<input type="hidden" name="action" value="jio_users_mutate_vip">';
wp_nonce_field('jio_users_mutate_vip');
$this->render_vip_return_fields($filters);
echo '<div data-aksrt-vip-ids></div>';
echo '<div data-jio-vip-ids></div>';
echo '<header class="aksrt-vip-dialog__head"><h2 id="aksrt-vip-dialog-title" class="aksrt-vip-dialog__title" data-aksrt-vip-title></h2>'
. '<button type="button" class="aksrt-vip-dialog__close" data-aksrt-vip-close aria-label="' . esc_attr__('关闭', 'aksrt') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></header>';
echo '<div class="aksrt-vip-dialog__body">';
echo '<p class="aksrt-vip-dialog__target" data-aksrt-vip-target></p>';
echo '<header class="jio-vip-dialog__head"><h2 id="jio-vip-dialog-title" class="jio-vip-dialog__title" data-jio-vip-title></h2>'
. '<button type="button" class="jio-vip-dialog__close" data-jio-vip-close aria-label="' . esc_attr__('关闭', 'jio') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></header>';
echo '<div class="jio-vip-dialog__body">';
echo '<p class="jio-vip-dialog__target" data-jio-vip-target></p>';
echo '<fieldset class="aksrt-vip-ops"><legend class="aksrt-native-field__label">' . esc_html__('操作', 'aksrt') . '</legend>';
echo '<fieldset class="jio-vip-ops"><legend class="jio-native-field__label">' . esc_html__('操作', 'jio') . '</legend>';
$ops = [
'set' => [__('设置会员', 'aksrt'), 'dashicons-awards'],
'extend' => [__('延长有效期', 'aksrt'), 'dashicons-clock'],
'restore' => [__('恢复回退会员', 'aksrt'), 'dashicons-undo'],
'disable' => [__('停用会员', 'aksrt'), 'dashicons-dismiss'],
'set' => [__('设置会员', 'jio'), 'dashicons-awards'],
'extend' => [__('延长有效期', 'jio'), 'dashicons-clock'],
'restore' => [__('恢复回退会员', 'jio'), 'dashicons-undo'],
'disable' => [__('停用会员', 'jio'), 'dashicons-dismiss'],
];
foreach ($ops as $key => [$label, $icon]) {
echo '<label class="aksrt-vip-op' . ($key === 'disable' ? ' is-danger' : '') . '"><input type="radio" name="vip_operation" value="' . esc_attr($key) . '"' . checked($key, 'set', false) . '>'
echo '<label class="jio-vip-op' . ($key === 'disable' ? ' is-danger' : '') . '"><input type="radio" name="vip_operation" value="' . esc_attr($key) . '"' . checked($key, 'set', false) . '>'
. '<span><span class="dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span>' . esc_html($label) . '</span></label>';
}
echo '</fieldset>';
echo '<p class="aksrt-vip-op-hint aksrt-native-muted" data-aksrt-vip-op-hint hidden></p>';
echo '<p class="jio-vip-op-hint jio-native-muted" data-jio-vip-op-hint hidden></p>';
echo '<div class="aksrt-native-form-grid aksrt-vip-pane" data-aksrt-vip-pane="set">';
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html__('会员档位', 'aksrt') . '</span><select name="level" data-aksrt-vip-level>';
echo '<div class="jio-native-form-grid jio-vip-pane" data-jio-vip-pane="set">';
echo '<label class="jio-native-field"><span class="jio-native-field__label">' . esc_html__('会员档位', 'jio') . '</span><select name="level" data-jio-vip-level>';
foreach ($names as $value => $label) {
echo '<option value="' . esc_attr((string) $value) . '">' . esc_html($label) . '</option>';
}
echo '</select></label>';
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html__('到期日', 'aksrt') . '</span><input type="date" name="expires_date" min="' . esc_attr($today) . '" required data-aksrt-vip-date>'
. '<span class="aksrt-native-field__hint">' . esc_html__('当天 23:59:59 到期', 'aksrt') . '</span></label>';
echo '<label class="aksrt-vip-switch is-wide"><input type="checkbox" name="permanent" value="1" data-aksrt-vip-permanent>' . esc_html__('永久会员(不设到期日)', 'aksrt') . '</label>';
echo '<p class="aksrt-native-field__hint is-wide">' . esc_html__('设置会员会直接覆盖当前档位与到期时间,并清除回退会员。', 'aksrt') . '</p>';
echo '<label class="jio-native-field"><span class="jio-native-field__label">' . esc_html__('到期日', 'jio') . '</span><input type="date" name="expires_date" min="' . esc_attr($today) . '" required data-jio-vip-date>'
. '<span class="jio-native-field__hint">' . esc_html__('当天 23:59:59 到期', 'jio') . '</span></label>';
echo '<label class="jio-vip-switch is-wide"><input type="checkbox" name="permanent" value="1" data-jio-vip-permanent>' . esc_html__('永久会员(不设到期日)', 'jio') . '</label>';
echo '<p class="jio-native-field__hint is-wide">' . esc_html__('设置会员会直接覆盖当前档位与到期时间,并清除回退会员。', 'jio') . '</p>';
echo '</div>';
echo '<div class="aksrt-native-form-grid aksrt-vip-pane" data-aksrt-vip-pane="extend" hidden>';
echo '<div class="aksrt-native-field is-wide"><span class="aksrt-native-field__label">' . esc_html__('延长时长', 'aksrt') . '</span><div class="aksrt-vip-duration">'
. '<input type="number" name="amount" min="1" max="3650" value="30" required aria-label="' . esc_attr__('延长数量', 'aksrt') . '">'
. '<select name="unit" aria-label="' . esc_attr__('延长单位', 'aksrt') . '"><option value="day">' . esc_html__('天', 'aksrt') . '</option><option value="month">' . esc_html__('月', 'aksrt') . '</option></select></div>'
. '<span class="aksrt-native-field__hint">' . esc_html__('从当前到期时间起顺延;仅对限时有效会员生效。', 'aksrt') . '</span></div>';
echo '<div class="jio-native-form-grid jio-vip-pane" data-jio-vip-pane="extend" hidden>';
echo '<div class="jio-native-field is-wide"><span class="jio-native-field__label">' . esc_html__('延长时长', 'jio') . '</span><div class="jio-vip-duration">'
. '<input type="number" name="amount" min="1" max="3650" value="30" required aria-label="' . esc_attr__('延长数量', 'jio') . '">'
. '<select name="unit" aria-label="' . esc_attr__('延长单位', 'jio') . '"><option value="day">' . esc_html__('天', 'jio') . '</option><option value="month">' . esc_html__('月', 'jio') . '</option></select></div>'
. '<span class="jio-native-field__hint">' . esc_html__('从当前到期时间起顺延;仅对限时有效会员生效。', 'jio') . '</span></div>';
echo '</div>';
echo '<div class="aksrt-native-callout is-warning aksrt-vip-pane" data-aksrt-vip-pane="restore" hidden>' . esc_html__('将取消当前限时升级,恢复为回退会员(档位与到期时间按升级前保存的记录)。', 'aksrt') . '</div>';
echo '<div class="aksrt-native-callout is-danger aksrt-vip-pane" data-aksrt-vip-pane="disable" hidden>' . esc_html__('停用后立即撤销前端会员权益,并清除回退会员记录。此操作无法撤销,只能重新设置会员。', 'aksrt') . '</div>';
echo '<div class="jio-native-callout is-warning jio-vip-pane" data-jio-vip-pane="restore" hidden>' . esc_html__('将取消当前限时升级,恢复为回退会员(档位与到期时间按升级前保存的记录)。', 'jio') . '</div>';
echo '<div class="jio-native-callout is-danger jio-vip-pane" data-jio-vip-pane="disable" hidden>' . esc_html__('停用后立即撤销前端会员权益,并清除回退会员记录。此操作无法撤销,只能重新设置会员。', 'jio') . '</div>';
echo '<label class="aksrt-native-field aksrt-vip-reason"><span class="aksrt-native-field__label">' . esc_html__('变更原因', 'aksrt') . ' <span class="aksrt-native-text--danger" aria-hidden="true">*</span></span>'
. '<input type="text" name="reason" required maxlength="255" placeholder="' . esc_attr__('例如:活动赠送、客服补偿、退款撤销', 'aksrt') . '">'
. '<span class="aksrt-native-field__hint">' . esc_html__('写入变更记录,便于后续追溯。', 'aksrt') . '</span></label>';
echo '<p class="aksrt-native-muted aksrt-vip-bulk-note" data-aksrt-vip-bulk-note hidden></p>';
echo '<label class="jio-native-field jio-vip-reason"><span class="jio-native-field__label">' . esc_html__('变更原因', 'jio') . ' <span class="jio-native-text--danger" aria-hidden="true">*</span></span>'
. '<input type="text" name="reason" required maxlength="255" placeholder="' . esc_attr__('例如:活动赠送、客服补偿、退款撤销', 'jio') . '">'
. '<span class="jio-native-field__hint">' . esc_html__('写入变更记录,便于后续追溯。', 'jio') . '</span></label>';
echo '<p class="jio-native-muted jio-vip-bulk-note" data-jio-vip-bulk-note hidden></p>';
echo '</div>';
echo '<footer class="aksrt-vip-dialog__foot"><button type="button" class="button" data-aksrt-vip-close>' . esc_html__('取消', 'aksrt') . '</button>'
. '<button type="submit" class="button button-primary" data-aksrt-vip-submit>' . esc_html__('确认变更', 'aksrt') . '</button></footer>';
echo '<footer class="jio-vip-dialog__foot"><button type="button" class="button" data-jio-vip-close>' . esc_html__('取消', 'jio') . '</button>'
. '<button type="submit" class="button button-primary" data-jio-vip-submit>' . esc_html__('确认变更', 'jio') . '</button></footer>';
echo '</form></dialog>';
}
private function render_vip_history(int $page): void {
$result = (new \AKSRT\Content\VipMembershipEventRepository())->query($page, self::VIP_PER_PAGE);
$result = (new \Jio\Content\VipMembershipEventRepository())->query($page, self::VIP_PER_PAGE);
$ops = [
'set' => [__('设置会员', 'aksrt'), 'success'],
'extend' => [__('延长有效期', 'aksrt'), 'info'],
'restore' => [__('恢复回退会员', 'aksrt'), 'warning'],
'disable' => [__('停用会员', 'aksrt'), 'danger'],
'expire_recover' => [__('到期自动回退', 'aksrt'), 'neutral'],
'set' => [__('设置会员', 'jio'), 'success'],
'extend' => [__('延长有效期', 'jio'), 'info'],
'restore' => [__('恢复回退会员', 'jio'), 'warning'],
'disable' => [__('停用会员', 'jio'), 'danger'],
'expire_recover' => [__('到期自动回退', 'jio'), 'neutral'],
];
$statuses = [
'succeeded' => [__('成功', 'aksrt'), 'success'],
'failed' => [__('失败', 'aksrt'), 'danger'],
'pending' => [__('处理中', 'aksrt'), 'warning'],
'succeeded' => [__('成功', 'jio'), 'success'],
'failed' => [__('失败', 'jio'), 'danger'],
'pending' => [__('处理中', 'jio'), 'warning'],
];
NativeUi::table_card_open(__('变更记录', 'aksrt'), '<span class="aksrt-native-muted">' . esc_html(sprintf(__('共 %d 条', 'aksrt'), (int) $result['total'])) . '</span>');
echo '<table class="wp-list-table widefat fixed striped aksrt-vip-history"><thead><tr><th class="column-time">' . esc_html__('时间', 'aksrt') . '</th><th class="column-user">' . esc_html__('用户', 'aksrt') . '</th><th>' . esc_html__('操作', 'aksrt') . '</th><th>' . esc_html__('操作人', 'aksrt') . '</th><th class="column-reason">' . esc_html__('原因', 'aksrt') . '</th><th>' . esc_html__('结果', 'aksrt') . '</th></tr></thead><tbody>';
NativeUi::table_card_open(__('变更记录', 'jio'), '<span class="jio-native-muted">' . esc_html(sprintf(__('共 %d 条', 'jio'), (int) $result['total'])) . '</span>');
echo '<table class="wp-list-table widefat fixed striped jio-vip-history"><thead><tr><th class="column-time">' . esc_html__('时间', 'jio') . '</th><th class="column-user">' . esc_html__('用户', 'jio') . '</th><th>' . esc_html__('操作', 'jio') . '</th><th>' . esc_html__('操作人', 'jio') . '</th><th class="column-reason">' . esc_html__('原因', 'jio') . '</th><th>' . esc_html__('结果', 'jio') . '</th></tr></thead><tbody>';
if ($result['items'] === []) {
echo NativeUi::empty_row(6, __('暂无变更记录', 'aksrt'), __('在会员列表中调整会员后会出现在这里。', 'aksrt')); // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
echo NativeUi::empty_row(6, __('暂无变更记录', 'jio'), __('在会员列表中调整会员后会出现在这里。', 'jio')); // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
}
foreach ($result['items'] as $event) {
$created = (string) $event['created_at'];
$time = $created !== '' ? get_date_from_gmt($created, 'Y-m-d H:i') : '—';
$actor = (string) ($event['actor_name'] ?? '');
if ($actor === '') {
$actor = ((string) ($event['source'] ?? '') === 'admin') ? '—' : __('系统', 'aksrt');
$actor = ((string) ($event['source'] ?? '') === 'admin') ? '—' : __('系统', 'jio');
}
$result_html = NativeUi::status_badge((string) $event['status'], $statuses);
if ((string) $event['status'] === 'failed' && (string) ($event['error_message'] ?? '') !== '') {
$result_html .= '<span class="aksrt-vip-note aksrt-native-text--danger">' . esc_html((string) $event['error_message']) . '</span>';
$result_html .= '<span class="jio-vip-note jio-native-text--danger">' . esc_html((string) $event['error_message']) . '</span>';
}
echo '<tr><td class="column-time">' . esc_html($time) . (!empty($event['batch_uuid']) ? '<span class="aksrt-vip-note aksrt-native-muted">' . esc_html__('批量', 'aksrt') . '</span>' : '') . '</td>'
echo '<tr><td class="column-time">' . esc_html($time) . (!empty($event['batch_uuid']) ? '<span class="jio-vip-note jio-native-muted">' . esc_html__('批量', 'jio') . '</span>' : '') . '</td>'
. '<td class="column-user">' . NativeUi::user_cell((int) $event['user_id'], (string) ($event['display_name'] ?: $event['user_login'] ?: '')) . '</td>' // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
. '<td>' . NativeUi::status_badge((string) $event['operation'], $ops) . '</td>' // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
. '<td>' . esc_html($actor) . '</td>'
+47 -47
View File
@@ -1,11 +1,11 @@
<?php
/**
* Native WP nav-menu item options using legacy's aksrt_menu_options meta contract.
* Native WP nav-menu item options using legacy's jio_menu_options meta contract.
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -22,11 +22,11 @@ class NavMenuOptions {
return;
}
wp_enqueue_media();
aksrt_icon_picker_assets();
jio_icon_picker_assets();
$path = get_template_directory() . '/assets/js/admin-nav-menu-senior.js';
if (file_exists($path)) {
wp_enqueue_script(
'aksrt-admin-nav-menu-senior',
'jio-admin-nav-menu-senior',
get_template_directory_uri() . '/assets/js/admin-nav-menu-senior.js',
['jquery'],
(string) filemtime($path),
@@ -41,7 +41,7 @@ class NavMenuOptions {
*/
public static function render_fields($item_id, $item): void {
$item_id = (int) $item_id;
$opts = get_post_meta($item_id, 'aksrt_menu_options', true);
$opts = get_post_meta($item_id, 'jio_menu_options', true);
$opts = is_array($opts) ? $opts : [];
$icon = (string) ($opts['icon'] ?? '');
$badge = (string) ($opts['badge'] ?? '');
@@ -55,63 +55,63 @@ class NavMenuOptions {
? wp_json_encode($opts['multi_column_links_opts'], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
: self::sample_multi_json();
?>
<p class="field-aksrt-icon description description-wide">
<label for="edit-menu-item-aksrt-icon-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('图标', 'aksrt'); ?>
<p class="field-jio-icon description description-wide">
<label for="edit-menu-item-jio-icon-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('图标', 'jio'); ?>
</label>
<?php
echo aksrt_icon_picker_field(
sprintf('aksrt_menu_options[%d][icon]', $item_id),
sprintf('edit-menu-item-aksrt-icon-%d', $item_id),
echo jio_icon_picker_field(
sprintf('jio_menu_options[%d][icon]', $item_id),
sprintf('edit-menu-item-jio-icon-%d', $item_id),
$icon
);
?>
</p>
<p class="field-aksrt-badge description description-thin">
<label for="edit-menu-item-aksrt-badge-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('徽章文字', 'aksrt'); ?><br>
<input type="text" id="edit-menu-item-aksrt-badge-<?php echo esc_attr((string) $item_id); ?>"
class="widefat" name="aksrt_menu_options[<?php echo esc_attr((string) $item_id); ?>][badge]"
<p class="field-jio-badge description description-thin">
<label for="edit-menu-item-jio-badge-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('徽章文字', 'jio'); ?><br>
<input type="text" id="edit-menu-item-jio-badge-<?php echo esc_attr((string) $item_id); ?>"
class="widefat" name="jio_menu_options[<?php echo esc_attr((string) $item_id); ?>][badge]"
value="<?php echo esc_attr($badge); ?>">
</label>
</p>
<p class="field-aksrt-badge-class description description-thin">
<label for="edit-menu-item-aksrt-badge-class-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('徽章 class', 'aksrt'); ?><br>
<input type="text" id="edit-menu-item-aksrt-badge-class-<?php echo esc_attr((string) $item_id); ?>"
class="widefat" name="aksrt_menu_options[<?php echo esc_attr((string) $item_id); ?>][badge_class]"
<p class="field-jio-badge-class description description-thin">
<label for="edit-menu-item-jio-badge-class-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('徽章 class', 'jio'); ?><br>
<input type="text" id="edit-menu-item-jio-badge-class-<?php echo esc_attr((string) $item_id); ?>"
class="widefat" name="jio_menu_options[<?php echo esc_attr((string) $item_id); ?>][badge_class]"
value="<?php echo esc_attr($badge_class); ?>">
</label>
</p>
<p class="field-aksrt-submenu description description-wide">
<p class="field-jio-submenu description description-wide">
<label>
<input type="checkbox" name="aksrt_menu_options[<?php echo esc_attr((string) $item_id); ?>][submenu_s]" value="1" <?php checked($submenu_s); ?>>
<?php esc_html_e('启用高级子菜单(仅一级菜单)', 'aksrt'); ?>
<input type="checkbox" name="jio_menu_options[<?php echo esc_attr((string) $item_id); ?>][submenu_s]" value="1" <?php checked($submenu_s); ?>>
<?php esc_html_e('启用高级子菜单(仅一级菜单)', 'jio'); ?>
</label>
</p>
<p class="field-aksrt-submenu-type description description-wide">
<label for="edit-menu-item-aksrt-submenu-type-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('高级子菜单类型', 'aksrt'); ?><br>
<select id="edit-menu-item-aksrt-submenu-type-<?php echo esc_attr((string) $item_id); ?>"
name="aksrt_menu_options[<?php echo esc_attr((string) $item_id); ?>][submenu_type]">
<option value="" <?php selected($submenu_type, ''); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
<option value="graphic_card" <?php selected($submenu_type, 'graphic_card'); ?>><?php esc_html_e('图文卡片', 'aksrt'); ?></option>
<option value="multi_column_links" <?php selected($submenu_type, 'multi_column_links'); ?>><?php esc_html_e('多列链接', 'aksrt'); ?></option>
<p class="field-jio-submenu-type description description-wide">
<label for="edit-menu-item-jio-submenu-type-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('高级子菜单类型', 'jio'); ?><br>
<select id="edit-menu-item-jio-submenu-type-<?php echo esc_attr((string) $item_id); ?>"
name="jio_menu_options[<?php echo esc_attr((string) $item_id); ?>][submenu_type]">
<option value="" <?php selected($submenu_type, ''); ?>><?php esc_html_e('无', 'jio'); ?></option>
<option value="graphic_card" <?php selected($submenu_type, 'graphic_card'); ?>><?php esc_html_e('图文卡片', 'jio'); ?></option>
<option value="multi_column_links" <?php selected($submenu_type, 'multi_column_links'); ?>><?php esc_html_e('多列链接', 'jio'); ?></option>
</select>
</label>
</p>
<p class="field-aksrt-graphic description description-wide">
<label for="edit-menu-item-aksrt-graphic-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('图文卡片 JSON(type=graphic_card)', 'aksrt'); ?><br>
<textarea rows="8" class="widefat code" id="edit-menu-item-aksrt-graphic-<?php echo esc_attr((string) $item_id); ?>"
name="aksrt_menu_options[<?php echo esc_attr((string) $item_id); ?>][graphic_card_opts_json]"><?php echo esc_textarea((string) $graphic_json); ?></textarea>
<p class="field-jio-graphic description description-wide">
<label for="edit-menu-item-jio-graphic-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('图文卡片 JSON(type=graphic_card)', 'jio'); ?><br>
<textarea rows="8" class="widefat code" id="edit-menu-item-jio-graphic-<?php echo esc_attr((string) $item_id); ?>"
name="jio_menu_options[<?php echo esc_attr((string) $item_id); ?>][graphic_card_opts_json]"><?php echo esc_textarea((string) $graphic_json); ?></textarea>
</label>
</p>
<p class="field-aksrt-multi description description-wide">
<label for="edit-menu-item-aksrt-multi-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('多列链接 JSON(type=multi_column_links)', 'aksrt'); ?><br>
<textarea rows="8" class="widefat code" id="edit-menu-item-aksrt-multi-<?php echo esc_attr((string) $item_id); ?>"
name="aksrt_menu_options[<?php echo esc_attr((string) $item_id); ?>][multi_column_links_opts_json]"><?php echo esc_textarea((string) $multi_json); ?></textarea>
<p class="field-jio-multi description description-wide">
<label for="edit-menu-item-jio-multi-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('多列链接 JSON(type=multi_column_links)', 'jio'); ?><br>
<textarea rows="8" class="widefat code" id="edit-menu-item-jio-multi-<?php echo esc_attr((string) $item_id); ?>"
name="jio_menu_options[<?php echo esc_attr((string) $item_id); ?>][multi_column_links_opts_json]"><?php echo esc_textarea((string) $multi_json); ?></textarea>
</label>
</p>
<?php
@@ -124,14 +124,14 @@ class NavMenuOptions {
public static function save($menu_id, $menu_item_db_id): void {
$menu_item_db_id = (int) $menu_item_db_id;
// phpcs:ignore WordPress.Security.NonceVerification.Missing
if (empty($_POST['aksrt_menu_options'][ $menu_item_db_id ]) || !is_array($_POST['aksrt_menu_options'][ $menu_item_db_id ])) {
if (empty($_POST['jio_menu_options'][ $menu_item_db_id ]) || !is_array($_POST['jio_menu_options'][ $menu_item_db_id ])) {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$raw = wp_unslash($_POST['aksrt_menu_options'][ $menu_item_db_id ]);
$raw = wp_unslash($_POST['jio_menu_options'][ $menu_item_db_id ]);
$clean = [
'icon' => aksrt_sanitize_icon_value((string) ($raw['icon'] ?? '')),
'icon' => jio_sanitize_icon_value((string) ($raw['icon'] ?? '')),
'badge' => sanitize_text_field((string) ($raw['badge'] ?? '')),
'badge_class' => sanitize_html_class((string) ($raw['badge_class'] ?? '')),
'submenu_s' => !empty($raw['submenu_s']),
@@ -147,7 +147,7 @@ class NavMenuOptions {
$clean['multi_column_links_opts'] = self::sanitize_multi_opts($multi);
}
update_post_meta($menu_item_db_id, 'aksrt_menu_options', $clean);
update_post_meta($menu_item_db_id, 'jio_menu_options', $clean);
}
/**
+35 -35
View File
@@ -2,18 +2,18 @@
/**
* Per-page legacy header slider configuration.
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
final class PageConfigMetaBox {
private const META_KEY = 'page_config';
private const NONCE_ACTION = 'aksrt_page_config_save';
private const NONCE_NAME = 'aksrt_page_config_nonce';
private const NONCE_ACTION = 'jio_page_config_save';
private const NONCE_NAME = 'jio_page_config_nonce';
public static function init(): void {
add_action('add_meta_boxes_page', [self::class, 'register']);
@@ -31,13 +31,13 @@ final class PageConfigMetaBox {
}
wp_enqueue_style('wp-color-picker');
wp_enqueue_script('wp-color-picker');
wp_add_inline_script('wp-color-picker', "jQuery(function($){ $('.aksrt-page-config-color').wpColorPicker(); });");
wp_add_inline_script('wp-color-picker', "jQuery(function($){ $('.jio-page-config-color').wpColorPicker(); });");
}
public static function register(): void {
add_meta_box(
'aksrt-page-config',
__('页面配置', 'aksrt'),
'jio-page-config',
__('页面配置', 'jio'),
[self::class, 'render'],
'page',
'normal',
@@ -58,40 +58,40 @@ final class PageConfigMetaBox {
];
?>
<style>
.aksrt-page-config-grid{display:grid;grid-template-columns:220px minmax(0,1fr);gap:14px 20px;align-items:start}
.aksrt-page-config-grid>label{font-weight:600;padding-top:7px}.aksrt-page-config-grid textarea{width:100%;font-family:Consolas,monospace}
.aksrt-page-config-grid .description{margin:4px 0 0}.aksrt-page-config-wide{grid-column:1/-1}
@media(max-width:782px){.aksrt-page-config-grid{grid-template-columns:1fr}.aksrt-page-config-wide{grid-column:auto}}
.jio-page-config-grid{display:grid;grid-template-columns:220px minmax(0,1fr);gap:14px 20px;align-items:start}
.jio-page-config-grid>label{font-weight:600;padding-top:7px}.jio-page-config-grid textarea{width:100%;font-family:Consolas,monospace}
.jio-page-config-grid .description{margin:4px 0 0}.jio-page-config-wide{grid-column:1/-1}
@media(max-width:782px){.jio-page-config-grid{grid-template-columns:1fr}.jio-page-config-wide{grid-column:auto}}
</style>
<div class="aksrt-page-config-grid">
<label for="aksrt-header-slider-show"><?php esc_html_e('独立配置顶部多功能组件', 'aksrt'); ?></label>
<div class="jio-page-config-grid">
<label for="jio-header-slider-show"><?php esc_html_e('独立配置顶部多功能组件', 'jio'); ?></label>
<div>
<label><input id="aksrt-header-slider-show" type="checkbox" name="page_config[header_slider_show]" value="1" <?php checked(!empty($config['header_slider_show'])); ?>> <?php esc_html_e('为当前页面使用独立内容和显示规则', 'aksrt'); ?></label>
<p class="description"><?php esc_html_e('仅当主题设置的“顶部多功能组件”已勾选当前页面所属类型时生效;此处不能单独启用组件。', 'aksrt'); ?></p>
<label><input id="jio-header-slider-show" type="checkbox" name="page_config[header_slider_show]" value="1" <?php checked(!empty($config['header_slider_show'])); ?>> <?php esc_html_e('为当前页面使用独立内容和显示规则', 'jio'); ?></label>
<p class="description"><?php esc_html_e('仅当主题设置的“顶部多功能组件”已勾选当前页面所属类型时生效;此处不能单独启用组件。', 'jio'); ?></p>
</div>
<label><?php esc_html_e('显示规则', 'aksrt'); ?></label>
<label><?php esc_html_e('显示规则', 'jio'); ?></label>
<div>
<?php foreach (['' => __('全部显示', 'aksrt'), 'only_pc' => __('仅在 PC 端显示', 'aksrt'), 'only_sm' => __('仅在移动端显示', 'aksrt')] as $value => $label) : ?>
<?php foreach (['' => __('全部显示', 'jio'), 'only_pc' => __('仅在 PC 端显示', 'jio'), 'only_sm' => __('仅在移动端显示', 'jio')] as $value => $label) : ?>
<label style="margin-right:16px"><input type="radio" name="page_config[header_slider_show_type]" value="<?php echo esc_attr($value); ?>" <?php checked($show_type, $value); ?>> <?php echo esc_html($label); ?></label>
<?php endforeach; ?>
</div>
<label for="aksrt-header-slider-nav-color"><?php esc_html_e('导航栏文字颜色', 'aksrt'); ?></label>
<div><input id="aksrt-header-slider-nav-color" class="aksrt-page-config-color" type="text" name="page_config[header_slider_nav_color]" value="<?php echo esc_attr((string) ($config['header_slider_nav_color'] ?? '#ffffff')); ?>" data-default-color="#ffffff"></div>
<label for="jio-header-slider-nav-color"><?php esc_html_e('导航栏文字颜色', 'jio'); ?></label>
<div><input id="jio-header-slider-nav-color" class="jio-page-config-color" type="text" name="page_config[header_slider_nav_color]" value="<?php echo esc_attr((string) ($config['header_slider_nav_color'] ?? '#ffffff')); ?>" data-default-color="#ffffff"></div>
<?php self::render_json_field('header_slider', __('背景内容', 'aksrt'), $config['header_slider'] ?? self::slide_defaults(), 16); ?>
<?php self::render_json_field('header_slider_option', __('背景显示设置', 'aksrt'), $config['header_slider_option'] ?? self::slider_defaults(), 12); ?>
<?php self::render_json_field('header_slider', __('背景内容', 'jio'), $config['header_slider'] ?? self::slide_defaults(), 16); ?>
<?php self::render_json_field('header_slider_option', __('背景显示设置', 'jio'), $config['header_slider_option'] ?? self::slider_defaults(), 12); ?>
<label><?php esc_html_e('叠加搜索组件', 'aksrt'); ?></label>
<label><?php esc_html_e('叠加搜索组件', 'jio'); ?></label>
<div><?php self::render_devices('header_slider_search_s', $devices['header_slider_search_s']); ?></div>
<?php self::render_json_field('header_slider_search_option', __('搜索组件设置', 'aksrt'), $config['header_slider_search_option'] ?? self::search_defaults(), 12); ?>
<?php self::render_json_field('header_slider_search_option', __('搜索组件设置', 'jio'), $config['header_slider_search_option'] ?? self::search_defaults(), 12); ?>
<label><?php esc_html_e('叠加封面卡片组件', 'aksrt'); ?></label>
<label><?php esc_html_e('叠加封面卡片组件', 'jio'); ?></label>
<div><?php self::render_devices('header_slider_card_s', $devices['header_slider_card_s']); ?></div>
<?php self::render_json_field('header_slider_card_option', __('图标卡片设置', 'aksrt'), $config['header_slider_card_option'] ?? self::card_defaults(), 16); ?>
<?php self::render_json_field('header_slider_card_option', __('图标卡片设置', 'jio'), $config['header_slider_card_option'] ?? self::card_defaults(), 16); ?>
</div>
<p class="description"><?php esc_html_e('复杂字段使用参考主题原始数组结构的 JSON;保存后统一写入 page_config 序列化元数据,不读取任何旧字段。', 'aksrt'); ?></p>
<p class="description"><?php esc_html_e('复杂字段使用参考主题原始数组结构的 JSON;保存后统一写入 page_config 序列化元数据,不读取任何旧字段。', 'jio'); ?></p>
<?php
}
@@ -99,14 +99,14 @@ final class PageConfigMetaBox {
private static function render_json_field(string $key, string $label, $value, int $rows): void {
$json = wp_json_encode(is_array($value) ? $value : [], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
?>
<label for="aksrt-<?php echo esc_attr($key); ?>"><?php echo esc_html($label); ?></label>
<div><textarea id="aksrt-<?php echo esc_attr($key); ?>" name="page_config[<?php echo esc_attr($key); ?>]" rows="<?php echo (int) $rows; ?>"><?php echo esc_textarea((string) $json); ?></textarea></div>
<label for="jio-<?php echo esc_attr($key); ?>"><?php echo esc_html($label); ?></label>
<div><textarea id="jio-<?php echo esc_attr($key); ?>" name="page_config[<?php echo esc_attr($key); ?>]" rows="<?php echo (int) $rows; ?>"><?php echo esc_textarea((string) $json); ?></textarea></div>
<?php
}
/** @param list<string> $selected */
private static function render_devices(string $key, array $selected): void {
foreach (['pc_s' => __('PC 端显示', 'aksrt'), 'm_s' => __('移动端显示', 'aksrt')] as $value => $label) {
foreach (['pc_s' => __('PC 端显示', 'jio'), 'm_s' => __('移动端显示', 'jio')] as $value => $label) {
printf(
'<label style="margin-right:16px"><input type="checkbox" name="page_config[%1$s][]" value="%2$s" %3$s> %4$s</label>',
esc_attr($key),
@@ -136,7 +136,7 @@ final class PageConfigMetaBox {
$config = [
'header_slider_show' => !empty($raw['header_slider_show']),
'header_slider_show_type' => self::enum((string) ($raw['header_slider_show_type'] ?? ''), ['', 'only_pc', 'only_sm']),
'header_slider_nav_color' => \AKSRT\Core\Color::sanitize((string) ($raw['header_slider_nav_color'] ?? '')) ?: '#ffffff',
'header_slider_nav_color' => \Jio\Core\Color::sanitize((string) ($raw['header_slider_nav_color'] ?? '')) ?: '#ffffff',
'header_slider' => self::sanitize_slides(self::decode_json($raw['header_slider'] ?? '')),
'header_slider_option' => self::sanitize_slider_options(self::decode_json($raw['header_slider_option'] ?? '')),
'header_slider_search_s' => self::sanitize_devices($raw['header_slider_search_s'] ?? []),
@@ -266,12 +266,12 @@ final class PageConfigMetaBox {
$cards[] = [
// legacy echoes card title/desc raw (admin authors <badge> etc.);
// wp_kses_post() would strip the custom badge element.
'title' => aksrt_icon_card_kses((string) ($card['title'] ?? '')),
'desc' => aksrt_icon_card_kses((string) ($card['desc'] ?? '')),
'title' => jio_icon_card_kses((string) ($card['title'] ?? '')),
'desc' => jio_icon_card_kses((string) ($card['desc'] ?? '')),
// Icon picker values may carry an inline "customsvg-<svg>" payload;
// sanitize_text_field would strip it down to a bare prefix.
'icon' => function_exists('aksrt_sanitize_icon_value')
? aksrt_sanitize_icon_value((string) ($card['icon'] ?? ''))
'icon' => function_exists('jio_sanitize_icon_value')
? jio_sanitize_icon_value((string) ($card['icon'] ?? ''))
: sanitize_text_field((string) ($card['icon'] ?? '')),
'customize_icon' => wp_kses_post((string) ($card['customize_icon'] ?? '')),
'icon_class' => sanitize_text_field((string) ($card['icon_class'] ?? '')),
@@ -296,7 +296,7 @@ final class PageConfigMetaBox {
/** @return list<array<string,mixed>> */
private static function slide_defaults(): array {
$base = defined('AKSRT_URI') ? trailingslashit(AKSRT_URI) . 'assets/img/' : '';
$base = defined('JIO_URI') ? trailingslashit(JIO_URI) . 'assets/img/' : '';
$slides = [];
foreach (['slider-bg.jpg', 'user_t.jpg'] as $image) {
$slides[] = [
+2 -2
View File
@@ -2,10 +2,10 @@
/**
* Page/display schema option search.
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
+11 -11
View File
@@ -1,17 +1,17 @@
<?php
/**
* 支付中心概览的数据接口:GET aksrt/v1/admin/pay-overview?from=Y-m-d&to=Y-m-d&group=
* 支付中心概览的数据接口:GET jio/v1/admin/pay-overview?from=Y-m-d&to=Y-m-d&group=
*
* 仅管理员(manage_options + wp_rest nonce,见 ControllerBase::check_permission)。
* 统计口径见 \AKSRT\Payment\OrderAnalytics。
* 统计口径见 \Jio\Payment\OrderAnalytics。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
use AKSRT\Payment\OrderAnalytics;
use AKSRT\Payment\OrderAnalyticsDaily;
use Jio\Payment\OrderAnalytics;
use Jio\Payment\OrderAnalyticsDaily;
defined('ABSPATH') || exit;
@@ -49,7 +49,7 @@ class PayOverviewController extends ControllerBase {
return $this->error($error, 400);
}
if ($group !== '' && !isset(OrderAnalytics::group_labels()[$group])) {
return $this->error(__('商品分类无效。', 'aksrt'), 400);
return $this->error(__('商品分类无效。', 'jio'), 400);
}
return $this->success(['data' => OrderAnalytics::get_instance()->report($from, $to, $group)]);
@@ -58,17 +58,17 @@ class PayOverviewController extends ControllerBase {
/** 区间校验:合法日期、起止有序、不晚于今天、不超过上限天数。返回空串表示通过。 */
public static function validate_range(string $from, string $to): string {
if (!OrderAnalyticsDaily::is_date($from) || !OrderAnalyticsDaily::is_date($to)) {
return __('日期格式无效。', 'aksrt');
return __('日期格式无效。', 'jio');
}
if ($from > $to) {
return __('开始日期不能晚于结束日期。', 'aksrt');
return __('开始日期不能晚于结束日期。', 'jio');
}
if ($to > current_time('Y-m-d')) {
return __('结束日期不能晚于今天。', 'aksrt');
return __('结束日期不能晚于今天。', 'jio');
}
if (count(OrderAnalyticsDaily::dates($from, $to)) > OrderAnalytics::MAX_RANGE_DAYS) {
/* translators: %d max days */
return sprintf(__('统计区间最长 %d 天。', 'aksrt'), OrderAnalytics::MAX_RANGE_DAYS);
return sprintf(__('统计区间最长 %d 天。', 'jio'), OrderAnalytics::MAX_RANGE_DAYS);
}
return '';
}
+2 -2
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
+26 -26
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -32,13 +32,13 @@ class PermissionsController extends ControllerBase {
}
public function get_capabilities(): \WP_REST_Response {
if (!class_exists('\AKSRT\Settings\Settings')) {
return $this->error(__('能力矩阵模块不可用', 'aksrt'), 500);
if (!class_exists('\Jio\Settings\Settings')) {
return $this->error(__('能力矩阵模块不可用', 'jio'), 500);
}
$caps = \AKSRT\Settings\Settings::get_user_capabilities();
$defs = \AKSRT\Settings\Settings::cap_definitions();
$columns = \AKSRT\Settings\Settings::cap_role_columns();
$caps = \Jio\Settings\Settings::get_user_capabilities();
$defs = \Jio\Settings\Settings::cap_definitions();
$columns = \Jio\Settings\Settings::cap_role_columns();
return $this->success([
'data' => [
@@ -54,19 +54,19 @@ class PermissionsController extends ControllerBase {
}
public function save_capabilities(\WP_REST_Request $request): \WP_REST_Response {
if (!class_exists('\AKSRT\Settings\Settings')) {
return $this->error(__('能力矩阵模块不可用', 'aksrt'), 500);
if (!class_exists('\Jio\Settings\Settings')) {
return $this->error(__('能力矩阵模块不可用', 'jio'), 500);
}
$params = $this->get_json_params($request);
$input = $params['user_caps'] ?? null;
if (!is_array($input)) {
return $this->error(__('无效的能力矩阵数据', 'aksrt'), 422);
return $this->error(__('无效的能力矩阵数据', 'jio'), 422);
}
$defs = \AKSRT\Settings\Settings::cap_definitions();
$columns = \AKSRT\Settings\Settings::cap_role_columns();
$defs = \Jio\Settings\Settings::cap_definitions();
$columns = \Jio\Settings\Settings::cap_role_columns();
$clean = [];
foreach ($defs as $cap_id => $def) {
@@ -84,13 +84,13 @@ class PermissionsController extends ControllerBase {
}
}
$all = \AKSRT\Settings\Settings::get_all();
$all = \Jio\Settings\Settings::get_all();
$all['user_caps'] = $clean;
update_option('aksrt_settings', $all);
\AKSRT\Settings\Settings::flush_settings_cache();
update_option('jio_settings', $all);
\Jio\Settings\Settings::flush_settings_cache();
return $this->success([
'message' => __('能力矩阵已保存', 'aksrt'),
'message' => __('能力矩阵已保存', 'jio'),
'data' => [
'user_caps' => $clean,
],
@@ -101,27 +101,27 @@ class PermissionsController extends ControllerBase {
$buckets = [
'user' => [
'key' => 'user',
'title' => __('用户治理', 'aksrt'),
'title' => __('用户治理', 'jio'),
'items' => [],
],
'post' => [
'key' => 'post',
'title' => __('投稿权限', 'aksrt'),
'title' => __('投稿权限', 'jio'),
'items' => [],
],
'comment' => [
'key' => 'comment',
'title' => __('评论权限', 'aksrt'),
'title' => __('评论权限', 'jio'),
'items' => [],
],
'upload' => [
'key' => 'upload',
'title' => __('上传权限', 'aksrt'),
'title' => __('上传权限', 'jio'),
'items' => [],
],
'download' => [
'key' => 'download',
'title' => __('下载权限', 'aksrt'),
'title' => __('下载权限', 'jio'),
'items' => [],
],
];
@@ -162,11 +162,11 @@ class PermissionsController extends ControllerBase {
private function get_capability_column_labels(): array {
return [
'all' => __('所有人', 'aksrt'),
'logged' => __('已登录', 'aksrt'),
'level' => __('等级≥', 'aksrt'),
'vip' => __('VIP≥', 'aksrt'),
'auth' => __('已认证', 'aksrt'),
'all' => __('所有人', 'jio'),
'logged' => __('已登录', 'jio'),
'level' => __('等级≥', 'jio'),
'vip' => __('VIP≥', 'jio'),
'auth' => __('已认证', 'jio'),
];
}
}
+2 -2
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
+16 -16
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema;
namespace Jio\Admin\Schema;
defined('ABSPATH') || exit;
@@ -18,7 +18,7 @@ class GlobalSchema {
'key' => 'common',
'title' => '常用功能',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'performance', 'title' => '图像异步懒加载', 'fields' => [
['key' => 'lazy_posts_thumb', 'label' => '文章缩略图懒加载', 'type' => 'switch', 'default' => true],
@@ -41,7 +41,7 @@ class GlobalSchema {
'key' => 'logo',
'title' => 'LOGO图像',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'logo', 'title' => 'Logo 设置', 'fields' => [
['key' => 'site_logo', 'label' => '站点 Logo', 'type' => 'media', 'default' => ''],
@@ -59,7 +59,7 @@ class GlobalSchema {
'key' => 'seo',
'title' => 'SEO优化',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'basic', 'title' => 'SEO基础', 'fields' => [
['key' => 'seo_enabled', 'label' => '启用核心SEO优化', 'type' => 'switch', 'default' => true, 'help' => '关闭后主题不再输出 SEO head 标签。'],
@@ -98,7 +98,7 @@ class GlobalSchema {
'key' => 'layout',
'title' => '显示&布局',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'color', 'title' => '色彩方案', 'fields' => [
['key' => 'theme_mode', 'label' => '主题模式', 'type' => 'radio', 'default' => 'time-auto', 'help' => '此处设置为默认风格,实际显示风格以用户设置优先。如需固定风格,则关闭还需下方主题切换按钮', 'options' => [
@@ -164,7 +164,7 @@ class GlobalSchema {
'key' => 'code-highlight',
'title' => '代码高亮',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'basic', 'title' => '代码高亮设置', 'fields' => [
['key' => 'highlight_kg', 'label' => '代码高亮开关', 'type' => 'switch', 'default' => true],
@@ -202,7 +202,7 @@ class GlobalSchema {
'key' => 'system-notice',
'title' => '系统公告',
'version' => '2.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'basic', 'title' => '公告设置', 'fields' => [
['key' => 'system_notice_enabled', 'label' => '启用系统公告', 'type' => 'switch', 'default' => true],
@@ -292,7 +292,7 @@ class GlobalSchema {
'key' => 'search',
'title' => '搜索功能',
'version' => '2.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'basic', 'title' => '搜索基础', 'fields' => [
['key' => 'search_placeholder', 'label' => '搜索框提示文字', 'type' => 'input', 'default' => '开启精彩搜索'],
@@ -335,12 +335,12 @@ class GlobalSchema {
['key' => 'meilisearch', 'title' => 'Meilisearch 智能搜索', 'fields' => [
['key' => 'search_ms_s', 'label' => '启用 Meilisearch', 'type' => 'switch', 'default' => false],
['key' => 'search_ms_opt', 'label' => 'Meilisearch 参数', 'type' => 'fieldset', 'sanitize_type' => 'array', 'default' => [
'api_url' => 'http://127.0.0.1:7700', 'api_key' => '', 'index_prefix' => 'aksrt',
'api_url' => 'http://127.0.0.1:7700', 'api_key' => '', 'index_prefix' => 'jio',
'synonyms' => [['key' => 'wordpress', 'val' => 'wp,wordp,wordpre']],
], 'dependsOn' => ['key' => 'search_ms_s', 'value' => true], 'fields' => [
['key' => 'api_url', 'label' => 'API 地址', 'type' => 'input', 'default' => 'http://127.0.0.1:7700'],
['key' => 'api_key', 'label' => 'API 密钥', 'type' => 'input', 'default' => ''],
['key' => 'index_prefix', 'label' => '索引前缀', 'type' => 'input', 'default' => 'aksrt'],
['key' => 'index_prefix', 'label' => '索引前缀', 'type' => 'input', 'default' => 'jio'],
['key' => 'synonyms', 'label' => '同义词', 'type' => 'repeater', 'default' => [['key' => 'wordpress', 'val' => 'wp,wordp,wordpre']], 'addText' => '添加同义词', 'fields' => [
['key' => 'key', 'label' => '关键词', 'type' => 'input', 'default' => ''],
['key' => 'val', 'label' => '同义词', 'type' => 'textarea', 'default' => ''],
@@ -353,7 +353,7 @@ class GlobalSchema {
'key' => 'share',
'title' => '社交分享',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [[
'key' => 'share',
'title' => '社交分享',
@@ -385,12 +385,12 @@ class GlobalSchema {
],
]],
],
'email' => \AKSRT\Settings\MailTemplates::build_schema(),
'email' => \Jio\Settings\MailTemplates::build_schema(),
'custom-code' => [
'key' => 'custom-code',
'title' => '自定义代码',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'css', 'title' => '自定义 CSS', 'fields' => [
['key' => 'custom_css', 'label' => '自定义 CSS', 'type' => 'code', 'language' => 'css', 'default' => ''],
@@ -408,7 +408,7 @@ class GlobalSchema {
'key' => 'i18n',
'title' => '多语言翻译',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'basic', 'title' => '基础配置', 'fields' => [
['key' => 'i18n_enabled', 'label' => '启用多语言', 'type' => 'switch', 'default' => false],
@@ -423,7 +423,7 @@ class GlobalSchema {
['label' => 'Español', 'value' => 'es'],
['label' => 'Русский', 'value' => 'ru'],
]],
['key' => 'i18n_auto_detect', 'label' => '自动检测浏览器语言', 'type' => 'switch', 'default' => true, 'help' => '基于浏览器 Accept-Language 自动切换(AKSRT 方案,区别于 legacy IP 定位方案)'],
['key' => 'i18n_auto_detect', 'label' => '自动检测浏览器语言', 'type' => 'switch', 'default' => true, 'help' => '基于浏览器 Accept-Language 自动切换(Jio 方案,区别于 legacy IP 定位方案)'],
]],
['key' => 'languages', 'title' => '语言管理', 'fields' => [
['key' => 'i18n_available_langs', 'label' => '可用语言列表', 'type' => 'textarea', 'default' => "zh-CN|简体中文\nen|English\nzh-TW|繁體中文\nja|日本語\nko|한국어\nfr|Français\nde|Deutsch\nes|Español\nru|Русский", 'help' => '每行一个语言,格式:代码|名称(9 种内置语言)'],
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema\PostList;
namespace Jio\Admin\Schema\PostList;
defined('ABSPATH') || exit;
@@ -14,7 +14,7 @@ class SchemaPostListList {
'key' => 'list',
'title' => '文章列表',
'version' => '9.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
[
'key' => 'order',
+2 -2
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema;
namespace Jio\Admin\Schema;
defined('ABSPATH') || exit;
+3 -3
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema;
namespace Jio\Admin\Schema;
defined('ABSPATH') || exit;
@@ -14,7 +14,7 @@ class SchemaExtensionsAudit {
'key' => 'audit',
'title' => 'API内容审核',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'provider', 'title' => '审核接口', 'fields' => [
['key' => 'content_audit_enabled', 'label' => '启用内容审核', 'type' => 'switch', 'default' => false],
+3 -3
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema;
namespace Jio\Admin\Schema;
defined('ABSPATH') || exit;
@@ -14,7 +14,7 @@ class SchemaExtensionsIP {
'key' => 'ip',
'title' => 'IP归属地',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'basic', 'title' => '基础配置', 'fields' => [
['key' => 'ip_geo_enabled', 'label' => '启用 IP 归属地', 'type' => 'switch', 'default' => false],
+3 -3
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema;
namespace Jio\Admin\Schema;
defined('ABSPATH') || exit;
@@ -14,7 +14,7 @@ class SchemaExtensionsPush {
'key' => 'push',
'title' => '百度推送',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'basic', 'title' => '推送配置', 'fields' => [
['key' => 'baidu_push_enabled', 'label' => '启用百度推送', 'type' => 'switch', 'default' => false],
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema;
namespace Jio\Admin\Schema;
defined('ABSPATH') || exit;
@@ -14,7 +14,7 @@ class SchemaExtensionsSecurity {
'key' => 'security',
'title' => '网站安全',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'brush_post', 'title' => '[防刷机制]发布限制', 'description' => '限制单个用户发布文章、帖子、板块的总数量(为0则不限制)', 'fields' => [
['key' => 'security_brush_post_10min', 'label' => '10分钟内最多发布 (篇)', 'type' => 'number', 'default' => 20, 'min' => 0, 'max' => 200],
+3 -3
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema;
namespace Jio\Admin\Schema;
defined('ABSPATH') || exit;
@@ -14,7 +14,7 @@ class SchemaExtensionsSystem {
'key' => 'system',
'title' => '系统工具',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'optimize', 'title' => 'WordPress 优化', 'fields' => [
['key' => 'hide_admin_bar', 'label' => '前台关闭顶部admin_bar', 'type' => 'switch', 'default' => true, 'help' => '开启后前台不显示WordPress顶部黑条'],
+7 -7
View File
@@ -2,10 +2,10 @@
/**
* legacy V9.0 page/display settings contract.
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema;
namespace Jio\Admin\Schema;
defined('ABSPATH') || exit;
@@ -33,7 +33,7 @@ class SchemaPageDisplay {
'key' => $key,
'title' => $title,
'version' => '9.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [['key' => $key, 'fields' => $fields]],
];
}
@@ -52,7 +52,7 @@ class SchemaPageDisplay {
self::field('nav_scroll_down_hide', '页面向下滚动时隐藏', 'switcher', true, ['dependsOn' => self::enabled('nav_fixed'), 'description' => '下滚动隐藏,上滚动显示。']),
self::field('nav_newposts', 'Add发布按钮', 'switcher', true, ['description' => '顶部导航显示文章投稿按钮。']),
self::field('nav_newposts_radius', '按钮圆角', 'switcher', true, ['dependsOn' => self::enabled('nav_newposts')]),
self::field('nav_newposts_class', '按钮颜色', 'palette', 'jb-blue', ['dependsOn' => self::enabled('nav_newposts'), 'options' => self::aksrt_palette()]),
self::field('nav_newposts_class', '按钮颜色', 'palette', 'jb-blue', ['dependsOn' => self::enabled('nav_newposts'), 'options' => self::jio_palette()]),
self::field('nav_newposts_text', '按钮文案', 'textarea', '<i class="fa fas fa-fw fa-pencil-alt"></i>发布', ['dependsOn' => self::enabled('nav_newposts'), 'sanitize_type' => 'html', 'rows' => 1]),
]);
}
@@ -85,7 +85,7 @@ class SchemaPageDisplay {
self::field('footer_t1_img', '日间模式图片', 'upload', $img . 'logo.png'),
self::field('footer_t1_img_dark', '夜间模式图片', 'upload', $img . 'logo_dark.png'),
self::field('footer_t1_t', '首行文字', 'text', ''),
self::field('fcode_t1_code', '更多内容', 'textarea', 'AKSRT 主题专为博客、自媒体、资讯类的网站设计开发,简约优雅的设计风格,全面的前端用户功能,简单的模块化配置,欢迎您的体验', ['sanitize_type' => 'html', 'rows' => 3]),
self::field('fcode_t1_code', '更多内容', 'textarea', 'Jio 主题专为博客、自媒体、资讯类的网站设计开发,简约优雅的设计风格,全面的前端用户功能,简单的模块化配置,欢迎您的体验', ['sanitize_type' => 'html', 'rows' => 3]),
self::field('fcode_t2_code_1', '版块二第一行', 'textarea', '<a href="#">友链申请</a>' . "\n" . '<a href="#">免责声明</a>' . "\n" . '<a href="#">广告合作</a>' . "\n" . '<a href="#">关于我们</a>', ['sanitize_type' => 'html', 'rows' => 4]),
self::field('fcode_t2_code_2', '版块二第二行', 'textarea', self::copyright_default(), ['sanitize_type' => 'html', 'rows' => 3]),
self::field('footer_contact_m_s', '图标联系方式移动端显示', 'switcher', true),
@@ -181,7 +181,7 @@ class SchemaPageDisplay {
}
private static function card_fields(): array {
return [self::field('show_widget_bg', '模块背景', 'switcher', false, ['description' => '开启后整个模块显示统一背景;关闭后每张卡片单独显示背景。']), self::field('icon_radius4', '方形图标', 'switcher', false), self::field('cards', '添加卡片', 'group', [], ['button_title' => '添加内容', 'fields' => [self::field('title', '文案标题', 'textarea', '', ['sanitize_type' => 'rich_html']), self::field('desc', '文案简介', 'textarea', '', ['sanitize_type' => 'rich_html']), self::field('icon', '选择图标', 'icon', 'fa fas fa-magic'), self::field('customize_icon', '自定义图标', 'textarea', '', ['sanitize_type' => 'html']), self::field('icon_class', '图标颜色', 'palette', 'c-yellow', ['options' => self::aksrt_palette(true)]), self::field('link', '跳转链接', 'link', [])]])];
return [self::field('show_widget_bg', '模块背景', 'switcher', false, ['description' => '开启后整个模块显示统一背景;关闭后每张卡片单独显示背景。']), self::field('icon_radius4', '方形图标', 'switcher', false), self::field('cards', '添加卡片', 'group', [], ['button_title' => '添加内容', 'fields' => [self::field('title', '文案标题', 'textarea', '', ['sanitize_type' => 'rich_html']), self::field('desc', '文案简介', 'textarea', '', ['sanitize_type' => 'rich_html']), self::field('icon', '选择图标', 'icon', 'fa fas fa-magic'), self::field('customize_icon', '自定义图标', 'textarea', '', ['sanitize_type' => 'html']), self::field('icon_class', '图标颜色', 'palette', 'c-yellow', ['options' => self::jio_palette(true)]), self::field('link', '跳转链接', 'link', [])]])];
}
private static function orderby_fields(): array { return [self::field('lists', '显示排序方式', 'select', ['modified', 'views', 'like', 'comment_count'], ['multiple' => true, 'sortable' => true, 'options' => self::post_order_options(false)]), self::field('dropdown', '更多排序方式', 'switcher', false)]; }
@@ -201,7 +201,7 @@ class SchemaPageDisplay {
private static function options(array $options): array { $out = []; foreach ($options as $value => $label) { $out[] = ['label' => $label, 'value' => (string) $value]; } return $out; }
private static function device_options(): array { return self::options(['pc_s' => 'PC端显示', 'm_s' => '移动端显示']); }
private static function solid_palette(): array { return self::options(['ff648f' => '#ff648f', 'c246f5' => '#c246f5', '469cf5' => '#469cf5', '27bf41' => '#27bf41', 'fd6b4e' => '#fd6b4e', '2d2422' => '#2d2422']); }
private static function aksrt_palette(bool $transparent = false): array { $colors = $transparent ? ['transparent' => 'rgba(114,114,114,.1)'] : []; $colors += ['c-red' => 'rgba(255,84,115,.4)', 'c-yellow' => 'rgba(255,111,6,.4)', 'c-cyan' => 'rgba(8,196,193,.4)', 'c-blue' => 'rgba(41,151,247,.4)', 'c-blue-2' => 'rgba(77,130,249,.4)', 'c-green' => 'rgba(18,185,40,.4)', 'c-purple' => 'rgba(213,72,245,.4)', 'jb-red' => '#ff5473', 'jb-yellow' => '#ff8a00', 'jb-cyan' => '#08c4c1', 'jb-blue' => '#2997f7', 'jb-green' => '#12b928', 'jb-purple' => '#d548f5', 'jb-pink' => '#ff648f']; return self::options($colors); }
private static function jio_palette(bool $transparent = false): array { $colors = $transparent ? ['transparent' => 'rgba(114,114,114,.1)'] : []; $colors += ['c-red' => 'rgba(255,84,115,.4)', 'c-yellow' => 'rgba(255,111,6,.4)', 'c-cyan' => 'rgba(8,196,193,.4)', 'c-blue' => 'rgba(41,151,247,.4)', 'c-blue-2' => 'rgba(77,130,249,.4)', 'c-green' => 'rgba(18,185,40,.4)', 'c-purple' => 'rgba(213,72,245,.4)', 'jb-red' => '#ff5473', 'jb-yellow' => '#ff8a00', 'jb-cyan' => '#08c4c1', 'jb-blue' => '#2997f7', 'jb-green' => '#12b928', 'jb-purple' => '#d548f5', 'jb-pink' => '#ff648f']; return self::options($colors); }
private static function post_order_options(bool $with_default): array { $options = $with_default ? ['' => '保持默认'] : []; $options += ['modified' => '更新', 'date' => '发布', 'views' => '浏览', 'like' => '点赞', 'comment_count' => '评论', 'favorite' => '收藏', 'rand' => '随机']; return self::options($options); }
private static function header_layout_options(): array { $base = self::asset('assets/images/header-layout/'); return [['label' => '布局一', 'value' => '1', 'image' => $base . 'header_layout_1.png'], ['label' => '布局二', 'value' => '2', 'image' => $base . 'header_layout_2.png'], ['label' => '布局三', 'value' => '3', 'image' => $base . 'header_layout_3.png']]; }
private static function mobile_header_layout_options(): array { $base = self::asset('assets/images/header-layout/'); return [['label' => '居中', 'value' => 'center', 'image' => $base . 'mobile_header_layout_center.png'], ['label' => '居左', 'value' => 'left', 'image' => $base . 'mobile_header_layout_left.png']]; }
+13 -13
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema;
namespace Jio\Admin\Schema;
defined('ABSPATH') || exit;
@@ -30,7 +30,7 @@ class SchemaPayment {
'key' => 'knowledge',
'title' => '知识付费',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'notice', 'title' => '说明', 'fields' => [
['key' => 'knowledge_notice', 'label' => '', 'type' => 'content', 'ui_only' => true, 'content' => '<p><b>知识付费</b></p><ul><li>知识付费功能主要分为文章付费、付费会员、余额系统、推广返佣、创作分成等功能组成,您可以根据您的需要开启对应功能</li><li>使用支付功能请先在「收款接口」页配置好收款通道</li></ul>'],
@@ -122,7 +122,7 @@ class SchemaPayment {
'key' => 'recharge',
'title' => '余额充值',
'version' => '1.2.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'notice', 'title' => '说明', 'fields' => [
['key' => 'recharge_notice', 'label' => '', 'type' => 'content', 'ui_only' => true, 'content' => '<p><b>用户余额功能:</b></p><ul><li>开启后用户可充值到余额,余额可用于全站消费</li><li>在下方设置默认的分成比例,同时支持单独为每一个用户设置独立的分成比例</li><li>可以在用户管理中,为某一个用户手动赠送余额</li></ul>'],
@@ -179,7 +179,7 @@ class SchemaPayment {
'key' => 'vip',
'title' => 'VIP会员',
'version' => '1.2.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'notice', 'title' => '说明', 'fields' => [
['key' => 'vip_notice', 'label' => '', 'type' => 'content', 'ui_only' => true, 'content' => '<ul><li>开启付费会员功能之前,请先配置好收款接口,确保网站收款正常</li><li>会员功能的设置项目较多,请仔细核对,避免出现价格、时间的问题</li><li>配合设置名称、有效期等可搭配出不同类型的会员</li><li>管理员可在后台为用户单独开启会员</li></ul>'],
@@ -322,7 +322,7 @@ class SchemaPayment {
'key' => 'revenue',
'title' => '创作分成',
'version' => '2.1.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'notice', 'title' => '说明', 'fields' => [
['key' => 'revenue_notice', 'label' => '', 'type' => 'content', 'ui_only' => true, 'content' => '<ul><li>开启创作分成后,用户发布的付费内容则按设置的比例收入与用户分成</li><li>在下方设置默认的分成比例,同时支持单独为每一个用户设置独立的分成比例</li><li>用户分成比例是按照订单实收现金并扣除推广佣金后,再按设置的比例分成</li><li>禁用此功能后,所有收款将不会与作者分成</li></ul>'],
@@ -357,7 +357,7 @@ class SchemaPayment {
'key' => 'affiliate',
'title' => '推广返佣',
'version' => '1.1.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'notice', 'title' => '说明', 'fields' => [
['key' => 'affiliate_notice', 'label' => '', 'type' => 'content', 'ui_only' => true, 'content' => '<ul><li>识别模式「绑定注册」:用户只要是通过任意推广链接注册的,今后此用户消费均会给推荐人返佣</li><li>识别模式「仅推广链接」:只有通过推广链接购买的才会给推荐人返佣,不会考虑是否是推荐注册的</li><li>开启的订单类型如果一个都不勾选,则代表该用户类型不参与此功能</li><li>您可以根据网站的VIP设置相互配合设置相应的规则</li><li>下面的设置为全局设置,还可以单独为每一个用户设置规则和比例</li></ul>'],
@@ -436,10 +436,10 @@ class SchemaPayment {
'key' => 'gateway',
'title' => '收款接口',
'version' => '2.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'notice', 'title' => '说明', 'fields' => [
['key' => 'gateway_notice', 'label' => '', 'type' => 'content', 'ui_only' => true, 'content' => '<ul><li>以下收款接口,主题仅提供API接入服务,收款平台的可靠性请自行斟酌!</li><li>涉及到资金及信息安全,有相关执照的商家推荐使用官方接口</li><li>微信 / 支付宝接口可在「官方」与「易支付」之间选择;PayPal 与 Stripe 独立开关</li><li>异步回调由各网关验签,无需额外口令;回调地址固定为 /wp-json/aksrt/v1/payment/{gateway}/{notify|return}</li></ul>'],
['key' => 'gateway_notice', 'label' => '', 'type' => 'content', 'ui_only' => true, 'content' => '<ul><li>以下收款接口,主题仅提供API接入服务,收款平台的可靠性请自行斟酌!</li><li>涉及到资金及信息安全,有相关执照的商家推荐使用官方接口</li><li>微信 / 支付宝接口可在「官方」与「易支付」之间选择;PayPal 与 Stripe 独立开关</li><li>异步回调由各网关验签,无需额外口令;回调地址固定为 /wp-json/jio/v1/payment/{gateway}/{notify|return}</li></ul>'],
]],
['key' => 'channels', 'title' => '收款接口选择', 'fields' => [
['key' => 'pay_wechat_sdk_options', 'label' => '微信收款接口', 'type' => 'select', 'default' => 'null', 'options' => [
@@ -469,7 +469,7 @@ class SchemaPayment {
['key' => 'settle_unit', 'label' => '结算货币单位', 'type' => 'input', 'default' => '元', 'help' => '如果接入的收款方式与本地的货币类型不同,则在此配置结算汇率及单位'],
]],
['key' => 'pay_sdk_custom_title_s', 'label' => '自定义收款标题', 'type' => 'switch', 'default' => false, 'help' => '默认为付款类型加网站名,如需自定义请开启'],
['key' => 'pay_sdk_custom_title', 'label' => '自定义收款标题', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'pay_sdk_custom_title_s', 'value' => true], 'help' => '可使用变量:订单类型 %order_type%,例如:%order_type%-AKSRT;注意控制总长度,太长会导致部分接口报错'],
['key' => 'pay_sdk_custom_title', 'label' => '自定义收款标题', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'pay_sdk_custom_title_s', 'value' => true], 'help' => '可使用变量:订单类型 %order_type%,例如:%order_type%-Jio;注意控制总长度,太长会导致部分接口报错'],
]],
['key' => 'order', 'title' => '订单与编号', 'fields' => [
['key' => 'payment_order_expiry', 'label' => '订单支付时效(分钟)', 'type' => 'number', 'default' => 30, 'min' => 5, 'max' => 1440, 'help' => '用户下单后,最大支付时间,超过后订单将自动关闭(不能低于5分钟)'],
@@ -486,7 +486,7 @@ class SchemaPayment {
], 'help' => '收银台支付方式显示顺序;仅显示已开启的接口'],
]],
['key' => 'alipay', 'title' => '支付宝官方', 'collapsible' => true, 'defaultCollapsed' => true, 'dependsOn' => ['key' => 'pay_alipay_sdk_options', 'value' => 'official_alipay'], 'fields' => [
['key' => 'alipay_notice', 'label' => '', 'type' => 'content', 'ui_only' => true, 'content' => '<ul><li>支付宝公钥为必填项目(公钥错误可以成功付款,但会回调失败)</li><li>回调地址固定为 /wp-json/aksrt/v1/payment/alipay/{notify|return}</li><li>电脑端按「电脑端收款」选择站内二维码或跳转;手机端开启 H5 时跳转,否则显示二维码</li></ul>'],
['key' => 'alipay_notice', 'label' => '', 'type' => 'content', 'ui_only' => true, 'content' => '<ul><li>支付宝公钥为必填项目(公钥错误可以成功付款,但会回调失败)</li><li>回调地址固定为 /wp-json/jio/v1/payment/alipay/{notify|return}</li><li>电脑端按「电脑端收款」选择站内二维码或跳转;手机端开启 H5 时跳转,否则显示二维码</li></ul>'],
['key' => 'alipay_appid', 'label' => '当面付:APPID', 'type' => 'input', 'default' => ''],
['key' => 'alipay_privatekey', 'label' => '当面付:应用私钥', 'type' => 'textarea', 'sanitize_type' => 'code', 'default' => '', 'rows' => 4, 'secret' => true],
['key' => 'alipay_pc_mode', 'label' => '电脑端收款', 'type' => 'select', 'default' => 'facepay', 'options' => [
@@ -499,7 +499,7 @@ class SchemaPayment {
['key' => 'alipay_root_cert_path', 'label' => '支付宝根证书路径', 'type' => 'input', 'default' => '', 'help' => 'alipayRootCert.crt 绝对路径。'],
]],
['key' => 'wechat', 'title' => '微信官方', 'collapsible' => true, 'defaultCollapsed' => true, 'dependsOn' => ['key' => 'pay_wechat_sdk_options', 'value' => 'official_wechat'], 'fields' => [
['key' => 'wechat_notice', 'label' => '', 'type' => 'content', 'ui_only' => true, 'content' => '<ul><li>微信企业支付:官方接口,商家可申请,有年费,申请较为复杂</li><li>回调地址固定为 /wp-json/aksrt/v1/payment/wechatpay/{notify|return},请配置 Native 支付回调链接</li><li>电脑端显示站内二维码;手机端按下方开关选择 H5 跳转或微信内 JSAPI,未开启时同样显示二维码</li></ul>'],
['key' => 'wechat_notice', 'label' => '', 'type' => 'content', 'ui_only' => true, 'content' => '<ul><li>微信企业支付:官方接口,商家可申请,有年费,申请较为复杂</li><li>回调地址固定为 /wp-json/jio/v1/payment/wechatpay/{notify|return},请配置 Native 支付回调链接</li><li>电脑端显示站内二维码;手机端按下方开关选择 H5 跳转或微信内 JSAPI,未开启时同样显示二维码</li></ul>'],
['key' => 'wechat_merchantid', 'label' => '商户号 PartnerID', 'type' => 'input', 'default' => ''],
['key' => 'wechat_appid', 'label' => '授权绑定的AppID', 'type' => 'input', 'default' => ''],
['key' => 'wechat_h5', 'label' => '移动端 H5 跳转', 'type' => 'switch', 'default' => false, 'help' => '需开通 H5 支付。开启后手机浏览器(非微信内)跳转微信付款;关闭则显示站内二维码'],
@@ -545,7 +545,7 @@ class SchemaPayment {
'key' => 'withdrawal',
'title' => '提现策略',
'version' => '1.1.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'basic', 'title' => '提现门槛', 'fields' => [
['key' => 'affiliate_min_withdrawal', 'label' => '提现最低金额限制', 'type' => 'input', 'sanitize_type' => 'money_decimal', 'default' => '50.00', 'help' => '当用户可提现总金额(推广佣金、余额、创作分成)高于多少时候,才能发起提现'],
+11 -11
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema;
namespace Jio\Admin\Schema;
defined('ABSPATH') || exit;
@@ -16,7 +16,7 @@ class SchemaPermissions {
*/
private static function upload_user_type_options(): array {
$options = [];
foreach (\AKSRT\Attachment\UploadLimits::user_type_options() as $value => $label) {
foreach (\Jio\Attachment\UploadLimits::user_type_options() as $value => $label) {
$options[] = ['label' => $label, 'value' => $value];
}
return $options;
@@ -29,16 +29,16 @@ class SchemaPermissions {
*/
private static function upload_mime_user_type_options(): array {
$options = [
['label' => __('所有用户', 'aksrt'), 'value' => 'all'],
['label' => __('所有用户', 'jio'), 'value' => 'all'],
];
foreach (\AKSRT\Attachment\UploadLimits::user_type_options() as $value => $label) {
foreach (\Jio\Attachment\UploadLimits::user_type_options() as $value => $label) {
$options[] = ['label' => $label, 'value' => $value];
}
return $options;
}
public static function build(): array {
$provider_meta = function_exists('aksrt_captcha_provider_metadata') ? aksrt_captcha_provider_metadata() : [];
$provider_meta = function_exists('jio_captcha_provider_metadata') ? jio_captcha_provider_metadata() : [];
$provider_options = [['label' => '关闭', 'value' => '']];
$provider_fields = [];
foreach ($provider_meta as $provider => $meta) {
@@ -50,7 +50,7 @@ class SchemaPermissions {
'key' => $provider,
'label' => (string) ($meta['label'] ?? $provider) . ' 配置',
'type' => 'custom',
'default' => function_exists('aksrt_captcha_provider_defaults') ? aksrt_captcha_provider_defaults($provider) : [],
'default' => function_exists('jio_captcha_provider_defaults') ? jio_captcha_provider_defaults($provider) : [],
'captcha_provider' => $provider,
];
}
@@ -63,7 +63,7 @@ class SchemaPermissions {
'key' => 'login',
'title' => '人机验证',
'version' => '1.1.0',
'option_key' => 'aksrt_captcha_config',
'option_key' => 'jio_captcha_config',
'groups' => [
['key' => 'basic', 'title' => '基础开关', 'fields' => [
['key' => 'enabled', 'label' => '启用人机验证', 'type' => 'switch', 'default' => false],
@@ -92,7 +92,7 @@ class SchemaPermissions {
'key' => 'attachment',
'title' => '附件配置',
'version' => '1.2.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'size-limits', 'title' => '上传大小限制', 'fields' => [
['key' => 'size_limit_notice', 'label' => '服务器环境提示', 'type' => 'notice', 'alertType' => 'warning', 'alertContent' => '设置上传大小,请考虑服务器负荷、硬盘,以及服务器最大能支持的范围。实际上传大小取「主题限制」与「PHP 环境限制」的较小值,超过 PHP 环境限制时请调整 php.ini 的 upload_max_filesize 与 post_max_size。'],
@@ -139,7 +139,7 @@ class SchemaPermissions {
'key' => 'moderation',
'title' => '评论审核',
'version' => '1.0.0',
'option_key' => 'aksrt_moderation_config',
'option_key' => 'jio_moderation_config',
'groups' => [
['key' => 'basic', 'title' => '审核总开关', 'fields' => [
['key' => 'enabled', 'label' => '启用审核系统', 'type' => 'switch', 'default' => false],
@@ -174,7 +174,7 @@ class SchemaPermissions {
'key' => 'capabilities',
'title' => '基本权限',
'version' => '1.1.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [],
],
],
+9 -9
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema;
namespace Jio\Admin\Schema;
defined('ABSPATH') || exit;
@@ -19,7 +19,7 @@ class SchemaPostList {
'key' => 'thumb',
'title' => '列表缩略图',
'version' => '9.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'source', 'title' => '图片来源', 'description' => '缩略图获取优先级:文章特色图像 > 外链特色图像 > 文章首图 > 分类封面图 > 备用缩略图 > 懒加载预载图。', 'fields' => [
['key' => 'list_thumb_slides_s', 'label' => '幻灯片缩略图', 'type' => 'switch', 'default' => true, 'description' => '允许幻灯片作为列表缩略图'],
@@ -47,7 +47,7 @@ class SchemaPostList {
'key' => 'page',
'title' => '文章页面',
'version' => '2.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'post_page', 'title' => '文章页面', 'fields' => [
['key' => 'article_image_cover', 'label' => '图片封面', 'type' => 'switcher', 'default' => true, 'description' => '允许设置文章页顶部显示封面图'],
@@ -130,7 +130,7 @@ class SchemaPostList {
'key' => 'filter',
'title' => '高级筛选',
'version' => '2.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'advanced_filter', 'title' => '高级筛选', 'fields' => [
['key' => 'custom_filter_show', 'label' => '高级筛选默认显示', 'type' => 'checkbox', 'default' => [], 'options' => [
@@ -180,7 +180,7 @@ class SchemaPostList {
'key' => 'feature',
'title' => '文章功能',
'version' => '2.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'feature', 'title' => '文章功能', 'fields' => [
['key' => 'article_nav', 'label' => '文章目录树', 'type' => 'switcher', 'default' => true, 'description' => '默认开关,每篇文章可单独设置。开启后请自行添加文章目录树模块到侧边栏(文章内标题超过3个才会显示)'],
@@ -222,7 +222,7 @@ class SchemaPostList {
'key' => 'comment',
'title' => '评论设置',
'version' => '2.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'comment', 'title' => '评论设置', 'fields' => [
['key' => 'close_comments', 'label' => '关闭文章评论功能', 'type' => 'switcher', 'default' => false, 'description' => '部分网站无需交互,或需备案审核,可在此关闭所有文章的评论功能。同时每一篇文章可单独关闭评论功能'],
@@ -258,7 +258,7 @@ class SchemaPostList {
'key' => 'pay',
'title' => '付费内容',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'basic', 'title' => '付费设置', 'fields' => [
['key' => 'post_pay_enable', 'label' => '启用付费内容', 'type' => 'switch', 'default' => false],
@@ -284,7 +284,7 @@ class SchemaPostList {
'key' => 'submit',
'title' => '前台投稿',
'version' => '2.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'submit', 'title' => '前台投稿', 'fields' => [
['key' => 'post_article_s', 'label' => '启用前台投稿', 'type' => 'switcher', 'default' => true, 'description' => '此功能启用后,可以在权限管理中设置用户的发布、审核权限'],
+2 -2
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema;
namespace Jio\Admin\Schema;
defined('ABSPATH') || exit;
+3 -3
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema;
namespace Jio\Admin\Schema;
defined('ABSPATH') || exit;
@@ -14,7 +14,7 @@ class SchemaToolsBackup {
'key' => 'backup',
'title' => '数据备份',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'backup', 'title' => '备份设置', 'fields' => [
['key' => 'backup_auto', 'label' => '自动备份', 'type' => 'switch', 'help' => '启用自动定时备份'],
+3 -3
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema;
namespace Jio\Admin\Schema;
defined('ABSPATH') || exit;
@@ -14,7 +14,7 @@ class SchemaToolsHealth {
'key' => 'health',
'title' => '健康检查',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'check', 'title' => '环境检查', 'fields' => [
['key' => 'php_version', 'label' => 'PHP 版本', 'type' => 'display', 'help' => '建议 7.4+'],
+4 -4
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema;
namespace Jio\Admin\Schema;
defined('ABSPATH') || exit;
@@ -14,11 +14,11 @@ class SchemaToolsImportExport {
'key' => 'import-export',
'title' => '导入导出',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'export', 'title' => '导出配置', 'fields' => [
['key' => 'export_settings', 'label' => '导出主题设置', 'type' => 'button', 'help' => '导出当前所有主题配置为 JSON 文件'],
['key' => 'export_options', 'label' => '导出 WordPress 选项', 'type' => 'button', 'help' => '导出 aksrt_settings 等选项'],
['key' => 'export_options', 'label' => '导出 WordPress 选项', 'type' => 'button', 'help' => '导出 jio_settings 等选项'],
]],
['key' => 'import', 'title' => '导入配置', 'fields' => [
['key' => 'import_settings', 'label' => '导入主题设置', 'type' => 'file', 'accept' => '.json', 'help' => '导入主题配置文件(将覆盖当前设置)'],
+13 -13
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema;
namespace Jio\Admin\Schema;
defined('ABSPATH') || exit;
@@ -111,7 +111,7 @@ class SchemaUsers {
'key' => 'auth',
'title' => '注册登录',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'register', 'title' => '注册设置', 'fields' => [
['key' => 'close_sign', 'label' => '一键禁用登录注册', 'type' => 'switch', 'default' => false, 'desc' => '开启后前台禁用全部注册/登录入口(含社交登录),不影响后台登录'],
@@ -139,7 +139,7 @@ class SchemaUsers {
['key' => 'sign_modal_auto_waiting_time', 'label' => '弹窗等待秒数', 'type' => 'number', 'default' => 5, 'min' => 1, 'max' => 100, 'desc' => '页面加载多少秒后弹出登录窗'],
['key' => 'sign_modal_auto_expires', 'label' => '弹窗周期(小时)', 'type' => 'number', 'default' => 1, 'min' => 0, 'max' => 2000, 'desc' => '关闭弹窗后多少小时内不再自动弹出,0=每次刷新都弹'],
['key' => 'replace_wp_login', 'label' => '替换WP登录页', 'type' => 'switch', 'default' => true],
['key' => 'admin_sign_key', 'label' => '应急登录 Key', 'type' => 'input', 'default' => '', 'desc' => '留空为禁用。管理员可通过 /wp-login.php?aksrt_bypass=<key> 绕过前台替换'],
['key' => 'admin_sign_key', 'label' => '应急登录 Key', 'type' => 'input', 'default' => '', 'desc' => '留空为禁用。管理员可通过 /wp-login.php?jio_bypass=<key> 绕过前台替换'],
['key' => 'user_signin_phone_s', 'label' => '手机号登录', 'type' => 'switch', 'default' => false, 'desc' => '允许用手机号作为用户名登录'],
['key' => 'user_signin_nopas_s', 'label' => '免密登录', 'type' => 'switch', 'default' => false],
['key' => 'user_signin_nopas_type', 'label' => '免密登录方式', 'type' => 'radio', 'default' => 'email', 'options' => [
@@ -234,7 +234,7 @@ class SchemaUsers {
'key' => 'notifications',
'title' => '消息通知',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'basic', 'title' => '基础设置', 'fields' => [
['key' => 'message_s', 'label' => '站内通知总开关', 'type' => 'switch', 'default' => true],
@@ -292,7 +292,7 @@ class SchemaUsers {
'key' => 'oauth',
'title' => '第三方登录',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'basic', 'title' => '基础设置', 'fields' => [
['key' => 'oauth_s', 'label' => '启用第三方登录', 'type' => 'switch', 'default' => false],
@@ -390,7 +390,7 @@ class SchemaUsers {
'key' => 'sms',
'title' => '短信接口',
'version' => '2.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'provider', 'title' => '短信接口', 'fields' => [
['key' => 'sms_sdk', 'label' => '设置短信接口', 'type' => 'select', 'default' => 'null', 'options' => [
@@ -432,7 +432,7 @@ class SchemaUsers {
'key' => 'credits',
'title' => '积分设置',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'basic', 'title' => '积分基础', 'fields' => [
['key' => 'points_s', 'label' => '启用积分系统', 'type' => 'switch', 'default' => true],
@@ -477,7 +477,7 @@ class SchemaUsers {
'key' => 'features',
'title' => '用户功能',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'profile-media', 'title' => '用户资料', 'fields' => [
['key' => 'avatar_upload_s', 'label' => '允许用户上传头像', 'type' => 'switch', 'default' => true],
@@ -510,7 +510,7 @@ class SchemaUsers {
'key' => 'verify',
'title' => '身份认证',
'version' => '2.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'apply', 'title' => '申请设置', 'fields' => [
['key' => 'user_auth_s', 'label' => '启用身份认证', 'type' => 'switch', 'default' => true],
@@ -525,7 +525,7 @@ class SchemaUsers {
'key' => 'level',
'title' => '用户等级',
'version' => '2.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'display', 'title' => '等级显示', 'fields' => [
['key' => 'user_level_s', 'label' => '启用用户等级', 'type' => 'switch', 'default' => true],
@@ -545,7 +545,7 @@ class SchemaUsers {
'key' => 'badge',
'title' => '用户徽章',
'version' => '2.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'display', 'title' => '徽章展示', 'fields' => [
['key' => 'user_medal_s', 'label' => '启用徽章/勋章系统', 'type' => 'switch', 'default' => false],
@@ -571,7 +571,7 @@ class SchemaUsers {
'key' => 'report',
'title' => '禁封/举报',
'version' => '1.1.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
// 字段集合、顺序、文案、默认值 1:1 对齐参考主题"用户&互动/禁封举报"
['key' => 'main', 'title' => '', 'fields' => [
+3 -3
View File
@@ -6,10 +6,10 @@
* assets/admin/src/schema/sections/users/ticket.js 保持 1:1
* (tests/widget-system/ticket-schema-contract.test.cjs 守护)。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin\Schema\Users;
namespace Jio\Admin\Schema\Users;
defined('ABSPATH') || exit;
@@ -20,7 +20,7 @@ class SchemaUsersTicket {
'key' => 'ticket',
'title' => '工单中心',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'option_key' => 'jio_settings',
'groups' => [
['key' => 'main', 'title' => '工单功能', 'icon' => 'fa fas fa-headset', 'fields' => [
['key' => 'ticket_s', 'label' => '工单功能', 'type' => 'switch', 'default' => true, 'help' => '开启后用户可在用户中心提交工单;关闭后工单中心不再开放,后台工单列表仍可查看历史工单'],
+59 -59
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -81,21 +81,21 @@ class SchemaProvider {
public function get_option_key(string $domain, string $section = ''): string {
if ($section !== '') {
$schema = $this->get_section_schema($domain, $section);
return (string) ($schema['option_key'] ?? 'aksrt_settings');
return (string) ($schema['option_key'] ?? 'jio_settings');
}
$primary = [
'global' => 'aksrt_settings',
'page_display' => 'aksrt_settings',
'post_list' => 'aksrt_settings',
'permissions' => 'aksrt_settings',
'users' => 'aksrt_settings',
'payment' => 'aksrt_settings',
'extensions' => 'aksrt_settings',
'tools' => 'aksrt_settings',
'global' => 'jio_settings',
'page_display' => 'jio_settings',
'post_list' => 'jio_settings',
'permissions' => 'jio_settings',
'users' => 'jio_settings',
'payment' => 'jio_settings',
'extensions' => 'jio_settings',
'tools' => 'jio_settings',
];
return $primary[$this->normalize_domain($domain)] ?? 'aksrt_settings';
return $primary[$this->normalize_domain($domain)] ?? 'jio_settings';
}
public function get_defaults(string $domain, string $section = ''): array {
@@ -271,7 +271,7 @@ class SchemaProvider {
}
if ($domain === 'users' && ($section === 'oauth' || $section === '')) {
$stored = get_option('aksrt_settings', []);
$stored = get_option('jio_settings', []);
$merged = is_array($stored) ? array_merge($stored, $data) : $data;
foreach ((array) ($data['__clear_secrets'] ?? []) as $secret_key) {
$merged[sanitize_key((string) $secret_key)] = '';
@@ -280,27 +280,27 @@ class SchemaProvider {
}
if ($domain === 'payment' && $section === 'withdrawal') {
$stored = get_option('aksrt_settings', []);
$stored = get_option('jio_settings', []);
$merged = is_array($stored) ? array_merge($stored, $data) : $data;
$count = $merged['affiliate_daily_withdrawal_count'] ?? 3;
$min_raw = $merged['affiliate_min_withdrawal'] ?? '50.00';
$limit_raw = $merged['affiliate_daily_withdrawal_limit'] ?? '500.00';
$time = (string) ($merged['affiliate_withdrawal_reset_time'] ?? '00:00');
if (filter_var($count, FILTER_VALIDATE_INT) === false || (int) $count < 0) {
$errors[] = __('每日可提现次数必须是非负整数', 'aksrt');
$errors[] = __('每日可提现次数必须是非负整数', 'jio');
}
try {
$min_minor = \AKSRT\Ledger\AssetAmount::balance(is_string($min_raw) ? $min_raw : '')->minor();
$limit_minor = \AKSRT\Ledger\AssetAmount::balance(is_string($limit_raw) ? $limit_raw : '')->minor();
$min_minor = \Jio\Ledger\AssetAmount::balance(is_string($min_raw) ? $min_raw : '')->minor();
$limit_minor = \Jio\Ledger\AssetAmount::balance(is_string($limit_raw) ? $limit_raw : '')->minor();
} catch (\InvalidArgumentException $e) {
$min_minor = 0;
$limit_minor = 0;
}
if ($limit_minor < $min_minor) {
$errors[] = __('每日提现上限不得低于最低提现金额', 'aksrt');
$errors[] = __('每日提现上限不得低于最低提现金额', 'jio');
}
if (preg_match('/^(?:[01]\\d|2[0-3]):[0-5]\\d$/', $time) !== 1) {
$errors[] = __('重置时间必须为 HH:MM', 'aksrt');
$errors[] = __('重置时间必须为 HH:MM', 'jio');
}
}
@@ -366,8 +366,8 @@ class SchemaProvider {
}
private function get_schema_builder_map(): array {
// Builders live under AKSRT\Admin\Schema (Composer PSR-4). Relative
// Schema\*::class resolves correctly from this AKSRT\Admin namespace.
// Builders live under Jio\Admin\Schema (Composer PSR-4). Relative
// Schema\*::class resolves correctly from this Jio\Admin namespace.
return [
'global' => Schema\GlobalSchema::class,
'page_display' => Schema\SchemaPageDisplay::class,
@@ -381,7 +381,7 @@ class SchemaProvider {
}
private function read_section_settings(array $schema): array {
$option_key = (string) ($schema['option_key'] ?? 'aksrt_settings');
$option_key = (string) ($schema['option_key'] ?? 'jio_settings');
$stored = get_option($option_key, []);
if (!is_array($stored)) {
$stored = [];
@@ -426,14 +426,14 @@ class SchemaProvider {
}
// Captcha: always surface the normalized positive scene list.
if ($option_key === 'aksrt_captcha_config' && function_exists('aksrt_get_captcha_config')) {
$normalized = aksrt_get_captcha_config();
if ($option_key === 'jio_captcha_config' && function_exists('jio_get_captcha_config')) {
$normalized = jio_get_captcha_config();
foreach (['enabled', 'active_provider', 'enabled_scenarios'] as $key) {
if (array_key_exists($key, $normalized)) {
$data[$key] = $normalized[$key];
}
}
foreach (function_exists('aksrt_captcha_providers') ? aksrt_captcha_providers() : [] as $provider) {
foreach (function_exists('jio_captcha_providers') ? jio_captcha_providers() : [] as $provider) {
$data[$provider] = $this->mask_captcha_provider_secrets($provider, (array) ($normalized[$provider] ?? []));
}
}
@@ -442,7 +442,7 @@ class SchemaProvider {
}
private function write_section_settings(array $schema, array $data): bool {
$option_key = (string) ($schema['option_key'] ?? 'aksrt_settings');
$option_key = (string) ($schema['option_key'] ?? 'jio_settings');
$stored = get_option($option_key, []);
if (!is_array($stored)) {
$stored = [];
@@ -482,28 +482,28 @@ class SchemaProvider {
}
// Captcha stores a single positive scene list.
if ($option_key === 'aksrt_captcha_config' && function_exists('aksrt_captcha_scenes')) {
if ($option_key === 'jio_captcha_config' && function_exists('jio_captcha_scenes')) {
$stored['enabled'] = !empty($stored['enabled']);
$active_provider = sanitize_key((string) ($stored['active_provider'] ?? ''));
$stored['active_provider'] = in_array($active_provider, aksrt_captcha_providers(), true) ? $active_provider : '';
$stored['enabled_scenarios'] = function_exists('aksrt_captcha_sanitize_scene_list')
? aksrt_captcha_sanitize_scene_list($stored['enabled_scenarios'] ?? [])
$stored['active_provider'] = in_array($active_provider, jio_captcha_providers(), true) ? $active_provider : '';
$stored['enabled_scenarios'] = function_exists('jio_captcha_sanitize_scene_list')
? jio_captcha_sanitize_scene_list($stored['enabled_scenarios'] ?? [])
: (array) ($stored['enabled_scenarios'] ?? []);
foreach (aksrt_captcha_providers() as $provider) {
$stored[$provider] = function_exists('aksrt_captcha_sanitize_provider_config')
? aksrt_captcha_sanitize_provider_config($provider, $stored[$provider] ?? [])
foreach (jio_captcha_providers() as $provider) {
$stored[$provider] = function_exists('jio_captcha_sanitize_provider_config')
? jio_captcha_sanitize_provider_config($provider, $stored[$provider] ?? [])
: (array) ($stored[$provider] ?? []);
}
}
$updated = update_option($option_key, $stored);
if ($option_key === 'aksrt_captcha_config' && function_exists('aksrt_captcha_config_reset')) {
aksrt_captcha_config_reset();
if ($option_key === 'jio_captcha_config' && function_exists('jio_captcha_config_reset')) {
jio_captcha_config_reset();
}
\AKSRT\Settings\Settings::flush_settings_cache();
\Jio\Settings\Settings::flush_settings_cache();
if (isset($stored['site_favicon'])) {
\AKSRT\Settings\Settings::sync_native_site_icon((string) $stored['site_favicon']);
\Jio\Settings\Settings::sync_native_site_icon((string) $stored['site_favicon']);
}
return $updated || true;
@@ -525,8 +525,8 @@ class SchemaProvider {
if (!empty($field['secret'])) {
$paths[] = $key;
}
if (!empty($field['captcha_provider']) && function_exists('aksrt_captcha_provider_secret_fields')) {
foreach (aksrt_captcha_provider_secret_fields((string) $field['captcha_provider']) as $secret) {
if (!empty($field['captcha_provider']) && function_exists('jio_captcha_provider_secret_fields')) {
foreach (jio_captcha_provider_secret_fields((string) $field['captcha_provider']) as $secret) {
$paths[] = $key . '.' . sanitize_key((string) $secret);
}
}
@@ -535,21 +535,21 @@ class SchemaProvider {
}
private function mask_captcha_provider_secrets(string $provider, array $config): array {
$clean = function_exists('aksrt_captcha_sanitize_provider_config')
? aksrt_captcha_sanitize_provider_config($provider, $config)
$clean = function_exists('jio_captcha_sanitize_provider_config')
? jio_captcha_sanitize_provider_config($provider, $config)
: $config;
foreach (function_exists('aksrt_captcha_provider_secret_fields') ? aksrt_captcha_provider_secret_fields($provider) : [] as $secret) {
foreach (function_exists('jio_captcha_provider_secret_fields') ? jio_captcha_provider_secret_fields($provider) : [] as $secret) {
$clean[$secret] = trim((string) ($config[$secret] ?? '')) !== '' ? '***' : '';
}
return $clean;
}
private function merge_captcha_provider_config(string $provider, array $stored, array $incoming, array $clear_paths): array {
$current = function_exists('aksrt_captcha_sanitize_provider_config')
? aksrt_captcha_sanitize_provider_config($provider, $stored)
$current = function_exists('jio_captcha_sanitize_provider_config')
? jio_captcha_sanitize_provider_config($provider, $stored)
: $stored;
$defaults = function_exists('aksrt_captcha_provider_defaults') ? aksrt_captcha_provider_defaults($provider) : [];
$secret_fields = function_exists('aksrt_captcha_provider_secret_fields') ? aksrt_captcha_provider_secret_fields($provider) : [];
$defaults = function_exists('jio_captcha_provider_defaults') ? jio_captcha_provider_defaults($provider) : [];
$secret_fields = function_exists('jio_captcha_provider_secret_fields') ? jio_captcha_provider_secret_fields($provider) : [];
foreach (array_keys($defaults) as $key) {
$path = $provider . '.' . $key;
if (in_array($path, $clear_paths, true)) {
@@ -565,8 +565,8 @@ class SchemaProvider {
}
$current[$key] = $value;
}
return function_exists('aksrt_captcha_sanitize_provider_config')
? aksrt_captcha_sanitize_provider_config($provider, $current)
return function_exists('jio_captcha_sanitize_provider_config')
? jio_captcha_sanitize_provider_config($provider, $current)
: $current;
}
@@ -659,7 +659,7 @@ class SchemaProvider {
}
try {
return \AKSRT\Ledger\AssetAmount::balance(trim($value))->decimal();
return \Jio\Ledger\AssetAmount::balance(trim($value))->decimal();
} catch (\InvalidArgumentException $e) {
return '';
}
@@ -679,8 +679,8 @@ class SchemaProvider {
// Inline whitelist for card title/desc: `badge` is a custom
// element that wp_kses_post() would strip, but legacy (Zibll)
// renders such markup verbatim.
return function_exists('aksrt_icon_card_kses')
? aksrt_icon_card_kses((string) $value)
return function_exists('jio_icon_card_kses')
? jio_icon_card_kses((string) $value)
: wp_kses_post((string) $value);
case 'textarea':
return sanitize_textarea_field((string) $value);
@@ -716,13 +716,13 @@ class SchemaProvider {
}
return $out;
case 'icon':
return function_exists('aksrt_sanitize_icon_value')
? aksrt_sanitize_icon_value((string) $value)
return function_exists('jio_sanitize_icon_value')
? jio_sanitize_icon_value((string) $value)
: sanitize_text_field((string) $value);
case 'color':
return \AKSRT\Core\Color::sanitize($value);
return \Jio\Core\Color::sanitize($value);
case 'color_alpha':
return \AKSRT\Core\Color::sanitize($value, true);
return \Jio\Core\Color::sanitize($value, true);
default:
return sanitize_text_field((string) $value);
}
@@ -730,8 +730,8 @@ class SchemaProvider {
private function sanitize_field_value($value, array $field) {
if (!empty($field['captcha_provider'])) {
return function_exists('aksrt_captcha_sanitize_provider_config')
? aksrt_captcha_sanitize_provider_config((string) $field['captcha_provider'], $value)
return function_exists('jio_captcha_sanitize_provider_config')
? jio_captcha_sanitize_provider_config((string) $field['captcha_provider'], $value)
: [];
}
$type = (string) ($field['type'] ?? 'text');
@@ -749,8 +749,8 @@ class SchemaProvider {
// fields already route through sanitize_value('icon'); group/repeater
// sub-fields (e.g. header_slider_card_option.cards[].icon) must too.
if ($type === 'icon') {
return function_exists('aksrt_sanitize_icon_value')
? aksrt_sanitize_icon_value((string) $value)
return function_exists('jio_sanitize_icon_value')
? jio_sanitize_icon_value((string) $value)
: $this->sanitize_value($value, 'icon');
}
+13 -13
View File
@@ -2,10 +2,10 @@
/**
* SchemaProvider OAuth 设置校验(代理模式 / 各平台凭据 / HTTPS 地址)。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -23,24 +23,24 @@ trait SchemaProviderOAuthValidation {
if ($mode === 'client') {
$url = trim((string) ($settings['oauth_proxy_server_url'] ?? ''));
if (!$this->is_secure_oauth_url($url)) {
$errors[] = __('客户端代理模式需要有效的 HTTPS 服务端地址', 'aksrt');
$errors[] = __('客户端代理模式需要有效的 HTTPS 服务端地址', 'jio');
}
if (!$secret_configured) {
$errors[] = __('客户端代理模式需要代理 HMAC 密钥', 'aksrt');
$errors[] = __('客户端代理模式需要代理 HMAC 密钥', 'jio');
}
if (empty($settings['oauth_proxy_client_providers']) || !is_array($settings['oauth_proxy_client_providers'])) {
$errors[] = __('客户端代理模式至少需要启用一个登录平台', 'aksrt');
$errors[] = __('客户端代理模式至少需要启用一个登录平台', 'jio');
}
return $errors;
}
if ($mode === 'server') {
if (!$secret_configured) {
$errors[] = __('服务端代理模式需要代理 HMAC 密钥', 'aksrt');
$errors[] = __('服务端代理模式需要代理 HMAC 密钥', 'jio');
}
$selected = is_array($settings['oauth_proxy_server_providers'] ?? null) ? $settings['oauth_proxy_server_providers'] : [];
if ($selected === []) {
$errors[] = __('服务端代理模式至少需要允许一个登录平台', 'aksrt');
$errors[] = __('服务端代理模式至少需要允许一个登录平台', 'jio');
}
$provider_fields = [
'github' => ['GitHub', 'github_appid', 'github_appkey'],
@@ -56,7 +56,7 @@ trait SchemaProviderOAuthValidation {
}
[$label, $id_key, $secret_key] = $provider_fields[$provider];
if (trim((string) ($settings[$id_key] ?? '')) === '' || !$this->oauth_secret_is_configured($settings[$secret_key] ?? '', $stored[$secret_key] ?? '')) {
$errors[] = sprintf(__('代理服务端的 %s 平台缺少完整凭据', 'aksrt'), $label);
$errors[] = sprintf(__('代理服务端的 %s 平台缺少完整凭据', 'jio'), $label);
}
}
return $errors;
@@ -77,7 +77,7 @@ trait SchemaProviderOAuthValidation {
}
$enabled++;
if (trim((string) ($settings[$id_key] ?? '')) === '' || !$this->oauth_secret_is_configured($settings[$secret_key] ?? '', $stored[$secret_key] ?? '')) {
$errors[] = sprintf(__('%s 登录已启用,请填写完整凭据', 'aksrt'), $label);
$errors[] = sprintf(__('%s 登录已启用,请填写完整凭据', 'jio'), $label);
}
}
@@ -92,8 +92,8 @@ trait SchemaProviderOAuthValidation {
: $this->oauth_secret_is_configured($settings['weixingzh_appsecret'] ?? '', $stored['weixingzh_appsecret'] ?? '');
if (!$base_ready || !$mode_ready) {
$errors[] = $gzh_type === 'subscribe'
? __('微信公众号订阅号登录已启用,请填写 Token、AppID、二维码和回复关键词', 'aksrt')
: __('微信公众号服务号登录已启用,请填写 Token、AppID 和 AppSecret', 'aksrt');
? __('微信公众号订阅号登录已启用,请填写 Token、AppID、二维码和回复关键词', 'jio')
: __('微信公众号服务号登录已启用,请填写 Token、AppID 和 AppSecret', 'jio');
}
}
@@ -104,12 +104,12 @@ trait SchemaProviderOAuthValidation {
|| !$this->oauth_secret_is_configured($settings['clogin_key'] ?? '', $stored['clogin_key'] ?? '')
|| empty($settings['clogin_oauth_s'])
) {
$errors[] = __('彩虹聚合登录已启用,请填写 HTTPS 接口地址、AppID、AppKey 并选择平台', 'aksrt');
$errors[] = __('彩虹聚合登录已启用,请填写 HTTPS 接口地址、AppID、AppKey 并选择平台', 'jio');
}
}
if ($enabled === 0) {
$errors[] = __('启用第三方登录后,至少需要启用一个本地登录提供商', 'aksrt');
$errors[] = __('启用第三方登录后,至少需要启用一个本地登录提供商', 'jio');
}
return $errors;
+2 -2
View File
@@ -2,10 +2,10 @@
/**
* SchemaProvider 枚举字段取值归一化(大小写不敏感匹配 / 选项声明值回写)。
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
+14 -14
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -41,7 +41,7 @@ class ToolsController extends ControllerBase {
$theme = wp_get_theme();
$info = [
'current_version' => $theme->get('Version') ?: '0.0.0',
'name' => $theme->get('Name') ?: 'AKSRT',
'name' => $theme->get('Name') ?: 'Jio',
'update_available'=> false,
'remote_version' => '',
'download_url' => '',
@@ -50,28 +50,28 @@ class ToolsController extends ControllerBase {
'dismissed' => '',
];
if (class_exists('\AKSRT\Core\UpdateChecker')) {
$stored = \AKSRT\Core\UpdateChecker::get_instance()->get_update_info();
if (class_exists('\Jio\Core\UpdateChecker')) {
$stored = \Jio\Core\UpdateChecker::get_instance()->get_update_info();
if (is_array($stored)) {
$info = array_merge($info, $stored);
}
}
$info['dismissed'] = (string) get_user_meta(get_current_user_id(), 'aksrt_dismissed_update', true);
$info['dismissed'] = (string) get_user_meta(get_current_user_id(), 'jio_dismissed_update', true);
return $this->success(['data' => $info]);
}
public function detect_update(): \WP_REST_Response {
if (!class_exists('\AKSRT\Core\UpdateChecker')) {
return $this->error(__('更新检查器不可用', 'aksrt'), 500);
if (!class_exists('\Jio\Core\UpdateChecker')) {
return $this->error(__('更新检查器不可用', 'jio'), 500);
}
\AKSRT\Core\UpdateChecker::get_instance()->do_check();
$stored = \AKSRT\Core\UpdateChecker::get_instance()->get_update_info();
\Jio\Core\UpdateChecker::get_instance()->do_check();
$stored = \Jio\Core\UpdateChecker::get_instance()->get_update_info();
return $this->success([
'message' => __('已完成更新检测', 'aksrt'),
'message' => __('已完成更新检测', 'jio'),
'data' => is_array($stored) ? $stored : [],
]);
}
@@ -81,13 +81,13 @@ class ToolsController extends ControllerBase {
$version = sanitize_text_field((string) ($params['version'] ?? ''));
if ($version === '') {
return $this->error(__('缺少版本号', 'aksrt'), 422);
return $this->error(__('缺少版本号', 'jio'), 422);
}
update_user_meta(get_current_user_id(), 'aksrt_dismissed_update', $version);
update_user_meta(get_current_user_id(), 'jio_dismissed_update', $version);
return $this->success([
'message' => __('已忽略此版本提醒', 'aksrt'),
'message' => __('已忽略此版本提醒', 'jio'),
'data' => ['version' => $version],
]);
}
+74 -74
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -61,10 +61,10 @@ class UserController extends ControllerBase {
'methods' => 'POST',
'callback' => [$this, 'rest_upload_comment_image'],
'permission_callback' => static function (): bool {
if (!is_user_logged_in() || !class_exists('\AKSRT\Settings\UserRuntime')) {
if (!is_user_logged_in() || !class_exists('\Jio\Settings\UserRuntime')) {
return false;
}
$report = \AKSRT\Settings\UserRuntime::report_settings();
$report = \Jio\Settings\UserRuntime::report_settings();
return !empty($report['enabled']) && !empty($report['img_enabled']);
},
]);
@@ -331,11 +331,11 @@ class UserController extends ControllerBase {
* 状态形状与 WeChatNotify::get_status() 对齐(该方法的既定用途即 REST 与 AJAX 复用)。
*/
public function get_wechat_status(\WP_REST_Request $request): \WP_REST_Response {
if (!class_exists('\AKSRT\Payment\WeChatNotify')) {
return $this->error(__('微信通知组件未加载', 'aksrt'), 500);
if (!class_exists('\Jio\Payment\WeChatNotify')) {
return $this->error(__('微信通知组件未加载', 'jio'), 500);
}
return $this->success(['data' => \AKSRT\Payment\WeChatNotify::get_instance()->get_status()]);
return $this->success(['data' => \Jio\Payment\WeChatNotify::get_instance()->get_status()]);
}
/**
@@ -346,7 +346,7 @@ class UserController extends ControllerBase {
*/
public function update_wechat_settings(\WP_REST_Request $request): \WP_REST_Response {
$params = $this->get_json_params($request);
$current = \AKSRT\Settings\Settings::get_wechat_settings();
$current = \Jio\Settings\Settings::get_wechat_settings();
$enabled = array_key_exists('enabled', $params) ? !empty($params['enabled']) : !empty($current['enabled']);
$app_id = array_key_exists('app_id', $params)
@@ -363,31 +363,31 @@ class UserController extends ControllerBase {
$app_secret = (string) ($current['app_secret'] ?? '');
}
\AKSRT\Settings\Settings::update_wechat_settings([
\Jio\Settings\Settings::update_wechat_settings([
'enabled' => $enabled,
'app_id' => $app_id,
'app_secret' => $app_secret,
'template_id' => $template_id,
]);
return $this->success(['message' => __('微信通知配置已保存', 'aksrt')]);
return $this->success(['message' => __('微信通知配置已保存', 'jio')]);
}
public function rest_handle_credit_settings(\WP_REST_Request $request): \WP_REST_Response {
if ($request->get_method() === 'GET') {
$config = \AKSRT\Settings\CreditsRuntime::get_credit_settings();
$config = \Jio\Settings\CreditsRuntime::get_credit_settings();
return $this->success(['data' => $config]);
}
$params = $this->get_json_params($request);
$current = get_option('aksrt_credit_settings', []);
$current = get_option('jio_credit_settings', []);
if (!is_array($current)) {
$current = [];
}
$merged = array_replace_recursive($current, $params);
update_option('aksrt_credit_settings', $merged);
update_option('jio_credit_settings', $merged);
return $this->success(['message' => __('积分配置已保存', 'aksrt')]);
return $this->success(['message' => __('积分配置已保存', 'jio')]);
}
public function rest_handle_auth_settings(\WP_REST_Request $request): \WP_REST_Response {
@@ -395,19 +395,19 @@ class UserController extends ControllerBase {
$allowed = $provider->get_allowed_fields('users', 'auth');
if ($request->get_method() === 'GET') {
$data = \AKSRT\Settings\Settings::get_all();
$data = \Jio\Settings\Settings::get_all();
return $this->success(['data' => array_intersect_key($data, $allowed)]);
}
$params = $this->get_json_params($request);
$settings = \AKSRT\Settings\Settings::get_all();
$settings = \Jio\Settings\Settings::get_all();
$sanitized = $this->sanitize_fields($params, $allowed);
$settings = array_merge($settings, $sanitized);
update_option('aksrt_settings', $settings);
\AKSRT\Settings\Settings::flush_settings_cache();
update_option('jio_settings', $settings);
\Jio\Settings\Settings::flush_settings_cache();
return $this->success(['message' => __('注册登录配置已保存', 'aksrt')]);
return $this->success(['message' => __('注册登录配置已保存', 'jio')]);
}
public function rest_handle_notification_settings(\WP_REST_Request $request): \WP_REST_Response {
@@ -415,17 +415,17 @@ class UserController extends ControllerBase {
$allowed = $provider->get_allowed_fields('users', 'notifications');
if ($request->get_method() === 'GET') {
$data = \AKSRT\Settings\Settings::get_all();
$data = \Jio\Settings\Settings::get_all();
return $this->success(['data' => array_intersect_key($data, $allowed)]);
}
$params = $this->get_json_params($request);
$settings = \AKSRT\Settings\Settings::get_all();
$settings = \Jio\Settings\Settings::get_all();
$settings = array_merge($settings, $this->sanitize_fields($params, $allowed));
update_option('aksrt_settings', $settings);
\AKSRT\Settings\Settings::flush_settings_cache();
update_option('jio_settings', $settings);
\Jio\Settings\Settings::flush_settings_cache();
return $this->success(['message' => __('消息通知配置已保存', 'aksrt')]);
return $this->success(['message' => __('消息通知配置已保存', 'jio')]);
}
public function rest_get_notification_overview(\WP_REST_Request $request): \WP_REST_Response {
@@ -436,19 +436,19 @@ class UserController extends ControllerBase {
$items = [];
$unread = 0;
if (class_exists('\AKSRT\Messages\Message')) {
if (class_exists('\Jio\Messages\Message')) {
$args = ['limit' => $per_page, 'offset' => ($page - 1) * $per_page];
if ($type !== '') {
$args['type'] = $type;
}
$items = \AKSRT\Messages\Message::get_instance()->get_notifications(get_current_user_id(), $args);
$items = \Jio\Messages\Message::get_instance()->get_notifications(get_current_user_id(), $args);
if ($is_read !== null && $is_read !== '') {
$want_read = (string) $is_read === '1';
$items = array_values(array_filter($items, static function ($item) use ($want_read) {
return !empty($item['is_read']) === $want_read;
}));
}
$unread = \AKSRT\Messages\Message::get_instance()->get_unread_count(get_current_user_id());
$unread = \Jio\Messages\Message::get_instance()->get_unread_count(get_current_user_id());
}
$total = count($items);
@@ -486,12 +486,12 @@ class UserController extends ControllerBase {
}
public function rest_mark_notification_read(\WP_REST_Request $request): \WP_REST_Response {
if (!class_exists('\AKSRT\Messages\Message')) {
return $this->success(['message' => __('消息系统未启用', 'aksrt')]);
if (!class_exists('\Jio\Messages\Message')) {
return $this->success(['message' => __('消息系统未启用', 'jio')]);
}
$params = $this->get_json_params($request);
$msg = \AKSRT\Messages\Message::get_instance();
$msg = \Jio\Messages\Message::get_instance();
if (!empty($params['mark_all'])) {
$msg->mark_all_read(get_current_user_id());
} else {
@@ -501,54 +501,54 @@ class UserController extends ControllerBase {
}
}
return $this->success(['message' => __('已更新消息状态', 'aksrt')]);
return $this->success(['message' => __('已更新消息状态', 'jio')]);
}
public function rest_delete_notification_item(\WP_REST_Request $request): \WP_REST_Response {
$user_id = get_current_user_id();
if ($user_id <= 0) {
return $this->error(__('请先登录', 'aksrt'), 401);
return $this->error(__('请先登录', 'jio'), 401);
}
$params = $this->get_json_params($request);
$id = sanitize_text_field((string) ($params['id'] ?? $request->get_param('id') ?? ''));
if ($id === '') {
return $this->error(__('参数错误', 'aksrt'), 400);
return $this->error(__('参数错误', 'jio'), 400);
}
if (function_exists('aksrt_delete_user_notification')) {
aksrt_delete_user_notification($user_id, $id);
} elseif (class_exists('\AKSRT\Messages\Message')) {
\AKSRT\Messages\Message::get_instance()->mark_read($user_id, $id);
if (function_exists('jio_delete_user_notification')) {
jio_delete_user_notification($user_id, $id);
} elseif (class_exists('\Jio\Messages\Message')) {
\Jio\Messages\Message::get_instance()->mark_read($user_id, $id);
}
return $this->success([
'message' => __('消息已删除', 'aksrt'),
'unread' => function_exists('aksrt_get_user_unread_notification_count')
? aksrt_get_user_unread_notification_count($user_id)
'message' => __('消息已删除', 'jio'),
'unread' => function_exists('jio_get_user_unread_notification_count')
? jio_get_user_unread_notification_count($user_id)
: 0,
]);
}
public function rest_run_notification_cleanup(\WP_REST_Request $request): \WP_REST_Response {
return $this->success(['message' => __('消息清理任务已受理', 'aksrt')]);
return $this->success(['message' => __('消息清理任务已受理', 'jio')]);
}
public function check_comment_image_upload_permission(?\WP_REST_Request $request = null) {
if (!is_user_logged_in()) {
return new \WP_Error('aksrt_auth_required', __('请先登录', 'aksrt'), ['status' => 401]);
return new \WP_Error('jio_auth_required', __('请先登录', 'jio'), ['status' => 401]);
}
if ($request !== null) {
$nonce = (string) $request->get_header('X-WP-Nonce');
if ($nonce === '' || !wp_verify_nonce($nonce, 'wp_rest')) {
return new \WP_Error('aksrt_rest_nonce', __('安全验证失败,请刷新页面后重试', 'aksrt'), ['status' => 403]);
return new \WP_Error('jio_rest_nonce', __('安全验证失败,请刷新页面后重试', 'jio'), ['status' => 403]);
}
}
$settings = class_exists('\\AKSRT\\Settings\\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
$settings = class_exists('\\Jio\\Settings\\Settings') ? \Jio\Settings\Settings::get_all() : [];
$image_enabled = !isset($settings['comment_img']) || !empty($settings['comment_img']);
if (!$image_enabled || empty($settings['comment_upload_img'])) {
return new \WP_Error('aksrt_comment_upload_disabled', __('评论图片上传功能未开启', 'aksrt'), ['status' => 403]);
return new \WP_Error('jio_comment_upload_disabled', __('评论图片上传功能未开启', 'jio'), ['status' => 403]);
}
$user_id = get_current_user_id();
if (!function_exists('aksrt_user_can') || !aksrt_user_can('upload_comment_image', $user_id)) {
return new \WP_Error('aksrt_comment_upload_forbidden', __('当前账号没有上传评论图片的权限', 'aksrt'), ['status' => 403]);
if (!function_exists('jio_user_can') || !jio_user_can('upload_comment_image', $user_id)) {
return new \WP_Error('jio_comment_upload_forbidden', __('当前账号没有上传评论图片的权限', 'jio'), ['status' => 403]);
}
return true;
}
@@ -556,7 +556,7 @@ class UserController extends ControllerBase {
public function rest_upload_comment_image(\WP_REST_Request $request): \WP_REST_Response {
$user_id = get_current_user_id();
if (empty($_FILES['image'])) {
return $this->error(__('请选择要上传的图片', 'aksrt'), 400);
return $this->error(__('请选择要上传的图片', 'jio'), 400);
}
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
@@ -566,15 +566,15 @@ class UserController extends ControllerBase {
$file = $_FILES[$file_key];
$upload_error = isset($file['error']) ? (int) $file['error'] : UPLOAD_ERR_NO_FILE;
if ($upload_error !== UPLOAD_ERR_OK) {
return $this->error(__('图片文件上传失败,请重新选择', 'aksrt'), 400);
return $this->error(__('图片文件上传失败,请重新选择', 'jio'), 400);
}
$max_size = class_exists('\AKSRT\Attachment\UploadLimits')
? \AKSRT\Attachment\UploadLimits::limit_bytes('image', $user_id)
$max_size = class_exists('\Jio\Attachment\UploadLimits')
? \Jio\Attachment\UploadLimits::limit_bytes('image', $user_id)
: (int) wp_max_upload_size();
$actual_size = !empty($file['tmp_name']) && is_readable($file['tmp_name']) ? (int) filesize($file['tmp_name']) : 0;
if ($actual_size <= 0 || $actual_size > $max_size) {
return $this->error(sprintf(__('图片大小不能超过 %s。', 'aksrt'), size_format($max_size)), 413);
return $this->error(sprintf(__('图片大小不能超过 %s。', 'jio'), size_format($max_size)), 413);
}
$mimes = [
@@ -586,12 +586,12 @@ class UserController extends ControllerBase {
$checked = wp_check_filetype_and_ext($file['tmp_name'], $file['name'], $mimes);
$image_size = wp_getimagesize($file['tmp_name']);
if (empty($checked['type']) || empty($checked['ext']) || !wp_match_mime_types('image', $checked['type']) || !$image_size || ((int) $image_size[0] * (int) $image_size[1]) > 40000000) {
return $this->error(__('仅支持有效的 JPG、PNG、GIF 或 WebP 图片', 'aksrt'), 415);
return $this->error(__('仅支持有效的 JPG、PNG、GIF 或 WebP 图片', 'jio'), 415);
}
if (class_exists('\AKSRT\Extensions\ContentAudit')) {
$audit = \AKSRT\Extensions\ContentAudit::get_instance()->audit_image((string) $file['tmp_name']);
if (class_exists('\Jio\Extensions\ContentAudit')) {
$audit = \Jio\Extensions\ContentAudit::get_instance()->audit_image((string) $file['tmp_name']);
if (empty($audit['passed'])) {
return $this->error(__('图片未通过内容审核', 'aksrt'), 422);
return $this->error(__('图片未通过内容审核', 'jio'), 422);
}
}
@@ -605,17 +605,17 @@ class UserController extends ControllerBase {
wp_update_post(['ID' => (int) $attachment_id, 'post_author' => $user_id]);
if (!wp_attachment_is_image((int) $attachment_id)) {
wp_delete_attachment((int) $attachment_id, true);
return $this->error(__('上传文件不是有效图片', 'aksrt'), 415);
return $this->error(__('上传文件不是有效图片', 'jio'), 415);
}
$url = wp_get_attachment_url((int) $attachment_id);
if (!$url) {
wp_delete_attachment((int) $attachment_id, true);
return $this->error(__('上传失败', 'aksrt'), 500);
return $this->error(__('上传失败', 'jio'), 500);
}
// 选图即上传:提交成功前处于待认领状态,× 移除或超时未提交会被清理。
\AKSRT\Attachment\PendingUploads::mark((int) $attachment_id);
\Jio\Attachment\PendingUploads::mark((int) $attachment_id);
return $this->success([
'message' => __('图片上传成功', 'aksrt'),
'message' => __('图片上传成功', 'jio'),
'data' => [
'id' => (int) $attachment_id,
'url' => $url,
@@ -627,18 +627,18 @@ class UserController extends ControllerBase {
$user_id = get_current_user_id();
return $this->success(['data' => [
'user_id' => $user_id,
'points' => \AKSRT\Ledger\LedgerService::get_instance()->balance($user_id, \AKSRT\Ledger\AssetType::POINTS),
'balance' => \AKSRT\Ledger\LedgerService::get_instance()->balance_decimal($user_id),
'points' => \Jio\Ledger\LedgerService::get_instance()->balance($user_id, \Jio\Ledger\AssetType::POINTS),
'balance' => \Jio\Ledger\LedgerService::get_instance()->balance_decimal($user_id),
]]);
}
public function check_asset_self_permission(\WP_REST_Request $request) {
if (!is_user_logged_in()) {
return new \WP_Error('aksrt_auth_required', __('请先登录', 'aksrt'), ['status' => 401]);
return new \WP_Error('jio_auth_required', __('请先登录', 'jio'), ['status' => 401]);
}
$nonce = (string) $request->get_header('X-WP-Nonce');
if ($nonce === '' || !wp_verify_nonce($nonce, 'wp_rest')) {
return new \WP_Error('aksrt_rest_nonce', __('安全验证失败,请刷新页面后重试', 'aksrt'), ['status' => 403]);
return new \WP_Error('jio_rest_nonce', __('安全验证失败,请刷新页面后重试', 'jio'), ['status' => 403]);
}
return true;
}
@@ -666,7 +666,7 @@ class UserController extends ControllerBase {
$rows = $wpdb->get_results($wpdb->prepare($sql, array_merge($params, [$per_page, ($page - 1) * $per_page])), ARRAY_A);
$items = [];
foreach ((array) $rows as $row) {
try { $balance = \AKSRT\Ledger\AssetAmount::balance((string) ($row['balance_value'] ?? '0'))->decimal(); } catch (\InvalidArgumentException $e) { $balance = '0.00'; }
try { $balance = \Jio\Ledger\AssetAmount::balance((string) ($row['balance_value'] ?? '0'))->decimal(); } catch (\InvalidArgumentException $e) { $balance = '0.00'; }
$items[] = [
'id' => (int) $row['id'], 'login' => (string) $row['login'], 'email' => (string) $row['email'],
'display_name' => (string) $row['display_name'], 'registered' => (string) $row['registered'],
@@ -684,10 +684,10 @@ class UserController extends ControllerBase {
$raw_delta = $params['delta'] ?? null;
$remark = sanitize_text_field((string) ($params['remark'] ?? ''));
if ($user_id <= 0 || !in_array($asset, ['points', 'balance'], true) || !get_userdata($user_id)) {
return $this->error(__('用户或资产类型无效', 'aksrt'), 400);
return $this->error(__('用户或资产类型无效', 'jio'), 400);
}
if (!current_user_can('edit_user', $user_id)) {
return $this->error(__('无权调整该用户资产', 'aksrt'), 403);
return $this->error(__('无权调整该用户资产', 'jio'), 403);
}
$negative = is_string($raw_delta) && str_starts_with($raw_delta, '-');
$unsigned = $negative ? substr($raw_delta, 1) : $raw_delta;
@@ -698,15 +698,15 @@ class UserController extends ControllerBase {
$negative = $raw_delta < 0;
} else {
if (!is_string($unsigned)) throw new \InvalidArgumentException();
$minor = \AKSRT\Ledger\AssetAmount::balance($unsigned)->minor();
$minor = \Jio\Ledger\AssetAmount::balance($unsigned)->minor();
}
} catch (\InvalidArgumentException $e) {
return $this->error(__('资产变动金额格式无效', 'aksrt'), 400);
return $this->error(__('资产变动金额格式无效', 'jio'), 400);
}
if ($minor <= 0) return $this->error(__('资产变动金额必须非零', 'aksrt'), 400);
$operation_key = \AKSRT\Ledger\OperationKey::from_request($request, 'admin-adjustment', get_current_user_id());
if ($minor <= 0) return $this->error(__('资产变动金额必须非零', 'jio'), 400);
$operation_key = \Jio\Ledger\OperationKey::from_request($request, 'admin-adjustment', get_current_user_id());
if (is_wp_error($operation_key)) return new \WP_REST_Response(['success' => false, 'message' => $operation_key->get_error_message()], 400);
$service = \AKSRT\Ledger\LedgerService::get_instance();
$service = \Jio\Ledger\LedgerService::get_instance();
$result = $negative
? $service->debit($user_id, $asset, $minor, $operation_key, 'admin_adjustment', ['type' => 'user', 'id' => $user_id], $remark, get_current_user_id())
: $service->credit($user_id, $asset, $minor, $operation_key, 'admin_adjustment', ['type' => 'user', 'id' => $user_id], $remark, get_current_user_id());
@@ -720,10 +720,10 @@ class UserController extends ControllerBase {
'user_id' => $user_id,
'asset' => $asset,
'balance' => $asset === 'balance'
? \AKSRT\Ledger\AssetAmount::from_minor(\AKSRT\Ledger\AssetType::BALANCE, $result->balance($user_id, $asset))->decimal()
? \Jio\Ledger\AssetAmount::from_minor(\Jio\Ledger\AssetType::BALANCE, $result->balance($user_id, $asset))->decimal()
: $result->balance($user_id, $asset),
'replayed' => $result->replayed,
], 'message' => __('资产调整已保存', 'aksrt')]);
], 'message' => __('资产调整已保存', 'jio')]);
}
}
+184 -184
View File
@@ -2,10 +2,10 @@
/**
* Shared public authentication REST operations.
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -30,7 +30,7 @@ trait UserControllerAuthPhoneOperations {
}
private function auth_require_captcha(string $scene, array $payload): ?\WP_REST_Response {
$result = aksrt_require_captcha($scene, $payload);
$result = jio_require_captcha($scene, $payload);
if ($result === true) {
return null;
}
@@ -38,24 +38,24 @@ trait UserControllerAuthPhoneOperations {
if (is_wp_error($result)) {
$data = $result->get_error_data();
$status = is_array($data) ? (int) ($data['status'] ?? 400) : 400;
$message = $result->get_error_message() ?: __('人机验证失败', 'aksrt');
$message = $result->get_error_message() ?: __('人机验证失败', 'jio');
return new \WP_REST_Response([
'success' => false,
'code' => $result->get_error_code() ?: 'aksrt_captcha_failed',
'code' => $result->get_error_code() ?: 'jio_captcha_failed',
'message' => $message,
], $status > 0 ? $status : 400);
}
return new \WP_REST_Response([
'success' => false,
'code' => 'aksrt_captcha_failed',
'message' => __('人机验证失败', 'aksrt'),
'code' => 'jio_captcha_failed',
'message' => __('人机验证失败', 'jio'),
], 400);
}
private function auth_rate_key(string $action, string $subject = ''): string {
$ip = sanitize_text_field((string) ($_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'));
return 'aksrt_auth_rate_' . md5($action . '|' . strtolower($subject) . '|' . $ip);
return 'jio_auth_rate_' . md5($action . '|' . strtolower($subject) . '|' . $ip);
}
private function auth_rate_limited(string $action, string $subject = '', int $limit = 8, int $ttl = 600): bool {
@@ -69,7 +69,7 @@ trait UserControllerAuthPhoneOperations {
}
private function auth_code_key(string $scene, string $target): string {
return 'aksrt_auth_code_' . md5($scene . '|' . strtolower(trim($target)));
return 'jio_auth_code_' . md5($scene . '|' . strtolower(trim($target)));
}
private function auth_store_code(string $scene, string $target, string $code, int $ttl = 10 * MINUTE_IN_SECONDS): void {
@@ -108,7 +108,7 @@ trait UserControllerAuthPhoneOperations {
}
private function auth_channel(string $target, string $requested = ''): string {
if ($requested === 'phone' || (class_exists('\\AKSRT\\Sms\\SMS') && \AKSRT\Sms\SMS::is_phonenumber($target))) {
if ($requested === 'phone' || (class_exists('\\Jio\\Sms\\SMS') && \Jio\Sms\SMS::is_phonenumber($target))) {
return 'phone';
}
return 'email';
@@ -125,7 +125,7 @@ trait UserControllerAuthPhoneOperations {
private function auth_registration_error(string $error): string {
if (str_contains($error, '已被') || str_contains($error, '已注册') || str_contains($error, '已使用')) {
return __('注册信息不可用,请检查后重试', 'aksrt');
return __('注册信息不可用,请检查后重试', 'jio');
}
return $error;
}
@@ -147,30 +147,30 @@ trait UserControllerAuthPhoneOperations {
public function auth_code(\WP_REST_Request $request): \WP_REST_Response {
$payload = $this->auth_payload($request);
if (!$this->auth_verify_rest_nonce($request, $payload)) {
return $this->error(__('安全验证失败,请刷新页面重试', 'aksrt'), 403);
return $this->error(__('安全验证失败,请刷新页面重试', 'jio'), 403);
}
$scene = sanitize_key((string) ($payload['scene'] ?? ''));
$target = trim(sanitize_text_field((string) ($payload['target'] ?? '')));
$channel = $this->auth_channel($target, sanitize_key((string) ($payload['channel'] ?? '')));
$obk = sanitize_text_field((string) ($payload['obk'] ?? ''));
$oauth_bind = $obk !== '' && class_exists('\AKSRT\Auth\Service') && is_array(\AKSRT\Auth\Service::get_oauth_bind_context($obk));
$oauth_bind = $obk !== '' && class_exists('\Jio\Auth\Service') && is_array(\Jio\Auth\Service::get_oauth_bind_context($obk));
if (!in_array($scene, ['login', 'register', 'resetpassword'], true)) {
return $this->error(__('请求类型无效', 'aksrt'));
return $this->error(__('请求类型无效', 'jio'));
}
if (class_exists('\AKSRT\Settings\Settings')) {
if (\AKSRT\Settings\Settings::close_sign()) return $this->error(__('登录注册已关闭', 'aksrt'), 403);
if (($scene === 'login' || $scene === 'resetpassword') && \AKSRT\Settings\Settings::close_signin() && !$oauth_bind) return $this->error(__('当前登录方式已关闭', 'aksrt'), 403);
if ($scene === 'register' && !\AKSRT\Settings\Settings::registration_enabled() && !$oauth_bind) return $this->error(__('当前已关闭新用户注册', 'aksrt'), 403);
$type = $scene === 'login' ? \AKSRT\Settings\Settings::passwordless_login_type() : ($scene === 'register' ? \AKSRT\Settings\Settings::signup_verification_type() : \AKSRT\Settings\Settings::reset_password_verification_type());
$enabled = $scene === 'login' ? \AKSRT\Settings\Settings::passwordless_login_enabled() : ($scene === 'register' ? \AKSRT\Settings\Settings::signup_verification_enabled() : true);
if (!$enabled || ($type !== 'email_phone' && $type !== $channel)) return $this->error(__('当前验证方式不可用', 'aksrt'), 403);
if (class_exists('\Jio\Settings\Settings')) {
if (\Jio\Settings\Settings::close_sign()) return $this->error(__('登录注册已关闭', 'jio'), 403);
if (($scene === 'login' || $scene === 'resetpassword') && \Jio\Settings\Settings::close_signin() && !$oauth_bind) return $this->error(__('当前登录方式已关闭', 'jio'), 403);
if ($scene === 'register' && !\Jio\Settings\Settings::registration_enabled() && !$oauth_bind) return $this->error(__('当前已关闭新用户注册', 'jio'), 403);
$type = $scene === 'login' ? \Jio\Settings\Settings::passwordless_login_type() : ($scene === 'register' ? \Jio\Settings\Settings::signup_verification_type() : \Jio\Settings\Settings::reset_password_verification_type());
$enabled = $scene === 'login' ? \Jio\Settings\Settings::passwordless_login_enabled() : ($scene === 'register' ? \Jio\Settings\Settings::signup_verification_enabled() : true);
if (!$enabled || ($type !== 'email_phone' && $type !== $channel)) return $this->error(__('当前验证方式不可用', 'jio'), 403);
}
if (($channel === 'email' && !is_email($target)) || ($channel === 'phone' && !\AKSRT\Sms\SMS::is_phonenumber($target))) {
return $this->error(__('请输入有效的邮箱或手机号', 'aksrt'));
if (($channel === 'email' && !is_email($target)) || ($channel === 'phone' && !\Jio\Sms\SMS::is_phonenumber($target))) {
return $this->error(__('请输入有效的邮箱或手机号', 'jio'));
}
if ($this->auth_rate_limited('code:' . $scene, $target, 5, HOUR_IN_SECONDS)) {
return $this->error(__('请求过于频繁,请稍后再试', 'aksrt'), 429);
return $this->error(__('请求过于频繁,请稍后再试', 'jio'), 429);
}
$captcha_scene = $scene === 'resetpassword' ? 'lostpassword' : $scene;
$captcha_error = $this->auth_require_captcha($captcha_scene, $payload);
@@ -178,27 +178,27 @@ trait UserControllerAuthPhoneOperations {
return $captcha_error;
}
$generic = __('如果信息有效,验证码将发送到该账号', 'aksrt');
$generic = __('如果信息有效,验证码将发送到该账号', 'jio');
if ($scene !== 'register' && !$this->auth_find_user($target, $channel)) {
return $this->success(['message' => $generic, 'countdown' => 60]);
}
if ($channel === 'phone') {
if (!class_exists('\AKSRT\Sms\SMS')) {
return $this->error(__('短信服务不可用', 'aksrt'), 503);
if (!class_exists('\Jio\Sms\SMS')) {
return $this->error(__('短信服务不可用', 'jio'), 503);
}
$code = (string) random_int(100000, 999999);
$result = \AKSRT\Sms\SMS::get_instance()->send_code($target, $code);
$result = \Jio\Sms\SMS::get_instance()->send_code($target, $code);
if (empty($result['success'])) {
return $this->error((string) ($result['message'] ?? __('验证码发送失败', 'aksrt')));
return $this->error((string) ($result['message'] ?? __('验证码发送失败', 'jio')));
}
$this->auth_store_code($scene, $target, $code, 30 * MINUTE_IN_SECONDS);
return $this->success(['message' => $generic, 'countdown' => (int) ($result['remaining_time'] ?? 60)]);
}
// [防刷机制]验证码发送限制(短信通道由 SMS::send_code 内部接入)。
if (class_exists('\AKSRT\Extensions\SendLimit')) {
$limit = \AKSRT\Extensions\SendLimit::check($target);
if (class_exists('\Jio\Extensions\SendLimit')) {
$limit = \Jio\Extensions\SendLimit::check($target);
if (empty($limit['ok'])) {
return $this->error((string) $limit['message'], 429);
}
@@ -218,8 +218,8 @@ trait UserControllerAuthPhoneOperations {
'{site_name}' => $site_name,
'{code}' => $code,
];
$sent = $scene_event !== '' && class_exists('\\AKSRT\\Settings\\Settings')
? \AKSRT\Settings\Settings::send_event_email($scene_event, $target, $placeholders)
$sent = $scene_event !== '' && class_exists('\\Jio\\Settings\\Settings')
? \Jio\Settings\Settings::send_event_email($scene_event, $target, $placeholders)
: false;
if (!$sent) {
@@ -227,9 +227,9 @@ trait UserControllerAuthPhoneOperations {
if ($scene !== 'register') {
return $this->success(['message' => $generic, 'countdown' => 60]);
}
return $this->error(__('验证码发送失败,请稍后再试', 'aksrt'), 500);
return $this->error(__('验证码发送失败,请稍后再试', 'jio'), 500);
}
$cooldown = class_exists('\AKSRT\Extensions\SendLimit') ? (int) \AKSRT\Extensions\SendLimit::record($target) : 0;
$cooldown = class_exists('\Jio\Extensions\SendLimit') ? (int) \Jio\Extensions\SendLimit::record($target) : 0;
$this->auth_store_code($scene, $target, $code);
return $this->success(['message' => $generic, 'countdown' => $cooldown > 0 ? $cooldown : 60]);
@@ -238,27 +238,27 @@ trait UserControllerAuthPhoneOperations {
public function auth_login(\WP_REST_Request $request): \WP_REST_Response {
$payload = $this->auth_payload($request);
if (!$this->auth_verify_rest_nonce($request, $payload)) {
return $this->error(__('安全验证失败,请刷新页面重试', 'aksrt'), 403);
return $this->error(__('安全验证失败,请刷新页面重试', 'jio'), 403);
}
$target = trim(sanitize_text_field((string) ($payload['target'] ?? $payload['log'] ?? '')));
$obk = sanitize_text_field((string) ($payload['obk'] ?? ''));
$oauth_data = $obk !== '' && class_exists('\AKSRT\Auth\Service')
? \AKSRT\Auth\Service::get_oauth_bind_context($obk)
$oauth_data = $obk !== '' && class_exists('\Jio\Auth\Service')
? \Jio\Auth\Service::get_oauth_bind_context($obk)
: null;
if ($obk !== '' && !$oauth_data) {
return $this->error(__('绑定会话已过期,请重新使用第三方登录', 'aksrt'), 410);
return $this->error(__('绑定会话已过期,请重新使用第三方登录', 'jio'), 410);
}
if (
class_exists('\AKSRT\Settings\Settings')
&& (\AKSRT\Settings\Settings::close_sign() || (!$oauth_data && \AKSRT\Settings\Settings::close_signin()))
class_exists('\Jio\Settings\Settings')
&& (\Jio\Settings\Settings::close_sign() || (!$oauth_data && \Jio\Settings\Settings::close_signin()))
) {
return $this->error(__('当前登录方式已关闭', 'aksrt'), 403);
return $this->error(__('当前登录方式已关闭', 'jio'), 403);
}
if ($target === '' || (\AKSRT\Sms\SMS::is_phonenumber($target) && empty($payload['code']) && class_exists('\AKSRT\Settings\Settings') && !\AKSRT\Settings\Settings::signin_phone_enabled())) {
return $this->error(__('账号或密码错误', 'aksrt'), 401);
if ($target === '' || (\Jio\Sms\SMS::is_phonenumber($target) && empty($payload['code']) && class_exists('\Jio\Settings\Settings') && !\Jio\Settings\Settings::signin_phone_enabled())) {
return $this->error(__('账号或密码错误', 'jio'), 401);
}
if ($this->auth_rate_limited('login', $target)) {
return $this->error(__('请求过于频繁,请稍后再试', 'aksrt'), 429);
return $this->error(__('请求过于频繁,请稍后再试', 'jio'), 429);
}
$captcha_error = $this->auth_require_captcha($oauth_data ? 'bind' : 'login', $payload);
if ($captcha_error) {
@@ -267,104 +267,104 @@ trait UserControllerAuthPhoneOperations {
$redirect = $this->auth_safe_redirect((string) ($payload['redirect_to'] ?? ''), home_url('/'));
if (!empty($payload['code'])) {
if (!class_exists('\AKSRT\Settings\Settings') || !\AKSRT\Settings\Settings::passwordless_login_enabled()) {
return $this->error(__('当前未开启免密登录', 'aksrt'));
if (!class_exists('\Jio\Settings\Settings') || !\Jio\Settings\Settings::passwordless_login_enabled()) {
return $this->error(__('当前未开启免密登录', 'jio'));
}
$channel = $this->auth_channel($target, sanitize_key((string) ($payload['channel'] ?? '')));
$configured = \AKSRT\Settings\Settings::passwordless_login_type();
$configured = \Jio\Settings\Settings::passwordless_login_type();
if (!$this->auth_channel_allowed($configured, $channel)) {
return $this->error(__('当前验证方式不可用', 'aksrt'), 403);
return $this->error(__('当前验证方式不可用', 'jio'), 403);
}
$user = $this->auth_find_user($target, $channel);
if (!$user || !$this->auth_consume_code('login', $target, sanitize_text_field((string) $payload['code']))) {
return $this->error(__('账号或验证码错误', 'aksrt'), 401);
return $this->error(__('账号或验证码错误', 'jio'), 401);
}
if ($oauth_data) {
$oauth_data = \AKSRT\Auth\Service::claim_oauth_bind_context($obk);
$oauth_data = \Jio\Auth\Service::claim_oauth_bind_context($obk);
if (!$oauth_data) {
return $this->error(__('绑定会话已被使用,请重新授权', 'aksrt'), 410);
return $this->error(__('绑定会话已被使用,请重新授权', 'jio'), 410);
}
$binding = \AKSRT\Auth\Service::finalize_oauth_binding((int) $user->ID, $oauth_data, $obk);
$binding = \Jio\Auth\Service::finalize_oauth_binding((int) $user->ID, $oauth_data, $obk);
if (is_wp_error($binding)) {
return $this->error($binding->get_error_message(), 409);
}
\AKSRT\Auth\OAuthAccountService::complete_login($user, !empty($payload['rememberme']));
$redirect = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url() : $redirect;
return $this->success(['message' => __('账号绑定成功', 'aksrt'), 'redirect' => $redirect]);
\Jio\Auth\OAuthAccountService::complete_login($user, !empty($payload['rememberme']));
$redirect = function_exists('jio_get_user_center_url') ? jio_get_user_center_url() : $redirect;
return $this->success(['message' => __('账号绑定成功', 'jio'), 'redirect' => $redirect]);
}
wp_set_current_user((int) $user->ID);
wp_set_auth_cookie((int) $user->ID, !empty($payload['rememberme']));
do_action('wp_login', $user->user_login, $user);
return $this->success(['message' => __('登录成功', 'aksrt'), 'redirect' => $redirect]);
return $this->success(['message' => __('登录成功', 'jio'), 'redirect' => $redirect]);
}
if (!class_exists('\AKSRT\Auth\Service')) {
return $this->error(__('认证服务不可用', 'aksrt'), 500);
if (!class_exists('\Jio\Auth\Service')) {
return $this->error(__('认证服务不可用', 'jio'), 500);
}
$payload['log'] = $target;
$payload['pwd'] = (string) ($payload['password'] ?? $payload['pwd'] ?? '');
$payload['redirect_to'] = $redirect;
if ($oauth_data) {
$resolved_login = \AKSRT\Auth\Service::resolve_email_to_username($target);
$resolved_login = \Jio\Auth\Service::resolve_email_to_username($target);
$user = wp_authenticate($resolved_login, (string) ($payload['password'] ?? $payload['pwd'] ?? ''));
if (is_wp_error($user) || !$user instanceof \WP_User) {
// 透传具体失败原因,不得泛化为“账号或密码错误”。
$bind_error = $user instanceof \WP_Error
? \AKSRT\Auth\Service::friendly_login_error($user->get_error_message())
? \Jio\Auth\Service::friendly_login_error($user->get_error_message())
: '';
return $this->error($bind_error !== '' ? $bind_error : __('账号或密码错误', 'aksrt'), 401);
return $this->error($bind_error !== '' ? $bind_error : __('账号或密码错误', 'jio'), 401);
}
$oauth_data = \AKSRT\Auth\Service::claim_oauth_bind_context($obk);
$oauth_data = \Jio\Auth\Service::claim_oauth_bind_context($obk);
if (!$oauth_data) {
return $this->error(__('绑定会话已被使用,请重新授权', 'aksrt'), 410);
return $this->error(__('绑定会话已被使用,请重新授权', 'jio'), 410);
}
$binding = \AKSRT\Auth\Service::finalize_oauth_binding((int) $user->ID, $oauth_data, $obk);
$binding = \Jio\Auth\Service::finalize_oauth_binding((int) $user->ID, $oauth_data, $obk);
if (is_wp_error($binding)) {
return $this->error($binding->get_error_message(), 409);
}
\AKSRT\Auth\OAuthAccountService::complete_login($user, !empty($payload['rememberme']));
$redirect = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url() : $redirect;
return $this->success(['message' => __('账号绑定成功', 'aksrt'), 'redirect' => $redirect]);
\Jio\Auth\OAuthAccountService::complete_login($user, !empty($payload['rememberme']));
$redirect = function_exists('jio_get_user_center_url') ? jio_get_user_center_url() : $redirect;
return $this->success(['message' => __('账号绑定成功', 'jio'), 'redirect' => $redirect]);
}
$result = \AKSRT\Auth\Service::login_user($payload);
$result = \Jio\Auth\Service::login_user($payload);
if (empty($result['success'])) {
// 透传具体失败原因:禁封等场景不得泛化为“账号或密码错误”。
$login_error = trim((string) ($result['error'] ?? ''));
return $this->error($login_error !== '' ? $login_error : __('账号或密码错误', 'aksrt'), 401);
return $this->error($login_error !== '' ? $login_error : __('账号或密码错误', 'jio'), 401);
}
return $this->success(['message' => $oauth_data ? __('账号绑定成功', 'aksrt') : __('登录成功', 'aksrt'), 'redirect' => $redirect]);
return $this->success(['message' => $oauth_data ? __('账号绑定成功', 'jio') : __('登录成功', 'jio'), 'redirect' => $redirect]);
}
public function auth_register(\WP_REST_Request $request): \WP_REST_Response {
$payload = $this->auth_payload($request);
if (!$this->auth_verify_rest_nonce($request, $payload)) {
return $this->error(__('安全验证失败,请刷新页面重试', 'aksrt'), 403);
return $this->error(__('安全验证失败,请刷新页面重试', 'jio'), 403);
}
$obk = sanitize_text_field((string) ($payload['obk'] ?? ''));
$oauth_data = $obk !== '' && class_exists('\AKSRT\Auth\Service')
? \AKSRT\Auth\Service::get_oauth_bind_context($obk)
$oauth_data = $obk !== '' && class_exists('\Jio\Auth\Service')
? \Jio\Auth\Service::get_oauth_bind_context($obk)
: null;
if ($obk !== '' && !$oauth_data) {
return $this->error(__('绑定会话已过期,请重新使用第三方登录', 'aksrt'), 410);
return $this->error(__('绑定会话已过期,请重新使用第三方登录', 'jio'), 410);
}
if (class_exists('\AKSRT\Settings\Settings') && (
\AKSRT\Settings\Settings::close_sign()
|| ($oauth_data ? !\AKSRT\Settings\Settings::oauth_registration_enabled() : !\AKSRT\Settings\Settings::registration_enabled())
if (class_exists('\Jio\Settings\Settings') && (
\Jio\Settings\Settings::close_sign()
|| ($oauth_data ? !\Jio\Settings\Settings::oauth_registration_enabled() : !\Jio\Settings\Settings::registration_enabled())
)) {
return $this->error(__('当前已关闭新用户注册', 'aksrt'), 403);
return $this->error(__('当前已关闭新用户注册', 'jio'), 403);
}
if ($this->auth_rate_limited('register')) {
return $this->error(__('请求过于频繁,请稍后再试', 'aksrt'), 429);
return $this->error(__('请求过于频繁,请稍后再试', 'jio'), 429);
}
$target = trim(sanitize_text_field((string) ($payload['target'] ?? $payload['user_email'] ?? $payload['phone'] ?? '')));
$channel = $this->auth_channel($target, sanitize_key((string) ($payload['channel'] ?? '')));
$signup_verification = class_exists('\AKSRT\Settings\Settings') && \AKSRT\Settings\Settings::signup_verification_enabled();
$configured = $signup_verification ? \AKSRT\Settings\Settings::signup_verification_type() : 'email';
$signup_verification = class_exists('\Jio\Settings\Settings') && \Jio\Settings\Settings::signup_verification_enabled();
$configured = $signup_verification ? \Jio\Settings\Settings::signup_verification_type() : 'email';
if (!$this->auth_channel_allowed($configured, $channel)) {
return $this->error(__('当前注册方式不可用', 'aksrt'), 403);
return $this->error(__('当前注册方式不可用', 'jio'), 403);
}
if (($channel === 'email' && !is_email($target)) || ($channel === 'phone' && !\AKSRT\Sms\SMS::is_phonenumber($target))) {
return $this->error(__('请输入有效的邮箱或手机号', 'aksrt'));
if (($channel === 'email' && !is_email($target)) || ($channel === 'phone' && !\Jio\Sms\SMS::is_phonenumber($target))) {
return $this->error(__('请输入有效的邮箱或手机号', 'jio'));
}
$captcha_error = $this->auth_require_captcha('register', $payload);
if ($captcha_error) {
@@ -373,43 +373,43 @@ trait UserControllerAuthPhoneOperations {
$verification_completed = false;
if ($signup_verification) {
if (!$this->auth_consume_code('register', $target, sanitize_text_field((string) ($payload['code'] ?? '')))) {
return $this->error(__('验证码错误或已过期', 'aksrt'));
return $this->error(__('验证码错误或已过期', 'jio'));
}
$verification_completed = true;
}
if (!class_exists('\AKSRT\Auth\Service')) {
return $this->error(__('认证服务不可用', 'aksrt'), 500);
if (!class_exists('\Jio\Auth\Service')) {
return $this->error(__('认证服务不可用', 'jio'), 500);
}
if ($channel === 'phone') {
$payload['phone'] = $target;
$result = \AKSRT\Auth\Service::register_by_phone($payload, $verification_completed, !empty($oauth_data));
$result = \Jio\Auth\Service::register_by_phone($payload, $verification_completed, !empty($oauth_data));
if (empty($result['success'])) {
return $this->error($this->auth_registration_error((string) ($result['error'] ?? __('注册失败', 'aksrt'))));
return $this->error($this->auth_registration_error((string) ($result['error'] ?? __('注册失败', 'jio'))));
}
$user_id = (int) ($result['user_id'] ?? 0);
if ($oauth_data && $user_id > 0) {
$binding = \AKSRT\Auth\Service::finalize_oauth_binding($user_id, $oauth_data, $obk);
$binding = \Jio\Auth\Service::finalize_oauth_binding($user_id, $oauth_data, $obk);
if (is_wp_error($binding)) {
$this->delete_oauth_registration_user($user_id);
return $this->error($binding->get_error_message(), 409);
}
$user = get_user_by('id', $user_id);
if ($user instanceof \WP_User) {
\AKSRT\Auth\OAuthAccountService::complete_login($user, true);
\Jio\Auth\OAuthAccountService::complete_login($user, true);
}
}
return $this->success(['message' => $oauth_data ? __('注册并绑定成功', 'aksrt') : __('注册成功', 'aksrt'), 'redirect' => $result['redirect'] ?? home_url('/'), 'notice' => (string) ($result['notice'] ?? '')]);
return $this->success(['message' => $oauth_data ? __('注册并绑定成功', 'jio') : __('注册成功', 'jio'), 'redirect' => $result['redirect'] ?? home_url('/'), 'notice' => (string) ($result['notice'] ?? '')]);
}
$payload['user_email'] = sanitize_email($target);
$payload['user_pass'] = (string) ($payload['password'] ?? '');
$payload['user_pass_confirm'] = (string) ($payload['password_confirm'] ?? $payload['password'] ?? '');
$result = \AKSRT\Auth\Service::register_user($payload, $verification_completed, !empty($oauth_data));
$result = \Jio\Auth\Service::register_user($payload, $verification_completed, !empty($oauth_data));
if (empty($result['success'])) {
return $this->error($this->auth_registration_error((string) ($result['error'] ?? __('注册失败', 'aksrt'))));
return $this->error($this->auth_registration_error((string) ($result['error'] ?? __('注册失败', 'jio'))));
}
$user_id = (int) ($result['user_id'] ?? 0);
if ($oauth_data && $user_id > 0) {
$binding = \AKSRT\Auth\Service::finalize_oauth_binding($user_id, $oauth_data, $obk);
$binding = \Jio\Auth\Service::finalize_oauth_binding($user_id, $oauth_data, $obk);
if (is_wp_error($binding)) {
$this->delete_oauth_registration_user($user_id);
return $this->error($binding->get_error_message(), 409);
@@ -417,13 +417,13 @@ trait UserControllerAuthPhoneOperations {
$user = get_user_by('id', $user_id);
if ($user instanceof \WP_User) {
if ($channel === 'email' && $verification_completed) {
\AKSRT\Auth\Service::mark_verified_email($user_id);
\Jio\Auth\Service::mark_verified_email($user_id);
}
\AKSRT\Auth\OAuthAccountService::complete_login($user, true);
\Jio\Auth\OAuthAccountService::complete_login($user, true);
}
}
return $this->success([
'message' => $oauth_data ? __('注册并绑定成功', 'aksrt') : __('注册成功', 'aksrt'),
'message' => $oauth_data ? __('注册并绑定成功', 'jio') : __('注册成功', 'jio'),
'redirect' => $result['redirect'] ?? home_url('/'),
'notice' => (string) ($result['notice'] ?? ''),
]);
@@ -432,20 +432,20 @@ trait UserControllerAuthPhoneOperations {
public function auth_reset_password(\WP_REST_Request $request): \WP_REST_Response {
$payload = $this->auth_payload($request);
if (!$this->auth_verify_rest_nonce($request, $payload)) {
return $this->error(__('安全验证失败,请刷新页面重试', 'aksrt'), 403);
return $this->error(__('安全验证失败,请刷新页面重试', 'jio'), 403);
}
$target = trim(sanitize_text_field((string) ($payload['target'] ?? '')));
$channel = $this->auth_channel($target, sanitize_key((string) ($payload['channel'] ?? '')));
if (class_exists('\AKSRT\Settings\Settings')) {
if (\AKSRT\Settings\Settings::close_sign() || \AKSRT\Settings\Settings::close_signin() || !$this->auth_channel_allowed(\AKSRT\Settings\Settings::reset_password_verification_type(), $channel)) {
return $this->error(__('当前验证方式不可用', 'aksrt'), 403);
if (class_exists('\Jio\Settings\Settings')) {
if (\Jio\Settings\Settings::close_sign() || \Jio\Settings\Settings::close_signin() || !$this->auth_channel_allowed(\Jio\Settings\Settings::reset_password_verification_type(), $channel)) {
return $this->error(__('当前验证方式不可用', 'jio'), 403);
}
}
if (($channel === 'email' && !is_email($target)) || ($channel === 'phone' && !\AKSRT\Sms\SMS::is_phonenumber($target))) {
return $this->error(__('请输入有效的邮箱或手机号', 'aksrt'));
if (($channel === 'email' && !is_email($target)) || ($channel === 'phone' && !\Jio\Sms\SMS::is_phonenumber($target))) {
return $this->error(__('请输入有效的邮箱或手机号', 'jio'));
}
if ($this->auth_rate_limited('reset-password', $target, 8, HOUR_IN_SECONDS)) {
return $this->error(__('请求过于频繁,请稍后再试', 'aksrt'), 429);
return $this->error(__('请求过于频繁,请稍后再试', 'jio'), 429);
}
$captcha_error = $this->auth_require_captcha('lostpassword', $payload);
if ($captcha_error) {
@@ -454,25 +454,25 @@ trait UserControllerAuthPhoneOperations {
$user = $this->auth_find_user($target, $channel);
$password = (string) ($payload['password'] ?? '');
$confirm = (string) ($payload['password_confirm'] ?? '');
if (!class_exists('\AKSRT\Auth\Service')) {
return $this->error(__('认证服务不可用', 'aksrt'), 500);
if (!class_exists('\Jio\Auth\Service')) {
return $this->error(__('认证服务不可用', 'jio'), 500);
}
$password_check = \AKSRT\Auth\Service::validate_password($password, $confirm);
$password_check = \Jio\Auth\Service::validate_password($password, $confirm);
if ($password_check !== true) {
return $this->error((string) $password_check);
}
if (!$user || !$this->auth_consume_code('resetpassword', $target, sanitize_text_field((string) ($payload['code'] ?? '')))) {
return $this->error(__('账号或验证码错误', 'aksrt'));
return $this->error(__('账号或验证码错误', 'jio'));
}
wp_set_password($password, (int) $user->ID);
return $this->success(['message' => __('密码已重置,请重新登录', 'aksrt'), 'redirect' => function_exists('aksrt_get_sign_page_url') ? aksrt_get_sign_page_url('signin') : wp_login_url()]);
return $this->success(['message' => __('密码已重置,请重新登录', 'jio'), 'redirect' => function_exists('jio_get_sign_page_url') ? jio_get_sign_page_url('signin') : wp_login_url()]);
}
public function user_phone_code(\WP_REST_Request $request): \WP_REST_Response {
$payload = $this->auth_payload($request);
if (!$this->auth_verify_rest_nonce($request, $payload)) return $this->error(__('安全验证失败', 'aksrt'), 403);
$settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
if (empty($settings['bind_phone'])) return $this->error(__('手机号绑定功能暂未开启', 'aksrt'), 403);
if (!$this->auth_verify_rest_nonce($request, $payload)) return $this->error(__('安全验证失败', 'jio'), 403);
$settings = class_exists('\Jio\Settings\Settings') ? \Jio\Settings\Settings::get_all() : [];
if (empty($settings['bind_phone'])) return $this->error(__('手机号绑定功能暂未开启', 'jio'), 403);
$stage = sanitize_key((string) ($payload['stage'] ?? 'bind_new_phone'));
$user_id = get_current_user_id();
$current_phone = (string) get_user_meta($user_id, 'phone_number', true);
@@ -482,49 +482,49 @@ trait UserControllerAuthPhoneOperations {
if ($current_phone !== '') {
delete_transient($this->auth_code_key('account_verify_old_phone_' . $user_id, $current_phone));
}
return $this->success(['message' => __('已取消本次验证', 'aksrt')]);
return $this->success(['message' => __('已取消本次验证', 'jio')]);
}
if ($stage === 'verify_old_phone') {
// 老号验证只接收脱敏中间位,发送目标恒为服务端绑定号,
// 避免客户端提交任意号码导致向第三方手机号发码。
if ($current_phone === '') return $this->error(__('当前绑定手机号已变化,请刷新页面重试', 'aksrt'), 409);
$parts = function_exists('aksrt_phone_mask_parts') ? aksrt_phone_mask_parts($current_phone) : null;
if ($current_phone === '') return $this->error(__('当前绑定手机号已变化,请刷新页面重试', 'jio'), 409);
$parts = function_exists('jio_phone_mask_parts') ? jio_phone_mask_parts($current_phone) : null;
if ($parts !== null) {
$middle = sanitize_text_field((string) ($payload['middle'] ?? ''));
if (!$this->auth_middle_matches($parts, $middle)) {
if (class_exists('\AKSRT\Extensions\SecurityHardening')) {
\AKSRT\Extensions\SecurityHardening::error_frequency_record();
if (class_exists('\Jio\Extensions\SecurityHardening')) {
\Jio\Extensions\SecurityHardening::error_frequency_record();
}
return $this->error(__('填写的号码与绑定手机号不一致', 'aksrt'), 400);
return $this->error(__('填写的号码与绑定手机号不一致', 'jio'), 400);
}
}
$phone = $current_phone;
} elseif ($stage === 'bind_new_phone') {
$phone = sanitize_text_field((string) ($payload['phone'] ?? ''));
if (!\AKSRT\Sms\SMS::is_phonenumber($phone)) return $this->error(__('手机号格式不正确', 'aksrt'));
if (!\Jio\Sms\SMS::is_phonenumber($phone)) return $this->error(__('手机号格式不正确', 'jio'));
if ($current_phone !== '' && !hash_equals($current_phone, $phone)) {
$proof = get_transient('aksrt_account_phone_old_' . $user_id);
if (!is_array($proof) || !hash_equals($current_phone, (string) ($proof['phone'] ?? ''))) return $this->error(__('请先验证老手机号', 'aksrt'), 403);
$proof = get_transient('jio_account_phone_old_' . $user_id);
if (!is_array($proof) || !hash_equals($current_phone, (string) ($proof['phone'] ?? ''))) return $this->error(__('请先验证老手机号', 'jio'), 403);
}
$users = get_users(['meta_key' => 'phone_number', 'meta_value' => $phone, 'number' => 1, 'fields' => 'ID']);
if (!empty($users) && (int) $users[0] !== $user_id) return $this->error(__('该手机号已被其他账号绑定', 'aksrt'), 409);
if ($current_phone !== '' && hash_equals($current_phone, $phone)) return $this->error(__('手机号不能与当前绑定号码相同', 'aksrt'), 409);
if (!empty($users) && (int) $users[0] !== $user_id) return $this->error(__('该手机号已被其他账号绑定', 'jio'), 409);
if ($current_phone !== '' && hash_equals($current_phone, $phone)) return $this->error(__('手机号不能与当前绑定号码相同', 'jio'), 409);
} else {
return $this->error(__('无效的验证步骤', 'aksrt'));
return $this->error(__('无效的验证步骤', 'jio'));
}
$captcha_error = $this->auth_require_captcha('bind_phone', $payload);
if ($captcha_error) return $captcha_error;
if (class_exists('\AKSRT\Extensions\SecurityHardening')) {
$wait = \AKSRT\Extensions\SecurityHardening::error_frequency_wait();
if ($wait > 0) return $this->error(sprintf(__('验证错误次数过多,请 %d 秒后再试', 'aksrt'), $wait), 429);
if (class_exists('\Jio\Extensions\SecurityHardening')) {
$wait = \Jio\Extensions\SecurityHardening::error_frequency_wait();
if ($wait > 0) return $this->error(sprintf(__('验证错误次数过多,请 %d 秒后再试', 'jio'), $wait), 429);
}
if (!class_exists('\AKSRT\Sms\SMS')) return $this->error(__('短信服务不可用', 'aksrt'), 503);
if (!class_exists('\Jio\Sms\SMS')) return $this->error(__('短信服务不可用', 'jio'), 503);
$code = (string) random_int(100000, 999999);
$result = \AKSRT\Sms\SMS::get_instance()->send_code($phone, $code);
if (empty($result['success'])) return $this->error((string) ($result['message'] ?? __('发送失败', 'aksrt')));
$result = \Jio\Sms\SMS::get_instance()->send_code($phone, $code);
if (empty($result['success'])) return $this->error((string) ($result['message'] ?? __('发送失败', 'jio')));
$this->auth_store_code('account_' . $stage . '_' . $user_id, $phone, $code, 30 * MINUTE_IN_SECONDS);
return $this->success(['message' => __('验证码短信已发送', 'aksrt'), 'countdown' => (int) ($result['remaining_time'] ?? 60)]);
return $this->success(['message' => __('验证码短信已发送', 'jio'), 'countdown' => (int) ($result['remaining_time'] ?? 60)]);
}
/**
@@ -532,33 +532,33 @@ trait UserControllerAuthPhoneOperations {
*/
public function user_phone_match(\WP_REST_Request $request): \WP_REST_Response {
$payload = $this->auth_payload($request);
if (!$this->auth_verify_rest_nonce($request, $payload)) return $this->error(__('安全验证失败', 'aksrt'), 403);
$settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
if (empty($settings['bind_phone'])) return $this->error(__('手机号绑定功能暂未开启', 'aksrt'), 403);
if (!$this->auth_verify_rest_nonce($request, $payload)) return $this->error(__('安全验证失败', 'jio'), 403);
$settings = class_exists('\Jio\Settings\Settings') ? \Jio\Settings\Settings::get_all() : [];
if (empty($settings['bind_phone'])) return $this->error(__('手机号绑定功能暂未开启', 'jio'), 403);
$user_id = get_current_user_id();
$current_phone = (string) get_user_meta($user_id, 'phone_number', true);
$parts = function_exists('aksrt_phone_mask_parts') ? aksrt_phone_mask_parts($current_phone) : null;
if ($parts === null) return $this->error(__('当前绑定手机号不支持此验证方式', 'aksrt'), 409);
$parts = function_exists('jio_phone_mask_parts') ? jio_phone_mask_parts($current_phone) : null;
if ($parts === null) return $this->error(__('当前绑定手机号不支持此验证方式', 'jio'), 409);
// 中间位是可枚举面:错误频率限制 + 每用户/IP 每小时尝试上限双保险。
if (class_exists('\AKSRT\Extensions\SecurityHardening')) {
$wait = \AKSRT\Extensions\SecurityHardening::error_frequency_wait();
if ($wait > 0) return $this->error(sprintf(__('验证错误次数过多,请 %d 秒后再试', 'aksrt'), $wait), 429);
if (class_exists('\Jio\Extensions\SecurityHardening')) {
$wait = \Jio\Extensions\SecurityHardening::error_frequency_wait();
if ($wait > 0) return $this->error(sprintf(__('验证错误次数过多,请 %d 秒后再试', 'jio'), $wait), 429);
}
$ip = function_exists('aksrt_get_client_ip') ? aksrt_get_client_ip() : (string) ($_SERVER['REMOTE_ADDR'] ?? '');
$ip = function_exists('jio_get_client_ip') ? jio_get_client_ip() : (string) ($_SERVER['REMOTE_ADDR'] ?? '');
if ($this->auth_rate_limited('phone-match', (string) $user_id . '|' . $ip, 20, HOUR_IN_SECONDS)) {
return $this->error(__('尝试次数过多,请稍后再试', 'aksrt'), 429);
return $this->error(__('尝试次数过多,请稍后再试', 'jio'), 429);
}
$middle = sanitize_text_field((string) ($payload['middle'] ?? ''));
if (!$this->auth_middle_matches($parts, $middle)) {
if (class_exists('\AKSRT\Extensions\SecurityHardening')) {
\AKSRT\Extensions\SecurityHardening::error_frequency_record();
if (class_exists('\Jio\Extensions\SecurityHardening')) {
\Jio\Extensions\SecurityHardening::error_frequency_record();
}
return $this->error(__('填写的号码与绑定手机号不一致', 'aksrt'), 400);
return $this->error(__('填写的号码与绑定手机号不一致', 'jio'), 400);
}
if (class_exists('\AKSRT\Extensions\SecurityHardening')) {
\AKSRT\Extensions\SecurityHardening::error_frequency_clear();
if (class_exists('\Jio\Extensions\SecurityHardening')) {
\Jio\Extensions\SecurityHardening::error_frequency_clear();
}
return $this->success(['match' => true]);
}
@@ -576,76 +576,76 @@ trait UserControllerAuthPhoneOperations {
public function user_verify_phone(\WP_REST_Request $request): \WP_REST_Response {
$payload = $this->auth_payload($request);
if (!$this->auth_verify_rest_nonce($request, $payload)) return $this->error(__('安全验证失败', 'aksrt'), 403);
$settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
if (empty($settings['bind_phone'])) return $this->error(__('手机号绑定功能暂未开启', 'aksrt'), 403);
if (!$this->auth_verify_rest_nonce($request, $payload)) return $this->error(__('安全验证失败', 'jio'), 403);
$settings = class_exists('\Jio\Settings\Settings') ? \Jio\Settings\Settings::get_all() : [];
if (empty($settings['bind_phone'])) return $this->error(__('手机号绑定功能暂未开启', 'jio'), 403);
$captcha_error = $this->auth_require_captcha('bind_phone', $payload);
if ($captcha_error) return $captcha_error;
$user_id = get_current_user_id();
$current_phone = (string) get_user_meta($user_id, 'phone_number', true);
$code = sanitize_text_field((string) ($payload['code'] ?? ''));
if ($current_phone === '') return $this->error(__('当前绑定手机号已变化,请刷新页面重试', 'aksrt'), 409);
if ($current_phone === '') return $this->error(__('当前绑定手机号已变化,请刷新页面重试', 'jio'), 409);
if (!$this->auth_consume_code('account_verify_old_phone_' . $user_id, $current_phone, $code)) {
if (class_exists('\AKSRT\Extensions\SecurityHardening')) {
\AKSRT\Extensions\SecurityHardening::error_frequency_record();
if (class_exists('\Jio\Extensions\SecurityHardening')) {
\Jio\Extensions\SecurityHardening::error_frequency_record();
}
return $this->error(__('验证码错误或已过期', 'aksrt'));
return $this->error(__('验证码错误或已过期', 'jio'));
}
if (class_exists('\AKSRT\Extensions\SecurityHardening')) {
\AKSRT\Extensions\SecurityHardening::error_frequency_clear();
if (class_exists('\Jio\Extensions\SecurityHardening')) {
\Jio\Extensions\SecurityHardening::error_frequency_clear();
}
set_transient('aksrt_account_phone_old_' . $user_id, ['phone' => $current_phone], 30 * MINUTE_IN_SECONDS);
return $this->success(['message' => __('手机号验证成功', 'aksrt')]);
set_transient('jio_account_phone_old_' . $user_id, ['phone' => $current_phone], 30 * MINUTE_IN_SECONDS);
return $this->success(['message' => __('手机号验证成功', 'jio')]);
}
public function user_bind_phone(\WP_REST_Request $request): \WP_REST_Response {
$payload = $this->auth_payload($request);
if (!$this->auth_verify_rest_nonce($request, $payload)) return $this->error(__('安全验证失败', 'aksrt'), 403);
if (!$this->auth_verify_rest_nonce($request, $payload)) return $this->error(__('安全验证失败', 'jio'), 403);
$captcha_error = $this->auth_require_captcha('bind_phone', $payload);
if ($captcha_error) return $captcha_error;
$settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
if (empty($settings['bind_phone'])) return $this->error(__('手机号绑定功能暂未开启', 'aksrt'), 403);
$settings = class_exists('\Jio\Settings\Settings') ? \Jio\Settings\Settings::get_all() : [];
if (empty($settings['bind_phone'])) return $this->error(__('手机号绑定功能暂未开启', 'jio'), 403);
$user_id = get_current_user_id();
$current_phone = (string) get_user_meta($user_id, 'phone_number', true);
$phone = sanitize_text_field((string) ($payload['phone'] ?? ''));
$code = sanitize_text_field((string) ($payload['code'] ?? ''));
if (!\AKSRT\Sms\SMS::is_phonenumber($phone)) return $this->error(__('手机号格式不正确', 'aksrt'));
if (!\Jio\Sms\SMS::is_phonenumber($phone)) return $this->error(__('手机号格式不正确', 'jio'));
if ($current_phone !== '') {
$proof = get_transient('aksrt_account_phone_old_' . $user_id);
if (!is_array($proof) || !hash_equals($current_phone, (string) ($proof['phone'] ?? ''))) return $this->error(__('请先验证老手机号', 'aksrt'), 403);
if (hash_equals($current_phone, $phone)) return $this->error(__('手机号不能与当前绑定号码相同', 'aksrt'), 409);
$proof = get_transient('jio_account_phone_old_' . $user_id);
if (!is_array($proof) || !hash_equals($current_phone, (string) ($proof['phone'] ?? ''))) return $this->error(__('请先验证老手机号', 'jio'), 403);
if (hash_equals($current_phone, $phone)) return $this->error(__('手机号不能与当前绑定号码相同', 'jio'), 409);
}
$users = get_users(['meta_key' => 'phone_number', 'meta_value' => $phone, 'number' => 1, 'fields' => 'ID']);
if (!empty($users) && (int) $users[0] !== $user_id) return $this->error(__('该手机号已被其他账号绑定', 'aksrt'), 409);
$agreement = function_exists('aksrt_get_auth_agreement_view') ? aksrt_get_auth_agreement_view() : ['enabled' => false, 'required' => false];
if (!empty($agreement['required']) && empty($payload['agree_terms'])) return $this->error(__('请先同意相关协议', 'aksrt'), 400);
if (!empty($users) && (int) $users[0] !== $user_id) return $this->error(__('该手机号已被其他账号绑定', 'jio'), 409);
$agreement = function_exists('jio_get_auth_agreement_view') ? jio_get_auth_agreement_view() : ['enabled' => false, 'required' => false];
if (!empty($agreement['required']) && empty($payload['agree_terms'])) return $this->error(__('请先同意相关协议', 'jio'), 400);
$code_scene = 'account_bind_new_phone_' . $user_id;
if (!$this->auth_consume_code($code_scene, $phone, $code, false)) {
if (class_exists('\AKSRT\Extensions\SecurityHardening')) {
\AKSRT\Extensions\SecurityHardening::error_frequency_record();
if (class_exists('\Jio\Extensions\SecurityHardening')) {
\Jio\Extensions\SecurityHardening::error_frequency_record();
}
return $this->error(__('验证码错误或已过期', 'aksrt'));
return $this->error(__('验证码错误或已过期', 'jio'));
}
if (!class_exists('\AKSRT\Auth\Service')) return $this->error(__('认证服务不可用', 'aksrt'), 500);
$result = \AKSRT\Auth\Service::bind_phone($payload, true);
if (empty($result['success'])) return $this->error((string) ($result['error'] ?? __('绑定失败', 'aksrt')));
if (class_exists('\AKSRT\Extensions\SecurityHardening')) {
\AKSRT\Extensions\SecurityHardening::error_frequency_clear();
if (!class_exists('\Jio\Auth\Service')) return $this->error(__('认证服务不可用', 'jio'), 500);
$result = \Jio\Auth\Service::bind_phone($payload, true);
if (empty($result['success'])) return $this->error((string) ($result['error'] ?? __('绑定失败', 'jio')));
if (class_exists('\Jio\Extensions\SecurityHardening')) {
\Jio\Extensions\SecurityHardening::error_frequency_clear();
}
delete_transient($this->auth_code_key($code_scene, $phone));
delete_transient('aksrt_account_phone_old_' . $user_id);
return $this->success(['message' => $result['message'] ?? __('绑定成功', 'aksrt')]);
delete_transient('jio_account_phone_old_' . $user_id);
return $this->success(['message' => $result['message'] ?? __('绑定成功', 'jio')]);
}
public function test_sms_send(\WP_REST_Request $request): \WP_REST_Response {
$params = $this->get_json_params($request);
$phone = sanitize_text_field((string) ($params['phone'] ?? ''));
if (!class_exists('\\AKSRT\\Sms\\SMS')) {
return $this->error(__('短信服务不可用', 'aksrt'), 503);
if (!class_exists('\\Jio\\Sms\\SMS')) {
return $this->error(__('短信服务不可用', 'jio'), 503);
}
$result = \AKSRT\Sms\SMS::get_instance()->test_send($phone);
$result = \Jio\Sms\SMS::get_instance()->test_send($phone);
return !empty($result['success'])
? $this->success(['message' => (string) ($result['message'] ?? __('测试短信已发送', 'aksrt'))])
: $this->error((string) ($result['message'] ?? __('测试短信发送失败', 'aksrt')));
? $this->success(['message' => (string) ($result['message'] ?? __('测试短信已发送', 'jio'))])
: $this->error((string) ($result['message'] ?? __('测试短信发送失败', 'jio')));
}
}
+12 -12
View File
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -13,7 +13,7 @@ trait UserControllerFollowOperations {
* POST /follow/toggle — 切换关注状态
*/
public function follow_toggle(\WP_REST_Request $request): \WP_REST_Response {
if (!class_exists('\AKSRT\User\Follow')) {
if (!class_exists('\Jio\User\Follow')) {
return $this->error('关注系统未启用', 500);
}
@@ -30,12 +30,12 @@ trait UserControllerFollowOperations {
return $this->error('用户不存在', 404);
}
$is_following = \AKSRT\User\Follow::get_instance()->toggle($current_user_id, $target_user_id);
$is_following = \Jio\User\Follow::get_instance()->toggle($current_user_id, $target_user_id);
return $this->success([
'data' => [
'is_following' => $is_following,
'follower_count' => \AKSRT\User\Follow::get_instance()->get_follower_count($target_user_id),
'follower_count' => \Jio\User\Follow::get_instance()->get_follower_count($target_user_id),
],
]);
}
@@ -44,7 +44,7 @@ trait UserControllerFollowOperations {
* GET /follow/status — 查询关注状态
*/
public function follow_status(\WP_REST_Request $request): \WP_REST_Response {
if (!class_exists('\AKSRT\User\Follow')) {
if (!class_exists('\Jio\User\Follow')) {
return $this->success(['data' => ['is_following' => false]]);
}
@@ -55,13 +55,13 @@ trait UserControllerFollowOperations {
return $this->error('参数无效');
}
$is_following = \AKSRT\User\Follow::get_instance()->is_following($current_user_id, $target_user_id);
$is_following = \Jio\User\Follow::get_instance()->is_following($current_user_id, $target_user_id);
return $this->success([
'data' => [
'is_following' => $is_following,
'follower_count' => \AKSRT\User\Follow::get_instance()->get_follower_count($target_user_id),
'following_count' => \AKSRT\User\Follow::get_instance()->get_following_count($target_user_id),
'follower_count' => \Jio\User\Follow::get_instance()->get_follower_count($target_user_id),
'following_count' => \Jio\User\Follow::get_instance()->get_following_count($target_user_id),
],
]);
}
@@ -70,7 +70,7 @@ trait UserControllerFollowOperations {
* GET /follow/counts — 获取关注/粉丝数
*/
public function follow_counts(\WP_REST_Request $request): \WP_REST_Response {
if (!class_exists('\AKSRT\User\Follow')) {
if (!class_exists('\Jio\User\Follow')) {
return $this->success(['data' => ['followers' => 0, 'following' => 0]]);
}
@@ -81,8 +81,8 @@ trait UserControllerFollowOperations {
return $this->success([
'data' => [
'followers' => \AKSRT\User\Follow::get_instance()->get_follower_count($user_id),
'following' => \AKSRT\User\Follow::get_instance()->get_following_count($user_id),
'followers' => \Jio\User\Follow::get_instance()->get_follower_count($user_id),
'following' => \Jio\User\Follow::get_instance()->get_following_count($user_id),
],
]);
}
@@ -2,10 +2,10 @@
/**
* Logged-in user profile media uploads.
*
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -13,8 +13,8 @@ trait UserControllerProfileMediaOperations {
public function rest_upload_user_avatar(\WP_REST_Request $request): \WP_REST_Response {
return $this->upload_profile_image(
['avatar', 'file'],
'aksrt_avatar_attachment_id',
__('头像已更新', 'aksrt'),
'jio_avatar_attachment_id',
__('头像已更新', 'jio'),
true
);
}
@@ -22,25 +22,25 @@ trait UserControllerProfileMediaOperations {
public function rest_delete_user_avatar(\WP_REST_Request $request): \WP_REST_Response {
$user_id = get_current_user_id();
if ($user_id <= 0) {
return $this->error(__('请先登录', 'aksrt'), 401);
return $this->error(__('请先登录', 'jio'), 401);
}
$attachment_id = (int) get_user_meta($user_id, 'aksrt_avatar_attachment_id', true);
delete_user_meta($user_id, 'aksrt_avatar_attachment_id');
$attachment_id = (int) get_user_meta($user_id, 'jio_avatar_attachment_id', true);
delete_user_meta($user_id, 'jio_avatar_attachment_id');
$this->delete_owned_profile_attachment($attachment_id, $user_id);
do_action('aksrt_user_avatar_changed', $user_id, 0, '');
do_action('jio_user_avatar_changed', $user_id, 0, '');
return $this->success([
'message' => __('已恢复默认头像', 'aksrt'),
'url' => function_exists('aksrt_get_default_avatar_url') ? aksrt_get_default_avatar_url() : '',
'message' => __('已恢复默认头像', 'jio'),
'url' => function_exists('jio_get_default_avatar_url') ? jio_get_default_avatar_url() : '',
]);
}
public function rest_upload_user_background(\WP_REST_Request $request): \WP_REST_Response {
return $this->upload_profile_image(
['background', 'file'],
'aksrt_background_attachment_id',
__('背景图已更新', 'aksrt'),
'jio_background_attachment_id',
__('背景图已更新', 'jio'),
false
);
}
@@ -51,16 +51,16 @@ trait UserControllerProfileMediaOperations {
private function upload_profile_image(array $file_keys, string $meta_key, string $message, bool $is_avatar): \WP_REST_Response {
$user_id = get_current_user_id();
if ($user_id <= 0) {
return $this->error(__('请先登录', 'aksrt'), 401);
return $this->error(__('请先登录', 'jio'), 401);
}
$upload_enabled = $is_avatar
? function_exists('aksrt_is_avatar_upload_enabled') && aksrt_is_avatar_upload_enabled()
: function_exists('aksrt_is_user_cover_upload_enabled') && aksrt_is_user_cover_upload_enabled();
? function_exists('jio_is_avatar_upload_enabled') && jio_is_avatar_upload_enabled()
: function_exists('jio_is_user_cover_upload_enabled') && jio_is_user_cover_upload_enabled();
if (!$upload_enabled) {
return $this->error($is_avatar
? __('当前账号没有上传头像的权限', 'aksrt')
: __('当前账号没有上传用户资料卡背景图的权限', 'aksrt'), 403);
? __('当前账号没有上传头像的权限', 'jio')
: __('当前账号没有上传用户资料卡背景图的权限', 'jio'), 403);
}
$file_key = '';
@@ -71,7 +71,7 @@ trait UserControllerProfileMediaOperations {
}
}
if ($file_key === '') {
return $this->error(__('请选择要上传的图片', 'aksrt'), 400);
return $this->error(__('请选择要上传的图片', 'jio'), 400);
}
require_once ABSPATH . 'wp-admin/includes/file.php';
@@ -81,16 +81,16 @@ trait UserControllerProfileMediaOperations {
$file = $_FILES[$file_key];
$upload_error = isset($file['error']) ? (int) $file['error'] : UPLOAD_ERR_NO_FILE;
if ($upload_error !== UPLOAD_ERR_OK) {
return $this->error(__('头像文件上传失败,请重新选择', 'aksrt'), 400);
return $this->error(__('头像文件上传失败,请重新选择', 'jio'), 400);
}
// 头像与资料卡背景图并入「前端图片上传限制」统一治理(参考主题同源)。
$max_size = class_exists('\AKSRT\Attachment\UploadLimits')
? \AKSRT\Attachment\UploadLimits::limit_bytes('image', $user_id)
$max_size = class_exists('\Jio\Attachment\UploadLimits')
? \Jio\Attachment\UploadLimits::limit_bytes('image', $user_id)
: (int) wp_max_upload_size();
$actual_size = !empty($file['tmp_name']) && is_readable($file['tmp_name']) ? (int) filesize($file['tmp_name']) : 0;
if ($actual_size <= 0 || $actual_size > $max_size) {
return $this->error(sprintf(__('图片大小不能超过 %s。', 'aksrt'), size_format($max_size)), 413);
return $this->error(sprintf(__('图片大小不能超过 %s。', 'jio'), size_format($max_size)), 413);
}
$checked = wp_check_filetype_and_ext($file['tmp_name'], $file['name'], [
@@ -100,17 +100,17 @@ trait UserControllerProfileMediaOperations {
'webp' => 'image/webp',
]);
if (empty($checked['type']) || empty($checked['ext']) || !wp_match_mime_types('image', $checked['type'])) {
return $this->error(__('仅支持 JPG、PNG、GIF 或 WebP 图片', 'aksrt'), 415);
return $this->error(__('仅支持 JPG、PNG、GIF 或 WebP 图片', 'jio'), 415);
}
$image_size = wp_getimagesize($file['tmp_name']);
if (!$image_size || empty($image_size[0]) || empty($image_size[1]) || ((int) $image_size[0] * (int) $image_size[1]) > 40000000) {
return $this->error(__('图片无效或尺寸过大', 'aksrt'), 415);
return $this->error(__('图片无效或尺寸过大', 'jio'), 415);
}
if (!$is_avatar && class_exists('\AKSRT\Extensions\ContentAudit')) {
$audit = \AKSRT\Extensions\ContentAudit::get_instance()->audit_image((string) $file['tmp_name']);
if (!$is_avatar && class_exists('\Jio\Extensions\ContentAudit')) {
$audit = \Jio\Extensions\ContentAudit::get_instance()->audit_image((string) $file['tmp_name']);
if (empty($audit['passed'])) {
return $this->error(__('图片未通过内容审核', 'aksrt'), 422);
return $this->error(__('图片未通过内容审核', 'jio'), 422);
}
}
@@ -131,21 +131,21 @@ trait UserControllerProfileMediaOperations {
wp_update_post(['ID' => $attachment_id, 'post_author' => $user_id]);
if (!wp_attachment_is_image($attachment_id)) {
wp_delete_attachment($attachment_id, true);
return $this->error(__('上传文件不是有效图片', 'aksrt'), 415);
return $this->error(__('上传文件不是有效图片', 'jio'), 415);
}
update_post_meta($attachment_id, '_aksrt_profile_media_user_id', $user_id);
update_post_meta($attachment_id, '_aksrt_profile_media_type', $is_avatar ? 'avatar' : 'background');
update_post_meta($attachment_id, '_jio_profile_media_user_id', $user_id);
update_post_meta($attachment_id, '_jio_profile_media_type', $is_avatar ? 'avatar' : 'background');
$url = wp_get_attachment_image_url($attachment_id, $is_avatar ? 'thumbnail' : 'full');
if (!$url) {
wp_delete_attachment($attachment_id, true);
return $this->error(__('上传失败', 'aksrt'), 500);
return $this->error(__('上传失败', 'jio'), 500);
}
$previous_id = (int) get_user_meta($user_id, $meta_key, true);
update_user_meta($user_id, $meta_key, $attachment_id);
if (!$is_avatar) {
update_user_meta($user_id, 'aksrt_background_image', esc_url_raw($url));
update_user_meta($user_id, 'jio_background_image', esc_url_raw($url));
}
if ($previous_id > 0 && $previous_id !== $attachment_id) {
@@ -153,7 +153,7 @@ trait UserControllerProfileMediaOperations {
}
if ($is_avatar) {
do_action('aksrt_user_avatar_changed', $user_id, $attachment_id, $url);
do_action('jio_user_avatar_changed', $user_id, $attachment_id, $url);
}
return $this->success([
@@ -176,7 +176,7 @@ trait UserControllerProfileMediaOperations {
return false;
}
$managed_user_id = (int) get_post_meta($attachment_id, '_aksrt_profile_media_user_id', true);
$managed_user_id = (int) get_post_meta($attachment_id, '_jio_profile_media_user_id', true);
if ($managed_user_id > 0 && $managed_user_id !== $user_id) {
return false;
}
+51 -51
View File
@@ -1,16 +1,16 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
trait UserControllerReportOperations {
public function get_report_list(\WP_REST_Request $request): \WP_REST_Response {
if (!class_exists('\AKSRT\Messages\ReportCenter')) {
if (!class_exists('\Jio\Messages\ReportCenter')) {
return $this->success([
'data' => [
'items' => [],
@@ -27,7 +27,7 @@ trait UserControllerReportOperations {
global $wpdb;
$table = \AKSRT\Core\DBMigrator::table('report');
$table = \Jio\Core\DBMigrator::table('report');
if (!$table) {
return $this->success([
'data' => [
@@ -92,7 +92,7 @@ trait UserControllerReportOperations {
$total = count($items);
$offset = ($page - 1) * $per_page;
$paged = array_slice($items, $offset, $per_page);
$counts = \AKSRT\Messages\ReportCenter::get_instance()->get_status_counts();
$counts = \Jio\Messages\ReportCenter::get_instance()->get_status_counts();
$categories = [];
foreach ((array) $rows as $row) {
$cat = sanitize_text_field((string) ($row['category'] ?? ''));
@@ -125,15 +125,15 @@ trait UserControllerReportOperations {
global $wpdb;
$report_id = (int) $request->get_param('id');
$table = \AKSRT\Core\DBMigrator::table('report');
$table = \Jio\Core\DBMigrator::table('report');
if ($report_id <= 0 || !$table) {
return $this->error(__('举报工单不存在', 'aksrt'), 404);
return $this->error(__('举报工单不存在', 'jio'), 404);
}
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $report_id), ARRAY_A);
if (!$row) {
return $this->error(__('举报工单不存在', 'aksrt'), 404);
return $this->error(__('举报工单不存在', 'jio'), 404);
}
$related = (int) $wpdb->get_var($wpdb->prepare(
@@ -157,7 +157,7 @@ trait UserControllerReportOperations {
public function process_report(\WP_REST_Request $request): \WP_REST_Response {
$report_id = (int) $request->get_param('id');
if ($report_id <= 0) {
return $this->error(__('参数无效', 'aksrt'));
return $this->error(__('参数无效', 'jio'));
}
return $this->handle_report_process($report_id, $this->get_json_params($request));
@@ -166,9 +166,9 @@ trait UserControllerReportOperations {
private function handle_report_process(int $report_id, array $payload): \WP_REST_Response {
global $wpdb;
$table = \AKSRT\Core\DBMigrator::table('report');
$table = \Jio\Core\DBMigrator::table('report');
if (!$table) {
return $this->error(__('举报系统未启用', 'aksrt'));
return $this->error(__('举报系统未启用', 'jio'));
}
$status = sanitize_key((string) ($payload['status'] ?? ''));
@@ -179,7 +179,7 @@ trait UserControllerReportOperations {
$valid_actions = array_column($this->get_report_action_options(), 'value');
if ($status === '' || !in_array($status, $valid_statuses, true)) {
return $this->error(__('处理状态无效', 'aksrt'));
return $this->error(__('处理状态无效', 'jio'));
}
if (!in_array($action_taken, $valid_actions, true)) {
$action_taken = 'none';
@@ -187,7 +187,7 @@ trait UserControllerReportOperations {
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $report_id), ARRAY_A);
if (!$row) {
return $this->error(__('举报工单不存在', 'aksrt'), 404);
return $this->error(__('举报工单不存在', 'jio'), 404);
}
$action_reason = (string) ($row['category'] ?? '');
@@ -226,11 +226,11 @@ trait UserControllerReportOperations {
);
if ($updated === false) {
return $this->error(__('处理失败', 'aksrt'));
return $this->error(__('处理失败', 'jio'));
}
return $this->success([
'message' => __('举报工单已处理', 'aksrt'),
'message' => __('举报工单已处理', 'jio'),
]);
}
@@ -328,7 +328,7 @@ trait UserControllerReportOperations {
if ($user instanceof \WP_User) {
$summary['display_name'] = $user->display_name;
$summary['title'] = $user->display_name;
$summary['url'] = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($target_id) : '';
$summary['url'] = function_exists('jio_get_user_home_url') ? jio_get_user_home_url($target_id) : '';
$summary['ban_state'] = $this->get_user_restriction_state($target_id);
}
}
@@ -340,64 +340,64 @@ trait UserControllerReportOperations {
* 用户当前限制状态摘要(banned|restricted|muted|空),与 Restriction 表实时同步。
*/
private function get_user_restriction_state(int $user_id): string {
if ($user_id <= 0 || !class_exists('\AKSRT\User\Restriction')) {
if ($user_id <= 0 || !class_exists('\Jio\User\Restriction')) {
return '';
}
$svc = \AKSRT\User\Restriction::get_instance();
$svc = \Jio\User\Restriction::get_instance();
if ($svc->is_banned($user_id)) {
return 'banned';
}
if ($svc->is_restricted($user_id, \AKSRT\User\Restriction::TYPE_SUSPEND)) {
if ($svc->is_restricted($user_id, \Jio\User\Restriction::TYPE_SUSPEND)) {
return 'restricted';
}
if ($svc->is_restricted($user_id, \AKSRT\User\Restriction::TYPE_MUTE)) {
if ($svc->is_restricted($user_id, \Jio\User\Restriction::TYPE_MUTE)) {
return 'muted';
}
return '';
}
private function apply_report_action(string $target_type, int $target_id, string $action_taken, string $reason = ''): string {
if (!class_exists('\AKSRT\Messages\ReportCenter')) {
if (!class_exists('\Jio\Messages\ReportCenter')) {
return '';
}
return \AKSRT\Messages\ReportCenter::get_instance()->apply_action($target_type, $target_id, $action_taken, $reason);
return \Jio\Messages\ReportCenter::get_instance()->apply_action($target_type, $target_id, $action_taken, $reason);
}
private function get_report_status_options(): array {
return [
['label' => __('待处理', 'aksrt'), 'value' => 'pending'],
['label' => __('处理中', 'aksrt'), 'value' => 'reviewing'],
['label' => __('已解决', 'aksrt'), 'value' => 'resolved'],
['label' => __('已驳回', 'aksrt'), 'value' => 'dismissed'],
['label' => __('待处理', 'jio'), 'value' => 'pending'],
['label' => __('处理中', 'jio'), 'value' => 'reviewing'],
['label' => __('已解决', 'jio'), 'value' => 'resolved'],
['label' => __('已驳回', 'jio'), 'value' => 'dismissed'],
];
}
private function get_report_target_type_options(): array {
return [
['label' => __('文章', 'aksrt'), 'value' => 'post'],
['label' => __('评论', 'aksrt'), 'value' => 'comment'],
['label' => __('用户', 'aksrt'), 'value' => 'user'],
['label' => __('文章', 'jio'), 'value' => 'post'],
['label' => __('评论', 'jio'), 'value' => 'comment'],
['label' => __('用户', 'jio'), 'value' => 'user'],
];
}
private function get_report_action_options(string $target_type = ''): array {
$common = [
['label' => __('仅更新状态', 'aksrt'), 'value' => 'none'],
['label' => __('升级人工处理', 'aksrt'), 'value' => 'escalated'],
['label' => __('仅更新状态', 'jio'), 'value' => 'none'],
['label' => __('升级人工处理', 'jio'), 'value' => 'escalated'],
];
if ($target_type === 'user' || $target_type === '') {
$common = array_merge($common, [
['label' => __('发送警告', 'aksrt'), 'value' => 'warn'],
['label' => __('禁言', 'aksrt'), 'value' => 'mute'],
['label' => __('停权', 'aksrt'), 'value' => 'suspend'],
['label' => __('封禁账号', 'aksrt'), 'value' => 'ban'],
['label' => __('解除限制', 'aksrt'), 'value' => 'clear'],
['label' => __('发送警告', 'jio'), 'value' => 'warn'],
['label' => __('禁言', 'jio'), 'value' => 'mute'],
['label' => __('停权', 'jio'), 'value' => 'suspend'],
['label' => __('封禁账号', 'jio'), 'value' => 'ban'],
['label' => __('解除限制', 'jio'), 'value' => 'clear'],
]);
}
if ($target_type === 'post' || $target_type === 'comment' || $target_type === '') {
$common[] = ['label' => __('转为待审内容', 'aksrt'), 'value' => 'content_removed'];
$common[] = ['label' => __('转为待审内容', 'jio'), 'value' => 'content_removed'];
}
return $common;
@@ -407,7 +407,7 @@ trait UserControllerReportOperations {
* POST /report/submit — 前台举报提交(登录用户)
*/
public function submit_report(\WP_REST_Request $request): \WP_REST_Response {
if (!class_exists('\AKSRT\Messages\ReportCenter')) {
if (!class_exists('\Jio\Messages\ReportCenter')) {
return $this->error('举报系统未启用', 500);
}
@@ -421,13 +421,13 @@ trait UserControllerReportOperations {
}
$other_category = '其他原因';
$categories = array_column(\AKSRT\Settings\UserRuntime::ban_settings()['reasons'], 'name');
$categories = array_column(\Jio\Settings\UserRuntime::ban_settings()['reasons'], 'name');
$categories[] = $other_category;
if (!in_array($category, $categories, true)) {
return $this->error(__('举报原因无效或已更新,请刷新页面后重新选择', 'aksrt'));
return $this->error(__('举报原因无效或已更新,请刷新页面后重新选择', 'jio'));
}
if ($category === $other_category && $reason === '') {
return $this->error(__('请填写举报原因的具体说明', 'aksrt'));
return $this->error(__('请填写举报原因的具体说明', 'jio'));
}
$valid_types = ['post', 'comment', 'user'];
@@ -437,23 +437,23 @@ trait UserControllerReportOperations {
$reporter_id = get_current_user_id();
if ($reporter_id <= 0) {
return $this->error(__('请先登录', 'aksrt'), 401);
return $this->error(__('请先登录', 'jio'), 401);
}
// 同一人对同一目标的待处理工单只允许存在一条
if (\AKSRT\Messages\ReportCenter::get_instance()->has_pending_report($reporter_id, $target_type, $target_id)) {
return $this->error(__('你已提交过相同的举报,请等待处理结果', 'aksrt'), 409);
if (\Jio\Messages\ReportCenter::get_instance()->has_pending_report($reporter_id, $target_type, $target_id)) {
return $this->error(__('你已提交过相同的举报,请等待处理结果', 'jio'), 409);
}
if ($target_type === 'user') {
if ($target_id === $reporter_id) {
return $this->error(__('不能举报自己', 'aksrt'), 400);
return $this->error(__('不能举报自己', 'jio'), 400);
}
if (!function_exists('aksrt_user_can') || !aksrt_user_can('user_report', $reporter_id, $target_id)) {
return $this->error(__('当前账号没有举报用户的权限', 'aksrt'), 403);
if (!function_exists('jio_user_can') || !jio_user_can('user_report', $reporter_id, $target_id)) {
return $this->error(__('当前账号没有举报用户的权限', 'jio'), 403);
}
}
$report_id = \AKSRT\Messages\ReportCenter::get_instance()->submit(
$report_id = \Jio\Messages\ReportCenter::get_instance()->submit(
$reporter_id,
$target_type,
$target_id,
@@ -463,13 +463,13 @@ trait UserControllerReportOperations {
if ($report_id > 0) {
// 举证图片以 URL 拼入举报内容,提交成功即认领。
\AKSRT\Attachment\PendingUploads::claim_text($reason, $reporter_id);
\Jio\Attachment\PendingUploads::claim_text($reason, $reporter_id);
return $this->success([
'message' => __('举报已提交,我们会尽快处理', 'aksrt'),
'message' => __('举报已提交,我们会尽快处理', 'jio'),
'data' => ['report_id' => $report_id],
]);
}
return $this->error(__('举报提交失败', 'aksrt'));
return $this->error(__('举报提交失败', 'jio'));
}
}
@@ -1,9 +1,9 @@
<?php
/**
* @package AKSRT
* @package Jio
*/
namespace AKSRT\Admin;
namespace Jio\Admin;
defined('ABSPATH') || exit;
@@ -16,11 +16,11 @@ trait UserControllerRestrictionOperations {
return $this->error('用户不存在', 404);
}
if (!class_exists('\AKSRT\User\Restriction')) {
if (!class_exists('\Jio\User\Restriction')) {
return $this->success(['data' => ['restrictions' => []]]);
}
$restrictions = \AKSRT\User\Restriction::get_instance()->get_user_restrictions($user_id, false);
$restrictions = \Jio\User\Restriction::get_instance()->get_user_restrictions($user_id, false);
return $this->success(['data' => ['restrictions' => $restrictions]]);
}
@@ -41,11 +41,11 @@ trait UserControllerRestrictionOperations {
return $this->error('请选择限制类型');
}
if (!class_exists('\AKSRT\User\Restriction')) {
if (!class_exists('\Jio\User\Restriction')) {
return $this->error('限制系统未启用', 501);
}
$result = \AKSRT\User\Restriction::get_instance()->restrict(
$result = \Jio\User\Restriction::get_instance()->restrict(
$user_id, $type, $reason, $scope, $expires_at, get_current_user_id()
);
@@ -59,11 +59,11 @@ trait UserControllerRestrictionOperations {
public function remove_user_restriction(\WP_REST_Request $request): \WP_REST_Response {
$restriction_id = (int) $request->get_param('id');
if (!class_exists('\AKSRT\User\Restriction')) {
if (!class_exists('\Jio\User\Restriction')) {
return $this->error('限制系统未启用', 501);
}
$result = \AKSRT\User\Restriction::get_instance()->remove_restriction($restriction_id);
$result = \Jio\User\Restriction::get_instance()->remove_restriction($restriction_id);
if (!$result) {
return $this->error('移除失败', 500);
}
@@ -72,27 +72,27 @@ trait UserControllerRestrictionOperations {
}
public function get_restriction_stats(\WP_REST_Request $request): \WP_REST_Response {
if (!class_exists('\AKSRT\User\Restriction')) {
if (!class_exists('\Jio\User\Restriction')) {
return $this->success(['data' => ['total' => 0, 'active' => 0, 'banned' => 0]]);
}
$stats = \AKSRT\User\Restriction::get_instance()->get_stats();
$stats = \Jio\User\Restriction::get_instance()->get_stats();
return $this->success(['data' => $stats]);
}
/**
* 前台禁封弹窗内容(1:1 复刻参考主题 zib_get_set_user_ban_modal,
* 经 aksrt_get_user_ban_modal 渲染;参考主题 refresh_modal 每次刷新同语义)。
* 经 jio_get_user_ban_modal 渲染;参考主题 refresh_modal 每次刷新同语义)。
*/
public function get_user_ban_modal(\WP_REST_Request $request): \WP_REST_Response {
$user_id = (int) $request->get_param('user_id');
if ($user_id <= 0 || !get_userdata($user_id)) {
return $this->error('用户不存在', 404);
}
if (!function_exists('aksrt_get_user_ban_modal')) {
if (!function_exists('jio_get_user_ban_modal')) {
return $this->error('禁封组件未加载', 501);
}
$html = aksrt_get_user_ban_modal($user_id);
$html = jio_get_user_ban_modal($user_id);
if ($html === '') {
return $this->error('抱歉,您暂时没有此权限', 403);
}
@@ -101,17 +101,17 @@ trait UserControllerRestrictionOperations {
/**
* 前台查看禁封状态弹窗内容(1:1 复刻参考主题 zib_get_ban_info_modal,
* 经 aksrt_get_user_ban_info_modal 渲染;本人或管理员可见)。
* 经 jio_get_user_ban_info_modal 渲染;本人或管理员可见)。
*/
public function get_user_ban_info_modal(\WP_REST_Request $request): \WP_REST_Response {
$user_id = (int) $request->get_param('user_id');
if ($user_id <= 0 || !get_userdata($user_id)) {
return $this->error('用户不存在', 404);
}
if (!function_exists('aksrt_get_user_ban_info_modal')) {
if (!function_exists('jio_get_user_ban_info_modal')) {
return $this->error('禁封组件未加载', 501);
}
$html = aksrt_get_user_ban_info_modal($user_id);
$html = jio_get_user_ban_info_modal($user_id);
if ($html === '') {
return $this->error('抱歉,您暂时没有此权限', 403);
}
@@ -120,17 +120,17 @@ trait UserControllerRestrictionOperations {
/**
* 前台禁封申诉弹窗内容(1:1 复刻参考主题 zib_get_user_ban_appeal_modal,
* 经 aksrt_get_user_ban_appeal_modal 渲染;仅本人可见)。
* 经 jio_get_user_ban_appeal_modal 渲染;仅本人可见)。
*/
public function get_user_ban_appeal_modal(\WP_REST_Request $request): \WP_REST_Response {
$user_id = (int) $request->get_param('user_id');
if ($user_id <= 0 || !get_userdata($user_id)) {
return $this->error('用户不存在', 404);
}
if (!function_exists('aksrt_get_user_ban_appeal_modal')) {
if (!function_exists('jio_get_user_ban_appeal_modal')) {
return $this->error('申诉组件未加载', 501);
}
$html = aksrt_get_user_ban_appeal_modal($user_id);
$html = jio_get_user_ban_appeal_modal($user_id);
if ($html === '') {
return $this->error('抱歉,您暂时没有此权限', 403);
}
@@ -139,17 +139,17 @@ trait UserControllerRestrictionOperations {
/**
* 前台禁封记录弹窗内容(1:1 复刻参考主题 zib_get_ban_log_modal,
* 经 aksrt_get_user_ban_log_modal 渲染;仅具备禁封权限的管理员可见)。
* 经 jio_get_user_ban_log_modal 渲染;仅具备禁封权限的管理员可见)。
*/
public function get_user_ban_log_modal(\WP_REST_Request $request): \WP_REST_Response {
$user_id = (int) $request->get_param('user_id');
if ($user_id <= 0 || !get_userdata($user_id)) {
return $this->error('用户不存在', 404);
}
if (!function_exists('aksrt_get_user_ban_log_modal')) {
if (!function_exists('jio_get_user_ban_log_modal')) {
return $this->error('禁封组件未加载', 501);
}
$html = aksrt_get_user_ban_log_modal($user_id);
$html = jio_get_user_ban_log_modal($user_id);
if ($html === '') {
return $this->error('抱歉,您暂时没有此权限', 403);
}
@@ -167,34 +167,34 @@ trait UserControllerRestrictionOperations {
if ($user_id <= 0 || !get_userdata($user_id)) {
return $this->error('用户不存在或参数传入错误', 404);
}
if (!function_exists('aksrt_current_user_can_ban') || !aksrt_current_user_can_ban($user_id)) {
if (!function_exists('jio_current_user_can_ban') || !jio_current_user_can_ban($user_id)) {
return $this->error('抱歉,您暂时没有此权限', 403);
}
if (!class_exists('\AKSRT\User\Restriction')) {
if (!class_exists('\Jio\User\Restriction')) {
return $this->error('限制系统未启用', 501);
}
$svc = \AKSRT\User\Restriction::get_instance();
$svc = \Jio\User\Restriction::get_instance();
$ban = (int) ($params['ban'] ?? 0);
$desc = isset($params['desc']) ? sanitize_textarea_field((string) wp_unslash($params['desc'])) : '';
// ── 解封 ──
if (0 === $ban) {
$svc->lift_user_restriction($user_id);
delete_user_meta($user_id, '_aksrt_no_appeal');
$this->send_ban_notice($user_id, __('您的账号限制已解除。', 'aksrt'), __('限制解除', 'aksrt'), $desc);
return $this->success(['message' => __('已解封此用户', 'aksrt'), 'data' => ['banned' => false]]);
delete_user_meta($user_id, '_jio_no_appeal');
$this->send_ban_notice($user_id, __('您的账号限制已解除。', 'jio'), __('限制解除', 'jio'), $desc);
return $this->success(['message' => __('已解封此用户', 'jio'), 'data' => ['banned' => false]]);
}
// ── 禁封 ──
$type = 2 === $ban ? \AKSRT\User\Restriction::TYPE_BAN : \AKSRT\User\Restriction::TYPE_SUSPEND;
$type = 2 === $ban ? \Jio\User\Restriction::TYPE_BAN : \Jio\User\Restriction::TYPE_SUSPEND;
$reason = isset($params['reason']) ? sanitize_text_field((string) wp_unslash($params['reason'])) : '';
if ($reason === '' || $reason === 'other') {
$reason = isset($params['reason_other']) ? sanitize_text_field((string) wp_unslash($params['reason_other'])) : '';
}
if ($reason === '') {
return $this->error(__('请输入禁封原因', 'aksrt'));
return $this->error(__('请输入禁封原因', 'jio'));
}
$time_day = isset($params['time_day']) ? (int) $params['time_day'] : 0;
@@ -203,34 +203,34 @@ trait UserControllerRestrictionOperations {
$no_appeal = !empty($params['no_appeal']);
$no_appeal_desc = isset($params['no_appeal_desc']) ? sanitize_textarea_field((string) wp_unslash($params['no_appeal_desc'])) : '';
if ($no_appeal && $no_appeal_desc === '') {
return $this->error(__('请输入禁止用户申诉的说明或原因', 'aksrt'));
return $this->error(__('请输入禁止用户申诉的说明或原因', 'jio'));
}
// 禁封说明并入原因列(与后台工单禁封表单同存储语义)。
if ($desc !== '') {
$reason .= "\n" . sprintf(__('禁封说明:%s', 'aksrt'), $desc);
$reason .= "\n" . sprintf(__('禁封说明:%s', 'jio'), $desc);
}
$result = $svc->restrict($user_id, $type, $reason, \AKSRT\User\Restriction::SCOPE_GLOBAL, $expires, get_current_user_id());
$result = $svc->restrict($user_id, $type, $reason, \Jio\User\Restriction::SCOPE_GLOBAL, $expires, get_current_user_id());
if (empty($result['success'])) {
return $this->error((string) ($result['message'] ?? __('操作失败', 'aksrt')));
return $this->error((string) ($result['message'] ?? __('操作失败', 'jio')));
}
if ($no_appeal) {
update_user_meta($user_id, '_aksrt_no_appeal', [
update_user_meta($user_id, '_jio_no_appeal', [
'desc' => $no_appeal_desc,
'at' => current_time('mysql'),
]);
}
// 站内信(后台禁封表单 / 参考主题 zib_updata_user_ban_msg 同语义)。
$notice = $type === \AKSRT\User\Restriction::TYPE_BAN
? __('您的账号已被禁封,将无法登录。', 'aksrt')
: __('您已被拉入小黑屋,发布等相关权限已被限制。', 'aksrt');
$notice = $type === \Jio\User\Restriction::TYPE_BAN
? __('您的账号已被禁封,将无法登录。', 'jio')
: __('您已被拉入小黑屋,发布等相关权限已被限制。', 'jio');
if ($time_day > 0) {
$notice .= ' ' . sprintf(__('期限:%1$d 天,%2$s 起自动解封。', 'aksrt'), $time_day, $expires);
$notice .= ' ' . sprintf(__('期限:%1$d 天,%2$s 起自动解封。', 'jio'), $time_day, $expires);
}
$this->send_ban_notice($user_id, $notice, __('账号限制', 'aksrt'), $desc);
$this->send_ban_notice($user_id, $notice, __('账号限制', 'jio'), $desc);
// 删除全部内容:全部公开内容与已审核评论移至回收站(参考主题同行为)。
if (!empty($params['delete_content'])) {
@@ -249,7 +249,7 @@ trait UserControllerRestrictionOperations {
}
}
$send_msg = 2 === $ban ? __('已禁封此用户', 'aksrt') : __('已将此用户拉入小黑屋', 'aksrt');
$send_msg = 2 === $ban ? __('已禁封此用户', 'jio') : __('已将此用户拉入小黑屋', 'jio');
return $this->success(['message' => $send_msg, 'data' => ['banned' => true]]);
}
@@ -258,13 +258,13 @@ trait UserControllerRestrictionOperations {
*/
private function send_ban_notice(int $user_id, string $notice, string $title, string $desc = ''): void
{
if (!class_exists('\AKSRT\Messages\Message')) {
if (!class_exists('\Jio\Messages\Message')) {
return;
}
$msg = $notice;
if ($desc !== '') {
$msg .= ' ' . sprintf(__('留言:%s', 'aksrt'), $desc);
$msg .= ' ' . sprintf(__('留言:%s', 'jio'), $desc);
}
\AKSRT\Messages\Message::get_instance()->send($user_id, 'system', $msg, ['title' => $title]);
\Jio\Messages\Message::get_instance()->send($user_id, 'system', $msg, ['title' => $title]);
}
}