<?php
declare(strict_types=1);
ob_start();
/**
* CAPTCHA generator v2.04 - Updated for PHP 8.3
*
* Error Codes:
* 1 = No hash received
* 2 = Font directory does not exist or is not readable
* 3 = No font files loaded (freetype)
* 4 = No font files loaded (non-freetype)
* 5 = Failed header test
*/
// Define LAZ_INCLUDE_PATH for includes
define('LAZ_INCLUDE_PATH', __DIR__);
// Includes
require_once LAZ_INCLUDE_PATH . '/admin/version.php';
require_once LAZ_INCLUDE_PATH . '/admin/config.inc.php';
require_once LAZ_INCLUDE_PATH . '/lib/' . $DB_CLASS;
require_once LAZ_INCLUDE_PATH . '/lib/vars.class.php';
require_once LAZ_INCLUDE_PATH . '/lib/template.class.php';
$number_of_chars = 5; // DO NOT CHANGE THIS
define('LAZ_TABLE_PREFIX', $table_prefix);
// Initialize the database variables object
$db = new guestbook_vars(LAZ_INCLUDE_PATH);
$db->getVars();
// Decide whether we use color or greyscale
$usecolor = $db->VARS['captcha_grey'] ? 0 : 1;
// Assign image dimensions (default 350x100 if invalid)
$image_height = (!empty($db->VARS['captcha_height']) && is_numeric($db->VARS['captcha_height']))
? (int) $db->VARS['captcha_height']
: 100;
$image_width = (!empty($db->VARS['captcha_width']) && is_numeric($db->VARS['captcha_width']))
? (int) $db->VARS['captcha_width']
: 350;
// The DB username is used as a key; fallback to 'Lazarus' if not present
$thekey = !empty($GB_DB['user']) ? $GB_DB['user'] : 'Lazarus';
// Create an image resource
$image = imagecreatetruecolor($image_width, $image_height);
// Background color (white)
$background = imagecolorallocate($image, 255, 255, 255);
// Make background transparent if desired
if ((int) $db->VARS['captcha_trans'] === 1) {
imagecolortransparent($image, $background);
}
// Fill background
imagefill($image, 0, 0, $background);
// If greyscale is selected, pick a single random grey level
$grey = mt_rand(120, 140);
// Extract `hash` parameter (timehash)
$hash = $_GET['hash'] ?? '';
/**
* Display an error code on the image and exit.
*/
function display_error(int $error_number): void
{
global $image;
$red = imagecolorallocate($image, 255, 0, 0);
imagestring($image, 5, 5, 8, 'ERROR! ('.$error_number.')', $red);
if ($error_number === 1) {
imagestring($image, 5, 5, 35, 'Is JavaScript enabled?', $red);
}
// Send headers
header('Cache-Control: no-cache');
header('Pragma: no-cache');
header("Expires: Sat, 1 Jan 2000 00:00:00 GMT");
// Output image
if (function_exists('imagepng')) {
header('Content-type: image/png');
imagepng($image);
} else {
header('Content-type: image/jpeg');
imagejpeg($image, null, 100);
}
echo trim(ob_get_clean());
// Cleanup
imagedestroy($image);
exit();
}
// (Optional) Check request headers if needed (commented out in original code)
/*
if ($db->VARS['check_headers'] == 1) {
if (($failedHeader = $db->check_headers(3)) != 0) {
display_error(5 . '.' . $failedHeader);
}
}
*/
// We must have a numeric `hash` to proceed
if (!empty($hash) && ctype_digit($hash)) {
// Check if FreeType is available
$freetype = function_exists('imagettftext');
// Font directory
$fontDir = LAZ_INCLUDE_PATH . '/fonts/';
if (is_dir($fontDir) && is_readable($fontDir)) {
// Collect fonts
$font_count = 0;
$font = [];
$handle = opendir($fontDir);
while (($file = readdir($handle)) !== false) {
if ($freetype) {
// Looking for .ttf
if (preg_match("/\.ttf$/i", $file) && is_readable($fontDir . $file)) {
$font[] = $fontDir . '/' . $file;
$font_count++;
}
} else {
// Non-freetype => .gdf
if (preg_match("/\.gdf$/i", $file) && is_readable($fontDir . $file)) {
$font[] = imageloadfont($fontDir . '/' . $file);
$font_count++;
}
}
}
closedir($handle);
// Error if no fonts loaded
if ($font_count === 0) {
display_error($freetype ? 3 : 4);
}
} else {
display_error(2);
}
// Draw line grid if captcha_grid is enabled
if (!empty($db->VARS['captcha_grid'])) {
for ($i = 0; $i < 3; $i++) {
$red = $usecolor ? mt_rand(40, 140) : $grey;
$green = $usecolor ? mt_rand(40, 140) : $grey;
$blue = $usecolor ? mt_rand(40, 140) : $grey;
$y1 = mt_rand(3, $image_height - 7);
$y2 = mt_rand(3, $image_height - 7);
$x1 = (int) (mt_rand(0, $image_width / 10) + $i * ($image_width / 3));
$x2 = (int) ($x1 + mt_rand(0, $image_width / 10) + ($image_width / 3));
// Cap x2 if out of bounds
if ($x2 > ($image_width - 1)) {
$x2 = $image_width - 1;
}
// Anti-aliasing above the line
if ($freetype) {
$line_color = imagecolorallocate($image, (int) round($red * 1.5), (int) round($green * 1.5), (int) round($blue * 1.5));
if (rand(0, 1)) {
imageline($image, $x1, $y1, $x2, $y2, $line_color);
}
imageline($image, $x1, $y1 + 1, $x2, $y2 + 1, $line_color);
}
// Main line thickness
for ($j = 2; $j < $image_width / 60 + 1; $j++) {
$line_color = imagecolorallocate($image, $red, $green, $blue);
imageline($image, $x1, $y1 + $j, $x2, $y2 + $j, $line_color);
}
// Anti-aliasing below the line
if ($freetype) {
$line_color = imagecolorallocate($image, (int) round($red * 1.5), (int) round($green * 1.5), (int) round($blue * 1.5));
imageline($image, $x1, $y1 + $j, $x2, $y2 + $j, $line_color);
if (rand(0, 1)) {
imageline($image, $x1, $y1 + $j + 1, $x2, $y2 + $j + 1, $line_color);
}
}
}
// Make the lines wavy
$wave = rand(3, 5);
$wave_width = rand(8, 15);
for ($i = 0; $i < 200; $i += 2) {
imagecopy($image, $image, $i - 2, (int) (sin($i / $wave_width) * $wave), $i, 0, 2, 40);
}
}
// Build the code for the CAPTCHA text
// This is your custom approach
$realcode = strtoupper(md5(md5($thekey) . md5($hash)));
// Array mapping numeric digits to letters
$captchanum = [
0 => 'V',
1 => 'H',
2 => 'K',
3 => 'M',
4 => 'P',
5 => 'S',
6 => 'T',
7 => 'W',
8 => 'X',
9 => 'Z'
];
// Horizontal position and text size
$xpos = mt_rand(5, 20);
$char_height = min((2 / 3) * $image_width / ($number_of_chars + 1), $image_height / 2) - 2;
$cur_x = -$char_height / 3;
// We only need characters at indices 0,7,14,21,28,... in $realcode
// but we’ll keep the original approach (loop i = 0..30 in steps of 7).
for ($i = 0; $i <= 30; $i += 7) {
$red = $usecolor ? mt_rand(40, 140) : $grey;
$green = $usecolor ? mt_rand(40, 140) : $grey;
$blue = $usecolor ? mt_rand(40, 140) : $grey;
$thecolor = imagecolorallocate($image, $red, $green, $blue);
// Convert digits to letters
$currentChar = $realcode[$i] ?? '';
if (is_numeric($currentChar)) {
$currentChar = $captchanum[(int) $currentChar] ?? $currentChar;
}
// Move horizontal position
$cur_x += $image_width / ($number_of_chars + 1);
if ($freetype) {
// TTF font
$font_height = $char_height * (1 + rand(0, 3) / 10 - 0.1);
$textX = (int) $cur_x;
$textY = mt_rand((int) ($font_height + ($font_height / 3)), $image_height - 3);
// Pick a random TTF file
$chosenFont = $font[array_rand($font)];
imagettftext(
$image,
$font_height,
rand(-200, 200) / 10.0,
$textX,
$textY,
$thecolor,
$chosenFont,
$currentChar
);
} else {
// GDF font
$fontface = mt_rand(0, count($font) - 1);
$vertpos = mt_rand(0, $image_height - imagefontheight($font[$fontface]) - 5);
imagechar($image, $font[$fontface], (int) $cur_x, $vertpos, $currentChar, $thecolor);
}
}
// Wave effect on text
$wave = rand(3, 5);
$wave_width = rand(8, 15);
for ($i = 0; $i < 200; $i += 2) {
imagecopy($image, $image, $i - 2, (int) (sin($i / $wave_width) * $wave), $i, 0, 2, 40);
}
// Add random noise?
if (!empty($db->VARS['captcha_noise'])) {
for ($i = 1; $i <= $image_height - 1; $i++) {
for ($j = 1; $j <= 30; $j++) {
$red = $usecolor ? mt_rand(40, 140) : $grey;
$green = $usecolor ? mt_rand(40, 140) : $grey;
$blue = $usecolor ? mt_rand(40, 140) : $grey;
$thecolor = imagecolorallocate($image, $red, $green, $blue);
imagesetpixel($image, mt_rand(1, $image_width - 1), $i, $thecolor);
}
}
}
} else {
// No or invalid hash
display_error(1);
}
// Send headers
header('Cache-Control: no-cache');
header('Pragma: no-cache');
header("Expires: Sat, 1 Jan 2000 00:00:00 GMT");
// Output image in GIF/PNG/JPEG
if (function_exists('imagegif')) {
header('Content-type: image/gif');
imagegif($image);
} elseif (function_exists('imagepng')) {
header('Content-type: image/png');
imagepng($image);
} else {
header('Content-type: image/jpeg');
imagejpeg($image);
}
// Clear output buffer and destroy image resource
echo trim(ob_get_clean());
imagedestroy($image);
?>