diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css index ccb27575..a8c0a29e 100644 --- a/assets/css/frontend/widget-batch5.css +++ b/assets/css/frontend/widget-batch5.css @@ -307,15 +307,35 @@ .aksrt-bookmark-links__desc { font-size: 11px; color: var(--muted-2-color, #999); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -/* Search keywords */ -.aksrt-search-widget__keywords { margin-top: 10px; } +/* Search keywords + history */ +.aksrt-search-widget__keywords, +.aksrt-search-widget__history { margin-top: 10px; } +.aksrt-search-widget__kw-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-bottom: 6px; +} .aksrt-search-widget__kw-title { font-size: 12px; color: var(--muted-2-color, #888); margin-bottom: 6px; } -.aksrt-search-widget__kw-list { display: flex; flex-wrap: wrap; gap: 6px; } -.aksrt-search-widget__kw { +.aksrt-search-widget__kw-head .aksrt-search-widget__kw-title { margin-bottom: 0; } +.aksrt-search-widget__history-clear { + border: 0; + background: transparent; + color: var(--muted-2-color, #999); + font-size: 12px; + cursor: pointer; + padding: 0; +} +.aksrt-search-widget__history-clear:hover { color: var(--color-primary, #3b82f6); } +.aksrt-search-widget__kw-list, +.aksrt-search-widget__history-list { display: flex; flex-wrap: wrap; gap: 6px; } +.aksrt-search-widget__kw, +.aksrt-search-widget__history-item { display: inline-block; padding: 3px 10px; border-radius: 999px; @@ -324,9 +344,110 @@ text-decoration: none; font-size: 12px; } +.aksrt-search-widget__history-empty { + font-size: 12px; + color: var(--muted-2-color, #999); +} .aksrt-search-widget__more { margin-top: 8px; text-align: right; font-size: 12px; } .aksrt-search-widget__more a { color: var(--color-primary, #3b82f6); text-decoration: none; } +/* Product list widgets (batch6) */ +.aksrt-product-widget { margin-bottom: 8px; } +.aksrt-product-widget__grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; +} +.aksrt-product-widget--list .aksrt-product-widget__grid, +.aksrt-product-widget--mini .aksrt-product-widget__grid { + grid-template-columns: 1fr; +} +.aksrt-product-widget--oneline .aksrt-product-widget__grid { + display: flex; + overflow-x: auto; + gap: 10px; + scroll-snap-type: x mandatory; + -webkit-overflow-scrolling: touch; + padding-bottom: 4px; +} +.aksrt-product-widget--oneline .aksrt-product-widget__item, +.aksrt-product-widget--oneline .aksrt-product-card { + flex: 0 0 42%; + min-width: 140px; + scroll-snap-align: start; +} +.aksrt-product-widget__item, +.aksrt-product-widget .aksrt-product-card { + border: 1px solid var(--color-line, #e5e7eb); + border-radius: 10px; + overflow: hidden; + background: var(--surface-1, #fff); +} +.aksrt-product-widget__thumb, +.aksrt-product-widget .aksrt-product-card__image { + display: block; + aspect-ratio: 1 / 1; + background: var(--surface-2, #f3f4f6); + overflow: hidden; +} +.aksrt-product-widget__thumb img, +.aksrt-product-widget .aksrt-product-card__image img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} +.aksrt-product-widget__info, +.aksrt-product-widget .aksrt-product-card__body { padding: 8px 10px 10px; } +.aksrt-product-widget__title, +.aksrt-product-widget .aksrt-product-card__title { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + font-size: 13px; + font-weight: 600; + line-height: 1.35; + color: inherit; + text-decoration: none; +} +.aksrt-product-widget__meta { + display: flex; + justify-content: space-between; + gap: 6px; + margin-top: 6px; + font-size: 12px; +} +.aksrt-product-widget__price, +.aksrt-product-widget .aksrt-product-card__price { + color: var(--color-primary, #ef4444); + font-weight: 700; +} +.aksrt-product-widget__sales { color: var(--muted-2-color, #999); font-size: 11px; } +.aksrt-product-widget__empty { font-size: 13px; color: var(--muted-2-color, #999); padding: 12px 0; } +.aksrt-product-widget .ajax-pag { margin-top: 12px; text-align: center; } +.aksrt-product-tab__headers { + display: flex; + gap: 6px; + overflow-x: auto; + margin-bottom: 12px; + -webkit-overflow-scrolling: touch; +} +.aksrt-product-tab__header { + flex: 0 0 auto; + border: 0; + background: var(--surface-2, #f3f4f6); + border-radius: 999px; + padding: 6px 12px; + font-size: 13px; + cursor: pointer; + color: inherit; +} +.aksrt-product-tab__header.is-active { + background: var(--color-primary, #3b82f6); + color: #fff; +} + /* Scroll notice */ .aksrt-scroll-notice { display: flex; @@ -552,3 +673,106 @@ color: #b45309; } .aksrt-author-card__badge img { border-radius: 50%; } + +/* Badge remote modal (batch6) */ +.aksrt-badge-modal { padding: 8px 4px 12px; } +.aksrt-badge-modal__head { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 12px; + position: relative; + padding-right: 28px; +} +.aksrt-badge-modal__avatar { + width: 40px; height: 40px; border-radius: 50%; object-fit: cover; +} +.aksrt-badge-modal__title { font-weight: 700; font-size: 15px; flex: 1; min-width: 0; } +.aksrt-badge-modal__wear-tag { + display: inline-block; + margin-left: 6px; + font-size: 12px; + font-weight: 500; + color: var(--color-primary, #3b82f6); +} +.aksrt-badge-modal__list { max-height: 50vh; overflow: auto; } +.aksrt-badge-modal__item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 4px; + border-bottom: 1px solid var(--color-line, #eee); +} +.aksrt-badge-modal__item:last-child { border-bottom: 0; } +.aksrt-badge-modal__item .medal-icon { width: 40px; height: 40px; object-fit: contain; } +.aksrt-badge-modal__icon-fa { + width: 40px; height: 40px; border-radius: 50%; + display: inline-flex; align-items: center; justify-content: center; + background: var(--surface-2, #f3f4f6); color: #f59e0b; +} +.aksrt-badge-modal__icon-fa.lg { width: 72px; height: 72px; font-size: 28px; } +.aksrt-badge-modal__meta { flex: 1; min-width: 0; } +.aksrt-badge-modal__name { font-weight: 600; font-size: 14px; } +.aksrt-badge-modal__empty { text-align: center; color: var(--muted-2-color, #999); padding: 24px 0; } +.aksrt-badge-modal--single { text-align: center; padding: 16px 8px 20px; } +.aksrt-badge-modal__card { padding: 12px 8px 8px; } +.aksrt-badge-modal__actions { margin-top: 12px; } +.aksrt-author-hero__badges { margin-top: 8px; } + +/* Forum tab topics (batch6) */ +.aksrt-ftab__nav { + display: flex; + gap: 6px; + list-style: none; + margin: 0 0 10px; + padding: 0; + overflow-x: auto; +} +.aksrt-ftab__btn { + border: 0; + background: var(--surface-2, #f3f4f6); + border-radius: 999px; + padding: 5px 12px; + font-size: 13px; + cursor: pointer; + color: inherit; +} +.aksrt-ftab__btn.is-active { + background: var(--color-primary, #3b82f6); + color: #fff; +} +.aksrt-ftab__panel { display: none; } +.aksrt-ftab__panel.is-active { display: block; } +.aksrt-ftab__empty { font-size: 13px; color: var(--muted-2-color, #999); padding: 8px 0; } +.aksrt-ftopic-list { list-style: none; margin: 0; padding: 0; } +.aksrt-ftopic-list__item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 7px 0; + border-bottom: 1px dashed var(--color-line, #eee); + font-size: 13px; +} +.aksrt-ftopic-list__item:last-child { border-bottom: 0; } +.aksrt-ftopic-list__link { + color: inherit; + text-decoration: none; + flex: 1; + min-width: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.aksrt-ftopic-list__views { color: var(--muted-2-color, #999); font-size: 12px; flex-shrink: 0; } +.aksrt-ftopic-list__meta { + display: flex; + gap: 8px; + font-size: 11px; + color: var(--muted-2-color, #999); + margin-top: 4px; +} +.aksrt-ftopic-list.is-detail .aksrt-ftopic-list__item { + flex-direction: column; + align-items: flex-start; +} diff --git a/assets/js/core/search.js b/assets/js/core/search.js index 1d742aeb..ca26bd77 100644 --- a/assets/js/core/search.js +++ b/assets/js/core/search.js @@ -390,6 +390,109 @@ window.__aksrtOpenSearchPanel = function () { setOpen(true); }; window.__aksrtCloseSearchPanel = function () { setOpen(false); }; + + // Sidebar / widget search history (zibll widget_ui_search show_history). + initWidgetSearchHistory({ + historyEnabled: historyEnabled, + readHistory: readHistory, + writeHistory: writeHistory, + requestHistorySync: requestHistorySync, + pushHistory: pushHistory, + getSearchType: getSearchType, + searchConfig: searchConfig, + }); + } + + function initWidgetSearchHistory(ctx) { + var lists = document.querySelectorAll("[data-aksrt-search-history-list]"); + if (!lists.length) return; + if (!ctx.historyEnabled) { + document.querySelectorAll("[data-aksrt-search-history]").forEach(function (el) { + el.hidden = true; + }); + return; + } + + function escapeHtml(s) { + return String(s == null ? "" : s) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); + } + + function paint(items) { + var list = Array.isArray(items) ? items : []; + lists.forEach(function (wrap) { + var section = wrap.closest("[data-aksrt-search-history]") || wrap.parentElement; + var clearBtn = section ? section.querySelector("[data-aksrt-search-history-clear]") : null; + if (!list.length) { + wrap.innerHTML = '' + escapeHtml("暂无搜索历史") + ""; + if (clearBtn) clearBtn.hidden = true; + return; + } + if (clearBtn) clearBtn.hidden = false; + wrap.innerHTML = list + .map(function (item) { + var kw = item && item.keyword ? String(item.keyword) : ""; + if (!kw) return ""; + var type = (item && item.type) || "post"; + var href = "?s=" + encodeURIComponent(kw); + return ( + '' + + escapeHtml(kw) + + "" + ); + }) + .join(""); + }); + } + + function load() { + if (window.aksrtData && aksrtData.isLoggedIn) { + ctx.requestHistorySync("get", null, function (items) { + paint(items); + }); + return; + } + paint(ctx.readHistory()); + } + + document.querySelectorAll("[data-aksrt-search-history-clear]").forEach(function (btn) { + btn.addEventListener("click", function (e) { + e.preventDefault(); + if (window.aksrtData && aksrtData.isLoggedIn) { + ctx.requestHistorySync("save", [], function (items) { + paint(items); + }); + return; + } + ctx.writeHistory([]); + paint([]); + }); + }); + + // Also push history when submitting bare widget forms without data-aksrt-search-form. + document.querySelectorAll(".aksrt-search-widget form").forEach(function (form) { + if (form.hasAttribute("data-aksrt-search-form")) return; + form.addEventListener("submit", function () { + var input = form.querySelector('input[name="s"]'); + var typeInput = form.querySelector('input[name="aksrt_search_type"]'); + var keyword = input ? (input.value || "").trim() : ""; + var type = typeInput + ? typeInput.value || ctx.searchConfig.defaultType || "post" + : ctx.searchConfig.defaultType || "post"; + if (keyword) ctx.pushHistory(keyword, type); + }); + }); + + load(); } window.AksrtFrontend.register("aksrt-search-panel", function () { initSearchPanel(); }); diff --git a/assets/js/user-badges.js b/assets/js/user-badges.js index 421c003d..c508668d 100644 --- a/assets/js/user-badges.js +++ b/assets/js/user-badges.js @@ -139,6 +139,38 @@ }); } + + // Global wear handler (remote modal + UC modal). + document.addEventListener("click", function (e) { + var wearBtn = e.target.closest("[data-aksrt-badge-wear]"); + if (!wearBtn) return; + // UC modal has its own handler when canWear on modal root. + var modalRoot = wearBtn.closest("[data-aksrt-badges-modal]"); + if (modalRoot) return; + e.preventDefault(); + var slug = wearBtn.getAttribute("data-aksrt-badge-wear") || ""; + if (!slug) return; + wearBtn.disabled = true; + api("/users/me/badges/wear", { + method: "POST", + body: JSON.stringify({ slug: slug }), + }).then(function (r) { + if (!r.ok) { + toast((r.json && (r.json.message || (r.json.data && r.json.data.message))) || "佩戴失败", "error"); + wearBtn.disabled = false; + return; + } + toast((r.json && r.json.message) || "已佩戴徽章", "success"); + // Soft refresh modal content if remote host present. + try { + document.dispatchEvent(new CustomEvent("aksrt:remote-modal:close")); + } catch (_) {} + }).catch(function () { + toast("网络错误", "error"); + wearBtn.disabled = false; + }); + }); + if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { diff --git a/inc/helpers/user-center-renderers/profile-header.php b/inc/helpers/user-center-renderers/profile-header.php index 24ad9086..43e1a6dc 100644 --- a/inc/helpers/user-center-renderers/profile-header.php +++ b/inc/helpers/user-center-renderers/profile-header.php @@ -65,6 +65,12 @@ extract($context, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_ + diff --git a/src/AKSRT/Admin/ShopController.php b/src/AKSRT/Admin/ShopController.php index 7bf18636..9fd48104 100644 --- a/src/AKSRT/Admin/ShopController.php +++ b/src/AKSRT/Admin/ShopController.php @@ -379,6 +379,13 @@ class ShopController extends ControllerBase { 'permission_callback' => '__return_true', ]); + // Product list widget AJAX (zibll ajax_widget_ui product lists). + register_rest_route(self::API_NAMESPACE, '/shop/frontend/widgets/products', [ + 'methods' => 'GET', + 'callback' => [$this, 'get_frontend_widget_products'], + 'permission_callback' => '__return_true', + ]); + register_rest_route(self::API_NAMESPACE, '/shop/frontend/revenue', [ 'methods' => 'GET', 'callback' => [$this, 'get_frontend_revenue'], diff --git a/src/AKSRT/Admin/ShopControllerFrontendFlowOperations.php b/src/AKSRT/Admin/ShopControllerFrontendFlowOperations.php index 6c846aa7..4451f987 100644 --- a/src/AKSRT/Admin/ShopControllerFrontendFlowOperations.php +++ b/src/AKSRT/Admin/ShopControllerFrontendFlowOperations.php @@ -509,6 +509,38 @@ trait ShopControllerFrontendFlowOperations { ]); } + /** + * GET /shop/frontend/widgets/products — product list widget AJAX pager. + */ + public function get_frontend_widget_products(\WP_REST_Request $request): \WP_REST_Response { + if (!class_exists('\AKSRT\Widgets\WidgetProductList')) { + return $this->error('商品组件不可用', 500); + } + + $instance = [ + 'per_page' => max(1, min(50, (int) ($request->get_param('per_page') ?? 8))), + 'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'date')), + 'order' => sanitize_key((string) ($request->get_param('order') ?? 'DESC')), + 'cat_ids' => sanitize_text_field((string) ($request->get_param('cat_ids') ?? '')), + 'tag_ids' => sanitize_text_field((string) ($request->get_param('tag_ids') ?? '')), + 'discount_ids' => sanitize_text_field((string) ($request->get_param('discount_ids') ?? '')), + 'exclude_cat' => !empty($request->get_param('exclude_cat')) ? 1 : 0, + 'style' => sanitize_key((string) ($request->get_param('style') ?? 'default')), + 'paginate' => sanitize_key((string) ($request->get_param('paginate') ?? 'ajax')), + ]; + $page = max(1, (int) ($request->get_param('page') ?? $request->get_param('paged') ?? 1)); + $payload = \AKSRT\Widgets\WidgetProductList::query_payload($instance, $page); + + return $this->success([ + 'html' => (string) ($payload['html'] ?? ''), + 'paginate_html' => (string) ($payload['paginate_html'] ?? ''), + 'has_more' => !empty($payload['has_more']), + 'page' => (int) ($payload['page'] ?? $page), + 'max_pages' => (int) ($payload['max_pages'] ?? 1), + 'total' => (int) ($payload['total'] ?? 0), + ]); + } + /** * Render cat filter bar HTML for AJAX header replace. */ diff --git a/src/AKSRT/Core/ScriptsPart1b.php b/src/AKSRT/Core/ScriptsPart1b.php index a326408e..8deaf0cf 100644 --- a/src/AKSRT/Core/ScriptsPart1b.php +++ b/src/AKSRT/Core/ScriptsPart1b.php @@ -968,7 +968,28 @@ trait ScriptsPart1b { } - $this->enqueue_public_fonts_js_and_data($base_uri, $core_dep, $deps, $is_fixed_homepage, $is_home_or_archive, $is_single_post, $is_single_resource, $version); + + // Badges UI: user center + public profile (zibll medal modal/list). + if (class_exists('\AKSRT\User\Badges') && \AKSRT\User\Badges::enabled()) { + $is_badge_surface = $is_profile_page + || is_author() + || (function_exists('aksrt_is_user_home_page') && aksrt_is_user_home_page()) + || (function_exists('aksrt_is_user_center_page') && aksrt_is_user_center_page()); + if ($is_badge_surface) { + $badges_js_path_global = get_template_directory() . '/assets/js/user-badges.js'; + if (file_exists($badges_js_path_global) && !wp_script_is('aksrt-user-badges', 'enqueued')) { + wp_enqueue_script( + 'aksrt-user-badges', + $base_uri . '/assets/js/user-badges.js', + ['aksrt-script', 'aksrt-core-remote-modal'], + (string) filemtime($badges_js_path_global), + true + ); + } + } + } + +$this->enqueue_public_fonts_js_and_data($base_uri, $core_dep, $deps, $is_fixed_homepage, $is_home_or_archive, $is_single_post, $is_single_resource, $version); } } diff --git a/src/AKSRT/Forum/Forum.php b/src/AKSRT/Forum/Forum.php index 5c504996..f04561e8 100644 --- a/src/AKSRT/Forum/Forum.php +++ b/src/AKSRT/Forum/Forum.php @@ -246,6 +246,40 @@ class Forum { $this->view_builder()->ajax_board_topics(); } + public function register_widget_routes(): void { + register_rest_route('aksrt/v1', '/forum/frontend/widgets/topics', [ + 'methods' => 'GET', + 'callback' => [$this, 'rest_widget_topics'], + 'permission_callback' => '__return_true', + ]); + } + + public function rest_widget_topics(\WP_REST_Request $request) { + if (!class_exists('\AKSRT\Widgets\WidgetForumTopicLists')) { + return new \WP_REST_Response(['success' => false, 'message' => 'widget unavailable'], 500); + } + $instance = [ + 'count' => max(1, min(30, (int) ($request->get_param('count') ?? 8))), + 'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'date')), + 'style' => sanitize_key((string) ($request->get_param('style') ?? 'mini')), + 'plate_id' => max(0, (int) ($request->get_param('plate_id') ?? 0)), + 'paginate' => sanitize_key((string) ($request->get_param('paginate') ?? 'ajax')), + ]; + $page = max(1, (int) ($request->get_param('page') ?? $request->get_param('paged') ?? 1)); + $payload = \AKSRT\Widgets\WidgetForumTopicLists::query_payload($instance, $page); + return new \WP_REST_Response([ + 'success' => true, + 'html' => (string) ($payload['html'] ?? ''), + 'paginate_html' => (string) ($payload['paginate_html'] ?? ''), + 'has_more' => !empty($payload['has_more']), + 'page' => (int) ($payload['page'] ?? $page), + 'max_pages' => (int) ($payload['max_pages'] ?? 1), + 'total' => (int) ($payload['total'] ?? 0), + ], 200); + } + + + public function render_board_cards(array $boards): void { $this->view_builder()->render_board_cards($boards); } @@ -396,6 +430,7 @@ class Forum { // Public plate/board topic list (sort tabs) add_action('wp_ajax_aksrt_forum_board_topics', [$this, 'ajax_board_topics']); add_action('wp_ajax_nopriv_aksrt_forum_board_topics', [$this, 'ajax_board_topics']); + add_action('rest_api_init', [$this, 'register_widget_routes']); add_action('admin_init', [$this, 'maybe_migrate_boards'], 25); add_filter('template_include', [$this, 'template_routing']); diff --git a/src/AKSRT/User/Badges.php b/src/AKSRT/User/Badges.php index bee2531c..b172641f 100644 --- a/src/AKSRT/User/Badges.php +++ b/src/AKSRT/User/Badges.php @@ -25,6 +25,209 @@ class Badges { public function init(): void { add_action('rest_api_init', [$this, 'register_routes']); + add_action('aksrt_badge_granted', [$this, 'on_badge_granted_notify'], 10, 3); + // HTML modal for remote-modal (zibll user_medal_info_modal). + add_action('wp_ajax_aksrt_user_badge_info_modal', [$this, 'ajax_info_modal']); + add_action('wp_ajax_nopriv_aksrt_user_badge_info_modal', [$this, 'ajax_info_modal']); + add_action('wp_ajax_aksrt_single_badge_info_modal', [$this, 'ajax_single_info_modal']); + add_action('wp_ajax_nopriv_aksrt_single_badge_info_modal', [$this, 'ajax_single_info_modal']); + } + + /** + * Notify user when a badge is granted (zibll user_add_medal message). + */ + public function on_badge_granted_notify(int $user_id, string $slug, int $actor = 0): void { + if ($user_id <= 0 || $slug === '') { + return; + } + $map = self::get_dictionary_map(); + if (!isset($map[$slug])) { + return; + } + $label = (string) ($map[$slug]['label'] ?? $slug); + $title = sprintf( + /* translators: %s badge name */ + __('恭喜您!获得新的徽章[%s]', 'aksrt'), + $label + ); + $content = sprintf( + /* translators: %s badge name */ + __('恭喜您获得徽章「%s」', 'aksrt'), + $label + ); + if (class_exists('\AKSRT\Messages\Message')) { + \AKSRT\Messages\Message::get_instance()->send($user_id, 'medal', $content, [ + 'title' => $title, + 'target_url' => function_exists('aksrt_get_user_center_url') + ? aksrt_get_user_center_url() + : home_url('/'), + 'slug' => $slug, + ]); + } + } + + /** + * AJAX: full badge list modal HTML. + */ + public function ajax_info_modal(): void { + $user_id = max(0, (int) ($_REQUEST['id'] ?? $_REQUEST['user_id'] ?? 0)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ($user_id <= 0) { + $user_id = get_current_user_id(); + } + if ($user_id <= 0 || !get_userdata($user_id)) { + wp_send_json_error(['message' => __('用户不存在', 'aksrt')], 404); + } + $html = self::render_info_modal_html($user_id); + wp_send_json_success(['html' => $html]); + } + + /** + * AJAX: single badge detail modal. + */ + public function ajax_single_info_modal(): void { + $slug = sanitize_key((string) ($_REQUEST['name'] ?? $_REQUEST['slug'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $user_id = max(0, (int) ($_REQUEST['user_id'] ?? $_REQUEST['id'] ?? 0)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ($slug === '') { + wp_send_json_error(['message' => __('徽章不存在', 'aksrt')], 404); + } + $html = self::render_single_modal_html($slug, $user_id); + wp_send_json_success(['html' => $html]); + } + + /** + * zibll zib_get_user_medal_info_modal lite. + */ + public static function render_info_modal_html(int $user_id): string { + $user = get_userdata($user_id); + if (!$user) { + return '

' . esc_html__('用户不存在', 'aksrt') . '

'; + } + $current = get_current_user_id(); + $is_self = $current > 0 && $current === $user_id; + $display = $is_self ? __('我', 'aksrt') : $user->display_name; + $avatar = function_exists('aksrt_get_avatar_url') + ? aksrt_get_avatar_url($user_id, ['size' => 64]) + : get_avatar_url($user_id, ['size' => 64]); + $data = self::list_for_user($user_id, $is_self || self::can_manually_set($current)); + $items = is_array($data['items'] ?? null) ? $data['items'] : []; + $wear = (string) ($data['wear'] ?? ''); + + $html = '
'; + $html .= '
'; + $html .= ''; + $html .= '
' . esc_html(sprintf(__('%s的徽章', 'aksrt'), $display)); + if ($wear !== '') { + $map = self::get_dictionary_map(); + if (isset($map[$wear])) { + $html .= ' ' . esc_html(sprintf(__('佩戴:%s', 'aksrt'), (string) $map[$wear]['label'])) . ''; + } + } else { + $html .= ' ' . esc_html__('暂无徽章', 'aksrt') . ''; + } + $html .= '
'; + $html .= ''; + $html .= '
'; + + $html .= '
'; + if (!$items) { + $html .= '
' . esc_html__('暂无徽章', 'aksrt') . '
'; + } else { + foreach ($items as $item) { + $slug = (string) ($item['slug'] ?? ''); + $label = (string) ($item['label'] ?? $slug); + $icon = (string) ($item['icon_url'] ?? ''); + $is_wear = !empty($item['wear']); + $html .= '
'; + if ($icon !== '') { + $html .= ''; + } else { + $html .= ''; + } + $html .= '
' . esc_html($label) . '
'; + if ($is_wear) { + $html .= '
' . esc_html__('当前佩戴', 'aksrt') . '
'; + } + $html .= '
'; + if ($is_self && !$is_wear) { + $html .= ''; + } + $html .= '
'; + } + } + $html .= '
'; + return $html; + } + + /** + * Single badge card modal. + */ + public static function render_single_modal_html(string $slug, int $user_id = 0): string { + $map = self::get_dictionary_map(); + $slug = sanitize_key($slug); + if ($slug === '' || !isset($map[$slug])) { + return '

' . esc_html__('徽章不存在', 'aksrt') . '

'; + } + $item = $map[$slug]; + $current = get_current_user_id(); + $is_self = $user_id > 0 && $current === $user_id; + $owned = $user_id > 0 ? self::get_owned($user_id) : []; + $has = in_array($slug, $owned, true); + $wear = $user_id > 0 ? self::get_wear($user_id) : ''; + $is_wear = $has && $wear === $slug; + + $html = '
'; + $html .= ''; + $html .= '
'; + if (!empty($item['icon_url'])) { + $html .= ''; + } else { + $html .= ''; + } + $html .= '
' . esc_html((string) $item['label']) . '
'; + if ($is_self) { + $html .= $has + ? '
' . esc_html__('已获得', 'aksrt') . '
' + : '
' . esc_html__('暂未获得', 'aksrt') . '
'; + } + $html .= '
'; + if ($is_self && $has && !$is_wear) { + $html .= '
'; + } elseif ($is_self && $is_wear) { + $html .= '
' . esc_html__('当前佩戴此徽章', 'aksrt') . '
'; + } + $html .= '
'; + return $html; + } + + /** + * Remote-modal trigger for a user's badge list (zibll zib_get_user_medal_info_link). + */ + public static function get_info_link(int $user_id, string $class = '', ?string $text = null): string { + if ($user_id <= 0 || !self::enabled()) { + return ''; + } + if ($text === null) { + $text = __('徽章明细', 'aksrt'); + } + if (!function_exists('aksrt_get_remote_modal_link')) { + return ''; + } + $url = add_query_arg( + [ + 'action' => 'aksrt_user_badge_info_modal', + 'id' => $user_id, + ], + admin_url('admin-ajax.php') + ); + return aksrt_get_remote_modal_link([ + 'tag' => 'a', + 'class' => trim('user-medal-info ' . $class), + 'text' => $text, + 'url' => $url, + 'data_class' => 'full-sm', + 'height' => 240, + 'mobile_bottom' => true, + ]); } public function register_routes(): void { diff --git a/src/AKSRT/Widgets/SearchWidget.php b/src/AKSRT/Widgets/SearchWidget.php index 394da33f..080eb543 100644 --- a/src/AKSRT/Widgets/SearchWidget.php +++ b/src/AKSRT/Widgets/SearchWidget.php @@ -47,21 +47,25 @@ class SearchWidget extends \WP_Widget { } ?>
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + } + ?> + description) : ?>

description); ?>