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).
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
<?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';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user