format('Y-m-d')); return hash('sha256', $ip.$salt); } /** * Hash an IP with a static salt for consistent hashing. * * Use this when you need the same hash across days but still want * the IP to be irreversible. */ public static function hashIp(?string $ip): ?string { if (! $ip) { return null; } return hash('sha256', $ip.config('app.key')); } /** * Generate a cache key for unique visitor detection. * * Combines a prefix with IP and date for same-day uniqueness. */ public static function uniqueVisitorCacheKey(string $prefix, string $ip): string { return sprintf('%s:%s:%s', $prefix, $ip, now()->format('Y-m-d')); } /** * Check if an IP is private/internal (not routable on internet). */ public static function isPrivateIp(?string $ip): bool { if (! $ip) { return true; } return filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) === false; } /** * Expand a shortened IPv6 address to full form. */ protected static function expandIpv6(string $ip): string { // Handle :: shorthand if (str_contains($ip, '::')) { $parts = explode('::', $ip); $left = $parts[0] ? explode(':', $parts[0]) : []; $right = isset($parts[1]) && $parts[1] ? explode(':', $parts[1]) : []; $missing = 8 - count($left) - count($right); $middle = array_fill(0, $missing, '0000'); $all = array_merge($left, $middle, $right); } else { $all = explode(':', $ip); } // Pad each group to 4 characters return implode(':', array_map( fn ($group) => str_pad($group, 4, '0', STR_PAD_LEFT), $all )); } }