set('captcha_code', $code); session()->set('captcha_expires', time() + 600); return $code; } public static function verify(?string $input): bool { $code = session()->get('captcha_code'); $exp = session()->get('captcha_expires'); session()->remove('captcha_code'); session()->remove('captcha_expires'); if (! $code || ! $exp || time() > $exp || ! $input) { return false; } return hash_equals(strtoupper(trim($code)), strtoupper(trim($input))); } public static function render(string $code): void { $w = 180; $h = 60; $img = imagecreatetruecolor($w, $h); $bg = imagecolorallocate($img, 243, 246, 252); imagefilledrectangle($img, 0, 0, $w, $h, $bg); // noise for ($i = 0; $i < 6; $i++) { $c = imagecolorallocate($img, random_int(190, 230), random_int(190, 230), random_int(220, 245)); imageline($img, random_int(0, $w), random_int(0, $h), random_int(0, $w), random_int(0, $h), $c); } for ($i = 0; $i < 220; $i++) { $c = imagecolorallocate($img, random_int(150, 220), random_int(150, 220), random_int(150, 220)); imagesetpixel($img, random_int(0, $w - 1), random_int(0, $h - 1), $c); } $fonts = glob(SYSTEMPATH . 'fonts/*.{ttf,TTF}', GLOB_BRACE) ?: []; for ($i = 0; $i < strlen($code); $i++) { $color = imagecolorallocate($img, random_int(10, 90), random_int(30, 100), random_int(120, 200)); $x = 15 + $i * 32 + random_int(-4, 4); $y = random_int(36, 48); if ($fonts) { imagettftext($img, random_int(22, 26), random_int(-12, 12), $x, $y, $color, $fonts[array_rand($fonts)], $code[$i]); } else { imagestring($img, 5, $x, $y - 16, $code[$i], $color); } } header('Content-Type: image/png'); header('Cache-Control: no-store, no-cache, must-revalidate'); imagepng($img); imagedestroy($img); exit; } }