diff --git a/.env.example b/.env.example index cbd1aa08..c3588b1b 100644 --- a/.env.example +++ b/.env.example @@ -99,6 +99,7 @@ FORCE_SCHEME= # Captcha settings # Available drivers: image, cloudflare_turnstile, google_recaptcha_v2 CAPTCHA_DRIVER=image +CAPTCHA_ATTENDANCE_ENABLED=true # Cloudflare Turnstile keys (used when CAPTCHA_DRIVER=cloudflare_turnstile) TURNSTILE_SITE_KEY= diff --git a/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php b/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php index 97ca00b6..9d3e3a31 100644 --- a/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php +++ b/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php @@ -13,6 +13,7 @@ use Filament\Forms\Components\TextInput; use Filament\Forms\Components\CheckboxList; use Filament\Schemas\Components\Fieldset; use Filament\Forms\Components\Repeater; +use Filament\Forms\Components\Toggle; use Filament\Schemas\Components\Utilities\Get; use Filament\Schemas\Components\Section; use App\Auth\Permission; @@ -30,6 +31,7 @@ use Filament\Facades\Filament; use Filament\Resources\Pages\Page; use Filament\Forms; use Illuminate\Support\HtmlString; +use Illuminate\Support\Arr; use Meilisearch\Contracts\Index\Settings; use Nexus\Database\NexusDB; @@ -64,6 +66,16 @@ class EditSetting extends Page implements HasForms private function fillForm() { $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); } @@ -298,7 +310,20 @@ class EditSetting extends Page implements HasForms 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 = [ + 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") ->options($driverOptions) ->label(__('label.setting.captcha.driver')) diff --git a/config/captcha.php b/config/captcha.php index 021a45ad..7cd90cc9 100644 --- a/config/captcha.php +++ b/config/captcha.php @@ -24,4 +24,8 @@ return [ 'size' => nexus_env('RECAPTCHA_SIZE', 'normal'), ], ], + + 'attendance' => [ + 'enabled' => nexus_env('CAPTCHA_ATTENDANCE_ENABLED', true), + ], ]; diff --git a/lang/chs/lang_attendance.php b/lang/chs/lang_attendance.php index dd9d617b..46e9a9bc 100644 --- a/lang/chs/lang_attendance.php +++ b/lang/chs/lang_attendance.php @@ -13,4 +13,5 @@ $lang_attendance = array 'retroactive_event_text' => '补', 'retroactive_confirm_tip' => '确定要补签: ', 'retroactive_description' => '点击白色背景的圆点进行补签。你目前拥有补签卡 %d 张。', + 'attend_button' => '立即签到', ); diff --git a/lang/cht/lang_attendance.php b/lang/cht/lang_attendance.php index 1b6db754..41eaa9c3 100644 --- a/lang/cht/lang_attendance.php +++ b/lang/cht/lang_attendance.php @@ -13,4 +13,5 @@ $lang_attendance = array 'retroactive_event_text' => '補', 'retroactive_confirm_tip' => '確定要補簽: ', 'retroactive_description' => '點擊白色背景的圓點進行補簽。你目前擁有補簽卡 %d 張。', + 'attend_button' => '立即簽到', ); diff --git a/lang/en/lang_attendance.php b/lang/en/lang_attendance.php index c5faadde..3899c91d 100644 --- a/lang/en/lang_attendance.php +++ b/lang/en/lang_attendance.php @@ -13,4 +13,5 @@ $lang_attendance = array 'retroactive_event_text' => 'Re', 'retroactive_confirm_tip' => 'Confirm to attend: ', 'retroactive_description' => 'Click on the dot on the white background to do attend. You currently have a attendance card %d.', + 'attend_button' => 'Check in now', ); diff --git a/public/attendance.php b/public/attendance.php index efb5551b..67b4325e 100644 --- a/public/attendance.php +++ b/public/attendance.php @@ -1,32 +1,10 @@ attend($attendance_initial_bonus, $attendance_step_bonus, $attendance_max_bonus, $attendance_continuous_bonus)){ -// list($count, $cdays, $points) = $result; -// stdhead($lang_attendance['title']); -// begin_main_frame(); -// begin_frame($lang_attendance['success']); -// printf('

'.$lang_attendance['attend_info'].'

', $count, $cdays, $points); -// end_frame(); -// echo ''; -// end_main_frame(); -// stdfoot(); -//}else{ -// stderr($lang_attendance['sorry'], $lang_attendance['already_attended']); -//} \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); @@ -44,38 +22,106 @@ $today = \Carbon\Carbon::today(); $tomorrow = \Carbon\Carbon::tomorrow(); $end = $today->clone()->endOfMonth(); $start = $today->clone()->subMonth(2); -$rep = new \App\Repositories\AttendanceRepository(); -$attendance = $rep->attend($CURUSER['id']); -$logs = $attendance->logs()->where('date', '>=', $start->format('Y-m-d'))->get()->keyBy('date'); -$interval = new \DateInterval('P1D'); -$period = new \DatePeriod($start, $interval, $end); -$interval = \Carbon\CarbonInterval::make($interval); -$period = \Carbon\CarbonPeriod::make($period); -$events = []; -foreach ($period as $value) { - if ($value->gte($tomorrow)) { - continue; - } - $checkDate = $value->format('Y-m-d'); - $eventBase = ['start' => $checkDate, 'end' => $checkDate]; - if ($logs->has($checkDate)) { - $logValue = $logs->get($checkDate); - $events[] = array_merge($eventBase, ['display' => 'background']); - if ($logValue->points > 0) { - $events[] = array_merge($eventBase, ['title' => $logValue->points]); - } - if ($logValue->is_retroactive) { - $events[] = array_merge($eventBase, ['title' => $lang_attendance['retroactive_event_text'], 'display' => 'list-item']); - } - } elseif ($value->lte($today) && $value->diffInDays($today, true) <= \App\Models\Attendance::MAX_RETROACTIVE_DAYS) { - $events[] = array_merge($eventBase, ['groupId' => 'to_do', 'display' => 'list-item']); - } +$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; } -$eventStr = json_encode($events); -$validRangeStr = json_encode(['start' => $start->format('Y-m-d'), 'end' => $end->clone()->addDays(1)->format('Y-m-d')]); -$js = <<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']); +begin_main_frame(); + +if ($hasAttendedToday) { + $todayDate = $today->format('Y-m-d'); + $baseQuery = \App\Models\AttendanceLog::query()->where('date', $todayDate); + $todayCounts = $baseQuery->count(); + $myLog = (clone $baseQuery)->where('uid', $CURUSER['id'])->first(['id']); + $myRanking = 0; + if ($myLog) { + $myRanking = (clone $baseQuery)->where('id', '<=', $myLog->id)->count(); + } + + $count = $attendance->total_days; + $cdays = $attendance->days; + $points = $attendance->points; + + $headerLeft = sprintf($lang_attendance['attend_info'] . $lang_attendance['retroactive_description'], $count, $cdays, $points, $CURUSER['attendance_card']); + $headerRight = nexus_trans('attendance.ranking', ['ranking' => $myRanking, 'counts' => $todayCounts]); + + begin_frame($lang_attendance['success']); + printf('

%s%s

', $headerLeft, $headerRight); + end_frame(); + + $logs = \App\Models\AttendanceLog::query() + ->where('uid', $CURUSER['id']) + ->where('date', '>=', $start->format('Y-m-d')) + ->get() + ->keyBy('date'); + + $interval = new \DateInterval('P1D'); + $period = new \DatePeriod($start, $interval, $end); + + $interval = \Carbon\CarbonInterval::make($interval); + $period = \Carbon\CarbonPeriod::make($period); + $events = []; + foreach ($period as $value) { + if ($value->gte($tomorrow)) { + continue; + } + $checkDate = $value->format('Y-m-d'); + $eventBase = ['start' => $checkDate, 'end' => $checkDate]; + if ($logs->has($checkDate)) { + $logValue = $logs->get($checkDate); + $events[] = array_merge($eventBase, ['display' => 'background']); + if ($logValue->points > 0) { + $events[] = array_merge($eventBase, ['title' => $logValue->points]); + } + if ($logValue->is_retroactive) { + $events[] = array_merge($eventBase, ['title' => $lang_attendance['retroactive_event_text'], 'display' => 'list-item']); + } + } elseif ($value->lte($today) && $value->diffInDays($today, true) <= \App\Models\Attendance::MAX_RETROACTIVE_DAYS) { + $events[] = array_merge($eventBase, ['groupId' => 'to_do', 'display' => 'list-item']); + } + } + + $eventStr = json_encode($events); + $validRangeStr = json_encode(['start' => $start->format('Y-m-d'), 'end' => $end->clone()->addDays(1)->format('Y-m-d')]); + + $calendarScript = <<total_days; - $cdays = $attendance->days; - $points = $attendance->points; - - stdhead($lang_attendance['title']); - begin_main_frame(); - begin_frame($lang_attendance['success']); - $headerLeft = sprintf($lang_attendance['attend_info'].$lang_attendance['retroactive_description'], $count, $cdays, $points, $CURUSER['attendance_card']); - $headerRight = nexus_trans('attendance.ranking', ['ranking' => $attendance->my_ranking, 'counts' => $attendance->today_counts]); - printf('

%s%s

', $headerLeft, $headerRight); - end_frame(); echo '
'; echo ''; - end_main_frame(); - stdfoot(); - } else { - stderr($lang_attendance['sorry'], $lang_attendance['already_attended']); + $buttonLabel = $lang_attendance['attend_button'] ?? 'Check in'; + begin_frame($lang_attendance['title']); + echo ''; + echo ''; + echo '
'; + echo '
'; + echo '
'; + echo ''; + if ($attendanceCaptchaEnabled && $iv == 'yes') { + show_image_code(); + } + echo ''; + echo '
'; + echo '
'; + echo '
'; + echo '
'; + end_frame(); } + +end_main_frame(); +stdfoot(); diff --git a/resources/lang/en/label.php b/resources/lang/en/label.php index 7d5f42e7..376180bc 100644 --- a/resources/lang/en/label.php +++ b/resources/lang/en/label.php @@ -144,6 +144,10 @@ return [ 'size_normal' => 'Normal', 'size_compact' => 'Compact', ], + 'attendance' => [ + 'enabled' => 'Require captcha for attendance check-in', + 'enabled_help' => 'When enabled, members must solve the captcha before signing in.', + ], ], 'meilisearch' => [ 'tab_header' => 'Meilisearch', diff --git a/resources/lang/zh_CN/label.php b/resources/lang/zh_CN/label.php index c2843fba..c3632bc7 100644 --- a/resources/lang/zh_CN/label.php +++ b/resources/lang/zh_CN/label.php @@ -185,6 +185,10 @@ return [ 'size_normal' => '普通', 'size_compact' => '紧凑', ], + 'attendance' => [ + 'enabled' => '启用签到验证码', + 'enabled_help' => '开启后,用户每天签到前必须完成验证码验证。', + ], ], 'meilisearch' => [ 'tab_header' => 'Meilisearch', diff --git a/resources/lang/zh_TW/label.php b/resources/lang/zh_TW/label.php index 210e23dc..77521adc 100644 --- a/resources/lang/zh_TW/label.php +++ b/resources/lang/zh_TW/label.php @@ -144,6 +144,10 @@ return [ 'size_normal' => '一般', 'size_compact' => '緊湊', ], + 'attendance' => [ + 'enabled' => '啟用簽到驗證碼', + 'enabled_help' => '啟用後,使用者簽到前必須完成驗證碼驗證。', + ], ], 'meilisearch' => [ 'tab_header' => 'Meilisearch',