| 1 |
<?php |
| 2 |
if (!defined('ABSPATH')) { |
| 3 |
exit; // Exit if accessed directly |
| 4 |
} |
| 5 |
|
| 6 |
class MxChat_User { |
| 7 |
|
| 8 |
// Function to get user identifier (username, email, or session ID) |
| 9 |
public static function mxchat_get_user_identifier() { |
| 10 |
if (is_user_logged_in()) { |
| 11 |
$current_user = wp_get_current_user(); |
| 12 |
//error_log('Current User: ' . print_r($current_user, true)); |
| 13 |
return $current_user->user_login; // Use username as identifier |
| 14 |
} else { |
| 15 |
//error_log('User is not logged in. Using IP as identifier.'); |
| 16 |
// Same resolver the rate limiter keys on, so the transcript |
| 17 |
// identifier and the rate-limit counter name the same address. |
| 18 |
return self::mxchat_get_client_ip(); |
| 19 |
} |
| 20 |
} |
| 21 |
|
| 22 |
// Function to get user email |
| 23 |
public static function mxchat_get_user_email() { |
| 24 |
if (is_user_logged_in()) { |
| 25 |
$current_user = wp_get_current_user(); |
| 26 |
return $current_user->user_email; |
| 27 |
} |
| 28 |
return null; // No email if not logged in |
| 29 |
} |
| 30 |
|
| 31 |
/** |
| 32 |
* The visitor's address, from a source the visitor cannot choose. |
| 33 |
* |
| 34 |
* REMOTE_ADDR is the answer. Client-IP and X-Forwarded-For are set by |
| 35 |
* whoever sends the request, so a counter keyed on them can be reset |
| 36 |
* with a header. The one exception is Cloudflare: when the connection |
| 37 |
* really comes from a Cloudflare edge (REMOTE_ADDR inside the ranges |
| 38 |
* Cloudflare publishes), CF-Connecting-IP is the visitor. Hosts behind |
| 39 |
* some other proxy can supply their own resolver with the |
| 40 |
* `mxchat_client_ip` filter; whatever comes back must still be a valid |
| 41 |
* address or REMOTE_ADDR is used. |
| 42 |
* |
| 43 |
* @return string A validated IP address, or 'unknown' when there is none. |
| 44 |
*/ |
| 45 |
public static function mxchat_get_client_ip() { |
| 46 |
$remote = isset($_SERVER['REMOTE_ADDR']) ? trim((string) wp_unslash($_SERVER['REMOTE_ADDR'])) : ''; |
| 47 |
$remote = filter_var($remote, FILTER_VALIDATE_IP) ? $remote : ''; |
| 48 |
|
| 49 |
$ip = $remote; |
| 50 |
if ($remote !== '' && !empty($_SERVER['HTTP_CF_CONNECTING_IP']) && self::mxchat_ip_is_cloudflare($remote)) { |
| 51 |
$candidate = trim((string) wp_unslash($_SERVER['HTTP_CF_CONNECTING_IP'])); |
| 52 |
if (filter_var($candidate, FILTER_VALIDATE_IP)) { |
| 53 |
$ip = $candidate; |
| 54 |
} |
| 55 |
} |
| 56 |
|
| 57 |
/** |
| 58 |
* Override the resolved visitor address, e.g. for a host behind a |
| 59 |
* trusted proxy that is not Cloudflare. Return a valid IP. |
| 60 |
* |
| 61 |
* @param string $ip The address MxChat resolved. |
| 62 |
* @param string $remote REMOTE_ADDR as received (validated, may be ''). |
| 63 |
*/ |
| 64 |
$filtered = apply_filters('mxchat_client_ip', $ip, $remote); |
| 65 |
if (is_string($filtered) && filter_var(trim($filtered), FILTER_VALIDATE_IP)) { |
| 66 |
$ip = trim($filtered); |
| 67 |
} |
| 68 |
|
| 69 |
return $ip !== '' ? $ip : 'unknown'; |
| 70 |
} |
| 71 |
|
| 72 |
/** |
| 73 |
* Cloudflare's published edge ranges (cloudflare.com/ips-v4, /ips-v6), |
| 74 |
* as of 2026-09-16. Refresh with the plugin. |
| 75 |
*/ |
| 76 |
public static function mxchat_cloudflare_ranges() { |
| 77 |
return array( |
| 78 |
'173.245.48.0/20', '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22', |
| 79 |
'141.101.64.0/18', '108.162.192.0/18', '190.93.240.0/20', '188.114.96.0/20', |
| 80 |
'197.234.240.0/22', '198.41.128.0/17', '162.158.0.0/15', '104.16.0.0/13', |
| 81 |
'104.24.0.0/14', '172.64.0.0/13', '131.0.72.0/22', |
| 82 |
'2400:cb00::/32', '2606:4700::/32', '2803:f800::/32', '2405:b500::/32', |
| 83 |
'2405:8100::/32', '2a06:98c0::/29', '2c0f:f248::/32', |
| 84 |
); |
| 85 |
} |
| 86 |
|
| 87 |
public static function mxchat_ip_is_cloudflare($ip) { |
| 88 |
foreach (self::mxchat_cloudflare_ranges() as $cidr) { |
| 89 |
if (self::mxchat_ip_in_cidr($ip, $cidr)) { |
| 90 |
return true; |
| 91 |
} |
| 92 |
} |
| 93 |
return false; |
| 94 |
} |
| 95 |
|
| 96 |
/** |
| 97 |
* Does $ip fall inside $cidr? Works for IPv4 and IPv6; families must match. |
| 98 |
*/ |
| 99 |
public static function mxchat_ip_in_cidr($ip, $cidr) { |
| 100 |
if (strpos($cidr, '/') === false) { |
| 101 |
return false; |
| 102 |
} |
| 103 |
list($subnet, $bits) = explode('/', $cidr, 2); |
| 104 |
$ip_bin = @inet_pton($ip); |
| 105 |
$subnet_bin = @inet_pton($subnet); |
| 106 |
if ($ip_bin === false || $subnet_bin === false || strlen($ip_bin) !== strlen($subnet_bin)) { |
| 107 |
return false; |
| 108 |
} |
| 109 |
$bits = (int) $bits; |
| 110 |
$max = strlen($ip_bin) * 8; |
| 111 |
if ($bits < 0 || $bits > $max) { |
| 112 |
return false; |
| 113 |
} |
| 114 |
$full_bytes = intdiv($bits, 8); |
| 115 |
if ($full_bytes > 0 && substr($ip_bin, 0, $full_bytes) !== substr($subnet_bin, 0, $full_bytes)) { |
| 116 |
return false; |
| 117 |
} |
| 118 |
$rest = $bits % 8; |
| 119 |
if ($rest === 0) { |
| 120 |
return true; |
| 121 |
} |
| 122 |
$mask = (0xFF << (8 - $rest)) & 0xFF; |
| 123 |
return ((ord($ip_bin[$full_bytes]) & $mask) === (ord($subnet_bin[$full_bytes]) & $mask)); |
| 124 |
} |
| 125 |
} |
| 126 |
|