Files
wordpress-theme-aksrt/src/Auth/ModerationPart2.php
T
kate522 8c5b1555c9 refactor: flatten src/AKSRT → src (drop theme-identifier dir)
Move all domain folders from src/AKSRT/{Domain} to src/{Domain}; remap
Composer PSR-4 "AKSRT\\": "src/"; update audit script + README + ADR
paths; remove dead tools/build-psr4-map.php (scanned deleted inc/classes).
2026-08-02 22:06:06 +08:00

230 lines
8.4 KiB
PHP

<?php
/**
* @package AKSRT
*/
namespace AKSRT\Auth;
defined('ABSPATH') || exit;
trait ModerationPart2 {
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 {
// Dedicated ISO country-code path for block lists (CN/US/…).
// IPGeo returns localized names ("中国") which are not ISO codes.
$provider = sanitize_key((string) ($config['geoip_provider'] ?? 'ipapi'));
$api_key = (string) ($config['geoip_api_key'] ?? '');
if ($provider === 'ipinfo' || $provider === 'ipinfo.io') {
$code = $this->geoip_ipinfo($ip, $api_key);
if ($code !== null) {
return $code;
}
}
$code = $this->geoip_ipapi($ip);
if ($code !== null) {
return $code;
}
// Last resort: map IPGeo country label → ISO when possible.
if (class_exists('\AKSRT\Extensions\IPGeo')) {
$result = \AKSRT\Extensions\IPGeo::get_instance()->lookup($ip);
$label = trim((string) ($result['country'] ?? ''));
if ($label !== '') {
return $this->country_label_to_iso($label);
}
}
return null;
}
/**
* Map common country labels / ISO aliases to ISO-3166 alpha-2.
*/
private function country_label_to_iso(string $label): ?string {
$raw = trim($label);
if ($raw === '') {
return null;
}
if (preg_match('/^[A-Za-z]{2}$/', $raw)) {
return strtoupper($raw);
}
$map = [
'中国' => 'CN', '中华人民共和国' => 'CN', 'china' => 'CN', 'cn' => 'CN',
'美国' => 'US', '美利坚合众国' => 'US', 'united states' => 'US', 'usa' => 'US', 'us' => 'US',
'日本' => 'JP', 'japan' => 'JP', 'jp' => 'JP',
'韩国' => 'KR', '大韩民国' => 'KR', 'south korea' => 'KR', 'korea' => 'KR', 'kr' => 'KR',
'香港' => 'HK', '中国香港' => 'HK', 'hong kong' => 'HK', 'hk' => 'HK',
'澳门' => 'MO', '中国澳门' => 'MO', 'macao' => 'MO', 'macau' => 'MO', 'mo' => 'MO',
'台湾' => 'TW', '中国台湾' => 'TW', 'taiwan' => 'TW', 'tw' => 'TW',
'新加坡' => 'SG', 'singapore' => 'SG', 'sg' => 'SG',
'英国' => 'GB', 'united kingdom' => 'GB', 'uk' => 'GB', 'gb' => 'GB',
'德国' => 'DE', 'germany' => 'DE', 'de' => 'DE',
'法国' => 'FR', 'france' => 'FR', 'fr' => 'FR',
'俄罗斯' => 'RU', 'russia' => 'RU', 'ru' => 'RU',
'澳大利亚' => 'AU', 'australia' => 'AU', 'au' => 'AU',
'加拿大' => 'CA', 'canada' => 'CA', 'ca' => 'CA',
'印度' => 'IN', 'india' => 'IN', 'in' => 'IN',
'越南' => 'VN', 'vietnam' => 'VN', 'vn' => 'VN',
'泰国' => 'TH', 'thailand' => 'TH', 'th' => 'TH',
'马来西亚' => 'MY', 'malaysia' => 'MY', 'my' => 'MY',
'菲律宾' => 'PH', 'philippines' => 'PH', 'ph' => 'PH',
'印度尼西亚' => 'ID', 'indonesia' => 'ID', 'id' => 'ID',
];
$key = function_exists('mb_strtolower') ? mb_strtolower($raw, 'UTF-8') : strtolower($raw);
if (isset($map[$raw])) {
return $map[$raw];
}
if (isset($map[$key])) {
return $map[$key];
}
return null;
}
/**
* 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 {
if (function_exists('aksrt_get_client_ip')) {
return (string) aksrt_get_client_ip();
}
$keys = [
'HTTP_CF_CONNECTING_IP',
'HTTP_TRUE_CLIENT_IP',
'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';
}
}