<?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);
?>