refactor(classes): organize inc/classes into domain folders
Move ~94 root class shells and root *-traits into domain directories (core, auth, user, credits, content, payment/gateways, forum, shop, sms, ai, messages, extensions, widgets, settings, admin). Update bootstrap require paths, co-locate traits with owners, fix forum/user relative requires, drop duplicate shop controller requires, and wire home-modules load. Path-only; class names and init order unchanged.
This commit is contained in:
1 parent
2a8fe68048
commit
ce1c13b947
115 files changed
+171
-224
No files matched your search
@@ -0,0 +1,404 @@
|
||||
<?php
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Moderation_Part_1 {
|
||||
|
||||
public function render_comments_custom_column(string $column, int $comment_id): void {
|
||||
if ($column !== 'aksrt_moderation') {
|
||||
return;
|
||||
}
|
||||
|
||||
$status = '';
|
||||
if (function_exists('get_comment')) {
|
||||
$comment_obj = get_comment($comment_id);
|
||||
if (is_object($comment_obj) && isset($comment_obj->comment_approved)) {
|
||||
$status = (string) $comment_obj->comment_approved;
|
||||
}
|
||||
}
|
||||
$status_label = $this->status_label($status);
|
||||
|
||||
$source = (string) get_comment_meta($comment_id, 'aksrt_moderation_source', true);
|
||||
$result_value = (string) get_comment_meta($comment_id, 'aksrt_moderation_result', true);
|
||||
$reason = (string) get_comment_meta($comment_id, 'aksrt_moderation_reason', true);
|
||||
$akismet = (string) get_comment_meta($comment_id, 'aksrt_moderation_akismet', true);
|
||||
$ai_score = (string) get_comment_meta($comment_id, 'aksrt_moderation_ai_score', true);
|
||||
$ai_flagged = (string) get_comment_meta($comment_id, 'aksrt_moderation_ai_flagged', true);
|
||||
$ai_top = (string) get_comment_meta($comment_id, 'aksrt_moderation_ai_top_category', true);
|
||||
$keyword = (string) get_comment_meta($comment_id, 'aksrt_moderation_keyword', true);
|
||||
$match_type = (string) get_comment_meta($comment_id, 'aksrt_moderation_keyword_match_type', true);
|
||||
$moderation_user_id = (string) get_comment_meta($comment_id, 'aksrt_moderation_user_id', true);
|
||||
$moderation_user_ip = (string) get_comment_meta($comment_id, 'aksrt_moderation_user_ip', true);
|
||||
|
||||
if ($source === '') {
|
||||
$source = 'wp_default';
|
||||
}
|
||||
if ($result_value === '') {
|
||||
$result_value = $this->result_by_approved($status);
|
||||
}
|
||||
if ($reason === '') {
|
||||
$reason = 'wp_default_policy';
|
||||
}
|
||||
if ($akismet === '') {
|
||||
$akismet_meta = get_comment_meta($comment_id, 'akismet_result', true);
|
||||
if ((string) $akismet_meta === 'true') {
|
||||
$akismet = 'spam';
|
||||
} elseif ((string) $akismet_meta === 'false') {
|
||||
$akismet = 'ham';
|
||||
} else {
|
||||
$akismet = 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
if ($source === 'wp_default' && $akismet === 'ham') {
|
||||
$source = 'akismet';
|
||||
if ($result_value === 'wp_default_policy') {
|
||||
$result_value = 'approved';
|
||||
}
|
||||
if ($reason === 'wp_default_policy') {
|
||||
$reason = 'ham';
|
||||
}
|
||||
} elseif ($source === 'wp_default' && $akismet === 'spam') {
|
||||
$source = 'akismet';
|
||||
$result_value = 'spam';
|
||||
if ($reason === 'wp_default_policy') {
|
||||
$reason = 'spam';
|
||||
}
|
||||
}
|
||||
|
||||
echo '<strong>' . esc_html($status_label) . '</strong><br>';
|
||||
echo 'Source: ' . esc_html($source) . ' | ';
|
||||
echo 'Result: ' . esc_html($result_value) . ' | ';
|
||||
echo 'Reason: ' . esc_html($reason) . ' | ';
|
||||
echo 'Akismet: ' . esc_html($akismet);
|
||||
|
||||
$extras = [];
|
||||
if ($ai_score !== '') {
|
||||
$extras[] = 'AI score: ' . $ai_score;
|
||||
}
|
||||
if ($ai_flagged !== '') {
|
||||
$extras[] = 'AI flagged: ' . $ai_flagged;
|
||||
}
|
||||
if ($ai_top !== '') {
|
||||
$extras[] = 'AI top: ' . $ai_top;
|
||||
}
|
||||
if ($keyword !== '') {
|
||||
$extras[] = 'Keyword: ' . $keyword;
|
||||
}
|
||||
if ($match_type !== '') {
|
||||
$extras[] = 'Match: ' . $match_type;
|
||||
}
|
||||
if ($moderation_user_id !== '') {
|
||||
$extras[] = 'UID: ' . $moderation_user_id;
|
||||
}
|
||||
if ($moderation_user_ip !== '') {
|
||||
$extras[] = 'IP: ' . $moderation_user_ip;
|
||||
}
|
||||
if (!empty($extras)) {
|
||||
echo '<br><small>' . esc_html(implode(' | ', $extras)) . '</small>';
|
||||
}
|
||||
}
|
||||
|
||||
private function new_default_result(): array {
|
||||
return [
|
||||
'result' => 'approved',
|
||||
'source' => 'wp_default',
|
||||
'reason' => 'wp_default_policy',
|
||||
'akismet' => 'unknown',
|
||||
'ai_score' => '',
|
||||
'ai_flagged' => '',
|
||||
'ai_top_category' => '',
|
||||
'keyword' => '',
|
||||
'keyword_match_type' => '',
|
||||
'ip' => '',
|
||||
];
|
||||
}
|
||||
|
||||
private function remember_result(array $commentdata, array $decision): void {
|
||||
$fp = $this->comment_fingerprint($commentdata);
|
||||
if ($fp === '') {
|
||||
return;
|
||||
}
|
||||
$this->moderation_result_map[$fp] = $decision;
|
||||
if (array_key_exists('comment_approved', $commentdata)) {
|
||||
$this->forced_approved_map[$fp] = $commentdata['comment_approved'];
|
||||
}
|
||||
}
|
||||
|
||||
private function resolve_result_from_runtime_or_default(array $commentdata, WP_Comment $comment): array {
|
||||
$fp_data = $this->comment_fingerprint($commentdata);
|
||||
if ($fp_data !== '' && isset($this->moderation_result_map[$fp_data])) {
|
||||
return $this->moderation_result_map[$fp_data];
|
||||
}
|
||||
|
||||
$fp_comment = $this->comment_fingerprint((array) $comment);
|
||||
if ($fp_comment !== '' && isset($this->moderation_result_map[$fp_comment])) {
|
||||
return $this->moderation_result_map[$fp_comment];
|
||||
}
|
||||
|
||||
$fallback = $this->new_default_result();
|
||||
$fallback['result'] = $this->result_by_approved((string) $comment->comment_approved);
|
||||
$fallback['reason'] = 'wp_default_policy';
|
||||
|
||||
$akismet_meta = get_comment_meta($comment->comment_ID, 'akismet_result', true);
|
||||
if ((string) $akismet_meta === 'true') {
|
||||
$fallback['source'] = 'akismet';
|
||||
$fallback['reason'] = 'spam';
|
||||
$fallback['akismet'] = 'spam';
|
||||
$fallback['result'] = 'spam';
|
||||
} elseif ((string) $akismet_meta === 'false') {
|
||||
$fallback['source'] = 'akismet';
|
||||
$fallback['reason'] = 'ham';
|
||||
$fallback['akismet'] = 'ham';
|
||||
}
|
||||
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
private function enrich_moderation_result(array $result, array $commentdata): array {
|
||||
if (!isset($result['result']) || $result['result'] === '') {
|
||||
$approved = isset($commentdata['comment_approved']) ? (string) $commentdata['comment_approved'] : '1';
|
||||
$result['result'] = $this->result_by_approved($approved);
|
||||
}
|
||||
if (!isset($result['source']) || $result['source'] === '') {
|
||||
$result['source'] = 'wp_default';
|
||||
}
|
||||
if (!isset($result['reason']) || $result['reason'] === '') {
|
||||
$result['reason'] = 'wp_default_policy';
|
||||
}
|
||||
if (!isset($result['akismet']) || $result['akismet'] === '') {
|
||||
$result['akismet'] = 'unknown';
|
||||
}
|
||||
if (!isset($result['ai_score'])) {
|
||||
$result['ai_score'] = '';
|
||||
}
|
||||
if (!isset($result['ai_flagged'])) {
|
||||
$result['ai_flagged'] = '';
|
||||
}
|
||||
if (!isset($result['ai_top_category'])) {
|
||||
$result['ai_top_category'] = '';
|
||||
}
|
||||
if (!isset($result['keyword'])) {
|
||||
$result['keyword'] = '';
|
||||
}
|
||||
if (!isset($result['keyword_match_type'])) {
|
||||
$result['keyword_match_type'] = '';
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function result_by_approved(string $approved): string {
|
||||
if ($approved === 'spam') {
|
||||
return 'spam';
|
||||
}
|
||||
if ($approved === '0') {
|
||||
return 'pending';
|
||||
}
|
||||
if ($approved === 'trash') {
|
||||
return 'trash';
|
||||
}
|
||||
return 'approved';
|
||||
}
|
||||
|
||||
private function status_label(string $approved): string {
|
||||
if ($approved === '1') {
|
||||
return 'Approved';
|
||||
}
|
||||
if ($approved === '0') {
|
||||
return 'Pending';
|
||||
}
|
||||
if ($approved === 'spam') {
|
||||
return 'Spam';
|
||||
}
|
||||
if ($approved === 'trash') {
|
||||
return 'Trash';
|
||||
}
|
||||
return ucfirst($approved);
|
||||
}
|
||||
|
||||
private function comment_fingerprint(array $data): string {
|
||||
$post_id = (string) ($data['comment_post_ID'] ?? '');
|
||||
$author = strtolower(trim((string) ($data['comment_author'] ?? '')));
|
||||
$email = strtolower(trim((string) ($data['comment_author_email'] ?? '')));
|
||||
$content_md5 = md5(trim((string) ($data['comment_content'] ?? '')));
|
||||
$ip = trim((string) ($data['comment_author_IP'] ?? ($data['comment_author_ip'] ?? '')));
|
||||
|
||||
if ($post_id === '' && $author === '' && $email === '' && $content_md5 === md5('')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return md5(implode('|', [$post_id, $author, $email, $content_md5, $ip]));
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 1. Akismet
|
||||
// ============================================================
|
||||
|
||||
private function check_akismet(array $data, array $config, string $ip) {
|
||||
if (empty($config['akismet_enabled'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$api_key = trim((string) ($config['akismet_api_key'] ?? ''));
|
||||
if ($api_key === '') {
|
||||
return 'error';
|
||||
}
|
||||
|
||||
$site_url = !empty($config['akismet_site_url']) ? (string) $config['akismet_site_url'] : home_url('/');
|
||||
|
||||
$payload = [
|
||||
'blog' => $site_url,
|
||||
'blog_lang' => $config['akismet_blog_lang'] ?? 'zh-CN',
|
||||
'blog_charset' => get_bloginfo('charset') ?: 'UTF-8',
|
||||
'user_ip' => $ip,
|
||||
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
|
||||
'referrer' => $_SERVER['HTTP_REFERER'] ?? '',
|
||||
'comment_type' => 'comment',
|
||||
'comment_author' => $data['comment_author'] ?? '',
|
||||
'comment_author_email' => $data['comment_author_email'] ?? '',
|
||||
'comment_author_url' => $data['comment_author_URL'] ?? ($data['comment_author_url'] ?? ''),
|
||||
'comment_content' => $data['comment_content'] ?? '',
|
||||
'is_test' => defined('WP_DEBUG') && WP_DEBUG ? '1' : '0',
|
||||
];
|
||||
|
||||
$endpoint = sprintf('https://%s.rest.akismet.com/1.1/comment-check', $api_key);
|
||||
$response = wp_remote_post($endpoint, [
|
||||
'body' => http_build_query($payload),
|
||||
'timeout' => 10,
|
||||
'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'],
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return 'error';
|
||||
}
|
||||
|
||||
$body = trim((string) wp_remote_retrieve_body($response));
|
||||
if ($body === 'true') {
|
||||
return 'spam';
|
||||
}
|
||||
if ($body === 'false') {
|
||||
return 'ham';
|
||||
}
|
||||
return 'error';
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 2. AI Moderation
|
||||
// ============================================================
|
||||
|
||||
private function check_ai_moderation(array $data, array $config) {
|
||||
if (empty($config['ai_enabled']) || empty($config['ai_moderate_comments'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$content = trim((string) ($data['comment_content'] ?? ''));
|
||||
if ($content === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 委托给统一内容审核服务
|
||||
if (!class_exists('AKSRT_Content_Audit')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$result = AKSRT_Content_Audit::get_instance()->audit_text($content);
|
||||
if ($result['passed']) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 映射到审核风控的 score 模型
|
||||
$score = 70;
|
||||
if (in_array('non_compliant', $result['labels'] ?? [], true)) {
|
||||
$score = 95;
|
||||
}
|
||||
|
||||
return [
|
||||
'flagged' => 1,
|
||||
'score' => $score,
|
||||
'top_category' => $result['reason'] ?? 'content_audit_flagged',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片审核 — 委托给统一内容审核服务。
|
||||
*
|
||||
* @param string $image_path Absolute path to the local image file.
|
||||
* @param array $config Optional config override (merged with get_config()).
|
||||
* @return array ['passed' => bool, 'reason' => string, ...]
|
||||
*/
|
||||
public function baidu_moderate_image(string $image_path, array $config = []): array {
|
||||
$config = array_merge($this->get_config(), $config);
|
||||
|
||||
if (empty($config['ai_enabled']) || empty($config['ai_moderate_background'])) {
|
||||
return ['passed' => true, 'reason' => 'disabled'];
|
||||
}
|
||||
|
||||
if (!class_exists('AKSRT_Content_Audit')) {
|
||||
return ['passed' => true, 'reason' => 'audit_service_missing'];
|
||||
}
|
||||
|
||||
return AKSRT_Content_Audit::get_instance()->audit_image($image_path);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 3. Blocked keywords
|
||||
// ============================================================
|
||||
|
||||
private function check_blocked_keywords(array $data, array $config): array {
|
||||
$keywords_raw = (string) ($config['blocked_keywords'] ?? '');
|
||||
if (trim($keywords_raw) === '') {
|
||||
return ['hit' => false, 'keyword' => '', 'match_type' => ''];
|
||||
}
|
||||
|
||||
$parts = preg_split('/[\r\n,;、\x{FF0C}\x{FF1B}]+/u', $keywords_raw);
|
||||
$keywords = array_values(array_filter(array_map('trim', (array) $parts), static function ($v) {
|
||||
return $v !== '';
|
||||
}));
|
||||
if (empty($keywords)) {
|
||||
return ['hit' => false, 'keyword' => '', 'match_type' => ''];
|
||||
}
|
||||
|
||||
$text = implode(' ', [
|
||||
(string) ($data['comment_content'] ?? ''),
|
||||
(string) ($data['comment_author'] ?? ''),
|
||||
(string) ($data['comment_author_email'] ?? ''),
|
||||
(string) ($data['comment_author_URL'] ?? ($data['comment_author_url'] ?? '')),
|
||||
]);
|
||||
$text_norm = $this->normalize_text($text);
|
||||
|
||||
foreach ($keywords as $keyword) {
|
||||
if ($keyword === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// User-defined regex keyword like /pattern/flags.
|
||||
if (preg_match('#^/.+/[imsxuADSUXJ]*$#', $keyword)) {
|
||||
$matched = @preg_match($keyword, $text);
|
||||
if ($matched === 1) {
|
||||
return ['hit' => true, 'keyword' => $keyword, 'match_type' => 'regex'];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->contains_text($text, $keyword)) {
|
||||
return ['hit' => true, 'keyword' => $keyword, 'match_type' => 'plain'];
|
||||
}
|
||||
|
||||
$keyword_norm = $this->normalize_text($keyword);
|
||||
if ($keyword_norm !== '' && $this->contains_text($text_norm, $keyword_norm)) {
|
||||
return ['hit' => true, 'keyword' => $keyword, 'match_type' => 'normalized'];
|
||||
}
|
||||
}
|
||||
|
||||
return ['hit' => false, 'keyword' => '', 'match_type' => ''];
|
||||
}
|
||||
|
||||
private function contains_text(string $haystack, string $needle): bool {
|
||||
if ($needle === '') {
|
||||
return false;
|
||||
}
|
||||
if (function_exists('mb_stripos')) {
|
||||
return mb_stripos($haystack, $needle, 0, 'UTF-8') !== false;
|
||||
}
|
||||
return stripos($haystack, $needle) !== false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Moderation_Part_2 {
|
||||
|
||||
|
||||
private function normalize_text(string $text): string {
|
||||
$text = preg_replace('/[\x{200B}-\x{200D}\x{FEFF}]/u', '', $text);
|
||||
$text = preg_replace('/\s+/u', '', (string) $text);
|
||||
if (function_exists('mb_strtolower')) {
|
||||
return mb_strtolower((string) $text, 'UTF-8');
|
||||
}
|
||||
return strtolower((string) $text);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 4. Rate limit
|
||||
// ============================================================
|
||||
|
||||
private function check_rate_limit(array $data, array $config, string $ip) {
|
||||
if (empty($config['rate_limit_enabled'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$min_interval = max(1, (int) ($config['rate_limit_min_interval'] ?? 8));
|
||||
$max_per_hour = max(1, (int) ($config['rate_limit_max_per_hour'] ?? 15));
|
||||
$prefix = 'aksrt_rate_';
|
||||
|
||||
$last_key = $prefix . md5($ip . '_last');
|
||||
$hour_key = $prefix . md5($ip . '_hourly');
|
||||
$exp_key = '_' . $prefix . md5($ip . '_hourly_expiry');
|
||||
|
||||
$last_comment_time = get_transient($last_key);
|
||||
if ($last_comment_time !== false) {
|
||||
$elapsed = time() - (int) $last_comment_time;
|
||||
if ($elapsed < $min_interval) {
|
||||
$wait_time = $min_interval - $elapsed;
|
||||
return sprintf(__('You are commenting too fast. Please wait %d seconds.', 'aksrt'), $wait_time);
|
||||
}
|
||||
}
|
||||
|
||||
$hourly_count = get_transient($hour_key);
|
||||
if ($hourly_count !== false && (int) $hourly_count >= $max_per_hour) {
|
||||
$reset_time = (int) (get_transient($exp_key) ?: time());
|
||||
$remaining = max(0, $reset_time - time());
|
||||
$minutes = (int) ceil($remaining / 60);
|
||||
return sprintf(__('You have reached the hourly comment limit. Try again in %d minutes.', 'aksrt'), $minutes);
|
||||
}
|
||||
|
||||
if ($hourly_count === false) {
|
||||
set_transient($hour_key, 1, HOUR_IN_SECONDS);
|
||||
set_transient($exp_key, time() + HOUR_IN_SECONDS, HOUR_IN_SECONDS);
|
||||
} else {
|
||||
set_transient($hour_key, ((int) $hourly_count) + 1, HOUR_IN_SECONDS);
|
||||
}
|
||||
set_transient($last_key, time(), $min_interval);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 5. GeoIP
|
||||
// ============================================================
|
||||
|
||||
private function check_geoip(array $config, string $ip) {
|
||||
if (empty($config['geoip_enabled'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$block_countries = $config['geoip_block_countries'] ?? [];
|
||||
if (!is_array($block_countries) || empty($block_countries)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$country_code = $this->lookup_geoip($ip, $config);
|
||||
if ($country_code === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$country_code = strtoupper($country_code);
|
||||
$block_upper = array_map('strtoupper', $block_countries);
|
||||
if (in_array($country_code, $block_upper, true)) {
|
||||
return sprintf(__('Comments from your region (%s) are currently blocked.', 'aksrt'), esc_html($country_code));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private function lookup_geoip(string $ip, array $config): ?string {
|
||||
// 优先复用 IP 归属地模块的查询能力(含缓存)
|
||||
if (class_exists('AKSRT_IP_Geo')) {
|
||||
$result = AKSRT_IP_Geo::get_instance()->lookup($ip);
|
||||
if (!empty($result['country'])) {
|
||||
return $result['country'];
|
||||
}
|
||||
}
|
||||
|
||||
// 回退到内置查询
|
||||
$ip_geo_config = class_exists('AKSRT_Settings')
|
||||
? AKSRT_Settings::get_ip_geo_settings()
|
||||
: [];
|
||||
$provider = (string) ($ip_geo_config['ip_geo_provider'] ?? $config['geoip_provider'] ?? 'ip-api.com');
|
||||
$api_key = (string) ($ip_geo_config['ip_geo_api_key'] ?? $config['geoip_api_key'] ?? '');
|
||||
|
||||
if ($provider === 'ipinfo' || $provider === 'ipinfo.io') {
|
||||
return $this->geoip_ipinfo($ip, $api_key);
|
||||
}
|
||||
return $this->geoip_ipapi($ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Free ip-api.com endpoint.
|
||||
*/
|
||||
private function geoip_ipapi(string $ip): ?string {
|
||||
$url = 'http://ip-api.com/json/' . urlencode($ip) . '?fields=status,countryCode,message';
|
||||
$response = wp_remote_get($url, [
|
||||
'timeout' => 5,
|
||||
'headers' => ['Accept' => 'application/json'],
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$body = json_decode((string) wp_remote_retrieve_body($response), true);
|
||||
if (($body['status'] ?? '') === 'success') {
|
||||
return $body['countryCode'] ?? null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function geoip_ipinfo(string $ip, string $api_key): ?string {
|
||||
$url = 'https://ipinfo.io/' . urlencode($ip) . '/json?token=' . urlencode($api_key ?: '');
|
||||
$response = wp_remote_get($url, [
|
||||
'timeout' => 5,
|
||||
'headers' => ['Accept' => 'application/json'],
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$body = json_decode((string) wp_remote_retrieve_body($response), true);
|
||||
return $body['country'] ?? null;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Utilities
|
||||
// ============================================================
|
||||
|
||||
private function get_client_ip(): string {
|
||||
$keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP'];
|
||||
foreach ($keys as $key) {
|
||||
if (!empty($_SERVER[$key])) {
|
||||
$parts = explode(',', (string) $_SERVER[$key]);
|
||||
$ip = trim($parts[0]);
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||
return $ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user