diff --git a/.env.example b/.env.example
index 670e5402..cbd1aa08 100644
--- a/.env.example
+++ b/.env.example
@@ -96,6 +96,22 @@ CHANNEL_NAME_SETTING=channel_setting
CHANNEL_NAME_MODEL_EVENT=channel_model_event
FORCE_SCHEME=
+# Captcha settings
+# Available drivers: image, cloudflare_turnstile, google_recaptcha_v2
+CAPTCHA_DRIVER=image
+
+# Cloudflare Turnstile keys (used when CAPTCHA_DRIVER=cloudflare_turnstile)
+TURNSTILE_SITE_KEY=
+TURNSTILE_SECRET_KEY=
+TURNSTILE_THEME=light
+TURNSTILE_SIZE=flexible
+
+# Google reCAPTCHA v2 keys (used when CAPTCHA_DRIVER=google_recaptcha_v2)
+RECAPTCHA_SITE_KEY=
+RECAPTCHA_SECRET_KEY=
+RECAPTCHA_THEME=light
+RECAPTCHA_SIZE=normal
+
CROWDIN_ACCESS_TOKEN=
CROWDIN_PROJECT_ID=
diff --git a/app/Services/Captcha/CaptchaDriverInterface.php b/app/Services/Captcha/CaptchaDriverInterface.php
new file mode 100644
index 00000000..743777c3
--- /dev/null
+++ b/app/Services/Captcha/CaptchaDriverInterface.php
@@ -0,0 +1,19 @@
+ */
+ protected array $drivers = [];
+
+ protected ?array $config = null;
+
+ public function driver(?string $name = null): CaptchaDriverInterface
+ {
+ $name = $name ?? $this->getDefaultDriver();
+
+ $driver = $this->getDriverInstance($name);
+
+ if ($name !== 'image' && !$driver->isEnabled()) {
+ return $this->driver('image');
+ }
+
+ return $driver;
+ }
+
+ public function render(array $context = []): string
+ {
+ return $this->driver()->render($context);
+ }
+
+ public function verify(array $payload, array $context = []): bool
+ {
+ try {
+ return $this->driver()->verify($payload, $context);
+ } catch (CaptchaValidationException $exception) {
+ throw $exception;
+ }
+ }
+
+ public function isEnabled(): bool
+ {
+ return $this->driver()->isEnabled();
+ }
+
+ protected function getDriverInstance(string $name): CaptchaDriverInterface
+ {
+ if (!isset($this->drivers[$name])) {
+ try {
+ $this->drivers[$name] = $this->resolveDriver($name);
+ } catch (\InvalidArgumentException $exception) {
+ if ($name !== 'image') {
+ return $this->getDriverInstance('image');
+ }
+ throw $exception;
+ }
+ }
+
+ return $this->drivers[$name];
+ }
+
+ protected function resolveDriver(string $name): CaptchaDriverInterface
+ {
+ $config = $this->getConfigValue("drivers.$name", []);
+
+ if (!is_array($config) || empty($config)) {
+ throw new \InvalidArgumentException("Captcha driver [$name] is not defined.");
+ }
+
+ $driverClass = Arr::get($config, 'class');
+
+ if (!$driverClass || !class_exists($driverClass)) {
+ throw new \InvalidArgumentException("Captcha driver class for [$name] is invalid.");
+ }
+
+ $driver = new $driverClass($config);
+
+ if (!$driver instanceof CaptchaDriverInterface) {
+ throw new \InvalidArgumentException("Captcha driver [$name] must implement " . CaptchaDriverInterface::class);
+ }
+
+ return $driver;
+ }
+
+ protected function getDefaultDriver(): string
+ {
+ return (string) $this->getConfigValue('default', 'image');
+ }
+
+ protected function getConfigValue(string $key, $default = null)
+ {
+ if ($this->config === null) {
+ $config = null;
+ if (function_exists('app')) {
+ try {
+ $repository = app('config');
+ if ($repository) {
+ $config = $repository->get('captcha');
+ }
+ } catch (\Throwable $exception) {
+ $config = null;
+ }
+ }
+
+ if (!is_array($config) && function_exists('nexus_config')) {
+ $config = nexus_config('captcha', []);
+ }
+
+ if (!is_array($config)) {
+ $path = (defined('ROOT_PATH') ? ROOT_PATH : dirname(__DIR__, 3) . DIRECTORY_SEPARATOR) . 'config/captcha.php';
+ $config = is_file($path) ? require $path : [];
+ }
+
+ $this->config = is_array($config) ? $config : [];
+ }
+
+ return Arr::get($this->config, $key, $default);
+ }
+}
diff --git a/app/Services/Captcha/Drivers/ImageCaptchaDriver.php b/app/Services/Captcha/Drivers/ImageCaptchaDriver.php
new file mode 100644
index 00000000..69952c99
--- /dev/null
+++ b/app/Services/Captcha/Drivers/ImageCaptchaDriver.php
@@ -0,0 +1,156 @@
+config = $config;
+ }
+
+ public function isEnabled(): bool
+ {
+ return true;
+ }
+
+ public function render(array $context = []): string
+ {
+ $labels = $context['labels'] ?? [];
+ $imageLabel = $labels['image'] ?? 'Security Image';
+ $codeLabel = $labels['code'] ?? 'Security Code';
+ $secret = $context['secret'] ?? '';
+
+ $imagehash = $this->issue();
+ $imageUrl = htmlspecialchars(sprintf('image.php?action=regimage&imagehash=%s&secret=%s', $imagehash, $secret ?? ''), ENT_QUOTES, 'UTF-8');
+
+ return implode("\n", [
+ sprintf('
| %s |  |
', htmlspecialchars($imageLabel, ENT_QUOTES, 'UTF-8'), $imageUrl),
+ sprintf('| %s | |
', htmlspecialchars($codeLabel, ENT_QUOTES, 'UTF-8'), htmlspecialchars($imagehash, ENT_QUOTES, 'UTF-8')),
+ ]);
+ }
+
+ public function verify(array $payload, array $context = []): bool
+ {
+ $imagehash = trim((string) ($payload['imagehash'] ?? ''));
+ $imagestring = trim((string) ($payload['imagestring'] ?? ''));
+
+ if ($imagehash === '' || $imagestring === '') {
+ throw new CaptchaValidationException('Missing captcha parameters.');
+ }
+
+ $query = sprintf(
+ "SELECT dateline FROM regimages WHERE imagehash='%s' AND imagestring='%s'",
+ mysql_real_escape_string($imagehash),
+ mysql_real_escape_string($imagestring)
+ );
+
+ $sql = sql_query($query);
+ $imgcheck = mysql_fetch_array($sql);
+
+ $this->deleteByHash($imagehash);
+
+ if (empty($imgcheck['dateline'])) {
+ throw new CaptchaValidationException('Invalid captcha response.');
+ }
+
+ return true;
+ }
+
+ public function issue(): string
+ {
+ $random = random_str();
+ $imagehash = md5($random);
+ $dateline = time();
+
+ $sql = sprintf(
+ "INSERT INTO `regimages` (`imagehash`, `imagestring`, `dateline`) VALUES ('%s', '%s', '%s')",
+ mysql_real_escape_string($imagehash),
+ mysql_real_escape_string($random),
+ mysql_real_escape_string((string) $dateline)
+ );
+
+ sql_query($sql);
+
+ return $imagehash;
+ }
+
+ public function outputImage(string $imagehash): void
+ {
+ $query = sprintf(
+ "SELECT imagestring FROM regimages WHERE imagehash=%s",
+ sqlesc($imagehash)
+ );
+
+ $sql = sql_query($query);
+ $regimage = mysql_fetch_array($sql);
+ $imagestring = $regimage['imagestring'] ?? '';
+
+ if ($imagestring === '') {
+ $this->renderFallback();
+ return;
+ }
+
+ $characters = implode(' ', str_split($imagestring));
+
+ if (!function_exists('imagecreatefrompng')) {
+ $this->renderFallback();
+ return;
+ }
+
+ $fontwidth = imageFontWidth(5);
+ $fontheight = imageFontHeight(5);
+ $textwidth = $fontwidth * strlen($characters);
+ $textheight = $fontheight;
+
+ $randimg = rand(1, 5);
+ $imagePath = ROOT_PATH . "public/pic/regimages/reg{$randimg}.png";
+
+ if (!is_file($imagePath)) {
+ $this->renderFallback();
+ return;
+ }
+
+ $im = imagecreatefrompng($imagePath);
+ $imgheight = imagesy($im);
+ $imgwidth = imagesx($im);
+ $textposh = (int) floor(($imgwidth - $textwidth) / 2);
+ $textposv = (int) floor(($imgheight - $textheight) / 2);
+
+ $dots = (int) floor($imgheight * $imgwidth / 35);
+ for ($i = 1; $i <= $dots; $i++) {
+ imagesetpixel($im, rand(0, $imgwidth - 1), rand(0, $imgheight - 1), imagecolorallocate($im, rand(0, 255), rand(0, 255), rand(0, 255)));
+ }
+
+ $textcolor = imagecolorallocate($im, 0, 0, 0);
+ imagestring($im, 5, $textposh, $textposv, $characters, $textcolor);
+
+ header('Content-type: image/png');
+ imagepng($im);
+ imagedestroy($im);
+ }
+
+ protected function deleteByHash(string $imagehash): void
+ {
+ if ($imagehash === '') {
+ return;
+ }
+
+ $delete = sprintf(
+ "DELETE FROM regimages WHERE imagehash='%s'",
+ mysql_real_escape_string($imagehash)
+ );
+
+ sql_query($delete);
+ }
+
+ protected function renderFallback(): void
+ {
+ http_response_code(404);
+ }
+}
diff --git a/app/Services/Captcha/Drivers/RecaptchaV2CaptchaDriver.php b/app/Services/Captcha/Drivers/RecaptchaV2CaptchaDriver.php
new file mode 100644
index 00000000..04d0a157
--- /dev/null
+++ b/app/Services/Captcha/Drivers/RecaptchaV2CaptchaDriver.php
@@ -0,0 +1,134 @@
+config = $config;
+ }
+
+ public function isEnabled(): bool
+ {
+ return !empty($this->config['site_key']) && !empty($this->config['secret_key']);
+ }
+
+ public function render(array $context = []): string
+ {
+ if (!$this->isEnabled()) {
+ return '';
+ }
+
+ $labels = $context['labels'] ?? [];
+ $label = $labels['image'] ?? $labels['code'] ?? 'Security Check';
+ $theme = $this->config['theme'] ?? 'light';
+ $size = $this->config['size'] ?? 'normal';
+ $validSizes = ['compact', 'normal'];
+ if (!in_array($size, $validSizes, true)) {
+ $size = 'normal';
+ }
+
+ $attributes = sprintf(
+ 'class="g-recaptcha" data-sitekey="%s" data-theme="%s" data-size="%s"',
+ htmlspecialchars($this->config['site_key'], ENT_QUOTES, 'UTF-8'),
+ htmlspecialchars($theme, ENT_QUOTES, 'UTF-8'),
+ htmlspecialchars($size, ENT_QUOTES, 'UTF-8')
+ );
+
+ return sprintf(
+ '| %s | %s |
',
+ htmlspecialchars($label, ENT_QUOTES, 'UTF-8'),
+ $attributes,
+ ''
+ );
+ }
+
+ public function verify(array $payload, array $context = []): bool
+ {
+ $token = trim((string) ($payload['request']['g-recaptcha-response'] ?? ''));
+
+ if ($token === '') {
+ throw new CaptchaValidationException('Captcha verification token is missing.');
+ }
+
+ $secret = $this->config['secret_key'] ?? '';
+
+ if ($secret === '') {
+ throw new CaptchaValidationException('Captcha secret key is not configured.');
+ }
+
+ $data = [
+ 'secret' => $secret,
+ 'response' => $token,
+ ];
+
+ $remoteIp = $context['ip'] ?? null;
+
+ if (!empty($remoteIp)) {
+ $data['remoteip'] = $remoteIp;
+ }
+
+ $result = $this->sendVerificationRequest('https://www.google.com/recaptcha/api/siteverify', $data);
+
+ if (!($result['success'] ?? false)) {
+ throw new CaptchaValidationException('Captcha verification failed.');
+ }
+
+ return true;
+ }
+
+ protected function sendVerificationRequest(string $url, array $data): array
+ {
+ $payload = http_build_query($data);
+
+ if (function_exists('curl_init')) {
+ $ch = curl_init($url);
+ curl_setopt_array($ch, [
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_POST => true,
+ CURLOPT_POSTFIELDS => $payload,
+ CURLOPT_TIMEOUT => 10,
+ CURLOPT_SSL_VERIFYPEER => true,
+ ]);
+
+ $response = curl_exec($ch);
+
+ if ($response === false) {
+ $error = curl_error($ch);
+ curl_close($ch);
+ throw new CaptchaValidationException('Captcha verification request failed: ' . $error);
+ }
+
+ curl_close($ch);
+ } else {
+ $context = stream_context_create([
+ 'http' => [
+ 'method' => 'POST',
+ 'header' => "Content-type: application/x-www-form-urlencoded\r\n",
+ 'content' => $payload,
+ 'timeout' => 10,
+ ],
+ ]);
+
+ $response = file_get_contents($url, false, $context);
+
+ if ($response === false) {
+ throw new CaptchaValidationException('Captcha verification request failed.');
+ }
+ }
+
+ $decoded = json_decode($response, true);
+
+ if (!is_array($decoded)) {
+ throw new CaptchaValidationException('Unexpected captcha verification response.');
+ }
+
+ return $decoded;
+ }
+}
diff --git a/app/Services/Captcha/Drivers/TurnstileCaptchaDriver.php b/app/Services/Captcha/Drivers/TurnstileCaptchaDriver.php
new file mode 100644
index 00000000..21a2e214
--- /dev/null
+++ b/app/Services/Captcha/Drivers/TurnstileCaptchaDriver.php
@@ -0,0 +1,143 @@
+config = $config;
+ }
+
+ public function isEnabled(): bool
+ {
+ return !empty($this->config['site_key']) && !empty($this->config['secret_key']);
+ }
+
+ public function render(array $context = []): string
+ {
+ if (!$this->isEnabled()) {
+ return '';
+ }
+
+ $labels = $context['labels'] ?? [];
+ $label = $labels['image'] ?? $labels['code'] ?? 'Security Check';
+ $theme = $this->config['theme'] ?? 'light';
+ $size = $this->config['size'] ?? 'auto';
+ if (is_string($size)) {
+ $size = strtolower($size);
+ }
+ $validSizes = ['compact', 'normal', 'flexible'];
+ if (!in_array($size, $validSizes, true)) {
+ $size = 'auto';
+ }
+
+ $attributes = sprintf(
+ 'class="cf-turnstile" data-sitekey="%s" data-theme="%s"%s',
+ htmlspecialchars($this->config['site_key'], ENT_QUOTES, 'UTF-8'),
+ htmlspecialchars($theme, ENT_QUOTES, 'UTF-8'),
+ $size === 'auto' ? '' : sprintf(' data-size="%s"', htmlspecialchars($size, ENT_QUOTES, 'UTF-8'))
+ );
+
+ $markup = sprintf(
+ '| %s | %s |
',
+ htmlspecialchars($label, ENT_QUOTES, 'UTF-8'),
+ $attributes,
+ self::$scriptInjected ? '' : ''
+ );
+
+ self::$scriptInjected = true;
+
+ return $markup;
+ }
+
+ public function verify(array $payload, array $context = []): bool
+ {
+ $token = trim((string) ($payload['request']['cf-turnstile-response'] ?? ''));
+
+ if ($token === '') {
+ throw new CaptchaValidationException('Captcha verification token is missing.');
+ }
+
+ $secret = $this->config['secret_key'] ?? '';
+
+ if ($secret === '') {
+ throw new CaptchaValidationException('Captcha secret key is not configured.');
+ }
+
+ $data = [
+ 'secret' => $secret,
+ 'response' => $token,
+ ];
+
+ $remoteIp = $context['ip'] ?? null;
+
+ if (!empty($remoteIp)) {
+ $data['remoteip'] = $remoteIp;
+ }
+
+ $result = $this->sendVerificationRequest('https://challenges.cloudflare.com/turnstile/v0/siteverify', $data);
+
+ if (!($result['success'] ?? false)) {
+ throw new CaptchaValidationException('Captcha verification failed.');
+ }
+
+ return true;
+ }
+
+ protected function sendVerificationRequest(string $url, array $data): array
+ {
+ $payload = http_build_query($data);
+
+ if (function_exists('curl_init')) {
+ $ch = curl_init($url);
+ curl_setopt_array($ch, [
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_POST => true,
+ CURLOPT_POSTFIELDS => $payload,
+ CURLOPT_TIMEOUT => 10,
+ CURLOPT_SSL_VERIFYPEER => true,
+ ]);
+
+ $response = curl_exec($ch);
+
+ if ($response === false) {
+ $error = curl_error($ch);
+ curl_close($ch);
+ throw new CaptchaValidationException('Captcha verification request failed: ' . $error);
+ }
+
+ curl_close($ch);
+ } else {
+ $context = stream_context_create([
+ 'http' => [
+ 'method' => 'POST',
+ 'header' => "Content-type: application/x-www-form-urlencoded\r\n",
+ 'content' => $payload,
+ 'timeout' => 10,
+ ],
+ ]);
+
+ $response = file_get_contents($url, false, $context);
+
+ if ($response === false) {
+ throw new CaptchaValidationException('Captcha verification request failed.');
+ }
+ }
+
+ $decoded = json_decode($response, true);
+
+ if (!is_array($decoded)) {
+ throw new CaptchaValidationException('Unexpected captcha verification response.');
+ }
+
+ return $decoded;
+ }
+}
diff --git a/app/Services/Captcha/Exceptions/CaptchaValidationException.php b/app/Services/Captcha/Exceptions/CaptchaValidationException.php
new file mode 100644
index 00000000..2171f52f
--- /dev/null
+++ b/app/Services/Captcha/Exceptions/CaptchaValidationException.php
@@ -0,0 +1,10 @@
+ nexus_env('CAPTCHA_DRIVER', 'image'),
+
+ 'drivers' => [
+ 'image' => [
+ 'class' => \App\Services\Captcha\Drivers\ImageCaptchaDriver::class,
+ ],
+
+ 'cloudflare_turnstile' => [
+ 'class' => \App\Services\Captcha\Drivers\TurnstileCaptchaDriver::class,
+ 'site_key' => nexus_env('TURNSTILE_SITE_KEY'),
+ 'secret_key' => nexus_env('TURNSTILE_SECRET_KEY'),
+ 'theme' => nexus_env('TURNSTILE_THEME', 'auto'),
+ 'size' => nexus_env('TURNSTILE_SIZE', 'auto'),
+ ],
+
+ 'google_recaptcha_v2' => [
+ 'class' => \App\Services\Captcha\Drivers\RecaptchaV2CaptchaDriver::class,
+ 'site_key' => nexus_env('RECAPTCHA_SITE_KEY'),
+ 'secret_key' => nexus_env('RECAPTCHA_SECRET_KEY'),
+ 'theme' => nexus_env('RECAPTCHA_THEME', 'light'),
+ 'size' => nexus_env('RECAPTCHA_SIZE', 'normal'),
+ ],
+ ],
+];
diff --git a/include/functions.php b/include/functions.php
index 89aba8fe..27641fae 100644
--- a/include/functions.php
+++ b/include/functions.php
@@ -1767,56 +1767,102 @@ function random_str($length="6")
}
return $str;
}
-function image_code () {
- $randomstr = random_str();
- $imagehash = md5($randomstr);
- $dateline = time();
- $sql = 'INSERT INTO `regimages` (`imagehash`, `imagestring`, `dateline`) VALUES (\''.$imagehash.'\', \''.$randomstr.'\', \''.$dateline.'\');';
- sql_query($sql);
- return $imagehash;
+function captcha_manager(): \App\Services\Captcha\CaptchaManager
+{
+ static $manager;
+
+ if (!$manager) {
+ $manager = new \App\Services\Captcha\CaptchaManager();
+ }
+
+ return $manager;
}
-function check_code ($imagehash, $imagestring, $where = 'signup.php',$maxattemptlog=false,$head=true) {
- global $lang_functions;
+function image_code () {
+ $driver = captcha_manager()->driver('image');
+
+ if (!method_exists($driver, 'issue')) {
+ throw new \RuntimeException('Image captcha driver is unavailable.');
+ }
+
+ return $driver->issue();
+}
+
+function check_code ($imagehash, $imagestring, $where = 'signup.php', $maxattemptlog = false, $head = true) {
+ global $lang_functions;
global $iv;
+
if ($iv !== 'yes') {
return true;
}
- $query = sprintf("SELECT * FROM regimages WHERE imagehash='%s' AND imagestring='%s'",
- mysql_real_escape_string((string)$imagehash),
- mysql_real_escape_string((string)$imagestring)
- );
- $sql = sql_query($query);
- $imgcheck = mysql_fetch_array($sql);
- if(!$imgcheck['dateline']) {
- $delete = sprintf("DELETE FROM regimages WHERE imagehash='%s'",
- mysql_real_escape_string((string)$imagehash)
- );
- sql_query($delete);
- if (!$maxattemptlog)
- stderr('Error',$lang_functions['std_invalid_image_code']."".$lang_functions['std_here_to_request_new'], false);
- else
- failedlogins($lang_functions['std_invalid_image_code']."".$lang_functions['std_here_to_request_new'],true,$head);
- }else{
- $delete = sprintf("DELETE FROM regimages WHERE imagehash='%s'",
- mysql_real_escape_string((string)$imagehash)
- );
- sql_query($delete);
- return true;
- }
+
+ $manager = captcha_manager();
+
+ if (!$manager->isEnabled()) {
+ return true;
+ }
+
+ $payload = [
+ 'imagehash' => $imagehash,
+ 'imagestring' => $imagestring,
+ 'request' => array_merge($_POST ?? [], $_GET ?? []),
+ ];
+
+ $context = [
+ 'where' => $where,
+ 'maxattemptlog' => $maxattemptlog,
+ 'head' => $head,
+ 'ip' => getip(),
+ ];
+
+ try {
+ if ($manager->verify($payload, $context)) {
+ return true;
+ }
+ } catch (\App\Services\Captcha\Exceptions\CaptchaValidationException $exception) {
+ $message = $exception->getMessage();
+
+ $defaultMessage = $lang_functions['std_invalid_image_code'] . "" . $lang_functions['std_here_to_request_new'];
+
+ if ($message === '' || $message === 'Invalid captcha response.' || $message === 'Missing captcha parameters.') {
+ $message = $defaultMessage;
+ }
+
+ if (!$maxattemptlog) {
+ stderr('Error', $message, false);
+ } else {
+ failedlogins($message, true, $head);
+ }
+ }
+
+ return false;
}
+
function show_image_code () {
- global $lang_functions;
- global $iv;
- if ($iv == "yes") {
- unset($imagehash);
- $imagehash = image_code () ;
- print ("| ".$lang_functions['row_security_image']." | ");
- print (" |
");
- print ("| ".$lang_functions['row_security_code']." | ");
- print("");
- print(" |
");
- }
+ global $lang_functions;
+ global $iv;
+
+ if ($iv !== 'yes') {
+ return;
+ }
+
+ $manager = captcha_manager();
+
+ if (!$manager->isEnabled()) {
+ return;
+ }
+
+ $markup = $manager->render([
+ 'labels' => [
+ 'image' => $lang_functions['row_security_image'],
+ 'code' => $lang_functions['row_security_code'],
+ ],
+ 'secret' => $_GET['secret'] ?? '',
+ ]);
+
+ if ($markup !== '') {
+ echo $markup;
+ }
}
function get_ip_location($ip)
diff --git a/include/globalfunctions.php b/include/globalfunctions.php
index c0a0e89d..dd8003f4 100644
--- a/include/globalfunctions.php
+++ b/include/globalfunctions.php
@@ -307,6 +307,7 @@ function nexus_config($key, $default = null)
$files = [
ROOT_PATH . 'config/nexus.php',
ROOT_PATH . 'config/emoji.php',
+ ROOT_PATH . 'config/captcha.php',
];
foreach ($files as $file) {
$basename = basename($file);
diff --git a/public/complains.php b/public/complains.php
index 8f8c8b1f..91b80f5b 100644
--- a/public/complains.php
+++ b/public/complains.php
@@ -18,7 +18,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST'){
switch($action = filter_input(INPUT_POST, 'action', FILTER_SANITIZE_FULL_SPECIAL_CHARS)){
case 'new':
cur_user_check();
- check_code ($_POST['imagehash'], $_POST['imagestring'],'complains.php');
+ check_code ($_POST['imagehash'] ?? null, $_POST['imagestring'] ?? null,'complains.php');
\Nexus\Database\NexusLock::lockOrFail("complains:lock:" . getip(), 10);
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
\Nexus\Database\NexusLock::lockOrFail("complains:lock:" . $email, 600);
diff --git a/public/confirm_resend.php b/public/confirm_resend.php
index ce9b6115..0022b1d9 100644
--- a/public/confirm_resend.php
+++ b/public/confirm_resend.php
@@ -29,7 +29,7 @@ bark($lang_confirm_resend['std_need_admin_verification']);
if ($_SERVER["REQUEST_METHOD"] == "POST")
{
if ($iv == "yes")
- check_code ($_POST['imagehash'], $_POST['imagestring'],"confirm_resend.php",true);
+ check_code ($_POST['imagehash'] ?? null, $_POST['imagestring'] ?? null,"confirm_resend.php",true);
$email = unesc(htmlspecialchars(trim($_POST["email"] ?? '')));
$wantpassword = unesc(htmlspecialchars(trim($_POST["wantpassword"])));
$passagain = unesc(htmlspecialchars(trim($_POST["passagain"])));
diff --git a/public/image.php b/public/image.php
index f013e5d4..fa2df541 100644
--- a/public/image.php
+++ b/public/image.php
@@ -1,60 +1,22 @@
driver('image');
+
+if (!method_exists($driver, 'outputImage')) {
+ http_response_code(404);
+ exit('Captcha driver does not support image rendering');
}
+
+$driver->outputImage($imagehash);
+
?>
diff --git a/public/recover.php b/public/recover.php
index 128c23f9..5e5979cc 100644
--- a/public/recover.php
+++ b/public/recover.php
@@ -28,7 +28,7 @@ $mailTwoFour = sprintf($lang_recover['mail_two_four'], $siteName);
if ($_SERVER["REQUEST_METHOD"] == "POST")
{
if ($iv == "yes")
- check_code ($_POST['imagehash'], $_POST['imagestring'],"recover.php",true);
+ check_code ($_POST['imagehash'] ?? null, $_POST['imagestring'] ?? null,"recover.php",true);
$email = unesc(htmlspecialchars(trim($_POST["email"] ?? '')));
$email = safe_email($email);
if (!$email)
diff --git a/public/takelogin.php b/public/takelogin.php
index 398f4b07..23a44761 100644
--- a/public/takelogin.php
+++ b/public/takelogin.php
@@ -15,7 +15,7 @@ function bark($text = "")
stderr($lang_takelogin['std_login_fail'], $text,false);
}
if ($iv == "yes") {
- check_code ($_POST['imagehash'], $_POST['imagestring'],'login.php',true);
+ check_code($_POST['imagehash'] ?? null, $_POST['imagestring'] ?? null, 'login.php', true);
}
//同时支持新旧两种登录方式
$useChallengeResponse = \App\Models\Setting::getIsUseChallengeResponseAuthentication();
diff --git a/public/takesignup.php b/public/takesignup.php
index 3358a344..94d4a075 100644
--- a/public/takesignup.php
+++ b/public/takesignup.php
@@ -21,13 +21,13 @@ if ($type == 'invite'){
registration_check();
failedloginscheck ("Invite Signup");
if ($iv == "yes")
- check_code ($_POST['imagehash'], $_POST['imagestring'],'signup.php?type=invite&invitenumber='.htmlspecialchars($_POST['hash']));
+ check_code ($_POST['imagehash'] ?? null, $_POST['imagestring'] ?? null,'signup.php?type=invite&invitenumber='.htmlspecialchars($_POST['hash']));
}
else{
registration_check("normal");
failedloginscheck ("Signup");
if ($iv == "yes")
- check_code ($_POST['imagehash'], $_POST['imagestring']);
+ check_code ($_POST['imagehash'] ?? null, $_POST['imagestring'] ?? null);
}
function isportopen($port)
{