diff --git a/assets/css/routes/user-center/settings.css b/assets/css/routes/user-center/settings.css index 37f5c53b..7dcb58f1 100644 --- a/assets/css/routes/user-center/settings.css +++ b/assets/css/routes/user-center/settings.css @@ -325,3 +325,37 @@ color: var(--color-ink-4); background: transparent; } + + +/* Tip QR upload on profile */ +.aksrt-reward-upload-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 16px; + margin-top: 12px; +} +.aksrt-reward-upload__preview { + min-height: 140px; + border: 1px dashed rgba(127,127,127,.35); + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + margin: 8px 0; + background: rgba(127,127,127,.04); +} +.aksrt-reward-upload__preview img { + max-width: 100%; + max-height: 180px; + object-fit: contain; + display: block; +} +.aksrt-reward-upload__file { + display: none; +} +.aksrt-reward-upload .btn-sm, +.aksrt-reward-upload .btn-primary { + margin-right: 8px; + margin-top: 4px; +} diff --git a/assets/js/routes/user-center.js b/assets/js/routes/user-center.js index 310ac0c6..4c1b84c9 100644 --- a/assets/js/routes/user-center.js +++ b/assets/js/routes/user-center.js @@ -120,6 +120,82 @@ } /* ==================== 个人信息保存 ==================== */ + + /* ==================== 打赏收款码上传 ==================== */ + function initRewardQrUpload() { + var form = document.getElementById('profile-form'); + if (!form) return; + var nonceInput = form.querySelector('input[name="_profile_nonce"]'); + var nonce = nonceInput ? nonceInput.value : ''; + + function setPreview(kind, url) { + var box = form.querySelector('[data-reward-preview="' + kind + '"]'); + if (!box) return; + if (url) { + box.innerHTML = ''; + } else { + box.innerHTML = '未上传'; + } + } + + form.querySelectorAll('[data-reward-upload]').forEach(function (btn) { + btn.addEventListener('click', function () { + var kind = btn.getAttribute('data-reward-upload'); + var fileInput = form.querySelector('input[data-reward-kind="' + kind + '"]'); + if (fileInput) fileInput.click(); + }); + }); + + form.querySelectorAll('[data-reward-clear]').forEach(function (btn) { + btn.addEventListener('click', function () { + var kind = btn.getAttribute('data-reward-clear'); + var hidden = form.querySelector('#rewards-' + kind); + var fileInput = form.querySelector('input[data-reward-kind="' + kind + '"]'); + if (hidden) hidden.value = ''; + if (fileInput) fileInput.value = ''; + setPreview(kind, ''); + }); + }); + + form.querySelectorAll('input[data-reward-kind]').forEach(function (input) { + input.addEventListener('change', function () { + var kind = input.getAttribute('data-reward-kind'); + var file = input.files && input.files[0]; + if (!file || !kind) return; + if (file.size > 2 * 1024 * 1024) { + showToast('图片不能超过 2MB'); + input.value = ''; + return; + } + var body = new FormData(); + body.append('action', 'aksrt_upload_reward_qr'); + body.append('_profile_nonce', nonce); + body.append('kind', kind); + body.append('file', file); + showToast(i18n.saving || '上传中…'); + fetch(ajaxUrl, { method: 'POST', body: body, credentials: 'same-origin' }) + .then(function (r) { return r.json(); }) + .then(function (res) { + if (res && res.success && res.data) { + var hidden = form.querySelector('#rewards-' + kind); + if (hidden) hidden.value = String(res.data.id || ''); + setPreview(kind, res.data.url || ''); + showToast(res.data.message || '上传成功'); + } else { + showToast((res && res.data && res.data.message) || '上传失败'); + } + }) + .catch(function () { + showToast(i18n.networkError || '网络错误'); + }) + .finally(function () { + input.value = ''; + }); + }); + }); + } + + function initProfileForm() { var form = document.getElementById('profile-form'); if (!form) return; @@ -676,6 +752,7 @@ document.addEventListener('DOMContentLoaded', function() { initDashboardNavigation(); initProfileForm(); + initRewardQrUpload(); initUnfavorite(); initNotifications(); initSettings(); @@ -683,6 +760,7 @@ } else { initDashboardNavigation(); initProfileForm(); + initRewardQrUpload(); initUnfavorite(); initNotifications(); initSettings(); diff --git a/functions.php b/functions.php index ec0f108a..11c2bd62 100644 --- a/functions.php +++ b/functions.php @@ -373,6 +373,9 @@ if (class_exists('AKSRT_User_Badges_Admin')) { if (class_exists('AKSRT_User_Level')) { AKSRT_User_Level::get_instance()->init(); } +if (class_exists('AKSRT_User_Metrics')) { + AKSRT_User_Metrics::get_instance()->init(); +} // WordPress 原生侧栏:支付/订单运营中心(zibll zibpay 对齐) if (is_admin() && class_exists('AKSRT_Admin_Pay_Menus')) { diff --git a/inc/bootstrap.php b/inc/bootstrap.php index 31c7d2c6..cb5926db 100644 --- a/inc/bootstrap.php +++ b/inc/bootstrap.php @@ -223,6 +223,7 @@ require_once AKSRT_DIR . '/inc/classes/class-aksrt-user-balance-admin.php'; require_once AKSRT_DIR . '/inc/classes/class-aksrt-user-badges.php'; require_once AKSRT_DIR . '/inc/classes/class-aksrt-user-badges-admin.php'; require_once AKSRT_DIR . '/inc/classes/class-aksrt-user-level.php'; +require_once AKSRT_DIR . '/inc/classes/class-aksrt-user-metrics.php'; // 加载数据表迁移器(CMS / 积分 / 下载 token) require_once AKSRT_DIR . '/inc/classes/db-traits/trait-aksrt-db-migrator-part-2.php'; diff --git a/inc/classes/class-aksrt-comment-actions.php b/inc/classes/class-aksrt-comment-actions.php index 712188fc..b1df3767 100644 --- a/inc/classes/class-aksrt-comment-actions.php +++ b/inc/classes/class-aksrt-comment-actions.php @@ -67,9 +67,7 @@ class AKSRT_Comment_Actions { register_rest_route('aksrt/v1', '/comment/moderate', [ 'methods' => 'POST', 'callback' => [$this, 'moderate_comment'], - 'permission_callback' => function () { - return current_user_can('moderate_comments'); - }, + 'permission_callback' => [$this, 'check_moderate_permission'], 'args' => [ 'comment_id' => ['required' => true, 'type' => 'integer', 'sanitize_callback' => 'absint'], 'status' => ['required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field'], diff --git a/inc/classes/class-aksrt-rebate.php b/inc/classes/class-aksrt-rebate.php index f79ab8a4..4df9520f 100644 --- a/inc/classes/class-aksrt-rebate.php +++ b/inc/classes/class-aksrt-rebate.php @@ -64,7 +64,15 @@ class AKSRT_Rebate { 'note' => $note, ]); - return $result ? (int) $wpdb->insert_id : 0; + $id = $result ? (int) $wpdb->insert_id : 0; + if ($id > 0) { + do_action('aksrt_pay_rebate_added', $user_id, $amount, [ + 'rebate_id' => $id, + 'order_number' => $order_number, + 'type' => $type, + ]); + } + return $id; } /** diff --git a/inc/classes/class-aksrt-revenue.php b/inc/classes/class-aksrt-revenue.php index a05314e5..a31df8a2 100644 --- a/inc/classes/class-aksrt-revenue.php +++ b/inc/classes/class-aksrt-revenue.php @@ -39,7 +39,15 @@ class AKSRT_Revenue { 'created_at' => current_time('mysql'), ], ['%d', '%s', '%f', '%s', '%s', '%s']); - return (int) $wpdb->insert_id; + $id = (int) $wpdb->insert_id; + if ($id > 0) { + do_action('aksrt_creator_income_added', $user_id, $amount, [ + 'order_number' => $order_number, + 'type' => $type, + 'revenue_id' => $id, + ]); + } + return $id; } /** diff --git a/inc/classes/class-aksrt-signin.php b/inc/classes/class-aksrt-signin.php index 4ebd0431..e939acdf 100644 --- a/inc/classes/class-aksrt-signin.php +++ b/inc/classes/class-aksrt-signin.php @@ -130,6 +130,8 @@ class AKSRT_Signin { return new WP_Error('aksrt_signin_dup', __('签到失败', 'aksrt'), ['status' => 409]); } + do_action('aksrt_user_signed_in', $user_id); + if ($reward > 0) { if (class_exists('AKSRT_Points_Free')) { $granted = AKSRT_Points_Free::grant_amount($user_id, $reward, 'sign_in', ['type' => 'user', 'id' => $user_id]); diff --git a/inc/classes/class-aksrt-user-badges.php b/inc/classes/class-aksrt-user-badges.php index f018a9d7..fc2854e4 100644 --- a/inc/classes/class-aksrt-user-badges.php +++ b/inc/classes/class-aksrt-user-badges.php @@ -477,11 +477,10 @@ class AKSRT_User_Badges { return (float) $counts; case 'followed': - $meta = get_user_meta($user_id, 'followed-user', true); - if (!is_array($meta)) { - $meta = get_user_meta($user_id, 'aksrt_followed_users', true); + if (class_exists('AKSRT_Follow')) { + return (float) AKSRT_Follow::get_instance()->get_follower_count($user_id); } - return is_array($meta) ? (float) count($meta) : 0.0; + return 0.0; case 'like': return (float) get_user_meta($user_id, 'aksrt_posts_like_sum', true); @@ -505,7 +504,23 @@ class AKSRT_User_Badges { return (float) get_user_meta($user_id, 'aksrt_bbs_score_sum', true); case 'views': - return (float) get_user_meta($user_id, 'aksrt_posts_views_sum', true); + $sum = (float) get_user_meta($user_id, 'aksrt_posts_views_sum', true); + if ($sum > 0) { + return $sum; + } + // Lazy backfill once from postmeta. + global $wpdb; + $sum = (float) $wpdb->get_var($wpdb->prepare( + "SELECT COALESCE(SUM(CAST(pm.meta_value AS UNSIGNED)),0) + FROM {$wpdb->posts} p + INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = 'aksrt_post_views' + WHERE p.post_author = %d AND p.post_type = 'post' AND p.post_status = 'publish'", + $user_id + )); + if ($sum > 0) { + update_user_meta($user_id, 'aksrt_posts_views_sum', $sum); + } + return $sum; case 'pay_income': return (float) get_user_meta($user_id, 'aksrt_pay_income_sum', true); diff --git a/inc/classes/class-aksrt-user-metrics.php b/inc/classes/class-aksrt-user-metrics.php new file mode 100644 index 00000000..ce95910b --- /dev/null +++ b/inc/classes/class-aksrt-user-metrics.php @@ -0,0 +1,361 @@ + 0 && class_exists('AKSRT_User_Badges') && method_exists('AKSRT_User_Badges', 'auto_evaluate')) { + AKSRT_User_Badges::auto_evaluate($user_id); + } + } + + public function on_post_liked(int $post_id, int $actor_id, bool $active): void { + $post = get_post($post_id); + if (!$post instanceof WP_Post || $post->post_type !== 'post') { + return; + } + $author = (int) $post->post_author; + if ($author <= 0 || $author === $actor_id) { + return; + } + self::bump_user_meta($author, self::META_POST_LIKE_SUM, $active ? 1.0 : -1.0); + if ($active) { + self::reevaluate_medals($author); + } + } + + public function on_comment_liked(int $comment_id, int $actor_id, bool $active): void { + $comment = get_comment($comment_id); + if (!$comment) { + return; + } + $author = (int) $comment->user_id; + if ($author <= 0 || $author === $actor_id) { + return; + } + self::bump_user_meta($author, self::META_COMMENT_LIKE_SUM, $active ? 1.0 : -1.0); + if ($active) { + self::reevaluate_medals($author); + } + } + + public function on_post_favorited(int $post_id, int $actor_id, bool $active): void { + unset($post_id, $actor_id, $active); + // Favorite is free-points / level only; no dedicated medal get_type. + } + + public function on_user_followed(int $follower_id, int $following_id): void { + unset($follower_id); + if ($following_id > 0) { + self::reevaluate_medals($following_id); + } + } + + public function on_post_viewed(int $post_id, int $viewer_id = 0): void { + unset($viewer_id); + $post = get_post($post_id); + if (!$post instanceof WP_Post || $post->post_type !== 'post') { + return; + } + $author = (int) $post->post_author; + if ($author <= 0) { + return; + } + self::bump_user_meta($author, self::META_POSTS_VIEWS_SUM, 1.0); + // Cheap throttle: reevaluate every 50 views on author sum. + $sum = (float) get_user_meta($author, self::META_POSTS_VIEWS_SUM, true); + if (((int) $sum % 50) === 0) { + self::reevaluate_medals($author); + } + } + + /** + * @param int $topic_id + * @param int $author_id + */ + public function on_forum_topic_featured($topic_id, $author_id = 0): void { + $author_id = (int) $author_id; + if ($author_id <= 0) { + $post = get_post((int) $topic_id); + $author_id = $post ? (int) $post->post_author : 0; + } + if ($author_id <= 0) { + return; + } + self::bump_user_meta($author_id, self::META_BBS_ESSENCE, 1.0); + self::reevaluate_medals($author_id); + } + + /** + * @param int $topic_id + * @param int $comment_id + * @param int $adoptee_id + */ + public function on_forum_reply_adopted($topic_id, $comment_id, $adoptee_id): void { + unset($topic_id, $comment_id); + $adoptee_id = (int) $adoptee_id; + if ($adoptee_id <= 0) { + return; + } + self::bump_user_meta($adoptee_id, self::META_BBS_ADOPT, 1.0); + self::reevaluate_medals($adoptee_id); + } + + /** + * @param int $comment_id + * @param int $author_id + */ + public function on_forum_comment_hot($comment_id, $author_id = 0): void { + $author_id = (int) $author_id; + if ($author_id <= 0) { + $c = get_comment((int) $comment_id); + $author_id = $c ? (int) $c->user_id : 0; + } + if ($author_id <= 0) { + return; + } + self::bump_user_meta($author_id, self::META_BBS_COMMENT_HOT, 1.0); + self::reevaluate_medals($author_id); + } + + /** + * @param int $topic_id + * @param int $author_id + */ + public function on_forum_topic_hot($topic_id, $author_id = 0): void { + $author_id = (int) $author_id; + if ($author_id <= 0) { + $post = get_post((int) $topic_id); + $author_id = $post ? (int) $post->post_author : 0; + } + if ($author_id <= 0) { + return; + } + self::bump_user_meta($author_id, self::META_BBS_POST_HOT, 1.0); + self::reevaluate_medals($author_id); + } + + /** + * @param int $topic_id + * @param int $author_id + * @param float $delta + */ + public function on_forum_topic_scored($topic_id, $author_id, $delta = 0): void { + unset($topic_id); + $author_id = (int) $author_id; + $delta = (float) $delta; + if ($author_id <= 0 || $delta == 0.0) { + return; + } + self::bump_user_meta($author_id, self::META_BBS_SCORE, $delta); + self::reevaluate_medals($author_id); + } + + public function on_user_signed_in(int $user_id): void { + if ($user_id <= 0) { + return; + } + self::bump_user_meta($user_id, self::META_CHECKIN_ALL_DAY, 1.0); + self::reevaluate_medals($user_id); + } + + /** + * @param int $user_id + * @param float $amount + * @param array $context + */ + public function on_creator_income_added($user_id, $amount, $context = []): void { + unset($context); + $user_id = (int) $user_id; + $amount = (float) $amount; + if ($user_id <= 0 || $amount <= 0) { + return; + } + self::bump_user_meta($user_id, self::META_PAY_INCOME, $amount); + self::reevaluate_medals($user_id); + } + + /** + * @param int $user_id + * @param float $amount + * @param array $context + */ + public function on_pay_rebate_added($user_id, $amount, $context = []): void { + unset($context); + $user_id = (int) $user_id; + $amount = (float) $amount; + if ($user_id <= 0 || $amount <= 0) { + return; + } + self::bump_user_meta($user_id, self::META_PAY_REBATE, $amount); + self::reevaluate_medals($user_id); + } + + /* ---------- level XP bridges ---------- */ + + private static function level_add(int $user_id, string $key): void { + if ($user_id > 0 && class_exists('AKSRT_User_Level')) { + AKSRT_User_Level::add($user_id, $key); + } + } + + public function level_on_post_liked(int $post_id, int $actor_id, bool $active): void { + if (!$active) { + return; + } + $post = get_post($post_id); + if (!$post instanceof WP_Post) { + return; + } + $author = (int) $post->post_author; + if ($author > 0 && $author !== $actor_id) { + self::level_add($author, 'post_like'); + } + } + + public function level_on_comment_liked(int $comment_id, int $actor_id, bool $active): void { + if (!$active) { + return; + } + $c = get_comment($comment_id); + if (!$c) { + return; + } + $author = (int) $c->user_id; + if ($author > 0 && $author !== $actor_id) { + self::level_add($author, 'comment_like'); + } + } + + public function level_on_post_favorited(int $post_id, int $actor_id, bool $active): void { + if (!$active) { + return; + } + $post = get_post($post_id); + if (!$post instanceof WP_Post) { + return; + } + $author = (int) $post->post_author; + if ($author > 0 && $author !== $actor_id) { + self::level_add($author, 'post_favorite'); + } + } + + public function level_on_user_followed(int $follower_id, int $following_id): void { + unset($follower_id); + self::level_add((int) $following_id, 'followed'); + } + + public function level_on_forum_topic_featured($topic_id, $author_id = 0): void { + unset($topic_id); + self::level_add((int) $author_id, 'bbs_essence'); + } + + public function level_on_forum_reply_adopted($topic_id, $comment_id, $adoptee_id): void { + unset($topic_id, $comment_id); + self::level_add((int) $adoptee_id, 'bbs_adopt'); + } + + public function level_on_forum_comment_hot($comment_id, $author_id = 0): void { + unset($comment_id); + self::level_add((int) $author_id, 'bbs_comment_hot'); + } + + public function level_on_forum_topic_hot($topic_id, $author_id = 0): void { + unset($topic_id); + self::level_add((int) $author_id, 'bbs_posts_hot'); + } + + public function level_on_forum_topic_scored($topic_id, $author_id, $delta = 0): void { + unset($topic_id); + if ((float) $delta > 0) { + self::level_add((int) $author_id, 'bbs_score_extra'); + } + } + + /** + * @param int $user_id + * @param int $topic_id + */ + public function level_on_forum_topic_created($user_id, $topic_id = 0): void { + unset($topic_id); + self::level_add((int) $user_id, 'bbs_posts_new'); + } + + public function level_on_user_signed_in(int $user_id): void { + // Sign-in XP: use sign_in key once/day via existing login guard path as well. + self::level_add($user_id, 'sign_in'); + } +} diff --git a/inc/classes/class-aksrt-user.php b/inc/classes/class-aksrt-user.php index 85d4b666..a7f45f0f 100644 --- a/inc/classes/class-aksrt-user.php +++ b/inc/classes/class-aksrt-user.php @@ -942,6 +942,59 @@ add_action('wp_ajax_aksrt_save_profile', function () { wp_send_json_success(['message' => __('保存成功!', 'aksrt')]); }); +// ============================================================ +// AJAX: 上传打赏收款码 +// ============================================================ +add_action('wp_ajax_aksrt_upload_reward_qr', function () { + if (!isset($_POST['_profile_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_profile_nonce'])), 'aksrt_profile_save')) { + wp_send_json_error(['message' => __('安全验证失败,请刷新页面重试', 'aksrt')]); + } + $user_id = get_current_user_id(); + if ($user_id <= 0) { + wp_send_json_error(['message' => __('请先登录', 'aksrt')]); + } + if (function_exists('aksrt_rewards_enabled') && !aksrt_rewards_enabled()) { + wp_send_json_error(['message' => __('打赏功能未启用', 'aksrt')]); + } + $kind = isset($_POST['kind']) ? sanitize_key((string) $_POST['kind']) : ''; + if (!in_array($kind, ['weixin', 'alipay'], true)) { + wp_send_json_error(['message' => __('无效类型', 'aksrt')]); + } + if (empty($_FILES['file']['name'])) { + wp_send_json_error(['message' => __('请选择图片', 'aksrt')]); + } + + require_once ABSPATH . 'wp-admin/includes/file.php'; + require_once ABSPATH . 'wp-admin/includes/media.php'; + require_once ABSPATH . 'wp-admin/includes/image.php'; + + $file = $_FILES['file']; + $check = wp_check_filetype_and_ext($file['tmp_name'], $file['name']); + $allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; + $mime = (string) ($check['type'] ?? $file['type'] ?? ''); + if ($mime === '' || !in_array($mime, $allowed, true)) { + wp_send_json_error(['message' => __('仅支持 JPG/PNG/GIF/WebP 图片', 'aksrt')]); + } + if (!empty($file['size']) && (int) $file['size'] > 2 * 1024 * 1024) { + wp_send_json_error(['message' => __('图片不能超过 2MB', 'aksrt')]); + } + + $attachment_id = media_handle_upload('file', 0); + if (is_wp_error($attachment_id)) { + wp_send_json_error(['message' => $attachment_id->get_error_message()]); + } + $meta = $kind === 'weixin' ? 'aksrt_rewards_weixin' : 'aksrt_rewards_alipay'; + update_user_meta($user_id, $meta, (int) $attachment_id); + $url = wp_get_attachment_image_url((int) $attachment_id, 'full'); + wp_send_json_success([ + 'message' => __('上传成功', 'aksrt'), + 'id' => (int) $attachment_id, + 'url' => is_string($url) ? $url : '', + 'kind' => $kind, + ]); +}); + + // ============================================================ // AJAX: 发送邮箱验证码(修改密码/更改邮箱通用) // ============================================================ diff --git a/inc/classes/forum/class-aksrt-forum-comment-service.php b/inc/classes/forum/class-aksrt-forum-comment-service.php index a934762e..008500c3 100644 --- a/inc/classes/forum/class-aksrt-forum-comment-service.php +++ b/inc/classes/forum/class-aksrt-forum-comment-service.php @@ -309,6 +309,7 @@ class AKSRT_Forum_Comment_Service { update_comment_meta($comment_id, '_aksrt_adopted', '1'); update_post_meta($topic_id, '_aksrt_adopted_comment', $comment_id); + do_action('aksrt_forum_reply_adopted', $topic_id, $comment_id, (int) $comment->user_id); update_post_meta($topic_id, '_aksrt_question_status', 1); // QUESTION_SOLVED $adoptee_id = (int) $comment->user_id; @@ -507,6 +508,8 @@ class AKSRT_Forum_Comment_Service { } update_comment_meta($comment_id, '_aksrt_is_hot', '1'); + $hot_author = (int) (get_comment($comment_id)->user_id ?? 0); + do_action('aksrt_forum_comment_hot', $comment_id, $hot_author); // Credit the comment author (free-task day cap). $author_id = (int) $comment->user_id; diff --git a/inc/classes/forum/class-aksrt-forum-score-service.php b/inc/classes/forum/class-aksrt-forum-score-service.php index 46a6dddf..3d47142a 100644 --- a/inc/classes/forum/class-aksrt-forum-score-service.php +++ b/inc/classes/forum/class-aksrt-forum-score-service.php @@ -121,6 +121,7 @@ class AKSRT_Forum_Score_Service { $total = (int) get_post_meta($topic_id, '_aksrt_score', true); $total += $amount; update_post_meta($topic_id, '_aksrt_score', $total); + do_action('aksrt_forum_topic_scored', $topic_id, (int) $author_id, (float) $amount); $reputation = (int) get_user_meta($author_id, 'aksrt_reputation', true); $reputation += $amount; @@ -236,6 +237,7 @@ class AKSRT_Forum_Score_Service { if ($author_id <= 0) { return; } + do_action('aksrt_forum_topic_hot', $topic_id, (int) $author_id); update_post_meta($topic_id, '_aksrt_hot_rewarded', '1'); $config = $this->forum->get_config(); $amount = (int) ($config['credits']['posts_hot'] ?? 5); diff --git a/inc/helpers/aksrt-frontend-tags.php b/inc/helpers/aksrt-frontend-tags.php index 330a736f..8558d9b4 100644 --- a/inc/helpers/aksrt-frontend-tags.php +++ b/inc/helpers/aksrt-frontend-tags.php @@ -604,6 +604,7 @@ if (!function_exists('aksrt_set_post_views')) : if (function_exists('aksrt_update_post_counter')) { aksrt_update_post_counter($post_id, '_aksrt_res_view_count', 1); aksrt_update_post_counter($post_id, 'aksrt_post_views', 1); + do_action('aksrt_post_viewed', (int) $post_id, get_current_user_id()); } else { $resource_count = (int) get_post_meta($post_id, '_aksrt_res_view_count', true); update_post_meta($post_id, '_aksrt_res_view_count', $resource_count + 1); @@ -613,6 +614,7 @@ if (!function_exists('aksrt_set_post_views')) : } else { if (function_exists('aksrt_update_post_counter')) { aksrt_update_post_counter($post_id, 'aksrt_post_views', 1); + do_action('aksrt_post_viewed', (int) $post_id, get_current_user_id()); } else { $count = (int) get_post_meta($post_id, 'aksrt_post_views', true); update_post_meta($post_id, 'aksrt_post_views', $count + 1); diff --git a/inc/helpers/aksrt-user-center.php b/inc/helpers/aksrt-user-center.php index 1fa11113..69c96edb 100644 --- a/inc/helpers/aksrt-user-center.php +++ b/inc/helpers/aksrt-user-center.php @@ -91,6 +91,32 @@ if (!function_exists('aksrt_save_user_center_profile')) { } update_user_meta($user_id, 'aksrt_user_contacts', aksrt_normalize_user_contacts($input['contacts'] ?? [])); + + // Tip QR images (attachment id or URL). Empty clears. + if (array_key_exists('rewards_weixin', $input) || array_key_exists('rewards_alipay', $input)) { + foreach (['rewards_weixin' => 'aksrt_rewards_weixin', 'rewards_alipay' => 'aksrt_rewards_alipay'] as $field => $meta) { + if (!array_key_exists($field, $input)) { + continue; + } + $raw = is_string($input[$field]) ? trim(wp_unslash($input[$field])) : ''; + if ($raw === '' || $raw === '0') { + delete_user_meta($user_id, $meta); + continue; + } + if (ctype_digit($raw)) { + $att = (int) $raw; + if ($att > 0 && wp_attachment_is_image($att)) { + update_user_meta($user_id, $meta, $att); + } + continue; + } + $url = esc_url_raw($raw); + if ($url !== '') { + update_user_meta($user_id, $meta, $url); + } + } + } + return true; } } diff --git a/inc/helpers/user-center-renderers/credits-points-tasks.php b/inc/helpers/user-center-renderers/credits-points-tasks.php index 86e1b1e2..fd4eaacf 100644 --- a/inc/helpers/user-center-renderers/credits-points-tasks.php +++ b/inc/helpers/user-center-renderers/credits-points-tasks.php @@ -52,22 +52,48 @@ $aksrt_points_task_action = static function (string $key): array { 'href' => $submit !== '' ? $submit : $home, ]; case 'comment_new': - return [ - 'type' => 'link', - 'label' => __('去完成', 'aksrt'), - 'href' => $home, - ]; - case 'login': - case 'post_like': - case 'post_favorite': - case 'followed': - case 'comment_like': - return [ - 'type' => 'link', - 'label' => __('去看看', 'aksrt'), - 'href' => $home, - ]; - case 'bbs_posts_new': + $posts = get_posts(['numberposts' => 1, 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC']); + $href = $home; + if (!empty($posts[0]) && $posts[0] instanceof WP_Post) { + $link = get_permalink($posts[0]); + if (is_string($link) && $link !== '') { + $href = $link . '#comments'; + } + } + return [ + 'type' => 'link', + 'label' => __('去完成', 'aksrt'), + 'href' => $href, + ]; + case 'login': + return [ + 'type' => 'none', + 'label' => __('登录即可领取', 'aksrt'), + ]; + case 'post_like': + case 'post_favorite': + case 'comment_like': + $posts = get_posts(['numberposts' => 1, 'post_status' => 'publish', 'orderby' => 'comment_count', 'order' => 'DESC']); + $href = $home; + if (!empty($posts[0]) && $posts[0] instanceof WP_Post) { + $link = get_permalink($posts[0]); + if (is_string($link) && $link !== '') { + $href = $link; + } + } + return [ + 'type' => 'link', + 'label' => __('去看看', 'aksrt'), + 'href' => $href, + ]; + case 'followed': + $center = function_exists('aksrt_get_user_center_url') ? (string) aksrt_get_user_center_url() : $home; + return [ + 'type' => 'link', + 'label' => __('去主页', 'aksrt'), + 'href' => $center !== '' ? $center : $home, + ]; + case 'bbs_posts_new': case 'bbs_reply': if (!$forum_on) { return ['type' => 'none', 'label' => '']; @@ -180,16 +206,19 @@ $pane = isset($points_tasks_pane) ? sanitize_key((string) $points_tasks_pane)
+
- - - - - - - - + + + + + + + + + + + diff --git a/inc/helpers/user-center-renderers/profile-form.php b/inc/helpers/user-center-renderers/profile-form.php index 31b0ade3..607fdd25 100644 --- a/inc/helpers/user-center-renderers/profile-form.php +++ b/inc/helpers/user-center-renderers/profile-form.php @@ -32,6 +32,56 @@ extract($context, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_ + + +
+

+

+
+
+ +
+ + + + + +
+ + + + +
+
+ +
+ + + + + +
+ + + + +
+
+
+ + +
+ + \"> + + + """ +new_cta = """ + + + + + \"> + + + + + """ +# try without escaped quotes +old_cta2 = old_cta.replace('\\"', '"') +new_cta2 = new_cta.replace('\\"', '"') +if old_cta2 in t: + t = t.replace(old_cta2, new_cta2, 1) + print('cta updated') +else: + # fuzzy find + i = t.find("data-aksrt-signin-goto") + print('signin btn at', i) + print(repr(t[i-120:i+220])) + # replace using unique start + s = t.find("") + e = t.find("", s) + e = t.find("\n", e) + if s > 0 and e > s: + t = t[:s] + new_cta2 + t[e+1:] + print('cta fuzzy updated') + else: + print('cta not found') + +p.write_text(t, encoding='utf-8') +print('tasks updated')