diff --git a/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php b/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php index c2068ebc..97ca00b6 100644 --- a/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php +++ b/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php @@ -11,7 +11,9 @@ use Filament\Forms\Components\Radio; use Filament\Forms\Components\Select; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\CheckboxList; +use Filament\Schemas\Components\Fieldset; use Filament\Forms\Components\Repeater; +use Filament\Schemas\Components\Utilities\Get; use Filament\Schemas\Components\Section; use App\Auth\Permission; use App\Filament\OptionsTrait; @@ -101,6 +103,11 @@ class EditSetting extends Page implements HasForms } } Setting::query()->upsert($data, ['name'], ['value']); + Setting::query()->whereIn('name', [ + 'captcha.driver', + 'captcha.turnstile', + 'captcha.recaptcha', + ])->delete(); $this->doAfterUpdate(); do_action("nexus_setting_update"); clear_setting_cache(); @@ -178,6 +185,12 @@ class EditSetting extends Page implements HasForms ->columns(2) ; + $tabs[] = Tab::make(__('label.setting.captcha.tab_header')) + ->id('captcha') + ->schema($this->getTabCaptchaSchema()) + ->columns(2) + ; + $tabs[] = Tab::make(__('label.setting.system.tab_header')) ->id('system') ->schema([ @@ -236,6 +249,131 @@ class EditSetting extends Page implements HasForms return $tabs; } + private function getTabCaptchaSchema(): array + { + $captchaPrefix = 'captcha'; + + $driverOptions = [ + 'image' => __('label.setting.captcha.drivers.image'), + 'cloudflare_turnstile' => __('label.setting.captcha.drivers.cloudflare_turnstile'), + 'google_recaptcha_v2' => __('label.setting.captcha.drivers.google_recaptcha_v2'), + ]; + + $defaultDriver = Setting::get('captcha.default'); + if (is_null($defaultDriver)) { + $defaultDriver = Setting::get('captcha.driver', nexus_env('CAPTCHA_DRIVER', 'image')); + } + + $turnstileSiteKey = Setting::get( + 'captcha.drivers.cloudflare_turnstile.site_key', + Setting::get('captcha.turnstile.site_key', nexus_env('TURNSTILE_SITE_KEY')) + ); + $turnstileSecretKey = Setting::get( + 'captcha.drivers.cloudflare_turnstile.secret_key', + Setting::get('captcha.turnstile.secret_key', nexus_env('TURNSTILE_SECRET_KEY')) + ); + $turnstileTheme = Setting::get( + 'captcha.drivers.cloudflare_turnstile.theme', + Setting::get('captcha.turnstile.theme', nexus_env('TURNSTILE_THEME', 'auto')) + ); + $turnstileSize = Setting::get( + 'captcha.drivers.cloudflare_turnstile.size', + Setting::get('captcha.turnstile.size', nexus_env('TURNSTILE_SIZE', 'flexible')) + ); + + $recaptchaSiteKey = Setting::get( + 'captcha.drivers.google_recaptcha_v2.site_key', + Setting::get('captcha.recaptcha.site_key', nexus_env('RECAPTCHA_SITE_KEY')) + ); + $recaptchaSecretKey = Setting::get( + 'captcha.drivers.google_recaptcha_v2.secret_key', + Setting::get('captcha.recaptcha.secret_key', nexus_env('RECAPTCHA_SECRET_KEY')) + ); + $recaptchaTheme = Setting::get( + 'captcha.drivers.google_recaptcha_v2.theme', + Setting::get('captcha.recaptcha.theme', nexus_env('RECAPTCHA_THEME', 'light')) + ); + $recaptchaSize = Setting::get( + 'captcha.drivers.google_recaptcha_v2.size', + Setting::get('captcha.recaptcha.size', nexus_env('RECAPTCHA_SIZE', 'normal')) + ); + + $schema = [ + Select::make("$captchaPrefix.default") + ->options($driverOptions) + ->label(__('label.setting.captcha.driver')) + ->helperText(__('label.setting.captcha.driver_help')) + ->default($defaultDriver) + ->reactive() + , + Fieldset::make(__('label.setting.captcha.turnstile.section')) + ->visible(fn (Get $get) => $get('captcha.default') === 'cloudflare_turnstile') + ->schema([ + TextInput::make('captcha.drivers.cloudflare_turnstile.site_key') + ->label(__('label.setting.captcha.turnstile.site_key')) + ->helperText(__('label.setting.captcha.turnstile.site_key_help')) + ->default($turnstileSiteKey) , + TextInput::make('captcha.drivers.cloudflare_turnstile.secret_key') + ->label(__('label.setting.captcha.turnstile.secret_key')) + ->helperText(__('label.setting.captcha.turnstile.secret_key_help')) + ->password() + ->revealable() + ->default($turnstileSecretKey), + Select::make('captcha.drivers.cloudflare_turnstile.theme') + ->label(__('label.setting.captcha.turnstile.theme')) + ->helperText(__('label.setting.captcha.turnstile.theme_help')) + ->options([ + 'auto' => __('label.setting.captcha.turnstile.theme_auto'), + 'light' => __('label.setting.captcha.turnstile.theme_light'), + 'dark' => __('label.setting.captcha.turnstile.theme_dark'), + ]) + ->default($turnstileTheme), + Select::make('captcha.drivers.cloudflare_turnstile.size') + ->label(__('label.setting.captcha.turnstile.size')) + ->helperText(__('label.setting.captcha.turnstile.size_help')) + ->options([ + 'normal' => __('label.setting.captcha.turnstile.size_normal'), + 'compact' => __('label.setting.captcha.turnstile.size_compact'), + 'flexible' => __('label.setting.captcha.turnstile.size_flexible'), + ]) + ->default($turnstileSize), + ]) + , + Fieldset::make(__('label.setting.captcha.recaptcha.section')) + ->visible(fn (Get $get) => $get('captcha.default') === 'google_recaptcha_v2') + ->schema([ + TextInput::make('captcha.drivers.google_recaptcha_v2.site_key') + ->label(__('label.setting.captcha.recaptcha.site_key')) + ->helperText(__('label.setting.captcha.recaptcha.site_key_help')) + ->default($recaptchaSiteKey), + TextInput::make('captcha.drivers.google_recaptcha_v2.secret_key') + ->label(__('label.setting.captcha.recaptcha.secret_key')) + ->helperText(__('label.setting.captcha.recaptcha.secret_key_help')) + ->password() + ->revealable() + ->default($recaptchaSecretKey), + Select::make('captcha.drivers.google_recaptcha_v2.theme') + ->label(__('label.setting.captcha.recaptcha.theme')) + ->helperText(__('label.setting.captcha.recaptcha.theme_help')) + ->options([ + 'light' => __('label.setting.captcha.recaptcha.theme_light'), + 'dark' => __('label.setting.captcha.recaptcha.theme_dark'), + ]) + ->default($recaptchaTheme), + Select::make('captcha.drivers.google_recaptcha_v2.size') + ->label(__('label.setting.captcha.recaptcha.size')) + ->helperText(__('label.setting.captcha.recaptcha.size_help')) + ->options([ + 'normal' => __('label.setting.captcha.recaptcha.size_normal'), + 'compact' => __('label.setting.captcha.recaptcha.size_compact'), + ]) + ->default($recaptchaSize), + ]) + ]; + + return $schema; + } + private function getHitAndRunSchema() { $default = [ diff --git a/app/Services/Captcha/CaptchaManager.php b/app/Services/Captcha/CaptchaManager.php index f4f47369..183e3df4 100644 --- a/app/Services/Captcha/CaptchaManager.php +++ b/app/Services/Captcha/CaptchaManager.php @@ -3,6 +3,7 @@ namespace App\Services\Captcha; use App\Services\Captcha\Exceptions\CaptchaValidationException; +use App\Models\Setting; use Illuminate\Support\Arr; class CaptchaManager @@ -113,6 +114,15 @@ class CaptchaManager } $this->config = is_array($config) ? $config : []; + + try { + $settings = Setting::get('captcha', []); + if (is_array($settings) && !empty($settings)) { + $this->config = array_replace_recursive($this->config, $settings); + } + } catch (\Throwable $exception) { + // ignore database errors at bootstrap phase + } } return Arr::get($this->config, $key, $default); diff --git a/app/Services/Captcha/Drivers/ImageCaptchaDriver.php b/app/Services/Captcha/Drivers/ImageCaptchaDriver.php index 69952c99..05adf6bb 100644 --- a/app/Services/Captcha/Drivers/ImageCaptchaDriver.php +++ b/app/Services/Captcha/Drivers/ImageCaptchaDriver.php @@ -31,7 +31,7 @@ class ImageCaptchaDriver implements CaptchaDriverInterface return implode("\n", [ sprintf('%sCAPTCHA', htmlspecialchars($imageLabel, ENT_QUOTES, 'UTF-8'), $imageUrl), - sprintf('%s', htmlspecialchars($codeLabel, ENT_QUOTES, 'UTF-8'), htmlspecialchars($imagehash, ENT_QUOTES, 'UTF-8')), + sprintf('%s', htmlspecialchars($codeLabel, ENT_QUOTES, 'UTF-8'), htmlspecialchars($imagehash, ENT_QUOTES, 'UTF-8')), ]); } diff --git a/include/functions.php b/include/functions.php index 75c3174c..97fa2c56 100644 --- a/include/functions.php +++ b/include/functions.php @@ -1847,16 +1847,23 @@ function show_image_code () { } $manager = captcha_manager(); + $driver = $manager->driver(); - if (!$manager->isEnabled()) { + if (!$driver->isEnabled()) { return; } - $markup = $manager->render([ - 'labels' => [ - 'image' => $lang_functions['row_security_image'], - 'code' => $lang_functions['row_security_code'], - ], + $labelKey = $driver instanceof \App\Services\Captcha\Drivers\ImageCaptchaDriver + ? 'row_security_image' + : 'row_security_challenge'; + + $labels = [ + 'image' => $lang_functions[$labelKey] ?? $lang_functions['row_security_image'], + 'code' => $lang_functions['row_security_code'], + ]; + + $markup = $driver->render([ + 'labels' => $labels, 'secret' => $_GET['secret'] ?? '', ]); diff --git a/lang/chs/lang_functions.php b/lang/chs/lang_functions.php index e869a348..ab9236ac 100644 --- a/lang/chs/lang_functions.php +++ b/lang/chs/lang_functions.php @@ -41,6 +41,7 @@ $lang_functions = array 'std_you_will_get' => '你将获得', 'std_by' => '由', 'row_security_image' => "验证图片:", + 'row_security_challenge' => "安全验证:", 'row_security_code' => "验证码:", 'text_slots' => "连接数:", 'text_unlimited' => "无限制", diff --git a/lang/cht/lang_functions.php b/lang/cht/lang_functions.php index 4b5a67e5..058fa452 100644 --- a/lang/cht/lang_functions.php +++ b/lang/cht/lang_functions.php @@ -41,6 +41,7 @@ $lang_functions = array 'std_you_will_get' => '妳將獲得', 'std_by' => '由', 'row_security_image' => "驗證圖片:", + 'row_security_challenge' => "安全驗證:", 'row_security_code' => "驗證碼:", 'text_slots' => "連接數:", 'text_unlimited' => "無限制", diff --git a/lang/da/lang_functions.php b/lang/da/lang_functions.php index 5ca76e4b..8d0ffd09 100644 --- a/lang/da/lang_functions.php +++ b/lang/da/lang_functions.php @@ -41,6 +41,7 @@ $lang_functions = array 'std_you_will_get' => 'You will get', 'std_by' => 'By', 'row_security_image' => "Security Image:", + 'row_security_challenge' => "Security Challenge:", 'row_security_code' => "Security Code:", 'text_slots' => "Slots:", 'text_unlimited' => "Unlimited", diff --git a/lang/de/lang_functions.php b/lang/de/lang_functions.php index 62425920..f8bdcd80 100644 --- a/lang/de/lang_functions.php +++ b/lang/de/lang_functions.php @@ -41,6 +41,7 @@ $lang_functions = array 'std_you_will_get' => 'You will get', 'std_by' => 'By', 'row_security_image' => "Security Image:", + 'row_security_challenge' => "Security Challenge:", 'row_security_code' => "Security Code:", 'text_slots' => "Slots:", 'text_unlimited' => "Unbegrenzt", diff --git a/lang/en/lang_functions.php b/lang/en/lang_functions.php index f936a931..c75b564a 100644 --- a/lang/en/lang_functions.php +++ b/lang/en/lang_functions.php @@ -41,6 +41,7 @@ $lang_functions = array 'std_you_will_get' => 'You will get', 'std_by' => 'By', 'row_security_image' => "Security Image:", + 'row_security_challenge' => "Security Challenge:", 'row_security_code' => "Security Code:", 'text_slots' => "Slots:", 'text_unlimited' => "Unlimited", diff --git a/lang/en/lang_login.php b/lang/en/lang_login.php index 996b32c7..1e809685 100644 --- a/lang/en/lang_login.php +++ b/lang/en/lang_login.php @@ -11,7 +11,7 @@ $lang_login = array 'p_remaining_tries' => "remaining tries.", 'p_no_account_signup' => "Don't have an account? Sign up right now!", 'p_forget_pass_recover' => "Forget your password? Recover your password via email", - 'p_account_banned' => "Account banned? view reason onuser ban log", + 'p_account_banned' => "Account banned? view reason on user ban log", 'p_resend_confirm' => "Did not receive confirmation mail or confirmation link is broken? Send confirmation mail again", 'rowhead_username' => "Username:", 'rowhead_password' => "Password:", diff --git a/lang/es/lang_functions.php b/lang/es/lang_functions.php index b0f277bc..1afd5269 100644 --- a/lang/es/lang_functions.php +++ b/lang/es/lang_functions.php @@ -41,6 +41,7 @@ $lang_functions = array 'std_you_will_get' => 'You will get', 'std_by' => 'By', 'row_security_image' => "Security Image:", + 'row_security_challenge' => "Security Challenge:", 'row_security_code' => "Security Code:", 'text_slots' => "Slots:", 'text_unlimited' => "Ilimitado", diff --git a/lang/fr/lang_functions.php b/lang/fr/lang_functions.php index b4efb9c4..7e74cccb 100644 --- a/lang/fr/lang_functions.php +++ b/lang/fr/lang_functions.php @@ -41,6 +41,7 @@ $lang_functions = array 'std_you_will_get' => 'You will get', 'std_by' => 'By', 'row_security_image' => "Security Image:", + 'row_security_challenge' => "Security Challenge:", 'row_security_code' => "Security Code:", 'text_slots' => "Slots:", 'text_unlimited' => "Illimité", diff --git a/lang/ja/lang_functions.php b/lang/ja/lang_functions.php index eb61c945..6fb874fc 100644 --- a/lang/ja/lang_functions.php +++ b/lang/ja/lang_functions.php @@ -41,6 +41,7 @@ $lang_functions = array 'std_you_will_get' => 'You will get', 'std_by' => 'By', 'row_security_image' => "Security Image:", + 'row_security_challenge' => "Security Challenge:", 'row_security_code' => "Security Code:", 'text_slots' => "Slots:", 'text_unlimited' => "Unlimited", diff --git a/lang/ru/lang_functions.php b/lang/ru/lang_functions.php index 6d4b1ded..20a6732d 100644 --- a/lang/ru/lang_functions.php +++ b/lang/ru/lang_functions.php @@ -41,6 +41,7 @@ $lang_functions = array 'std_you_will_get' => 'You will get', 'std_by' => 'By', 'row_security_image' => "Security Image:", + 'row_security_challenge' => "Security Challenge:", 'row_security_code' => "Security Code:", 'text_slots' => "Slots:", 'text_unlimited' => "Неограниченный", diff --git a/public/complains.php b/public/complains.php index 91b80f5b..52a913b4 100644 --- a/public/complains.php +++ b/public/complains.php @@ -164,9 +164,13 @@ if($_SERVER['REQUEST_METHOD'] === 'POST'){

+ - - + +
autocomplete="email" />
diff --git a/public/confirm_resend.php b/public/confirm_resend.php index 0022b1d9..86673660 100644 --- a/public/confirm_resend.php +++ b/public/confirm_resend.php @@ -110,13 +110,14 @@ else

-
- - - - - + + +

-
+ + + + diff --git a/public/login.php b/public/login.php index ed7d14d2..9aac3024 100644 --- a/public/login.php +++ b/public/login.php @@ -59,9 +59,10 @@ if (!$useChallengeResponseAuthentication) {


[]

/>
/>
+
/>
- - - + + + +
style="width: 180px; border: 1px solid gray"/>
/>
autocomplete="current-password" />
/>
- + + diff --git a/public/signup.php b/public/signup.php index a0fffbdc..756697c4 100644 --- a/public/signup.php +++ b/public/signup.php @@ -78,24 +78,25 @@ print("
".$lang_signup['text_select_lang']. $s . "
"); +$formInputStyle = 'style="width: min(100%, 320px); min-width: 180px; border: 1px solid gray; box-sizing: border-box"'; if ($isPreRegisterEmailAndUsername && !empty($inv["pre_register_username"])) { - $usernameInput = sprintf('', $inv["pre_register_username"]); + $usernameInput = sprintf('', $formInputStyle, htmlspecialchars($inv["pre_register_username"], ENT_QUOTES)); } else { - $usernameInput = ''; + $usernameInput = ''; } if ($isPreRegisterEmailAndUsername && !empty($inv["pre_register_email"])) { - $emailInput = sprintf('', $inv["pre_register_email"]); + $emailInput = sprintf('', $formInputStyle, htmlspecialchars($inv["pre_register_email"], ENT_QUOTES)); } else { - $emailInput = ''; + $emailInput = ''; } ?> - - + diff --git a/resources/lang/en/label.php b/resources/lang/en/label.php index a4cca958..7d5f42e7 100644 --- a/resources/lang/en/label.php +++ b/resources/lang/en/label.php @@ -103,6 +103,48 @@ return [ 'max_uploaded_duration' => 'Maximum upload volume multiplier effective time range', 'max_uploaded_duration_help' => 'Unit: hours. The maximum upload volume multiplier takes effect within this time range after the torrent is published, and does not take effect beyond this range. A setting of 0 is always in effect', ], + 'captcha' => [ + 'tab_header' => 'Captcha', + 'driver' => 'Captcha driver', + 'driver_help' => 'Choose which verification mechanism is displayed on public forms.', + 'drivers' => [ + 'image' => 'Built-in image captcha', + 'cloudflare_turnstile' => 'Cloudflare Turnstile', + 'google_recaptcha_v2' => 'Google reCAPTCHA v2', + ], + 'turnstile' => [ + 'section' => 'Cloudflare Turnstile', + 'site_key' => 'Site key', + 'site_key_help' => 'Copied from the Cloudflare Turnstile dashboard.', + 'secret_key' => 'Secret key', + 'secret_key_help' => 'Keep this value private.', + 'theme' => 'Theme', + 'theme_help' => 'Automatically adapts when set to Auto.', + 'theme_auto' => 'Auto', + 'theme_light' => 'Light', + 'theme_dark' => 'Dark', + 'size' => 'Widget size', + 'size_help' => 'Flexible stretches to match the container width.', + 'size_normal' => 'Normal', + 'size_compact' => 'Compact', + 'size_flexible' => 'Flexible', + ], + 'recaptcha' => [ + 'section' => 'Google reCAPTCHA v2', + 'site_key' => 'Site key', + 'site_key_help' => 'Provided by the Google reCAPTCHA admin console.', + 'secret_key' => 'Secret key', + 'secret_key_help' => 'Keep this value private.', + 'theme' => 'Theme', + 'theme_help' => 'Use dark when your site runs a dark palette.', + 'theme_light' => 'Light', + 'theme_dark' => 'Dark', + 'size' => 'Widget size', + 'size_help' => 'Compact is suitable for narrow layouts.', + 'size_normal' => 'Normal', + 'size_compact' => 'Compact', + ], + ], 'meilisearch' => [ 'tab_header' => 'Meilisearch', 'enabled' => 'Whether to enable Meilisearch', diff --git a/resources/lang/zh_CN/label.php b/resources/lang/zh_CN/label.php index 025cc46f..c2843fba 100644 --- a/resources/lang/zh_CN/label.php +++ b/resources/lang/zh_CN/label.php @@ -144,6 +144,48 @@ return [ 'max_uploaded_duration' => '最大上传量倍数有效时间范围', 'max_uploaded_duration_help' => '单位:小时。种子发布后的这个时间范围内,最大上传量倍数生效,超过此范围不生效。设置为 0 一直生效', ], + 'captcha' => [ + 'tab_header' => '验证码', + 'driver' => '验证码驱动', + 'driver_help' => '选择在登录等公开页面展示的验证码方案。', + 'drivers' => [ + 'image' => '内置图片验证码', + 'cloudflare_turnstile' => 'Cloudflare Turnstile', + 'google_recaptcha_v2' => 'Google reCAPTCHA v2', + ], + 'turnstile' => [ + 'section' => 'Cloudflare Turnstile 配置', + 'site_key' => '站点密钥', + 'site_key_help' => '在 Cloudflare Turnstile 后台获取。', + 'secret_key' => '私钥', + 'secret_key_help' => '请妥善保管,不要泄露。', + 'theme' => '主题', + 'theme_help' => '选择“自动”时会根据页面自动适配。', + 'theme_auto' => '自动', + 'theme_light' => '浅色', + 'theme_dark' => '深色', + 'size' => '组件尺寸', + 'size_help' => '弹性模式会根据容器宽度自动拉伸。', + 'size_normal' => '普通', + 'size_compact' => '紧凑', + 'size_flexible' => '弹性', + ], + 'recaptcha' => [ + 'section' => 'Google reCAPTCHA v2 配置', + 'site_key' => '站点密钥', + 'site_key_help' => '在 Google reCAPTCHA 控制台获取。', + 'secret_key' => '私钥', + 'secret_key_help' => '请妥善保管,不要泄露。', + 'theme' => '主题', + 'theme_help' => '深色主题适用于深色背景。', + 'theme_light' => '浅色', + 'theme_dark' => '深色', + 'size' => '组件尺寸', + 'size_help' => '紧凑模式适合较窄的布局。', + 'size_normal' => '普通', + 'size_compact' => '紧凑', + ], + ], 'meilisearch' => [ 'tab_header' => 'Meilisearch', 'enabled' => '是否启用 Meilisearch', diff --git a/resources/lang/zh_TW/label.php b/resources/lang/zh_TW/label.php index ea34cf21..210e23dc 100644 --- a/resources/lang/zh_TW/label.php +++ b/resources/lang/zh_TW/label.php @@ -103,6 +103,48 @@ return [ 'max_uploaded_duration' => '最大上傳量倍數有效時間範圍', 'max_uploaded_duration_help' => '單位:小時。種子發布後的這個時間範圍內,最大上傳量倍數生效,超過此範圍不生效。設置為 0 一直生效', ], + 'captcha' => [ + 'tab_header' => '驗證碼', + 'driver' => '驗證碼驅動', + 'driver_help' => '選擇在登入等公開頁面顯示的驗證方案。', + 'drivers' => [ + 'image' => '內建圖片驗證碼', + 'cloudflare_turnstile' => 'Cloudflare Turnstile', + 'google_recaptcha_v2' => 'Google reCAPTCHA v2', + ], + 'turnstile' => [ + 'section' => 'Cloudflare Turnstile 設定', + 'site_key' => '站點金鑰', + 'site_key_help' => '於 Cloudflare Turnstile 後台取得。', + 'secret_key' => '私鑰', + 'secret_key_help' => '請妥善保存,避免外洩。', + 'theme' => '主題', + 'theme_help' => '選擇「自動」時會依頁面自動調整。', + 'theme_auto' => '自動', + 'theme_light' => '淺色', + 'theme_dark' => '深色', + 'size' => '元件尺寸', + 'size_help' => '彈性模式會依容器寬度伸縮。', + 'size_normal' => '一般', + 'size_compact' => '緊湊', + 'size_flexible' => '彈性', + ], + 'recaptcha' => [ + 'section' => 'Google reCAPTCHA v2 設定', + 'site_key' => '站點金鑰', + 'site_key_help' => '於 Google reCAPTCHA 控制台取得。', + 'secret_key' => '私鑰', + 'secret_key_help' => '請妥善保存,避免外洩。', + 'theme' => '主題', + 'theme_help' => '深色主題適用於深色背景。', + 'theme_light' => '淺色', + 'theme_dark' => '深色', + 'size' => '元件尺寸', + 'size_help' => '緊湊模式適合較狹窄的版面。', + 'size_normal' => '一般', + 'size_compact' => '緊湊', + ], + ], 'meilisearch' => [ 'tab_header' => 'Meilisearch', 'enabled' => '是否啟用 Meilisearch',
name="email" autocomplete="email" />
".$lang_signup['text_cookies_note']."


+
class="wantpassword" autocomplete="new-password" />
class="passagain" autocomplete="new-password" />