618 lines
22 KiB
PHP
618 lines
22 KiB
PHP
<?php
|
||
/**
|
||
* AKSRT Invite
|
||
*
|
||
* 邀请注册系统:
|
||
* - 邀请人:每人一个唯一邀请码(aksrt_invite_code user_meta),生成后复用
|
||
* - 被邀请人:访问 ?invite=XYZ 时 set cookie aksrt_invite (30 天)
|
||
* - 注册时:从 cookie 读 invite_code,写入 aksrt_invite 表 status=registered
|
||
* - 邮箱验证完成后:状态升级 verified → 触发奖励 → status=rewarded
|
||
* - 反作弊:同 IP 当日上限(ip_daily_cap)
|
||
*
|
||
* REST:
|
||
* GET /invite/me 当前用户邀请码 + 统计
|
||
* GET /invite/list 当前用户的邀请列表
|
||
*
|
||
* @package AKSRT
|
||
*/
|
||
|
||
defined('ABSPATH') || exit;
|
||
|
||
class AKSRT_Invite {
|
||
|
||
const COOKIE_NAME = 'aksrt_invite';
|
||
const COOKIE_TTL_DAYS = 30;
|
||
const META_INVITE_CODE = 'aksrt_invite_code';
|
||
const OPT_STANDALONE_CODES = 'aksrt_standalone_invite_codes';
|
||
|
||
/**
|
||
* 反向映射 option:code => user_id
|
||
*
|
||
* 注意:option 大小受限(默认非 autoload),10 万级用户没问题,
|
||
* 百万级仍可走,性能瓶颈在 update_option 写入;此处采用懒写策略:
|
||
* 仅在 get_or_create_code 生成新码时同步写入。
|
||
*/
|
||
const OPT_REVERSE_MAP = 'aksrt_invite_code_map';
|
||
|
||
private static $instance = null;
|
||
|
||
private function __construct() {}
|
||
|
||
public static function get_instance(): self {
|
||
if (null === self::$instance) self::$instance = new self();
|
||
return self::$instance;
|
||
}
|
||
|
||
public function init(): void {
|
||
// 在请求最早期捕获邀请码
|
||
add_action('init', [$this, 'capture_invite_cookie'], 1);
|
||
// 注册时记录邀请关系
|
||
add_action('user_register', [$this, 'on_user_register'], 20, 1);
|
||
// 邮箱验证后兑现
|
||
add_action('aksrt_email_verified', [$this, 'on_email_verified'], 10, 1);
|
||
// 用户删除时清理邀请码映射
|
||
add_action('delete_user', [$this, 'on_delete_user'], 10, 1);
|
||
|
||
add_action('rest_api_init', [$this, 'register_routes']);
|
||
}
|
||
|
||
public function register_routes(): void {
|
||
register_rest_route('aksrt/v1', '/invite/me', [
|
||
'methods' => 'GET',
|
||
'callback' => [$this, 'rest_me'],
|
||
'permission_callback' => function () {
|
||
return is_user_logged_in();
|
||
},
|
||
]);
|
||
register_rest_route('aksrt/v1', '/invite/list', [
|
||
'methods' => 'GET',
|
||
'callback' => [$this, 'rest_list'],
|
||
'permission_callback' => function () {
|
||
return is_user_logged_in();
|
||
},
|
||
]);
|
||
}
|
||
|
||
/** 捕获 ?invite=XYZ */
|
||
public function capture_invite_cookie(): void {
|
||
if (is_admin()) return;
|
||
if (empty($_GET['invite'])) return;
|
||
|
||
// 邀请注册功能关闭时不处理
|
||
if (class_exists('AKSRT_Settings') && !AKSRT_Settings::invite_registration_enabled()) return;
|
||
|
||
$code = sanitize_key((string) $_GET['invite']);
|
||
if ($code === '') return;
|
||
|
||
// 校验邀请码是否对应真实用户
|
||
$inviter_id = $this->resolve_code($code);
|
||
if ($inviter_id <= 0) return;
|
||
|
||
// 已登录用户不能用自己的邀请码
|
||
if (is_user_logged_in() && $inviter_id === get_current_user_id()) return;
|
||
|
||
// 不覆盖已存在的(防多次访问被覆盖)
|
||
if (!empty($_COOKIE[self::COOKIE_NAME])) return;
|
||
|
||
$expires = time() + DAY_IN_SECONDS * self::COOKIE_TTL_DAYS;
|
||
// 不覆盖 secure 标记的检测
|
||
$secure = is_ssl();
|
||
// headers_sent 检查
|
||
if (!headers_sent()) {
|
||
setcookie(self::COOKIE_NAME, $code, [
|
||
'expires' => $expires,
|
||
'path' => COOKIEPATH ?: '/',
|
||
'domain' => COOKIE_DOMAIN,
|
||
'secure' => $secure,
|
||
'httponly' => true,
|
||
'samesite' => 'Lax',
|
||
]);
|
||
$_COOKIE[self::COOKIE_NAME] = $code;
|
||
}
|
||
}
|
||
|
||
/** 注册:写入 aksrt_invite 表 */
|
||
public function on_user_register(int $invitee_id): void {
|
||
// 邀请注册功能关闭时不记录邀请关系
|
||
if (class_exists('AKSRT_Settings') && !AKSRT_Settings::invite_registration_enabled()) return;
|
||
|
||
// 优先级 1:表单显式填写的邀请码(由 AKSRT_Auth_Service 注入)
|
||
$code = '';
|
||
$inviter_id = 0;
|
||
if (class_exists('AKSRT_Auth_Service')) {
|
||
$ctx = AKSRT_Auth_Service::take_pending_invite_context();
|
||
if (!empty($ctx['inviter_id']) && $ctx['inviter_id'] !== $invitee_id) {
|
||
$inviter_id = (int) $ctx['inviter_id'];
|
||
$code = (string) $ctx['code'];
|
||
}
|
||
}
|
||
|
||
// 优先级 2:fallback 到 cookie
|
||
if ($inviter_id <= 0) {
|
||
if (empty($_COOKIE[self::COOKIE_NAME])) return;
|
||
$code = sanitize_key((string) $_COOKIE[self::COOKIE_NAME]);
|
||
if ($code === '') return;
|
||
$inviter_id = $this->get_user_by_code($code);
|
||
}
|
||
|
||
if ($inviter_id <= 0 || $inviter_id === $invitee_id) return;
|
||
|
||
$this->record_invite_registration($inviter_id, $invitee_id, $code, 'registered');
|
||
|
||
// 清 cookie,避免再次触发
|
||
if (!headers_sent()) {
|
||
setcookie(self::COOKIE_NAME, '', time() - 3600, COOKIEPATH ?: '/', COOKIE_DOMAIN);
|
||
}
|
||
|
||
// 若配置不要求邮箱验证就立即兑现
|
||
$invite_cfg = class_exists('AKSRT_Settings')
|
||
? AKSRT_Settings::get_invite_reward_settings()
|
||
: [
|
||
'require_verify' => true,
|
||
];
|
||
if (empty($invite_cfg['require_verify'])) {
|
||
$this->reward_pair($inviter_id, $invitee_id);
|
||
}
|
||
}
|
||
|
||
/** 邮箱验证后兑现奖励(钩子由 AKSRT_Auth_Service 触发,可后续接入) */
|
||
public function on_email_verified(int $user_id): void {
|
||
// 邀请注册功能关闭时不兑现奖励
|
||
if (class_exists('AKSRT_Settings') && !AKSRT_Settings::invite_registration_enabled()) return;
|
||
|
||
global $wpdb;
|
||
$table = AKSRT_DB_Migrator::table('invite');
|
||
if (!$table) return;
|
||
|
||
$row = $wpdb->get_row($wpdb->prepare(
|
||
"SELECT * FROM {$table} WHERE invitee_id = %d AND status IN ('registered', 'verified') LIMIT 1",
|
||
$user_id
|
||
), ARRAY_A);
|
||
if (!$row) return;
|
||
|
||
$wpdb->update(
|
||
$table,
|
||
['status' => 'verified'],
|
||
['id' => (int) $row['id']],
|
||
['%s'],
|
||
['%d']
|
||
);
|
||
|
||
if ((int) $row['inviter_id'] <= 0) {
|
||
return;
|
||
}
|
||
|
||
$this->reward_pair((int) $row['inviter_id'], $user_id);
|
||
}
|
||
|
||
/** 用户被删除时清理邀请码映射 */
|
||
public function on_delete_user(int $user_id): void {
|
||
$code = (string) get_user_meta($user_id, self::META_INVITE_CODE, true);
|
||
if ($code === '') return;
|
||
|
||
$map = $this->load_reverse_map();
|
||
if (isset($map[$code]) && (int) $map[$code] === $user_id) {
|
||
unset($map[$code]);
|
||
update_option(self::OPT_REVERSE_MAP, $map, false);
|
||
}
|
||
}
|
||
|
||
/** 双向发放奖励 */
|
||
private function reward_pair(int $inviter_id, int $invitee_id): void {
|
||
if (!class_exists('AKSRT_Credits') || !class_exists('AKSRT_Settings')) return;
|
||
|
||
$invite_cfg = AKSRT_Settings::get_invite_reward_settings();
|
||
if (empty($invite_cfg['enabled'])) return;
|
||
|
||
// 反作弊:同 IP 当日上限
|
||
if (!$this->ip_daily_check($invite_cfg)) return;
|
||
|
||
$inviter_amount = (int) ($invite_cfg['inviter_amount'] ?? 0);
|
||
$invitee_amount = (int) ($invite_cfg['invitee_amount'] ?? 0);
|
||
|
||
if ($inviter_amount > 0) {
|
||
AKSRT_Credits::add($inviter_id, $inviter_amount, 'invite', ['type' => 'user', 'id' => $invitee_id]);
|
||
}
|
||
if ($invitee_amount > 0) {
|
||
AKSRT_Credits::add($invitee_id, $invitee_amount, 'invitee', ['type' => 'user', 'id' => $inviter_id]);
|
||
}
|
||
|
||
// 标记 rewarded
|
||
global $wpdb;
|
||
$table = AKSRT_DB_Migrator::table('invite');
|
||
if ($table) {
|
||
$wpdb->update(
|
||
$table,
|
||
[
|
||
'status' => 'rewarded',
|
||
'rewarded_at' => current_time('mysql', true),
|
||
],
|
||
[
|
||
'inviter_id' => $inviter_id,
|
||
'invitee_id' => $invitee_id,
|
||
],
|
||
['%s', '%s'],
|
||
['%d', '%d']
|
||
);
|
||
}
|
||
}
|
||
|
||
/** 同 IP 当日邀请兑现次数限制 */
|
||
private function ip_daily_check(array $invite_cfg): bool {
|
||
$cap = (int) ($invite_cfg['ip_daily_cap'] ?? 3);
|
||
if ($cap <= 0) return true;
|
||
|
||
$ip = function_exists('aksrt_get_client_ip') ? aksrt_get_client_ip() : '';
|
||
if (!$ip) return true;
|
||
|
||
global $wpdb;
|
||
$log_table = AKSRT_DB_Migrator::table('credit_log');
|
||
if (!$log_table) return true;
|
||
|
||
$today_utc = (new DateTimeImmutable('today', wp_timezone()))->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s');
|
||
$count = (int) $wpdb->get_var($wpdb->prepare(
|
||
"SELECT COUNT(*) FROM {$log_table} WHERE reason = 'invite' AND ip = %s AND created_at >= %s",
|
||
$ip,
|
||
$today_utc
|
||
));
|
||
return $count < $cap;
|
||
}
|
||
|
||
/** 取或生成用户的邀请码 */
|
||
public function get_or_create_code(int $user_id): string {
|
||
$code = (string) get_user_meta($user_id, self::META_INVITE_CODE, true);
|
||
|
||
// 邀请码长度(后台可配置,4-12,默认 6)
|
||
$code_length = class_exists('AKSRT_Settings')
|
||
? AKSRT_Settings::invite_code_length()
|
||
: max(4, min(12, (int) aksrt_get_setting('invite_code_length', 6)));
|
||
|
||
// 兼容配置变更:已有码只要长度合理就复用
|
||
if ($code !== '' && strlen($code) <= $code_length + 2) return $code;
|
||
|
||
// 生成唯一码
|
||
$alphabet = 'abcdefghjkmnpqrstuvwxyz23456789';
|
||
$map = $this->load_reverse_map();
|
||
for ($i = 0; $i < 5; $i++) {
|
||
$code = '';
|
||
for ($j = 0; $j < $code_length; $j++) {
|
||
$code .= $alphabet[random_int(0, strlen($alphabet) - 1)];
|
||
}
|
||
if (!isset($map[$code]) && !$this->code_exists_legacy($code)) break;
|
||
}
|
||
update_user_meta($user_id, self::META_INVITE_CODE, $code);
|
||
|
||
// 同步反向映射
|
||
$map[$code] = $user_id;
|
||
update_option(self::OPT_REVERSE_MAP, $map, false);
|
||
|
||
return $code;
|
||
}
|
||
|
||
/**
|
||
* 公开 API:根据邀请码反查邀请人 user_id(不存在返回 0)。
|
||
* 给注册流程在 wp_create_user 之前做"邀请码是否合法"的预校验用。
|
||
*/
|
||
public function resolve_code(string $code): int {
|
||
$code = sanitize_key($code);
|
||
if ($code === '') return 0;
|
||
return $this->get_user_by_code($code);
|
||
}
|
||
|
||
/**
|
||
* 校验邀请码是否合法。
|
||
*
|
||
* 用户邀请码与后台批量生成的独立邀请码都视为有效。
|
||
*/
|
||
public function validate_code(string $code): bool {
|
||
$code = sanitize_key($code);
|
||
if ($code === '') {
|
||
return false;
|
||
}
|
||
|
||
if ($this->get_user_by_code($code) > 0) {
|
||
return true;
|
||
}
|
||
|
||
return $this->find_standalone_code_index($code) >= 0;
|
||
}
|
||
|
||
/**
|
||
* 注册完成后处理邀请码。
|
||
*
|
||
* - 用户邀请码:沿用 inviter/invitee canonical 关系
|
||
* - 独立邀请码:记录使用人并标记为已消耗,不参与奖励
|
||
*/
|
||
public function handle_registration_invite(int $invitee_id, string $invite_code): void {
|
||
$invitee_id = (int) $invitee_id;
|
||
$invite_code = sanitize_key($invite_code);
|
||
|
||
if ($invitee_id <= 0 || $invite_code === '') {
|
||
return;
|
||
}
|
||
|
||
if (class_exists('AKSRT_Settings') && !AKSRT_Settings::invite_registration_enabled()) {
|
||
return;
|
||
}
|
||
|
||
$inviter_id = $this->get_user_by_code($invite_code);
|
||
if ($inviter_id > 0) {
|
||
if ($inviter_id === $invitee_id) {
|
||
return;
|
||
}
|
||
|
||
$this->record_invite_registration($inviter_id, $invitee_id, $invite_code, 'registered');
|
||
return;
|
||
}
|
||
|
||
$index = $this->find_standalone_code_index($invite_code);
|
||
if ($index < 0) {
|
||
return;
|
||
}
|
||
|
||
$requires_verify = class_exists('AKSRT_Settings') && AKSRT_Settings::registration_requires_email_verification();
|
||
$status = $requires_verify ? 'registered' : 'verified';
|
||
|
||
if ($this->record_invite_registration(0, $invitee_id, $invite_code, $status)) {
|
||
$this->mark_standalone_code_used($invite_code, $invitee_id);
|
||
}
|
||
}
|
||
|
||
/** 反查邀请码对应的用户(O(1) option 查表,回退 user_meta 扫描) */
|
||
private function get_user_by_code(string $code): int {
|
||
if ($code === '') return 0;
|
||
|
||
$map = $this->load_reverse_map();
|
||
if (isset($map[$code])) {
|
||
$uid = (int) $map[$code];
|
||
if ($uid > 0) {
|
||
// 校验:确保该用户的 meta 仍然指向该 code(用户可能已被删除或 meta 被清空)
|
||
$meta_code = (string) get_user_meta($uid, self::META_INVITE_CODE, true);
|
||
if ($meta_code === $code) {
|
||
return $uid;
|
||
}
|
||
// 失配 → 修正映射
|
||
unset($map[$code]);
|
||
update_option(self::OPT_REVERSE_MAP, $map, false);
|
||
}
|
||
}
|
||
|
||
// 回退 + 自愈:扫描 user_meta(应只在迁移期或映射损坏时触发)
|
||
$uid = $this->code_exists_legacy($code);
|
||
if ($uid > 0) {
|
||
$map[$code] = $uid;
|
||
update_option(self::OPT_REVERSE_MAP, $map, false);
|
||
}
|
||
return $uid;
|
||
}
|
||
|
||
private function load_reverse_map(): array {
|
||
$map = get_option(self::OPT_REVERSE_MAP, []);
|
||
return is_array($map) ? $map : [];
|
||
}
|
||
|
||
/**
|
||
* 写入邀请关系记录。
|
||
*/
|
||
private function record_invite_registration(int $inviter_id, int $invitee_id, string $code, string $status = 'registered'): bool {
|
||
global $wpdb;
|
||
|
||
$table = AKSRT_DB_Migrator::table('invite');
|
||
if (!$table || $invitee_id <= 0 || $code === '') {
|
||
return false;
|
||
}
|
||
|
||
$existing_code = $wpdb->get_var($wpdb->prepare(
|
||
"SELECT id FROM {$table} WHERE invite_code = %s LIMIT 1",
|
||
$code
|
||
));
|
||
if ($existing_code) {
|
||
return false;
|
||
}
|
||
|
||
$existing_invitee = $wpdb->get_var($wpdb->prepare(
|
||
"SELECT id FROM {$table} WHERE invitee_id = %d LIMIT 1",
|
||
$invitee_id
|
||
));
|
||
if ($existing_invitee) {
|
||
return false;
|
||
}
|
||
|
||
$result = $wpdb->insert(
|
||
$table,
|
||
[
|
||
'inviter_id' => max(0, $inviter_id),
|
||
'invitee_id' => $invitee_id,
|
||
'invite_code' => $code,
|
||
'status' => $status,
|
||
'created_at' => current_time('mysql', true),
|
||
],
|
||
['%d', '%d', '%s', '%s', '%s']
|
||
);
|
||
|
||
return (bool) $result;
|
||
}
|
||
|
||
private function load_standalone_codes(): array {
|
||
$codes = get_option(self::OPT_STANDALONE_CODES, []);
|
||
return is_array($codes) ? array_values($codes) : [];
|
||
}
|
||
|
||
private function save_standalone_codes(array $codes): void {
|
||
update_option(self::OPT_STANDALONE_CODES, array_values($codes), false);
|
||
}
|
||
|
||
private function normalize_standalone_code(array $entry): array {
|
||
$code = sanitize_key((string) ($entry['code'] ?? ''));
|
||
$used_by = (int) ($entry['used_by'] ?? 0);
|
||
$used = !empty($entry['used']) || $used_by > 0;
|
||
|
||
return [
|
||
'code' => $code,
|
||
'created_at' => sanitize_text_field((string) ($entry['created_at'] ?? '')),
|
||
'used' => $used,
|
||
'used_by' => $used_by,
|
||
'used_at' => sanitize_text_field((string) ($entry['used_at'] ?? '')),
|
||
'disabled' => !empty($entry['disabled']),
|
||
'note' => sanitize_textarea_field((string) ($entry['note'] ?? '')),
|
||
'batch_id' => sanitize_text_field((string) ($entry['batch_id'] ?? '')),
|
||
'created_by' => (int) ($entry['created_by'] ?? 0),
|
||
];
|
||
}
|
||
|
||
private function find_standalone_code_index(string $code): int {
|
||
$code = sanitize_key($code);
|
||
$codes = $this->load_standalone_codes();
|
||
|
||
foreach ($codes as $index => $entry) {
|
||
$normalized = $this->normalize_standalone_code((array) $entry);
|
||
if ($normalized['code'] !== $code) {
|
||
continue;
|
||
}
|
||
|
||
if ($normalized['disabled'] || $normalized['used']) {
|
||
return -1;
|
||
}
|
||
|
||
return $index;
|
||
}
|
||
|
||
return -1;
|
||
}
|
||
|
||
private function mark_standalone_code_used(string $code, int $invitee_id): void {
|
||
$code = sanitize_key($code);
|
||
$codes = $this->load_standalone_codes();
|
||
|
||
foreach ($codes as $index => $entry) {
|
||
$normalized = $this->normalize_standalone_code((array) $entry);
|
||
if ($normalized['code'] !== $code) {
|
||
continue;
|
||
}
|
||
|
||
$normalized['used'] = true;
|
||
$normalized['used_by'] = $invitee_id;
|
||
$normalized['used_at'] = current_time('mysql', true);
|
||
$codes[$index] = $normalized;
|
||
$this->save_standalone_codes($codes);
|
||
return;
|
||
}
|
||
}
|
||
|
||
/** 旧路径:通过 user_meta 反查(保留作为自愈兜底) */
|
||
private function code_exists_legacy(string $code): int {
|
||
$users = get_users([
|
||
'meta_key' => self::META_INVITE_CODE,
|
||
'meta_value' => $code,
|
||
'number' => 1,
|
||
'fields' => 'ID',
|
||
]);
|
||
if (empty($users)) return 0;
|
||
return (int) $users[0];
|
||
}
|
||
|
||
private function code_exists(string $code): bool {
|
||
return $this->get_user_by_code($code) > 0;
|
||
}
|
||
|
||
// ---------- REST ----------
|
||
|
||
public function rest_me(WP_REST_Request $request) {
|
||
if (class_exists('AKSRT_Settings') && !AKSRT_Settings::invite_registration_enabled()) {
|
||
return new WP_REST_Response(['code' => 'error', 'message' => '邀请注册功能已关闭'], 403);
|
||
}
|
||
|
||
$user_id = get_current_user_id();
|
||
$code = $this->get_or_create_code($user_id);
|
||
|
||
global $wpdb;
|
||
$table = AKSRT_DB_Migrator::table('invite');
|
||
$stats = [
|
||
'registered' => 0,
|
||
'verified' => 0,
|
||
'rewarded' => 0,
|
||
];
|
||
if ($table) {
|
||
$rows = $wpdb->get_results($wpdb->prepare(
|
||
"SELECT status, COUNT(*) AS c FROM {$table} WHERE inviter_id = %d GROUP BY status",
|
||
$user_id
|
||
), ARRAY_A);
|
||
if (is_array($rows)) {
|
||
foreach ($rows as $r) {
|
||
$status = (string) $r['status'];
|
||
$count = (int) $r['c'];
|
||
|
||
// The stored status is terminal; dashboard stats are milestone totals.
|
||
if ($status === 'registered') {
|
||
$stats['registered'] += $count;
|
||
} elseif ($status === 'verified') {
|
||
$stats['registered'] += $count;
|
||
$stats['verified'] += $count;
|
||
} elseif ($status === 'rewarded') {
|
||
$stats['registered'] += $count;
|
||
$stats['verified'] += $count;
|
||
$stats['rewarded'] += $count;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
$invite_url = add_query_arg('invite', $code, aksrt_get_register_url());
|
||
|
||
return new WP_REST_Response([
|
||
'code' => $code,
|
||
'invite_url' => $invite_url,
|
||
'stats' => $stats,
|
||
], 200);
|
||
}
|
||
|
||
public function rest_list(WP_REST_Request $request) {
|
||
if (class_exists('AKSRT_Settings') && !AKSRT_Settings::invite_registration_enabled()) {
|
||
return new WP_REST_Response(['code' => 'error', 'message' => '邀请注册功能已关闭'], 403);
|
||
}
|
||
|
||
global $wpdb;
|
||
$user_id = get_current_user_id();
|
||
$page = max(1, (int) $request->get_param('page'));
|
||
$per_page = min(50, max(1, (int) $request->get_param('per_page') ?: 20));
|
||
$offset = ($page - 1) * $per_page;
|
||
|
||
$table = AKSRT_DB_Migrator::table('invite');
|
||
if (!$table) {
|
||
return new WP_REST_Response(['items' => [], 'total' => 0], 200);
|
||
}
|
||
|
||
$total = (int) $wpdb->get_var($wpdb->prepare(
|
||
"SELECT COUNT(*) FROM {$table} WHERE inviter_id = %d",
|
||
$user_id
|
||
));
|
||
$rows = $wpdb->get_results($wpdb->prepare(
|
||
"SELECT * FROM {$table} WHERE inviter_id = %d ORDER BY created_at DESC LIMIT %d OFFSET %d",
|
||
$user_id,
|
||
$per_page,
|
||
$offset
|
||
), ARRAY_A);
|
||
|
||
$items = [];
|
||
foreach ((array) $rows as $r) {
|
||
$invitee = $r['invitee_id'] ? get_userdata((int) $r['invitee_id']) : null;
|
||
$items[] = [
|
||
'id' => (int) $r['id'],
|
||
'invitee_id' => (int) $r['invitee_id'],
|
||
'invitee_name' => $invitee ? $invitee->display_name : '',
|
||
'invitee_avatar' => $invitee ? get_avatar_url((int) $invitee->ID, ['size' => 64]) : '',
|
||
'status' => (string) $r['status'],
|
||
'created_at' => (string) $r['created_at'],
|
||
'rewarded_at' => (string) $r['rewarded_at'],
|
||
];
|
||
}
|
||
|
||
return new WP_REST_Response([
|
||
'items' => $items,
|
||
'total' => $total,
|
||
'page' => $page,
|
||
'per_page' => $per_page,
|
||
], 200);
|
||
}
|
||
}
|