优化了一些问题
This commit is contained in:
+228
-264
@@ -7,300 +7,264 @@ namespace AKSRT\Auth;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class OAuthProxyClient {
|
||||
final class OAuthProxyClient {
|
||||
|
||||
private static $instance = null;
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function __construct() {}
|
||||
private function __construct() {}
|
||||
|
||||
/**
|
||||
* 初始化:注册回调路由
|
||||
*/
|
||||
public function init(): void {
|
||||
add_action('template_redirect', [$this, 'handle_proxy_callback']);
|
||||
add_action('init', [$this, 'add_rewrite_rules']);
|
||||
}
|
||||
public function init(): void {
|
||||
add_action('init', [$this, 'add_rewrite_rules']);
|
||||
add_action('init', [$this, 'maybe_flush_rewrite_rules'], 20);
|
||||
add_action('template_redirect', [$this, 'handle_proxy_callback'], 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 URL 重写规则
|
||||
*/
|
||||
public function add_rewrite_rules(): void {
|
||||
add_rewrite_rule(
|
||||
'^oauth/proxy/callback/?$',
|
||||
'index.php?aksrt_proxy_callback=1',
|
||||
'top'
|
||||
);
|
||||
public function add_rewrite_rules(): void {
|
||||
add_rewrite_rule('^oauth/proxy/callback/?$', 'index.php?aksrt_proxy_callback=1', 'top');
|
||||
add_filter('query_vars', static function (array $vars): array {
|
||||
$vars[] = 'aksrt_proxy_callback';
|
||||
return $vars;
|
||||
});
|
||||
}
|
||||
|
||||
add_filter('query_vars', function ($vars) {
|
||||
$vars[] = 'aksrt_proxy_callback';
|
||||
return $vars;
|
||||
});
|
||||
}
|
||||
public function maybe_flush_rewrite_rules(): void {
|
||||
$version = defined('AKSRT_VERSION') ? AKSRT_VERSION . ':oauth-proxy-client-v2' : 'oauth-proxy-client-v2';
|
||||
if (get_option('aksrt_oauth_proxy_client_routes_flushed') !== $version) {
|
||||
flush_rewrite_rules(false);
|
||||
update_option('aksrt_oauth_proxy_client_routes_flushed', $version, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成代理登录 URL
|
||||
*
|
||||
* @param string $provider 平台标识
|
||||
* @param string $action 操作类型: 'login' | 'bind'
|
||||
* @return string 代理登录 URL
|
||||
*/
|
||||
public function get_proxy_auth_url(string $provider, string $action = 'login'): string {
|
||||
$server_url = \AKSRT\Settings\Settings::oauth_proxy_server_url();
|
||||
$secret = \AKSRT\Settings\Settings::oauth_proxy_secret();
|
||||
public function get_proxy_auth_url(string $provider, string $action = 'login'): string {
|
||||
$provider = sanitize_key($provider);
|
||||
$action = $action === 'bind' ? 'bind' : 'login';
|
||||
$server_url = OAuthProxyProtocol::server_base_url(OAuthProviderCatalog::proxy_server_url());
|
||||
$secret = OAuthProviderCatalog::proxy_secret();
|
||||
|
||||
if (empty($server_url) || empty($secret)) {
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
!OAuthProviderCatalog::master_enabled()
|
||||
|| OAuthProviderCatalog::proxy_mode() !== 'client'
|
||||
|| !OAuthProviderCatalog::is_direct($provider)
|
||||
|| !in_array($provider, OAuthProviderCatalog::proxy_client_providers(), true)
|
||||
|| $server_url === ''
|
||||
|| $secret === ''
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$back_url = home_url('/oauth/proxy/callback');
|
||||
$uid = get_current_user_id();
|
||||
$nonce = wp_create_nonce('aksrt_proxy_' . $provider . '_' . $uid);
|
||||
$user_id = get_current_user_id();
|
||||
if ($action === 'bind' && $user_id <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$params = [
|
||||
'provider' => $provider,
|
||||
'agent_back_url' => $back_url,
|
||||
'action' => $action,
|
||||
'nonce' => $nonce,
|
||||
'uid' => $uid,
|
||||
'ts' => time(),
|
||||
];
|
||||
$redirect_to = isset($_GET['redirect_to'])
|
||||
? OAuthUrlPolicy::validated_redirect((string) wp_unslash($_GET['redirect_to']), home_url('/'))
|
||||
: home_url('/');
|
||||
$callback = OAuthProxyProtocol::callback_url(home_url('/oauth/proxy/callback/'));
|
||||
if ($callback === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$params['sign'] = $this->sign($params, $secret);
|
||||
try {
|
||||
$request_id = OAuthTransactionStore::issue('proxy_client', [
|
||||
'request_id' => '',
|
||||
'provider' => $provider,
|
||||
'action' => $action,
|
||||
'user_id' => $user_id,
|
||||
'redirect_to' => $redirect_to,
|
||||
'callback' => $callback,
|
||||
'issuer' => OAuthProxyProtocol::issuer($server_url),
|
||||
], OAuthProxyProtocol::REPLAY_TTL, true);
|
||||
$updated = OAuthTransactionStore::update($request_id, 'proxy_client', static function (array $row) use ($request_id): array {
|
||||
$row['payload']['request_id'] = $request_id;
|
||||
return $row;
|
||||
}, true);
|
||||
if (!$updated) {
|
||||
OAuthTransactionStore::claim($request_id, 'proxy_client', true);
|
||||
return '';
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$server_url = rtrim($server_url, '/');
|
||||
return $server_url . '/oauth/proxy/' . $provider . '?' . http_build_query($params);
|
||||
}
|
||||
$params = [
|
||||
'v' => OAuthProxyProtocol::VERSION,
|
||||
'request_id' => $request_id,
|
||||
'provider' => $provider,
|
||||
'action' => $action,
|
||||
'agent_back_url' => $callback,
|
||||
'ts' => (string) time(),
|
||||
];
|
||||
$params['sign'] = OAuthProxyProtocol::sign_request($params, $secret);
|
||||
|
||||
/**
|
||||
* 处理代理回调(从服务端重定向回来)
|
||||
*/
|
||||
public function handle_proxy_callback(): void {
|
||||
if (!get_query_var('aksrt_proxy_callback')) {
|
||||
return;
|
||||
}
|
||||
return $server_url . '/oauth/proxy/' . rawurlencode($provider) . '/?' . http_build_query($params, '', '&', PHP_QUERY_RFC3986);
|
||||
}
|
||||
|
||||
$secret = \AKSRT\Settings\Settings::oauth_proxy_secret();
|
||||
if (empty($secret)) {
|
||||
wp_die(__('代理登录配置错误', 'aksrt'));
|
||||
}
|
||||
public function handle_proxy_callback(): void {
|
||||
if (!get_query_var('aksrt_proxy_callback')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sign = sanitize_text_field($_GET['sign'] ?? '');
|
||||
$provider = sanitize_key($_GET['provider'] ?? '');
|
||||
$action = sanitize_key($_GET['action'] ?? 'login');
|
||||
$fields = OAuthProxyProtocol::callback_fields($_GET);
|
||||
$signature = sanitize_text_field((string) wp_unslash($_GET['sign'] ?? ''));
|
||||
$secret = OAuthProviderCatalog::proxy_secret();
|
||||
$request_id = sanitize_text_field($fields['request_id']);
|
||||
$provider = sanitize_key($fields['provider']);
|
||||
$action = $fields['action'] === 'bind' ? 'bind' : 'login';
|
||||
|
||||
if (empty($sign) || empty($provider)) {
|
||||
wp_die(__('代理回调参数无效', 'aksrt'));
|
||||
}
|
||||
if (
|
||||
OAuthProviderCatalog::proxy_mode() !== 'client'
|
||||
|| $fields['v'] !== OAuthProxyProtocol::VERSION
|
||||
|| !OAuthProxyProtocol::token_valid($request_id)
|
||||
|| !OAuthProxyProtocol::token_valid($fields['jti'])
|
||||
|| !OAuthProxyProtocol::timestamp_valid($fields['ts'])
|
||||
|| !OAuthProxyProtocol::verify_callback($fields, $secret, $signature)
|
||||
) {
|
||||
$this->redirect_error('proxy_response_invalid', $provider, $action);
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
$verify_params = $_GET;
|
||||
unset($verify_params['sign']);
|
||||
$row = OAuthTransactionStore::peek($request_id, 'proxy_client', true);
|
||||
$payload = is_array($row['payload'] ?? null) ? $row['payload'] : [];
|
||||
$expected_provider = sanitize_key((string) ($payload['provider'] ?? ''));
|
||||
$expected_action = (string) ($payload['action'] ?? 'login') === 'bind' ? 'bind' : 'login';
|
||||
$expected_issuer = (string) ($payload['issuer'] ?? '');
|
||||
$issuer = OAuthProxyProtocol::issuer($fields['issuer']);
|
||||
|
||||
if (!$this->verify_sign($verify_params, $secret, $sign)) {
|
||||
wp_die(__('代理回调签名验证失败', 'aksrt'));
|
||||
}
|
||||
if (
|
||||
!$row
|
||||
|| !hash_equals((string) ($payload['request_id'] ?? ''), $request_id)
|
||||
|| !hash_equals($expected_provider, $provider)
|
||||
|| !hash_equals($expected_action, $action)
|
||||
|| $issuer === ''
|
||||
|| !hash_equals($expected_issuer, $issuer)
|
||||
|| !OAuthProviderCatalog::is_direct($provider)
|
||||
|| !in_array($provider, OAuthProviderCatalog::proxy_client_providers(), true)
|
||||
) {
|
||||
$this->redirect_error('proxy_response_mismatch', $expected_provider ?: $provider, $expected_action);
|
||||
}
|
||||
|
||||
// 验证时间戳(5分钟内有效)
|
||||
$ts = (int) ($_GET['ts'] ?? 0);
|
||||
if (abs(time() - $ts) > 300) {
|
||||
wp_die(__('代理回调已过期,请重新登录', 'aksrt'));
|
||||
}
|
||||
if ($action === 'bind' && (int) ($payload['user_id'] ?? 0) !== get_current_user_id()) {
|
||||
$this->redirect_error('state_user_mismatch', $provider, $action);
|
||||
}
|
||||
|
||||
// 提取用户数据
|
||||
$provider_id = sanitize_text_field($_GET['provider_id'] ?? '');
|
||||
if (empty($provider_id)) {
|
||||
wp_die(__('代理回调缺少用户标识', 'aksrt'));
|
||||
}
|
||||
$jti_scope = 'proxy_client_jti:' . $issuer . ':' . $fields['jti'];
|
||||
$jti_lock = OAuthTransactionStore::acquire_lock($jti_scope, OAuthProxyProtocol::REPLAY_TTL);
|
||||
if ($jti_lock === false) {
|
||||
$this->redirect_error('proxy_replayed', $provider, $action);
|
||||
}
|
||||
|
||||
$user_data = [
|
||||
'provider' => $provider,
|
||||
'provider_id' => $provider_id,
|
||||
'username' => sanitize_user($_GET['username'] ?? '', true),
|
||||
'name' => sanitize_text_field($_GET['name'] ?? ''),
|
||||
'email' => sanitize_email($_GET['email'] ?? ''),
|
||||
'email_verified' => !empty($_GET['email_verified']),
|
||||
'avatar_url' => esc_url_raw($_GET['avatar_url'] ?? ''),
|
||||
'bio' => sanitize_text_field($_GET['bio'] ?? ''),
|
||||
];
|
||||
|
||||
if ($action === 'bind') {
|
||||
$this->handle_proxy_bind($provider, $user_data);
|
||||
return;
|
||||
}
|
||||
$claimed = OAuthTransactionStore::claim($request_id, 'proxy_client', true);
|
||||
if (!$claimed) {
|
||||
$this->redirect_error('proxy_replayed', $provider, $action);
|
||||
}
|
||||
|
||||
$this->handle_proxy_login($provider, $user_data);
|
||||
}
|
||||
if ($fields['error'] !== '') {
|
||||
$this->redirect_error($this->public_error($fields['error']), $provider, $action);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理代理登录
|
||||
*/
|
||||
private function handle_proxy_login(string $provider, array $user_data): void {
|
||||
$user = $this->find_or_create_user_from_proxy($provider, $user_data);
|
||||
$identity = $this->identity($provider, $fields);
|
||||
if ($identity['provider_id'] === '') {
|
||||
$this->redirect_error('proxy_identity_invalid', $provider, $action);
|
||||
}
|
||||
|
||||
// 未匹配已有账号 → 跳转绑定页
|
||||
if (is_wp_error($user) && $user->get_error_code() === 'oauth_no_match') {
|
||||
$error_data = $user->get_error_data();
|
||||
$obk = 'aksrt_ob_' . wp_generate_password(24, false);
|
||||
set_transient($obk, [
|
||||
'provider' => $error_data['provider'],
|
||||
'provider_id' => $error_data['user_data']['provider_id'] ?? '',
|
||||
'user_data' => $error_data['user_data'],
|
||||
'token' => [],
|
||||
], 15 * MINUTE_IN_SECONDS);
|
||||
if ($action === 'bind') {
|
||||
$result = OAuthAccountService::bind(get_current_user_id(), $provider, $identity);
|
||||
if (is_wp_error($result)) {
|
||||
$this->redirect_error(
|
||||
$result->get_error_code() === 'oauth_binding_conflict' ? 'binding_conflict' : 'binding_failed',
|
||||
$provider,
|
||||
$action
|
||||
);
|
||||
}
|
||||
$this->redirect_success('bind', $provider, $this->settings_url());
|
||||
}
|
||||
|
||||
$bind_url = aksrt_get_bind_url();
|
||||
wp_safe_redirect(add_query_arg('obk', $obk, $bind_url));
|
||||
exit;
|
||||
}
|
||||
$user = OAuthAccountService::resolve($provider, $identity);
|
||||
if (is_wp_error($user) && $user->get_error_code() === 'oauth_no_match') {
|
||||
try {
|
||||
$obk = OAuthTransactionStore::issue('obk', [
|
||||
'provider' => $provider,
|
||||
'provider_id' => $identity['provider_id'],
|
||||
'user_data' => $identity,
|
||||
'redirect_to' => OAuthUrlPolicy::validated_redirect((string) ($payload['redirect_to'] ?? ''), home_url('/')),
|
||||
], 15 * MINUTE_IN_SECONDS, true);
|
||||
} catch (\Throwable $e) {
|
||||
$this->redirect_error('account_match_failed', $provider, $action);
|
||||
}
|
||||
$bind_url = function_exists('aksrt_get_bind_url')
|
||||
? aksrt_get_bind_url($obk)
|
||||
: home_url('/sign/?tab=bind&obk=' . rawurlencode($obk));
|
||||
wp_safe_redirect($bind_url);
|
||||
exit;
|
||||
}
|
||||
if (!$user instanceof \WP_User) {
|
||||
$this->redirect_error('account_match_failed', $provider, $action);
|
||||
}
|
||||
|
||||
if (is_wp_error($user)) {
|
||||
wp_die(__('登录失败:', 'aksrt') . esc_html($user->get_error_message()));
|
||||
}
|
||||
OAuthAccountService::complete_login($user, true);
|
||||
$this->redirect_success(
|
||||
'login',
|
||||
$provider,
|
||||
OAuthUrlPolicy::validated_redirect((string) ($payload['redirect_to'] ?? ''), home_url('/'))
|
||||
);
|
||||
}
|
||||
|
||||
// 登录成功
|
||||
wp_set_auth_cookie($user->ID, true);
|
||||
$redirect_url = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url() : home_url('/');
|
||||
wp_safe_redirect(add_query_arg(['oauth_action' => 'login', 'oauth_bound' => $provider], $redirect_url));
|
||||
exit;
|
||||
}
|
||||
private function identity(string $provider, array $fields): array {
|
||||
return [
|
||||
'provider' => $provider,
|
||||
'provider_id' => sanitize_text_field($fields['provider_id']),
|
||||
'username' => sanitize_text_field($fields['username']),
|
||||
'name' => sanitize_text_field($fields['name']),
|
||||
'email' => sanitize_email($fields['email']),
|
||||
'email_verified' => $fields['email_verified'] === '1',
|
||||
'avatar_url' => esc_url_raw($fields['avatar_url']),
|
||||
'url' => esc_url_raw($fields['url']),
|
||||
'bio' => sanitize_text_field($fields['bio']),
|
||||
'locale' => sanitize_text_field($fields['locale']),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理代理绑定(已登录用户绑定第三方账号)
|
||||
*/
|
||||
private function handle_proxy_bind(string $provider, array $user_data): void {
|
||||
$current_uid = get_current_user_id();
|
||||
if (!$current_uid) {
|
||||
wp_safe_redirect(aksrt_get_login_url());
|
||||
exit;
|
||||
}
|
||||
private function public_error(string $error): string {
|
||||
$error = sanitize_key($error);
|
||||
$allowed = [
|
||||
'provider_cancelled',
|
||||
'code_missing',
|
||||
'provider_unavailable',
|
||||
'identity_invalid',
|
||||
'callback_failed',
|
||||
];
|
||||
return in_array($error, $allowed, true) ? $error : 'callback_failed';
|
||||
}
|
||||
|
||||
// 检查是否已被其他用户占用
|
||||
$existing = $this->find_user_by_provider_meta($provider, $user_data['provider_id']);
|
||||
if ($existing && (int) $existing->ID !== $current_uid) {
|
||||
wp_die(__('该第三方账号已被其他用户绑定', 'aksrt'));
|
||||
}
|
||||
private function settings_url(): string {
|
||||
return function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url('settings') : home_url('/');
|
||||
}
|
||||
|
||||
$this->bind_provider_to_user($current_uid, $provider, $user_data);
|
||||
private function redirect_success(string $action, string $provider, string $target): void {
|
||||
wp_safe_redirect(add_query_arg([
|
||||
'oauth_action' => $action,
|
||||
'oauth_provider' => sanitize_key($provider),
|
||||
], $target));
|
||||
exit;
|
||||
}
|
||||
|
||||
$redirect_url = function_exists('aksrt_get_user_center_url')
|
||||
? add_query_arg(['oauth_action' => 'bind', 'oauth_bound' => $provider], aksrt_get_user_center_url('settings'))
|
||||
: home_url('/');
|
||||
wp_safe_redirect($redirect_url);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找或创建用户(代理模式)
|
||||
*
|
||||
* @return \WP_User|\WP_Error
|
||||
*/
|
||||
private function find_or_create_user_from_proxy(string $provider, array $user_data) {
|
||||
$email = $user_data['email'] ?? '';
|
||||
|
||||
// 1. 通过 provider_id 查找
|
||||
if (!empty($user_data['provider_id'])) {
|
||||
$existing = $this->find_user_by_provider_meta($provider, $user_data['provider_id']);
|
||||
if ($existing instanceof \WP_User) {
|
||||
$this->ensure_binding_meta($existing->ID, $provider, $user_data);
|
||||
return $existing;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 通过邮箱查找
|
||||
if (!empty($email) && is_email($email)) {
|
||||
$existing = get_user_by('email', $email);
|
||||
if ($existing && !$existing instanceof \WP_Error) {
|
||||
$this->ensure_binding_meta($existing->ID, $provider, $user_data);
|
||||
return $existing;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 未匹配
|
||||
return new \WP_Error('oauth_no_match', __('需要绑定已有账号', 'aksrt'), [
|
||||
'provider' => $provider,
|
||||
'user_data' => $user_data,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保绑定 meta 存在
|
||||
*/
|
||||
private function ensure_binding_meta(int $user_id, string $provider, array $user_data): void {
|
||||
update_user_meta($user_id, "aksrt_{$provider}_id", $user_data['provider_id'] ?? '');
|
||||
|
||||
$bindings = get_user_meta($user_id, 'aksrt_oauth_bindings', true) ?: [];
|
||||
if (!is_array($bindings)) {
|
||||
$bindings = [];
|
||||
}
|
||||
if (!in_array($provider, $bindings, true)) {
|
||||
$bindings[] = $provider;
|
||||
update_user_meta($user_id, 'aksrt_oauth_bindings', $bindings);
|
||||
}
|
||||
|
||||
if (!empty($user_data['avatar_url'])) {
|
||||
update_user_meta($user_id, 'aksrt_oauth_avatar', esc_url_raw($user_data['avatar_url']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定第三方账号到用户
|
||||
*/
|
||||
private function bind_provider_to_user(int $user_id, string $provider, array $user_data): void {
|
||||
if (!empty($user_data['provider_id'])) {
|
||||
update_user_meta($user_id, "aksrt_{$provider}_id", $user_data['provider_id']);
|
||||
}
|
||||
|
||||
$bindings = get_user_meta($user_id, 'aksrt_oauth_bindings', true) ?: [];
|
||||
if (!is_array($bindings)) {
|
||||
$bindings = [];
|
||||
}
|
||||
if (!in_array($provider, $bindings, true)) {
|
||||
$bindings[] = $provider;
|
||||
update_user_meta($user_id, 'aksrt_oauth_bindings', $bindings);
|
||||
}
|
||||
|
||||
if (!empty($user_data['avatar_url'])) {
|
||||
update_user_meta($user_id, 'aksrt_oauth_avatar', esc_url_raw($user_data['avatar_url']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 provider meta 查找用户
|
||||
*/
|
||||
private function find_user_by_provider_meta(string $provider, string $provider_id): ?\WP_User {
|
||||
if (empty($provider_id)) {
|
||||
return null;
|
||||
}
|
||||
$users = get_users([
|
||||
'meta_key' => "aksrt_{$provider}_id",
|
||||
'meta_value' => $provider_id,
|
||||
'number' => 1,
|
||||
]);
|
||||
return !empty($users[0]) ? $users[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 HMAC-SHA256 签名
|
||||
*/
|
||||
private function sign(array $data, string $secret): string {
|
||||
unset($data['sign']);
|
||||
ksort($data);
|
||||
$payload = http_build_query($data);
|
||||
return hash_hmac('sha256', $payload, $secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 HMAC-SHA256 签名
|
||||
*/
|
||||
private function verify_sign(array $data, string $secret, string $expected_sign): bool {
|
||||
$computed = $this->sign($data, $secret);
|
||||
return hash_equals($computed, $expected_sign);
|
||||
}
|
||||
private function redirect_error(string $code, string $provider = '', string $action = 'login'): void {
|
||||
$target = $action === 'bind' && is_user_logged_in()
|
||||
? $this->settings_url()
|
||||
: (function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url());
|
||||
wp_safe_redirect(add_query_arg([
|
||||
'oauth_action' => 'error',
|
||||
'oauth_provider' => sanitize_key($provider),
|
||||
'oauth_error' => sanitize_key($code),
|
||||
], $target));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user