142 lines
7.0 KiB
PHP
142 lines
7.0 KiB
PHP
<?php
|
|
/**
|
|
* Administrator membership mutations.
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Content;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
trait VipServiceMembership {
|
|
|
|
/** @return array{level:int,expires:int,base_level:int,base_expires:int,expired_level:int} */
|
|
public function get_membership_snapshot(int $user_id): array {
|
|
return [
|
|
'level' => max(0, min(2, (int) get_user_meta($user_id, 'aksrt_vip_level', true))),
|
|
'expires' => max(0, (int) get_user_meta($user_id, 'aksrt_vip_expires', true)),
|
|
'base_level' => max(0, min(2, (int) get_user_meta($user_id, 'aksrt_vip_base_level', true))),
|
|
'base_expires' => max(0, (int) get_user_meta($user_id, 'aksrt_vip_base_expires', true)),
|
|
'expired_level' => max(0, min(2, (int) get_user_meta($user_id, 'aksrt_vip_level_expired', true))),
|
|
];
|
|
}
|
|
|
|
/** @param array<string,mixed> $payload @param array<string,mixed> $context */
|
|
public function mutate_membership(int $user_id, string $operation, array $payload, array $context = []): array|\WP_Error {
|
|
if ($user_id <= 0 || !get_userdata($user_id)) {
|
|
return new \WP_Error('aksrt_vip_user_missing', __('用户不存在。', 'aksrt'));
|
|
}
|
|
if (!in_array($operation, ['set', 'extend', 'disable', 'restore'], true)) {
|
|
return new \WP_Error('aksrt_vip_operation_invalid', __('会员操作无效。', 'aksrt'));
|
|
}
|
|
$reason = sanitize_text_field((string) ($context['reason'] ?? ''));
|
|
if ($reason === '') {
|
|
return new \WP_Error('aksrt_vip_reason_required', __('请填写变更原因。', 'aksrt'));
|
|
}
|
|
|
|
$before = $this->get_membership_snapshot($user_id);
|
|
$repository = new VipMembershipEventRepository();
|
|
$event_uuid = $repository->begin([
|
|
'batch_uuid' => $context['batch_uuid'] ?? '',
|
|
'user_id' => $user_id,
|
|
'actor_user_id' => (int) ($context['actor_user_id'] ?? 0),
|
|
'operation' => $operation,
|
|
'source' => $context['source'] ?? 'admin',
|
|
'reason' => $reason,
|
|
'request' => $payload,
|
|
'before' => $before,
|
|
]);
|
|
if (is_wp_error($event_uuid)) {
|
|
return $event_uuid;
|
|
}
|
|
|
|
$target = $this->membership_target($before, $operation, $payload);
|
|
if (is_wp_error($target)) {
|
|
$repository->complete($event_uuid, 'failed', $before, $target->get_error_code(), $target->get_error_message());
|
|
return $target;
|
|
}
|
|
|
|
delete_user_meta($user_id, 'aksrt_vip_base_level');
|
|
delete_user_meta($user_id, 'aksrt_vip_base_expires');
|
|
delete_user_meta($user_id, 'aksrt_vip_level_expired');
|
|
update_user_meta($user_id, 'aksrt_vip_level', $target['level']);
|
|
update_user_meta($user_id, 'aksrt_vip_expires', $target['expires']);
|
|
$after = $this->get_membership_snapshot($user_id);
|
|
if ($after['level'] !== $target['level'] || $after['expires'] !== $target['expires'] || $after['base_level'] !== 0 || $after['base_expires'] !== 0 || $after['expired_level'] !== 0) {
|
|
$this->restore_membership_snapshot($user_id, $before);
|
|
$after = $this->get_membership_snapshot($user_id);
|
|
$error = new \WP_Error('aksrt_vip_write_failed', __('会员状态保存失败。', 'aksrt'));
|
|
$repository->complete($event_uuid, 'failed', $after, $error->get_error_code(), $error->get_error_message());
|
|
return $error;
|
|
}
|
|
|
|
unset($this->vip_member_cache[$user_id], $this->vip_catalog_cache[$user_id]);
|
|
$repository->complete($event_uuid, 'succeeded', $after);
|
|
do_action('aksrt_vip_membership_mutated', $user_id, $operation, $before, $after, $context, $event_uuid);
|
|
return ['event_uuid' => $event_uuid, 'before' => $before, 'after' => $after];
|
|
}
|
|
|
|
/** @param array<string,mixed> $snapshot */
|
|
private function restore_membership_snapshot(int $user_id, array $snapshot): void {
|
|
foreach ([
|
|
'aksrt_vip_level' => 'level',
|
|
'aksrt_vip_expires' => 'expires',
|
|
'aksrt_vip_base_level' => 'base_level',
|
|
'aksrt_vip_base_expires' => 'base_expires',
|
|
'aksrt_vip_level_expired' => 'expired_level',
|
|
] as $meta_key => $snapshot_key) {
|
|
$value = (int) ($snapshot[$snapshot_key] ?? 0);
|
|
if ($value > 0 || $meta_key === 'aksrt_vip_level' || ($meta_key === 'aksrt_vip_expires' && (int) ($snapshot['level'] ?? 0) > 0)) {
|
|
update_user_meta($user_id, $meta_key, $value);
|
|
} else {
|
|
delete_user_meta($user_id, $meta_key);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** @param array<string,mixed> $before @param array<string,mixed> $payload @return array{level:int,expires:int}|\WP_Error */
|
|
private function membership_target(array $before, string $operation, array $payload): array|\WP_Error {
|
|
if ($operation === 'restore') {
|
|
$level = max(0, min(2, (int) $before['base_level']));
|
|
$expires = max(0, (int) $before['base_expires']);
|
|
if ($level <= 0 || ($expires > 0 && $expires <= time())) {
|
|
return new \WP_Error('aksrt_vip_restore_unavailable', __('没有有效的回退会员可恢复。', 'aksrt'));
|
|
}
|
|
return ['level' => $level, 'expires' => $expires];
|
|
}
|
|
if ($operation === 'disable') {
|
|
return ['level' => 0, 'expires' => 0];
|
|
}
|
|
if ($operation === 'set') {
|
|
$level = (int) ($payload['level'] ?? 0);
|
|
if (!in_array($level, [1, 2], true)) {
|
|
return new \WP_Error('aksrt_vip_level_invalid', __('会员档位无效。', 'aksrt'));
|
|
}
|
|
$expires = (int) ($payload['expires'] ?? -1);
|
|
if ($expires < 0 || ($expires > 0 && $expires < time())) {
|
|
return new \WP_Error('aksrt_vip_expiry_invalid', __('到期时间无效或已过期。', 'aksrt'));
|
|
}
|
|
return ['level' => $level, 'expires' => $expires];
|
|
}
|
|
$amount = (int) ($payload['amount'] ?? 0);
|
|
$unit = sanitize_key((string) ($payload['unit'] ?? 'day'));
|
|
if ($before['level'] < 1) {
|
|
return new \WP_Error('aksrt_vip_extend_unavailable', __('无有效会员,无法延长。', 'aksrt'));
|
|
}
|
|
if ($before['expires'] <= 0) {
|
|
return new \WP_Error('aksrt_vip_extend_permanent', __('永久会员无需延长。', 'aksrt'));
|
|
}
|
|
if ($amount < 1 || $amount > 3650 || !in_array($unit, ['day', 'month'], true)) {
|
|
return new \WP_Error('aksrt_vip_duration_invalid', __('延长时长无效。', 'aksrt'));
|
|
}
|
|
$base = max(time(), $before['expires']);
|
|
$date = (new \DateTimeImmutable('@' . $base))->setTimezone(wp_timezone());
|
|
$next = $date->modify('+' . $amount . ' ' . ($unit === 'month' ? 'months' : 'days'));
|
|
if (!$next) {
|
|
return new \WP_Error('aksrt_vip_duration_invalid', __('无法计算新的到期时间。', 'aksrt'));
|
|
}
|
|
return ['level' => $before['level'], 'expires' => $next->getTimestamp()];
|
|
}
|
|
}
|