mirror of
https://github.com/lkddi/nexusphp.git
synced 2026-04-24 12:07:23 +08:00
feat(attendance): make captcha requirement configurable
- add Filament toggle to control whether attendance check-in requires captcha - persist the toggle under captcha.attendance.enabled with sensible defaults Signed-off-by: Qi HU <github@spcsky.com>
This commit is contained in:
@@ -99,6 +99,7 @@ FORCE_SCHEME=
|
|||||||
# Captcha settings
|
# Captcha settings
|
||||||
# Available drivers: image, cloudflare_turnstile, google_recaptcha_v2
|
# Available drivers: image, cloudflare_turnstile, google_recaptcha_v2
|
||||||
CAPTCHA_DRIVER=image
|
CAPTCHA_DRIVER=image
|
||||||
|
CAPTCHA_ATTENDANCE_ENABLED=true
|
||||||
|
|
||||||
# Cloudflare Turnstile keys (used when CAPTCHA_DRIVER=cloudflare_turnstile)
|
# Cloudflare Turnstile keys (used when CAPTCHA_DRIVER=cloudflare_turnstile)
|
||||||
TURNSTILE_SITE_KEY=
|
TURNSTILE_SITE_KEY=
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use Filament\Forms\Components\TextInput;
|
|||||||
use Filament\Forms\Components\CheckboxList;
|
use Filament\Forms\Components\CheckboxList;
|
||||||
use Filament\Schemas\Components\Fieldset;
|
use Filament\Schemas\Components\Fieldset;
|
||||||
use Filament\Forms\Components\Repeater;
|
use Filament\Forms\Components\Repeater;
|
||||||
|
use Filament\Forms\Components\Toggle;
|
||||||
use Filament\Schemas\Components\Utilities\Get;
|
use Filament\Schemas\Components\Utilities\Get;
|
||||||
use Filament\Schemas\Components\Section;
|
use Filament\Schemas\Components\Section;
|
||||||
use App\Auth\Permission;
|
use App\Auth\Permission;
|
||||||
@@ -30,6 +31,7 @@ use Filament\Facades\Filament;
|
|||||||
use Filament\Resources\Pages\Page;
|
use Filament\Resources\Pages\Page;
|
||||||
use Filament\Forms;
|
use Filament\Forms;
|
||||||
use Illuminate\Support\HtmlString;
|
use Illuminate\Support\HtmlString;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
use Meilisearch\Contracts\Index\Settings;
|
use Meilisearch\Contracts\Index\Settings;
|
||||||
use Nexus\Database\NexusDB;
|
use Nexus\Database\NexusDB;
|
||||||
|
|
||||||
@@ -64,6 +66,16 @@ class EditSetting extends Page implements HasForms
|
|||||||
private function fillForm()
|
private function fillForm()
|
||||||
{
|
{
|
||||||
$settings = Setting::getFromDb();
|
$settings = Setting::getFromDb();
|
||||||
|
|
||||||
|
$fallbackEnabled = (bool) config('captcha.attendance.enabled', true);
|
||||||
|
$rawSetting = Arr::get($settings, 'captcha.attendance.enabled', $fallbackEnabled);
|
||||||
|
if (is_string($rawSetting)) {
|
||||||
|
$normalized = in_array(strtolower($rawSetting), ['1', 'true', 'yes'], true);
|
||||||
|
} else {
|
||||||
|
$normalized = (bool) $rawSetting;
|
||||||
|
}
|
||||||
|
Arr::set($settings, 'captcha.attendance.enabled', $normalized);
|
||||||
|
|
||||||
$this->form->fill($settings);
|
$this->form->fill($settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +310,20 @@ class EditSetting extends Page implements HasForms
|
|||||||
Setting::get('captcha.recaptcha.size', nexus_env('RECAPTCHA_SIZE', 'normal'))
|
Setting::get('captcha.recaptcha.size', nexus_env('RECAPTCHA_SIZE', 'normal'))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$attendanceCaptchaSetting = Setting::get('captcha.attendance.enabled', true);
|
||||||
|
if (is_string($attendanceCaptchaSetting)) {
|
||||||
|
$attendanceCaptchaEnabled = in_array(strtolower($attendanceCaptchaSetting), ['1', 'true', 'yes'], true);
|
||||||
|
} else {
|
||||||
|
$attendanceCaptchaEnabled = (bool) $attendanceCaptchaSetting;
|
||||||
|
}
|
||||||
|
|
||||||
$schema = [
|
$schema = [
|
||||||
|
Toggle::make("$captchaPrefix.attendance.enabled")
|
||||||
|
->label(__('label.setting.captcha.attendance.enabled'))
|
||||||
|
->helperText(__('label.setting.captcha.attendance.enabled_help'))
|
||||||
|
->default($attendanceCaptchaEnabled)
|
||||||
|
->columnSpanFull()
|
||||||
|
,
|
||||||
Select::make("$captchaPrefix.default")
|
Select::make("$captchaPrefix.default")
|
||||||
->options($driverOptions)
|
->options($driverOptions)
|
||||||
->label(__('label.setting.captcha.driver'))
|
->label(__('label.setting.captcha.driver'))
|
||||||
|
|||||||
@@ -24,4 +24,8 @@ return [
|
|||||||
'size' => nexus_env('RECAPTCHA_SIZE', 'normal'),
|
'size' => nexus_env('RECAPTCHA_SIZE', 'normal'),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'attendance' => [
|
||||||
|
'enabled' => nexus_env('CAPTCHA_ATTENDANCE_ENABLED', true),
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
+52
-34
@@ -1,9 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require '../include/bittorrent.php';
|
require '../include/bittorrent.php';
|
||||||
dbconn();
|
dbconn();
|
||||||
require get_langfile_path();
|
require get_langfile_path();
|
||||||
loggedinorreturn();
|
loggedinorreturn();
|
||||||
parked();
|
parked();
|
||||||
|
|
||||||
|
\Nexus\Nexus::css('vendor/fullcalendar-5.10.2/main.min.css', 'header', true);
|
||||||
|
\Nexus\Nexus::js('vendor/fullcalendar-5.10.2/main.min.js', 'footer', true);
|
||||||
|
|
||||||
$lang = get_langfolder_cookie();
|
$lang = get_langfolder_cookie();
|
||||||
$localesMap = [
|
$localesMap = [
|
||||||
'en' => 'en-us',
|
'en' => 'en-us',
|
||||||
@@ -11,40 +16,52 @@ $localesMap = [
|
|||||||
'cht' => 'zh-tw',
|
'cht' => 'zh-tw',
|
||||||
];
|
];
|
||||||
$localeJs = $localesMap[$lang] ?? 'en-us';
|
$localeJs = $localesMap[$lang] ?? 'en-us';
|
||||||
|
|
||||||
\Nexus\Nexus::css('vendor/fullcalendar-5.10.2/main.min.css', 'header', true);
|
|
||||||
\Nexus\Nexus::js('vendor/fullcalendar-5.10.2/main.min.js', 'footer', true);
|
|
||||||
\Nexus\Nexus::js("vendor/fullcalendar-5.10.2/locales/{$localeJs}.js", 'footer', true);
|
\Nexus\Nexus::js("vendor/fullcalendar-5.10.2/locales/{$localeJs}.js", 'footer', true);
|
||||||
|
|
||||||
$rep = new \App\Repositories\AttendanceRepository();
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
if ($iv == "yes") {
|
|
||||||
check_code($_POST['imagehash'] ?? null, $_POST['imagestring'] ?? null, 'attendance.php');
|
|
||||||
}
|
|
||||||
$attendance = $rep->attend($CURUSER['id']);
|
|
||||||
if (!$attendance->is_updated) {
|
|
||||||
stderr($lang_attendance['sorry'], $lang_attendance['already_attended']);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$attendance = $rep->getAttendance($CURUSER['id']);
|
|
||||||
if (!$attendance) {
|
|
||||||
$attendance = new \App\Models\Attendance([
|
|
||||||
'uid' => $CURUSER['id'],
|
|
||||||
'points' => 0,
|
|
||||||
'days' => 0,
|
|
||||||
'total_days' => 0,
|
|
||||||
]);
|
|
||||||
$attendance->added = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$today = \Carbon\Carbon::today();
|
$today = \Carbon\Carbon::today();
|
||||||
$tomorrow = \Carbon\Carbon::tomorrow();
|
$tomorrow = \Carbon\Carbon::tomorrow();
|
||||||
$end = $today->clone()->endOfMonth();
|
$end = $today->clone()->endOfMonth();
|
||||||
$start = $today->clone()->subMonth(2);
|
$start = $today->clone()->subMonth(2);
|
||||||
|
|
||||||
$hasAttendedToday = $attendance->added && $attendance->added->isSameDay($today);
|
$attendanceRepository = new \App\Repositories\AttendanceRepository();
|
||||||
|
|
||||||
|
$attendanceCaptchaSetting = \App\Models\Setting::get('captcha.attendance.enabled', config('captcha.attendance.enabled', true));
|
||||||
|
if (is_string($attendanceCaptchaSetting)) {
|
||||||
|
$attendanceCaptchaEnabled = in_array(strtolower($attendanceCaptchaSetting), ['1', 'true', 'yes'], true);
|
||||||
|
} else {
|
||||||
|
$attendanceCaptchaEnabled = (bool) $attendanceCaptchaSetting;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if ($attendanceCaptchaEnabled && $iv == 'yes') {
|
||||||
|
check_code($_POST['imagehash'] ?? null, $_POST['imagestring'] ?? null, 'attendance.php');
|
||||||
|
}
|
||||||
|
$attendance = $attendanceRepository->attend($CURUSER['id']);
|
||||||
|
if (!$attendance->is_updated) {
|
||||||
|
stderr($lang_attendance['sorry'], $lang_attendance['already_attended']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$attendance = $attendanceRepository->getAttendance($CURUSER['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$hasAttendedToday = $attendance && $attendance->added && $attendance->added->isSameDay($today);
|
||||||
|
|
||||||
|
if (!$attendanceCaptchaEnabled && !$hasAttendedToday) {
|
||||||
|
$attendance = $attendanceRepository->attend($CURUSER['id']);
|
||||||
|
$hasAttendedToday = $attendance && $attendance->added && $attendance->added->isSameDay($today);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$attendance) {
|
||||||
|
$attendance = new \App\Models\Attendance([
|
||||||
|
'uid' => $CURUSER['id'],
|
||||||
|
'points' => 0,
|
||||||
|
'days' => 0,
|
||||||
|
'total_days' => 0,
|
||||||
|
]);
|
||||||
|
$attendance->added = null;
|
||||||
|
$hasAttendedToday = false;
|
||||||
|
}
|
||||||
|
|
||||||
stdhead($lang_attendance['title']);
|
stdhead($lang_attendance['title']);
|
||||||
begin_main_frame();
|
begin_main_frame();
|
||||||
|
|
||||||
@@ -74,6 +91,7 @@ if ($hasAttendedToday) {
|
|||||||
->where('date', '>=', $start->format('Y-m-d'))
|
->where('date', '>=', $start->format('Y-m-d'))
|
||||||
->get()
|
->get()
|
||||||
->keyBy('date');
|
->keyBy('date');
|
||||||
|
|
||||||
$interval = new \DateInterval('P1D');
|
$interval = new \DateInterval('P1D');
|
||||||
$period = new \DatePeriod($start, $interval, $end);
|
$period = new \DatePeriod($start, $interval, $end);
|
||||||
|
|
||||||
@@ -103,7 +121,7 @@ if ($hasAttendedToday) {
|
|||||||
$eventStr = json_encode($events);
|
$eventStr = json_encode($events);
|
||||||
$validRangeStr = json_encode(['start' => $start->format('Y-m-d'), 'end' => $end->clone()->addDays(1)->format('Y-m-d')]);
|
$validRangeStr = json_encode(['start' => $start->format('Y-m-d'), 'end' => $end->clone()->addDays(1)->format('Y-m-d')]);
|
||||||
|
|
||||||
$js = <<<EOP
|
$calendarScript = <<<EOP
|
||||||
let events = JSON.parse('$eventStr')
|
let events = JSON.parse('$eventStr')
|
||||||
let validRange = JSON.parse('$validRangeStr')
|
let validRange = JSON.parse('$validRangeStr')
|
||||||
let confirmText = "{$lang_attendance['retroactive_confirm_tip']}"
|
let confirmText = "{$lang_attendance['retroactive_confirm_tip']}"
|
||||||
@@ -115,7 +133,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
events: events,
|
events: events,
|
||||||
validRange: validRange,
|
validRange: validRange,
|
||||||
eventClick: function(info) {
|
eventClick: function(info) {
|
||||||
console.log(info.event);
|
|
||||||
if (info.event.groupId == 'to_do') {
|
if (info.event.groupId == 'to_do') {
|
||||||
retroactive(info.event.startStr)
|
retroactive(info.event.startStr)
|
||||||
}
|
}
|
||||||
@@ -126,11 +143,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
function retroactive(dateStr) {
|
function retroactive(dateStr) {
|
||||||
if (!window.confirm(confirmText + dateStr + ' ?')) {
|
if (!window.confirm(confirmText + dateStr + ' ?')) {
|
||||||
console.log("cancel")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jQuery.post('ajax.php', {params: {date: dateStr}, action: 'attendanceRetroactive'}, function (response) {
|
jQuery.post('ajax.php', {params: {date: dateStr}, action: 'attendanceRetroactive'}, function (response) {
|
||||||
console.log(response);
|
|
||||||
if (response.ret != 0) {
|
if (response.ret != 0) {
|
||||||
alert(response.msg)
|
alert(response.msg)
|
||||||
} else {
|
} else {
|
||||||
@@ -140,14 +155,14 @@ function retroactive(dateStr) {
|
|||||||
}
|
}
|
||||||
EOP;
|
EOP;
|
||||||
|
|
||||||
\Nexus\Nexus::js($js, 'footer', false);
|
\Nexus\Nexus::js($calendarScript, 'footer', false);
|
||||||
|
|
||||||
echo '<div style="display: flex;justify-content: center;padding: 20px 0"><div id="calendar" style="width: 60%"></div></div>';
|
echo '<div style="display: flex;justify-content: center;padding: 20px 0"><div id="calendar" style="width: 60%"></div></div>';
|
||||||
echo '<ul>';
|
echo '<ul>';
|
||||||
printf('<li>'.$lang_attendance['initial'].'</li>', $attendance_initial_bonus);
|
printf('<li>'.$lang_attendance['initial'].'</li>', $attendance_initial_bonus);
|
||||||
printf('<li>'.$lang_attendance['steps'].'</li>', $attendance_step_bonus, $attendance_max_bonus);
|
printf('<li>'.$lang_attendance['steps'].'</li>', $attendance_step_bonus, $attendance_max_bonus);
|
||||||
echo '<li><ol>';
|
echo '<li><ol>';
|
||||||
foreach($attendance_continuous_bonus as $day => $value){
|
foreach ($attendance_continuous_bonus as $day => $value) {
|
||||||
printf('<li>'.$lang_attendance['continuous'].'</li>', $day, $value);
|
printf('<li>'.$lang_attendance['continuous'].'</li>', $day, $value);
|
||||||
}
|
}
|
||||||
echo '</ol></li>';
|
echo '</ol></li>';
|
||||||
@@ -160,7 +175,9 @@ EOP;
|
|||||||
echo '<div style="margin-top: 20px; text-align: center;">';
|
echo '<div style="margin-top: 20px; text-align: center;">';
|
||||||
echo '<form method="post" action="attendance.php" style="display: inline-block;">';
|
echo '<form method="post" action="attendance.php" style="display: inline-block;">';
|
||||||
echo '<table border="0" cellpadding="5">';
|
echo '<table border="0" cellpadding="5">';
|
||||||
show_image_code();
|
if ($attendanceCaptchaEnabled && $iv == 'yes') {
|
||||||
|
show_image_code();
|
||||||
|
}
|
||||||
echo '<tr><td class="toolbox" colspan="2" align="center"><input type="submit" value="' . htmlspecialchars($buttonLabel, ENT_QUOTES, 'UTF-8') . '" class="btn" /></td></tr>';
|
echo '<tr><td class="toolbox" colspan="2" align="center"><input type="submit" value="' . htmlspecialchars($buttonLabel, ENT_QUOTES, 'UTF-8') . '" class="btn" /></td></tr>';
|
||||||
echo '</table>';
|
echo '</table>';
|
||||||
echo '</form>';
|
echo '</form>';
|
||||||
@@ -169,5 +186,6 @@ EOP;
|
|||||||
echo '</tbody></table>';
|
echo '</tbody></table>';
|
||||||
end_frame();
|
end_frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
end_main_frame();
|
end_main_frame();
|
||||||
stdfoot();
|
stdfoot();
|
||||||
|
|||||||
@@ -144,6 +144,10 @@ return [
|
|||||||
'size_normal' => 'Normal',
|
'size_normal' => 'Normal',
|
||||||
'size_compact' => 'Compact',
|
'size_compact' => 'Compact',
|
||||||
],
|
],
|
||||||
|
'attendance' => [
|
||||||
|
'enabled' => 'Require captcha for attendance check-in',
|
||||||
|
'enabled_help' => 'When enabled, members must solve the captcha before signing in.',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
'meilisearch' => [
|
'meilisearch' => [
|
||||||
'tab_header' => 'Meilisearch',
|
'tab_header' => 'Meilisearch',
|
||||||
|
|||||||
@@ -185,6 +185,10 @@ return [
|
|||||||
'size_normal' => '普通',
|
'size_normal' => '普通',
|
||||||
'size_compact' => '紧凑',
|
'size_compact' => '紧凑',
|
||||||
],
|
],
|
||||||
|
'attendance' => [
|
||||||
|
'enabled' => '启用签到验证码',
|
||||||
|
'enabled_help' => '开启后,用户每天签到前必须完成验证码验证。',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
'meilisearch' => [
|
'meilisearch' => [
|
||||||
'tab_header' => 'Meilisearch',
|
'tab_header' => 'Meilisearch',
|
||||||
|
|||||||
@@ -144,6 +144,10 @@ return [
|
|||||||
'size_normal' => '一般',
|
'size_normal' => '一般',
|
||||||
'size_compact' => '緊湊',
|
'size_compact' => '緊湊',
|
||||||
],
|
],
|
||||||
|
'attendance' => [
|
||||||
|
'enabled' => '啟用簽到驗證碼',
|
||||||
|
'enabled_help' => '啟用後,使用者簽到前必須完成驗證碼驗證。',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
'meilisearch' => [
|
'meilisearch' => [
|
||||||
'tab_header' => 'Meilisearch',
|
'tab_header' => 'Meilisearch',
|
||||||
|
|||||||
Reference in New Issue
Block a user