feat(identity): author card, per-user tip, comment caps, task status

Author card uses configurable limit/orderby/style and self CTAs.
Tip is per-user weixin/alipay QR modal (no site-global image).
Comment action permissions honor user_caps matrix.
Free points tasks expose done_today/passive status + better comment deep link.
audit-admin-contracts errors=0.
This commit is contained in:
2026-07-17 01:21:35 +08:00
parent b4d04ae346
commit 8d4e94a02c
10 changed files with 374 additions and 148 deletions
@@ -49,10 +49,11 @@ export const pageSchema = {
post_article_video_cover_scale: 56, post_article_video_cover_scale: 56,
item_heading_bold: false, item_heading_bold: false,
post_button_toptext: '喜欢就支持一下吧', post_button_toptext: '喜欢就支持一下吧',
post_rewards_enabled: false, post_rewards_s: false,
post_rewards_text: '', post_rewards_text: '赞赏',
post_rewards_image: '', post_author_card_limit: 6,
post_rewards_btn_text: '赞赏', post_author_card_orderby: 'views',
post_author_card_style: 'card',
article_maxheight_kg: false, article_maxheight_kg: false,
article_maxheight: 1000, article_maxheight: 1000,
}, },
@@ -71,6 +72,17 @@ export const pageSchema = {
{ key: 'breadcrumbs_single_text', label: '末项显示「正文」', type: 'switch', default: true, dependsOn: { key: 'breadcrumbs_single_s', value: true }, help: '关闭后末项显示文章标题' }, { key: 'breadcrumbs_single_text', label: '末项显示「正文」', type: 'switch', default: true, dependsOn: { key: 'breadcrumbs_single_s', value: true }, help: '关闭后末项显示文章标题' },
{ key: 'post_prevnext_s', label: '上一篇/下一篇', type: 'switch', default: true }, { key: 'post_prevnext_s', label: '上一篇/下一篇', type: 'switch', default: true },
{ key: 'post_authordesc_s', label: '作者简介', type: 'switch', default: true }, { key: 'post_authordesc_s', label: '作者简介', type: 'switch', default: true },
{ key: 'post_author_card_limit', label: '作者卡文章数', type: 'number', default: 6, min: 0, max: 12, dependsOn: { key: 'post_authordesc_s', value: true } },
{ key: 'post_author_card_orderby', label: '作者卡文章排序', type: 'select', default: 'views', options: [
{ label: '浏览量', value: 'views' },
{ label: '发布时间', value: 'date' },
{ label: '更新时间', value: 'modified' },
{ label: '评论数', value: 'comment_count' },
], dependsOn: { key: 'post_authordesc_s', value: true } },
{ key: 'post_author_card_style', label: '作者卡文章样式', type: 'radio', default: 'card', options: [
{ label: '卡片', value: 'card' },
{ label: '列表', value: 'list' },
], dependsOn: { key: 'post_authordesc_s', value: true } },
{ key: 'post_single_hide_time_s', label: '隐藏文章时间', type: 'switch', default: false }, { key: 'post_single_hide_time_s', label: '隐藏文章时间', type: 'switch', default: false },
], ],
}, },
@@ -137,10 +149,8 @@ export const pageSchema = {
{ key: 'post_copyright_enabled', label: '版权信息', type: 'switch', default: true }, { key: 'post_copyright_enabled', label: '版权信息', type: 'switch', default: true },
{ key: 'post_copyright_text', label: '版权默认文案', type: 'textarea', default: '', help: '留空则自动生成"本文由 XX 原创发布"' }, { key: 'post_copyright_text', label: '版权默认文案', type: 'textarea', default: '', help: '留空则自动生成"本文由 XX 原创发布"' },
{ key: 'post_button_toptext', label: '按钮上方文案', type: 'input', default: '喜欢就支持一下吧', help: '显示在点赞/打赏/分享按钮上方' }, { key: 'post_button_toptext', label: '按钮上方文案', type: 'input', default: '喜欢就支持一下吧', help: '显示在点赞/打赏/分享按钮上方' },
{ key: 'post_rewards_enabled', label: '打赏', type: 'switch', default: false }, { key: 'post_rewards_s', label: '用户打赏', type: 'switch', default: false, help: '开启后用户可配置微信/支付宝收款码;无码则不显示按钮。' },
{ key: 'post_rewards_btn_text', label: '打赏按钮文案', type: 'input', default: '赞赏', dependsOn: { key: 'post_rewards_enabled', value: true } }, { key: 'post_rewards_text', label: '打赏按钮文案', type: 'input', default: '赞赏', dependsOn: { key: 'post_rewards_s', value: true } },
{ key: 'post_rewards_text', label: '打赏说明', type: 'input', default: '', dependsOn: { key: 'post_rewards_enabled', value: true } },
{ key: 'post_rewards_image', label: '打赏二维码 URL', type: 'input', default: '', dependsOn: { key: 'post_rewards_enabled', value: true } },
{ key: 'article_maxheight_kg', label: '正文限高', type: 'switch', default: false, help: '开启后长文默认折叠,可展开全文' }, { key: 'article_maxheight_kg', label: '正文限高', type: 'switch', default: false, help: '开启后长文默认折叠,可展开全文' },
{ key: 'article_maxheight', label: '限高像素', type: 'number', default: 1000, min: 200, max: 5000, dependsOn: { key: 'article_maxheight_kg', value: true } }, { key: 'article_maxheight', label: '限高像素', type: 'number', default: 1000, min: 200, max: 5000, dependsOn: { key: 'article_maxheight_kg', value: true } },
], ],
+28 -74
View File
@@ -1,8 +1,5 @@
/** /**
* Reward (打赏) — dedicated module. * Reward (打赏) — per-user QR modal openers.
*
* Handles the reward modal: amount selection, custom amount input,
* QR code display, and open/close interaction.
* *
* @package AKSRT * @package AKSRT
*/ */
@@ -10,95 +7,52 @@
'use strict'; 'use strict';
function initReward() { function initReward() {
var btn = document.getElementById('action-reward'); function closeAll() {
if (!btn) return; document.querySelectorAll('.reward-modal.is-open').forEach(function (m) {
m.classList.remove('is-open');
m.setAttribute('aria-hidden', 'true');
});
document.body.style.overflow = '';
}
var modal = document.getElementById('reward-modal'); function openForUser(userId) {
if (!modal) return; var id = String(userId || '');
var modal = id
var closeBtn = modal.querySelector('.reward-modal__close'); ? document.getElementById('reward-modal-' + id)
var overlay = modal.querySelector('.reward-modal__overlay'); : document.querySelector('.reward-modal');
var amountBtns = modal.querySelectorAll('.reward-amounts__btn'); if (!modal) return;
var customWrap = modal.querySelector('.reward-custom'); closeAll();
var customInput = modal ? modal.querySelector('.reward-custom__input') : null;
var customConfirm = modal ? modal.querySelector('.reward-custom__confirm') : null;
var selectedAmount = 0;
/* ---- open / close ---- */
function openReward() {
modal.classList.add('is-open'); modal.classList.add('is-open');
modal.setAttribute('aria-hidden', 'false');
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
} }
function closeReward() {
modal.classList.remove('is-open');
document.body.style.overflow = '';
// reset selection
selectedAmount = 0;
amountBtns.forEach(function (b) { b.classList.remove('is-active'); });
if (customInput) customInput.value = '';
}
btn.addEventListener('click', openReward);
document.querySelectorAll('[data-aksrt-reward-open]').forEach(function (el) { document.querySelectorAll('[data-aksrt-reward-open]').forEach(function (el) {
if (el === btn) return;
el.addEventListener('click', function (e) { el.addEventListener('click', function (e) {
e.preventDefault(); e.preventDefault();
openReward(); openForUser(el.getAttribute('data-user-id') || '');
});
});
document.querySelectorAll('[data-aksrt-reward-close]').forEach(function (el) {
el.addEventListener('click', function (e) {
e.preventDefault();
closeAll();
}); });
}); });
if (closeBtn) closeBtn.addEventListener('click', closeReward);
if (overlay) overlay.addEventListener('click', closeReward);
document.addEventListener('keydown', function (e) { document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && modal.classList.contains('is-open')) closeReward(); if (e.key === 'Escape') closeAll();
}); });
/* ---- amount selection ---- */
amountBtns.forEach(function (b) {
b.addEventListener('click', function () {
amountBtns.forEach(function (x) { x.classList.remove('is-active'); });
b.classList.add('is-active');
selectedAmount = parseInt(b.dataset.amount || '0', 10);
// clear custom input when preset is chosen
if (customInput) customInput.value = '';
});
});
/* ---- custom amount ---- */
if (customInput) {
customInput.addEventListener('focus', function () {
amountBtns.forEach(function (x) { x.classList.remove('is-active'); });
selectedAmount = 0;
});
customInput.addEventListener('input', function () {
// strip non-digits
customInput.value = customInput.value.replace(/[^\d]/g, '');
});
}
if (customConfirm) {
customConfirm.addEventListener('click', function () {
var val = customInput ? parseInt(customInput.value, 10) : 0;
if (val > 0) {
selectedAmount = val;
amountBtns.forEach(function (x) { x.classList.remove('is-active'); });
}
});
}
} }
/* ---- register with AksrtFrontend lifecycle ---- */
if (window.AksrtFrontend && typeof window.AksrtFrontend.register === 'function') { if (window.AksrtFrontend && typeof window.AksrtFrontend.register === 'function') {
window.AksrtFrontend.register('aksrt-reward', function () { window.AksrtFrontend.register('aksrt-reward', function () {
initReward(); initReward();
}); });
} else if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initReward);
} else { } else {
// fallback: run on DOMContentLoaded initReward();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initReward);
} else {
initReward();
}
} }
})(); })();
+1
View File
@@ -45,6 +45,7 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-coupon-order.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-credits.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-credits.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-null.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-null.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-identity.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-identity.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-rewards.php';
require_once AKSRT_DIR . '/inc/helpers/menu-decoration.php'; require_once AKSRT_DIR . '/inc/helpers/menu-decoration.php';
require_once AKSRT_DIR . '/inc/helpers/class-aksrt-nav-walker.php'; require_once AKSRT_DIR . '/inc/helpers/class-aksrt-nav-walker.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-transactions.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-transactions.php';
@@ -75,6 +75,17 @@ class AKSRT_Admin_Schema_Post_List {
['key' => 'breadcrumbs_single_text', 'label' => '末项显示「正文」', 'type' => 'switch', 'default' => true, 'depends_on' => 'breadcrumbs_single_s', 'description' => '关闭后末项显示文章标题'], ['key' => 'breadcrumbs_single_text', 'label' => '末项显示「正文」', 'type' => 'switch', 'default' => true, 'depends_on' => 'breadcrumbs_single_s', 'description' => '关闭后末项显示文章标题'],
['key' => 'post_prevnext_s', 'label' => '上一篇/下一篇', 'type' => 'switch', 'default' => true], ['key' => 'post_prevnext_s', 'label' => '上一篇/下一篇', 'type' => 'switch', 'default' => true],
['key' => 'post_authordesc_s', 'label' => '作者简介', 'type' => 'switch', 'default' => true], ['key' => 'post_authordesc_s', 'label' => '作者简介', 'type' => 'switch', 'default' => true],
['key' => 'post_author_card_limit', 'label' => '作者卡文章数', 'type' => 'number', 'default' => 6, 'min' => 0, 'max' => 12],
['key' => 'post_author_card_orderby', 'label' => '作者卡文章排序', 'type' => 'select', 'default' => 'views', 'options' => [
['label' => '浏览量', 'value' => 'views'],
['label' => '发布时间', 'value' => 'date'],
['label' => '更新时间', 'value' => 'modified'],
['label' => '评论数', 'value' => 'comment_count'],
]],
['key' => 'post_author_card_style', 'label' => '作者卡文章样式', 'type' => 'radio', 'default' => 'card', 'options' => [
['label' => '卡片', 'value' => 'card'],
['label' => '列表', 'value' => 'list'],
]],
['key' => 'post_single_hide_time_s', 'label' => '隐藏文章时间', 'type' => 'switch', 'default' => false], ['key' => 'post_single_hide_time_s', 'label' => '隐藏文章时间', 'type' => 'switch', 'default' => false],
]], ]],
['key' => 'cover', 'title' => '封面展示', 'fields' => [ ['key' => 'cover', 'title' => '封面展示', 'fields' => [
@@ -127,8 +138,8 @@ class AKSRT_Admin_Schema_Post_List {
['key' => 'post_copyright_enabled', 'label' => '版权信息', 'type' => 'switch', 'default' => true], ['key' => 'post_copyright_enabled', 'label' => '版权信息', 'type' => 'switch', 'default' => true],
['key' => 'post_copyright_text', 'label' => '版权默认文案', 'type' => 'textarea', 'default' => '', 'description' => '留空则自动生成"本文由 XX 原创发布"'], ['key' => 'post_copyright_text', 'label' => '版权默认文案', 'type' => 'textarea', 'default' => '', 'description' => '留空则自动生成"本文由 XX 原创发布"'],
['key' => 'post_wechat_share', 'label' => '微信分享', 'type' => 'switch', 'default' => false], ['key' => 'post_wechat_share', 'label' => '微信分享', 'type' => 'switch', 'default' => false],
['key' => 'post_rewards_enabled', 'label' => '打赏功能', 'type' => 'switch', 'default' => false], ['key' => 'post_rewards_s', 'label' => '用户打赏', 'type' => 'switch', 'default' => false],
['key' => 'post_rewards_text', 'label' => '打赏文案', 'type' => 'input', 'default' => ''], ['key' => 'post_rewards_text', 'label' => '打赏按钮文案', 'type' => 'input', 'default' => '赞赏'],
['key' => 'post_button_toptext', 'label' => '按钮上方文案', 'type' => 'input', 'default' => '喜欢就支持一下吧'], ['key' => 'post_button_toptext', 'label' => '按钮上方文案', 'type' => 'input', 'default' => '喜欢就支持一下吧'],
]], ]],
['key' => 'content', 'title' => '内容处理', 'fields' => [ ['key' => 'content', 'title' => '内容处理', 'fields' => [
+18 -6
View File
@@ -180,9 +180,12 @@ class AKSRT_Points_Free {
* @return list<array{key:string,title:string,desc:string,points:int,enabled:bool}> * @return list<array{key:string,title:string,desc:string,points:int,enabled:bool}>
*/ */
public static function task_list_for_user(int $user_id = 0): array { public static function task_list_for_user(int $user_id = 0): array {
unset($user_id); // reserved for per-user progress later
$cfg = self::settings(); $cfg = self::settings();
$list = []; $list = [];
$login_done = false;
if ($user_id > 0) {
$login_done = (string) get_user_meta($user_id, self::META_LOGIN, true) === self::today_key();
}
foreach (self::catalog() as $key => $meta) { foreach (self::catalog() as $key => $meta) {
$pts = (int) ($cfg['tasks'][$key] ?? 0); $pts = (int) ($cfg['tasks'][$key] ?? 0);
// Hide sign_up from daily task list (one-time); still grant on register. // Hide sign_up from daily task list (one-time); still grant on register.
@@ -192,12 +195,21 @@ class AKSRT_Points_Free {
if ($pts <= 0) { if ($pts <= 0) {
continue; continue;
} }
$status = 'todo';
if (in_array($key, ['login', 'sign_in'], true) && $login_done) {
$status = 'done_today';
} elseif (in_array($key, ['post_like', 'post_favorite', 'followed', 'comment_like', 'bbs_essence', 'bbs_adopt', 'bbs_posts_hot', 'bbs_comment_hot', 'bbs_score_extra'], true)) {
$status = 'passive';
}
$list[] = [ $list[] = [
'key' => $key, 'key' => $key,
'title' => $meta[0], 'title' => $meta[0],
'desc' => $meta[1], 'desc' => $meta[1],
'points' => $pts, 'points' => $pts,
'enabled' => true, 'enabled' => true,
'status' => $status,
'progress' => null,
'target' => null,
]; ];
} }
return $list; return $list;
@@ -237,10 +237,20 @@ class AKSRT_Settings_Post_Display {
$authordesc = !isset($settings['post_authordesc_s']) || !empty($settings['post_authordesc_s']); $authordesc = !isset($settings['post_authordesc_s']) || !empty($settings['post_authordesc_s']);
$hide_time = !empty($settings['post_single_hide_time_s']); $hide_time = !empty($settings['post_single_hide_time_s']);
$share_enabled = !isset($settings['share_s']) || !empty($settings['share_s']); $share_enabled = !isset($settings['share_s']) || !empty($settings['share_s']);
$rewards_enabled = !empty($settings['post_rewards_enabled']) || !empty($settings['post_rewards_s']); $rewards_enabled = !empty($settings['post_rewards_s']) || !empty($settings['post_rewards_enabled']);
$rewards_image = (string) ($settings['post_rewards_image'] ?? $settings['post_rewards_qrcode'] ?? $settings['post_rewards_img'] ?? '');
$rewards_text = sanitize_text_field((string) ($settings['post_rewards_text'] ?? '')); $rewards_text = sanitize_text_field((string) ($settings['post_rewards_text'] ?? ''));
$rewards_btn_text = sanitize_text_field((string) ($settings['post_rewards_btn_text'] ?? '赞赏')); if ($rewards_text === '') {
$rewards_text = '赞赏';
}
$author_card_limit = max(0, min(12, (int) ($settings['post_author_card_limit'] ?? 6)));
$author_card_orderby = sanitize_key((string) ($settings['post_author_card_orderby'] ?? 'views'));
if (!in_array($author_card_orderby, ['views', 'date', 'modified', 'comment_count'], true)) {
$author_card_orderby = 'views';
}
$author_card_style = sanitize_key((string) ($settings['post_author_card_style'] ?? 'card'));
if (!in_array($author_card_style, ['card', 'list'], true)) {
$author_card_style = 'card';
}
$maxheight_enabled = !empty($settings['article_maxheight_kg']) $maxheight_enabled = !empty($settings['article_maxheight_kg'])
|| !empty($settings['post_article_maxheight_kg']) || !empty($settings['post_article_maxheight_kg'])
|| !empty($settings['post_maxheight_kg']); || !empty($settings['post_maxheight_kg']);
@@ -318,11 +328,15 @@ class AKSRT_Settings_Post_Display {
// Canonical + legacy share. // Canonical + legacy share.
'share_enabled' => $share_enabled, 'share_enabled' => $share_enabled,
'share_s' => $share_enabled, 'share_s' => $share_enabled,
// Rewards (Zibll post_rewards_s). // Author card posts (Zibll user_card_box defaults).
'author_card_limit' => $author_card_limit,
'author_card_orderby' => $author_card_orderby,
'author_card_style' => $author_card_style,
// Rewards (Zibll post_rewards_s) — per-user QR only.
'rewards_enabled' => $rewards_enabled, 'rewards_enabled' => $rewards_enabled,
'rewards_image' => $rewards_image, 'post_rewards_s' => $rewards_enabled,
'rewards_text' => $rewards_text, 'rewards_text' => $rewards_text,
'rewards_btn_text' => $rewards_btn_text !== '' ? $rewards_btn_text : '赞赏', 'rewards_btn_text' => $rewards_text,
// Content max height (Zibll article_maxheight_kg / article_maxheight). // Content max height (Zibll article_maxheight_kg / article_maxheight).
'maxheight_enabled' => $maxheight_enabled, 'maxheight_enabled' => $maxheight_enabled,
'maxheight' => $maxheight, 'maxheight' => $maxheight,
+52 -21
View File
@@ -835,28 +835,59 @@ if (!function_exists('aksrt_comment_action_permissions')) :
* @return array{can_edit:bool,can_delete:bool,can_moderate:bool,can_pin:bool} * @return array{can_edit:bool,can_delete:bool,can_moderate:bool,can_pin:bool}
*/ */
function aksrt_comment_action_permissions($comment): array { function aksrt_comment_action_permissions($comment): array {
$comment = $comment instanceof WP_Comment ? $comment : get_comment($comment); $comment = $comment instanceof WP_Comment ? $comment : get_comment($comment);
$none = ['can_edit' => false, 'can_delete' => false, 'can_moderate' => false, 'can_pin' => false]; $none = ['can_edit' => false, 'can_delete' => false, 'can_moderate' => false, 'can_pin' => false];
if (!($comment instanceof WP_Comment) || !is_user_logged_in()) { if (!($comment instanceof WP_Comment) || !is_user_logged_in()) {
return $none; return $none;
}
$uid = get_current_user_id();
$is_wp_mod = current_user_can('moderate_comments') || current_user_can('manage_options');
$is_owner = (int) $comment->user_id > 0 && (int) $comment->user_id === $uid;
$post_id = (int) $comment->comment_post_ID;
$post = $post_id > 0 ? get_post($post_id) : null;
$is_post_author = $post instanceof WP_Post && (int) $post->post_author === $uid;
$ctx = $post_id > 0 ? $post_id : null;
$can = static function (string $cap) use ($uid, $ctx): bool {
if (function_exists('aksrt_user_can')) {
return aksrt_user_can($cap, $uid, $ctx);
}
if (class_exists('AKSRT_Settings_Capabilities')) {
return AKSRT_Settings_Capabilities::user_can($cap, $uid, $ctx);
}
return false;
};
$edit_window = 10;
if (class_exists('AKSRT_Settings')) {
$display = AKSRT_Settings::get_comment_display_settings();
$edit_window = (int) ($display['edit_window'] ?? 10);
}
$within_window = $edit_window > 0
&& (time() - strtotime((string) $comment->comment_date_gmt)) < ($edit_window * MINUTE_IN_SECONDS);
$can_edit_own = $is_owner && $within_window && $can('comment_edit');
$can_edit_others = (!$is_owner) && (
($is_post_author && $can('comment_edit_my_post'))
|| $can('comment_edit_other')
);
$can_delete_own = $is_owner && $can('comment_delete');
$can_delete_others = (!$is_owner) && (
($is_post_author && $can('comment_delete_my_post'))
|| $can('comment_delete_other')
);
$can_pin = (int) $comment->comment_parent === 0 && (
($is_post_author && $can('comment_set_topping_my_post'))
|| $can('comment_set_topping_other')
);
return [
'can_edit' => $is_wp_mod || $can_edit_own || $can_edit_others,
'can_delete' => $is_wp_mod || $can_delete_own || $can_delete_others,
'can_moderate' => $is_wp_mod || $can('comment_audit_other') || ($is_post_author && $can('comment_audit_my_post')),
'can_pin' => $is_wp_mod || $can_pin,
];
} }
$is_moderator = current_user_can('moderate_comments');
$is_owner = (int) $comment->user_id > 0 && (int) $comment->user_id === get_current_user_id();
// Owner self-edit window (minutes); moderators bypass the window.
$edit_window = 10;
if (class_exists('AKSRT_Settings')) {
$display = AKSRT_Settings::get_comment_display_settings();
$edit_window = (int) ($display['edit_window'] ?? 10);
}
$within_window = $edit_window > 0
&& (time() - strtotime((string) $comment->comment_date_gmt)) < ($edit_window * MINUTE_IN_SECONDS);
return [
'can_edit' => $is_moderator || ($is_owner && $within_window),
'can_delete' => $is_moderator || $is_owner,
'can_moderate' => $is_moderator,
'can_pin' => $is_moderator && (int) $comment->comment_parent === 0,
];
}
endif; endif;
if (!function_exists('aksrt_is_comment_pinned')) : if (!function_exists('aksrt_is_comment_pinned')) :
/** /**
+113
View File
@@ -0,0 +1,113 @@
<?php
/**
* Per-user tip (打赏) helpers — Zibll post_rewards_s + user QR parity.
*
* Meta: aksrt_rewards_weixin, aksrt_rewards_alipay (attachment id or URL).
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
if (!function_exists('aksrt_rewards_enabled')) {
function aksrt_rewards_enabled(): bool {
if (!class_exists('AKSRT_Settings') || !method_exists('AKSRT_Settings', 'get_post_page_settings')) {
return false;
}
$s = AKSRT_Settings::get_post_page_settings();
return !empty($s['rewards_enabled']) || !empty($s['post_rewards_s']);
}
}
if (!function_exists('aksrt_get_user_rewards_img_urls')) {
/**
* @return array{weixin:string,alipay:string}
*/
function aksrt_get_user_rewards_img_urls(int $user_id): array {
$out = ['weixin' => '', 'alipay' => ''];
if ($user_id <= 0) {
return $out;
}
foreach (['weixin' => 'aksrt_rewards_weixin', 'alipay' => 'aksrt_rewards_alipay'] as $key => $meta) {
$raw = get_user_meta($user_id, $meta, true);
if (is_numeric($raw) && (int) $raw > 0) {
$url = wp_get_attachment_image_url((int) $raw, 'full');
$out[$key] = is_string($url) ? $url : '';
} else {
$out[$key] = esc_url_raw((string) $raw);
}
}
return $out;
}
}
if (!function_exists('aksrt_user_has_rewards_qr')) {
function aksrt_user_has_rewards_qr(int $user_id): bool {
$urls = aksrt_get_user_rewards_img_urls($user_id);
return $urls['weixin'] !== '' || $urls['alipay'] !== '';
}
}
if (!function_exists('aksrt_get_rewards_button')) {
function aksrt_get_rewards_button(int $user_id, string $class = 'but jb-blue ml10 pw-1em'): string {
if ($user_id <= 0 || !aksrt_rewards_enabled() || !aksrt_user_has_rewards_qr($user_id)) {
return '';
}
$label = '赞赏';
if (class_exists('AKSRT_Settings') && method_exists('AKSRT_Settings', 'get_post_page_settings')) {
$s = AKSRT_Settings::get_post_page_settings();
$label = (string) ($s['rewards_btn_text'] ?? $s['rewards_text'] ?? $label);
if ($label === '') {
$label = '赞赏';
}
}
return '<button type="button" class="' . esc_attr($class) . ' aksrt-reward-open" data-aksrt-reward-open data-user-id="' . (int) $user_id . '"><i class="fa fa-gift em09" aria-hidden="true"></i> ' . esc_html($label) . '</button>';
}
}
if (!function_exists('aksrt_render_rewards_modal_for_user')) {
function aksrt_render_rewards_modal_for_user(int $user_id): void {
static $done = [];
if ($user_id <= 0 || isset($done[$user_id])) {
return;
}
if (!aksrt_rewards_enabled() || !aksrt_user_has_rewards_qr($user_id)) {
return;
}
$done[$user_id] = true;
$urls = aksrt_get_user_rewards_img_urls($user_id);
$label = '赞赏';
if (class_exists('AKSRT_Settings') && method_exists('AKSRT_Settings', 'get_post_page_settings')) {
$s = AKSRT_Settings::get_post_page_settings();
$label = (string) ($s['rewards_text'] ?? $label);
if ($label === '') {
$label = __('赞赏', 'aksrt');
}
}
?>
<div class="reward-modal" id="reward-modal-<?php echo (int) $user_id; ?>" data-user-id="<?php echo (int) $user_id; ?>" aria-hidden="true">
<div class="reward-modal__overlay" data-aksrt-reward-close></div>
<div class="reward-modal__dialog" role="dialog" aria-modal="true">
<button type="button" class="reward-modal__close" data-aksrt-reward-close aria-label="<?php esc_attr_e('关闭', 'aksrt'); ?>"><i class="fas fa-times" aria-hidden="true"></i></button>
<div class="reward-modal__content">
<h3><?php echo esc_html($label); ?></h3>
<div class="reward-modal__qrcodes flex jc ac hh">
<?php if ($urls['weixin'] !== '') : ?>
<div class="reward-modal__qrcode text-center mr10">
<img src="<?php echo esc_url($urls['weixin']); ?>" alt="<?php esc_attr_e('微信收款码', 'aksrt'); ?>" loading="lazy" decoding="async" />
<div class="em09 muted-2-color mt6"><?php esc_html_e('微信', 'aksrt'); ?></div>
</div>
<?php endif; ?>
<?php if ($urls['alipay'] !== '') : ?>
<div class="reward-modal__qrcode text-center">
<img src="<?php echo esc_url($urls['alipay']); ?>" alt="<?php esc_attr_e('支付宝收款码', 'aksrt'); ?>" loading="lazy" decoding="async" />
<div class="em09 muted-2-color mt6"><?php esc_html_e('支付宝', 'aksrt'); ?></div>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php
}
}
+108 -30
View File
@@ -240,7 +240,23 @@ if (!function_exists('aksrt_single_render_actions')) :
</button> </button>
<?php endif; ?> <?php endif; ?>
<?php if (!empty($settings['rewards_enabled']) && !empty($settings['rewards_image'])) : ?> <?php
$reward_author = (int) get_the_author_meta('ID');
$reward_btn = ($reward_author > 0 && function_exists('aksrt_get_rewards_button'))
? aksrt_get_rewards_button($reward_author, 'action action-rewards')
: '';
if ($reward_btn === '' && !empty($settings['rewards_enabled']) && function_exists('aksrt_user_has_rewards_qr') && $reward_author > 0 && aksrt_user_has_rewards_qr($reward_author)) {
$label = esc_html((string) ($settings['rewards_btn_text'] ?? __('赞赏', 'aksrt')));
$reward_btn = '<button type="button" class="action action-rewards" data-aksrt-reward-open data-user-id="' . (int) $reward_author . '"><i class="fas fa-gift" aria-hidden="true"></i><span class="action-btn__label">' . $label . '</span></button>';
}
if ($reward_btn !== '') :
echo $reward_btn; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if (function_exists('aksrt_render_rewards_modal_for_user')) {
aksrt_render_rewards_modal_for_user($reward_author);
}
endif;
// legacy placeholder to keep structure; actual button echoed above
if (false) : ?>
<button type="button" class="action action-rewards" id="action-reward"> <button type="button" class="action action-rewards" id="action-reward">
<i class="fas fa-gift" aria-hidden="true"></i> <i class="fas fa-gift" aria-hidden="true"></i>
<span class="action-btn__label"><?php echo esc_html($settings['rewards_btn_text'] ?? __('赞赏', 'aksrt')); ?></span> <span class="action-btn__label"><?php echo esc_html($settings['rewards_btn_text'] ?? __('赞赏', 'aksrt')); ?></span>
@@ -348,15 +364,42 @@ if (!function_exists('aksrt_single_render_author_card')) :
$avatar = function_exists('aksrt_get_avatar_html') $avatar = function_exists('aksrt_get_avatar_html')
? aksrt_get_avatar_html($author_id, 80, ['class' => 'avatar avatar-lg']) ? aksrt_get_avatar_html($author_id, 80, ['class' => 'avatar avatar-lg'])
: get_avatar($author_id, 80, '', esc_attr($author_name), ['class' => 'avatar avatar-lg']); : get_avatar($author_id, 80, '', esc_attr($author_name), ['class' => 'avatar avatar-lg']);
$recent = get_posts([ $page_s = class_exists('AKSRT_Settings') && method_exists('AKSRT_Settings', 'get_post_page_settings')
'author' => $author_id, ? AKSRT_Settings::get_post_page_settings()
'post_type' => 'post', : [];
'post_status' => 'publish', $card_limit = max(0, min(12, (int) ($page_s['author_card_limit'] ?? 6)));
'posts_per_page' => 6, $card_orderby = sanitize_key((string) ($page_s['author_card_orderby'] ?? 'views'));
'orderby' => 'date', $card_style = sanitize_key((string) ($page_s['author_card_style'] ?? 'card'));
'order' => 'DESC', $recent = [];
'no_found_rows' => true, if ($card_limit > 0) {
]); $q = [
'author' => $author_id,
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $card_limit,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
];
$cur = get_the_ID();
if ($cur) {
$q['post__not_in'] = [(int) $cur];
}
if ($card_orderby === 'views') {
$q['meta_key'] = 'views';
$q['orderby'] = 'meta_value_num';
$q['order'] = 'DESC';
} elseif ($card_orderby === 'comment_count') {
$q['orderby'] = 'comment_count';
$q['order'] = 'DESC';
} elseif ($card_orderby === 'modified') {
$q['orderby'] = 'modified';
$q['order'] = 'DESC';
} else {
$q['orderby'] = 'date';
$q['order'] = 'DESC';
}
$recent = get_posts($q);
}
$is_self = is_user_logged_in() && get_current_user_id() === $author_id; $is_self = is_user_logged_in() && get_current_user_id() === $author_id;
$login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(get_permalink()); $login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(get_permalink());
$aksrt_pm_on = class_exists('AKSRT_Settings') && !empty(AKSRT_Settings::private_message_settings()['enabled']); $aksrt_pm_on = class_exists('AKSRT_Settings') && !empty(AKSRT_Settings::private_message_settings()['enabled']);
@@ -387,7 +430,20 @@ if (!function_exists('aksrt_single_render_author_card')) :
<div class="user-desc mt10 muted-2-color em09"><?php echo esc_html($bio); ?></div> <div class="user-desc mt10 muted-2-color em09"><?php echo esc_html($bio); ?></div>
<?php endif; ?> <?php endif; ?>
<div class="user-btns mt20"> <div class="user-btns mt20">
<?php if (!$is_self) : ?> <?php if ($is_self) : ?>
<?php
$submit = function_exists('aksrt_get_page_url_by_template')
? (string) aksrt_get_page_url_by_template('templates/pages/page-submit.php')
: '';
$center = function_exists('aksrt_get_user_center_url')
? (string) aksrt_get_user_center_url()
: $author_url;
if ($submit !== '') :
?>
<a class="but pw-1em mr6 jb-pink" href="<?php echo esc_url($submit); ?>"><?php esc_html_e('发布文章', 'aksrt'); ?></a>
<?php endif; ?>
<a class="but pw-1em mr6 jb-blue" href="<?php echo esc_url($center); ?>"><?php esc_html_e('用户中心', 'aksrt'); ?></a>
<?php else : ?>
<?php if (is_user_logged_in()) : ?> <?php if (is_user_logged_in()) : ?>
<button type="button" class="but pw-1em mr6 c-red follow" data-aksrt-follow="<?php echo (int) $author_id; ?>"> <button type="button" class="but pw-1em mr6 c-red follow" data-aksrt-follow="<?php echo (int) $author_id; ?>">
<i class="fa fa-heart em09" aria-hidden="true"></i> <span><?php esc_html_e('关注', 'aksrt'); ?></span> <i class="fa fa-heart em09" aria-hidden="true"></i> <span><?php esc_html_e('关注', 'aksrt'); ?></span>
@@ -397,34 +453,56 @@ if (!function_exists('aksrt_single_render_author_card')) :
<i class="fa fa-heart em09" aria-hidden="true"></i> <span><?php esc_html_e('关注', 'aksrt'); ?></span> <i class="fa fa-heart em09" aria-hidden="true"></i> <span><?php esc_html_e('关注', 'aksrt'); ?></span>
</a> </a>
<?php endif; ?> <?php endif; ?>
<?php if (is_user_logged_in() && $aksrt_pm_on && $aksrt_msg_url !== '') : ?>
<a class="but pw-1em mr6 c-blue" href="<?php echo esc_url(add_query_arg('msg_type', 'conversation', $aksrt_msg_url)); ?>">
<i class="fa fa-comment em09" aria-hidden="true"></i> <?php esc_html_e('私信', 'aksrt'); ?>
</a>
<?php endif; ?>
<?php
$tip = function_exists('aksrt_get_rewards_button') ? aksrt_get_rewards_button($author_id, 'but pw-1em mr6 c-yellow') : '';
if ($tip !== '') {
echo $tip; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if (function_exists('aksrt_render_rewards_modal_for_user')) {
aksrt_render_rewards_modal_for_user($author_id);
}
}
?>
<a class="but pw-1em mr6 jb-blue" href="<?php echo esc_url($author_url); ?>"><?php esc_html_e('个人主页', 'aksrt'); ?></a>
<?php endif; ?> <?php endif; ?>
<?php if (!$is_self && is_user_logged_in() && $aksrt_pm_on && $aksrt_msg_url !== '') : ?>
<a class="but pw-1em mr6 c-blue" href="<?php echo esc_url(add_query_arg('msg_type', 'conversation', $aksrt_msg_url)); ?>">
<i class="fa fa-comment em09" aria-hidden="true"></i> <?php esc_html_e('私信', 'aksrt'); ?>
</a>
<?php elseif (!$is_self && $rewards_on) : ?>
<button type="button" class="but pw-1em mr6 c-yellow" data-aksrt-reward-open>
<i class="fa fa-gift em09" aria-hidden="true"></i> <?php esc_html_e('赞赏', 'aksrt'); ?>
</button>
<?php endif; ?>
<a class="but pw-1em mr6 jb-blue" href="<?php echo esc_url($author_url); ?>"><?php esc_html_e('个人主页', 'aksrt'); ?></a>
</div> </div>
</div> </div>
</div> </div>
<?php if (!empty($recent)) : ?> <?php if (!empty($recent)) : ?>
<div class="user-card-posts mt10"> <div class="user-card-posts mt10">
<ul class="no-thumb"> <?php if ($card_style === 'card') : ?>
<?php foreach ($recent as $rp) : ?> <div class="more-posts-mini flex hh">
<?php if ($rp instanceof WP_Post) : ?> <?php foreach ($recent as $rp) : ?>
<li><a class="icon-circle" href="<?php echo esc_url(get_permalink($rp)); ?>"><?php echo esc_html(get_the_title($rp)); ?></a></li> <?php if ($rp instanceof WP_Post) :
<?php endif; ?> $thumb = get_the_post_thumbnail_url($rp, 'medium');
<?php endforeach; ?> ?>
</ul> <a class="item mr10 mb10" href="<?php echo esc_url(get_permalink($rp)); ?>" style="display:block;width:46%;">
<?php if ($thumb) : ?>
<img class="fit-cover radius8" src="<?php echo esc_url($thumb); ?>" alt="" loading="lazy" decoding="async" style="width:100%;aspect-ratio:16/10;object-fit:cover;" />
<?php endif; ?>
<div class="em09 mt6 text-ellipsis"><?php echo esc_html(get_the_title($rp)); ?></div>
</a>
<?php endif; ?>
<?php endforeach; ?>
</div>
<?php else : ?>
<ul class="no-thumb">
<?php foreach ($recent as $rp) : ?>
<?php if ($rp instanceof WP_Post) : ?>
<li><a class="icon-circle" href="<?php echo esc_url(get_permalink($rp)); ?>"><?php echo esc_html(get_the_title($rp)); ?></a></li>
<?php endif; ?>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div> </div>
<?php else : ?> <?php else : ?>
<div class="user-card-posts mt10 muted-2-color em09 text-center"><?php esc_html_e('暂无更多文章', 'aksrt'); ?></div> <div class="user-card-posts mt10 muted-2-color em09 text-center"><?php esc_html_e('暂无更多文章', 'aksrt'); ?></div>
<?php endif; ?> <?php endif;
</div> ?> </div>
</div> </div>
<?php <?php
} }
@@ -167,8 +167,10 @@ $pane = isset($points_tasks_pane) ? sanitize_key((string) $points_tasks_pane)
<?php <?php
$task_key = sanitize_key((string) ($task['key'] ?? '')); $task_key = sanitize_key((string) ($task['key'] ?? ''));
$action = $aksrt_points_task_action($task_key); $action = $aksrt_points_task_action($task_key);
$status = sanitize_key((string) ($task['status'] ?? 'todo'));
$done = in_array($status, ['done_today', 'done'], true);
?> ?>
<div class="border-bottom padding-h10 uc-points-tasks__row" data-task-key="<?php echo esc_attr($task_key); ?>"> <div class="border-bottom padding-h10 uc-points-tasks__row<?php echo $done ? ' is-done' : ''; ?>" data-task-key="<?php echo esc_attr($task_key); ?>" data-status="<?php echo esc_attr($status); ?>">
<div class="flex jsb ac"> <div class="flex jsb ac">
<div class="flex1 mr20"> <div class="flex1 mr20">
<div class="font-bold mb6"><?php echo esc_html((string) $task['title']); ?></div> <div class="font-bold mb6"><?php echo esc_html((string) $task['title']); ?></div>