From 548d3e5c94c335a64d12a37f3c3485503ef04c4e Mon Sep 17 00:00:00 2001 From: masaton0216 Date: Thu, 22 Jan 2026 10:21:29 +0900 Subject: [PATCH 1/9] =?UTF-8?q?add:=20=E3=82=B9=E3=83=91=E3=83=A0=E3=83=95?= =?UTF-8?q?=E3=82=A3=E3=83=AB=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0=EF=BC=88=E3=82=B9=E3=83=91?= =?UTF-8?q?=E3=83=A0=E7=AE=A1=E7=90=86=EF=BC=8B=E3=83=95=E3=82=A9=E3=83=BC?= =?UTF-8?q?=E3=83=A0=E3=83=97=E3=83=A9=E3=82=B0=E3=82=A4=E3=83=B3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Enums/SpamBlockType.php | 26 ++ app/Models/Common/SpamList.php | 80 ++++ app/Models/User/Forms/Forms.php | 4 +- app/Plugins/Manage/SpamManage/SpamManage.php | 288 +++++++++++++ app/Plugins/User/Forms/FormsPlugin.php | 386 ++++++++++++++++++ ...6_01_18_143634_create_spam_lists_table.php | 45 ++ ..._01_18_143635_add_spam_filter_to_forms.php | 34 ++ ..._143636_add_ip_address_to_forms_inputs.php | 32 ++ .../views/plugins/manage/menus_list.blade.php | 7 + .../views/plugins/manage/spam/edit.blade.php | 100 +++++ .../views/plugins/manage/spam/index.blade.php | 217 ++++++++++ .../plugins/manage/spam/spam_tab.blade.php | 31 ++ .../default/forms_edit_spam_filter.blade.php | 191 +++++++++ .../forms/default/forms_list_inputs.blade.php | 68 +++ .../user/forms/forms_frame_edit_tab.blade.php | 9 + 15 files changed, 1517 insertions(+), 1 deletion(-) create mode 100644 app/Enums/SpamBlockType.php create mode 100644 app/Models/Common/SpamList.php create mode 100644 app/Plugins/Manage/SpamManage/SpamManage.php create mode 100644 database/migrations/2026_01_18_143634_create_spam_lists_table.php create mode 100644 database/migrations/2026_01_18_143635_add_spam_filter_to_forms.php create mode 100644 database/migrations/2026_01_18_143636_add_ip_address_to_forms_inputs.php create mode 100644 resources/views/plugins/manage/spam/edit.blade.php create mode 100644 resources/views/plugins/manage/spam/index.blade.php create mode 100644 resources/views/plugins/manage/spam/spam_tab.blade.php create mode 100644 resources/views/plugins/user/forms/default/forms_edit_spam_filter.blade.php diff --git a/app/Enums/SpamBlockType.php b/app/Enums/SpamBlockType.php new file mode 100644 index 000000000..f7946ce8e --- /dev/null +++ b/app/Enums/SpamBlockType.php @@ -0,0 +1,26 @@ + + * @copyright OpenSource-WorkShop Co.,Ltd. All Rights Reserved + * @category スパム管理 + * @package Enum + */ +class SpamBlockType extends EnumsBase +{ + // 定数メンバ + const email = 'email'; + const domain = 'domain'; + const ip_address = 'ip_address'; + + // key/valueの連想配列 + const enum = [ + self::email => 'メールアドレス', + self::domain => 'ドメイン', + self::ip_address => 'IPアドレス', + ]; +} diff --git a/app/Models/Common/SpamList.php b/app/Models/Common/SpamList.php new file mode 100644 index 000000000..3d2491530 --- /dev/null +++ b/app/Models/Common/SpamList.php @@ -0,0 +1,80 @@ + + * @copyright OpenSource-WorkShop Co.,Ltd. All Rights Reserved + * @category スパム管理 + * @package Model + */ +class SpamList extends Model +{ + // 保存時のユーザー関連データの保持(履歴なしUserable) + use UserableNohistory; + use SoftDeletes; + + /** + * create()やupdate()で入力を受け付ける ホワイトリスト + */ + protected $fillable = [ + 'target_plugin_name', + 'target_id', + 'block_type', + 'block_value', + 'memo', + ]; + + /** + * フォームプラグイン用のスパムリストを取得 + * + * @param int|null $forms_id フォームID(nullの場合は全体のみ) + * @return \Illuminate\Database\Eloquent\Collection + */ + public static function getFormsSpamLists($forms_id = null) + { + $query = self::where('target_plugin_name', 'forms'); + + if ($forms_id) { + $query->where(function ($q) use ($forms_id) { + $q->where('target_id', $forms_id) + ->orWhereNull('target_id'); + }); + } else { + $query->whereNull('target_id'); + } + + return $query->orderBy('block_type') + ->orderBy('created_at', 'desc') + ->get(); + } + + /** + * 適用範囲の表示名を取得 + * + * @return string + */ + public function getScopeDisplayName() + { + if (is_null($this->target_id)) { + return '全体'; + } + return 'このフォーム'; + } + + /** + * 全体適用かどうか + * + * @return bool + */ + public function isGlobalScope() + { + return is_null($this->target_id); + } +} diff --git a/app/Models/User/Forms/Forms.php b/app/Models/User/Forms/Forms.php index 6735fa456..91bbeac53 100644 --- a/app/Models/User/Forms/Forms.php +++ b/app/Models/User/Forms/Forms.php @@ -45,7 +45,9 @@ class Forms extends Model 'data_save_flag', 'after_message', 'numbering_use_flag', - 'numbering_prefix' + 'numbering_prefix', + 'use_spam_filter_flag', + 'spam_filter_message', ]; /** diff --git a/app/Plugins/Manage/SpamManage/SpamManage.php b/app/Plugins/Manage/SpamManage/SpamManage.php new file mode 100644 index 000000000..ec7c6b1aa --- /dev/null +++ b/app/Plugins/Manage/SpamManage/SpamManage.php @@ -0,0 +1,288 @@ + + * @copyright OpenSource-WorkShop Co.,Ltd. All Rights Reserved + * @category スパム管理 + * @package Controller + * @plugin_title スパム管理 + * @plugin_desc スパムリストに関する機能が集まった管理機能です。 + */ +class SpamManage extends ManagePluginBase +{ + /** + * 権限定義 + */ + public function declareRole() + { + // 権限チェックテーブル + $role_check_table = array(); + $role_check_table["index"] = array('admin_site'); + $role_check_table["store"] = array('admin_site'); + $role_check_table["edit"] = array('admin_site'); + $role_check_table["update"] = array('admin_site'); + $role_check_table["destroy"] = array('admin_site'); + $role_check_table["downloadCsv"] = array('admin_site'); + return $role_check_table; + } + + /** + * スパムリスト一覧表示 + * + * @return view + * @method_title スパムリスト一覧 + * @method_desc スパムリストを一覧で確認できます。 + * @method_detail メールアドレス、ドメイン、IPアドレスを登録してスパムをブロックできます。 + */ + public function index($request) + { + // ページネートの表示ページを取得 + $page = $this->getPaginatePageFromRequestOrSession($request, 'spam_list_page', 'page'); + + // 検索条件を取得 + $search_block_type = $request->input('search_block_type', ''); + $search_block_value = $request->input('search_block_value', ''); + $search_scope_type = $request->input('search_scope_type', ''); + $search_memo = $request->input('search_memo', ''); + + // スパムリストを取得(検索条件適用) + $query = SpamList::query(); + + // 種別 + if (!empty($search_block_type)) { + $query->where('block_type', $search_block_type); + } + + // 値(部分一致) + if (!empty($search_block_value)) { + $query->where('block_value', 'like', '%' . $search_block_value . '%'); + } + + // 適用範囲 + if ($search_scope_type === 'global') { + $query->whereNull('target_id'); + } elseif ($search_scope_type === 'form') { + $query->whereNotNull('target_id'); + } + + // メモ(部分一致) + if (!empty($search_memo)) { + $query->where('memo', 'like', '%' . $search_memo . '%'); + } + + $spam_lists = $query->orderBy('block_type') + ->orderBy('created_at', 'desc') + ->paginate(20, ['*'], 'page', $page) + ->appends($request->except('page')); + + // フォーム一覧を取得(適用範囲選択用) + $forms = Forms::orderBy('forms_name')->get(); + + // 画面の呼び出し + return view('plugins.manage.spam.index', [ + "function" => __FUNCTION__, + "plugin_name" => "spam", + "spam_lists" => $spam_lists, + "forms" => $forms, + "search_block_type" => $search_block_type, + "search_block_value" => $search_block_value, + "search_scope_type" => $search_scope_type, + "search_memo" => $search_memo, + ]); + } + + /** + * スパムリスト追加処理 + */ + public function store($request) + { + // httpメソッド確認 + if (!$request->isMethod('post')) { + abort(403, '権限がありません。'); + } + + // 項目のエラーチェック + $validator = Validator::make($request->all(), [ + 'block_type' => ['required', 'in:' . implode(',', SpamBlockType::getMemberKeys())], + 'block_value' => ['required', 'max:255'], + 'target_forms_id' => ['required_if:scope_type,form'], + ], [ + 'target_forms_id.required_if' => '適用範囲で特定フォームを選択した場合、フォームを選択してください。', + ]); + $validator->setAttributeNames([ + 'block_type' => '種別', + 'block_value' => '値', + 'target_forms_id' => 'フォーム', + ]); + + // エラーがあった場合は入力画面に戻る。 + if ($validator->fails()) { + return redirect('manage/spam') + ->withErrors($validator) + ->withInput(); + } + + // 適用範囲の処理 + $target_id = null; + if ($request->scope_type === 'form' && $request->filled('target_forms_id')) { + $target_id = $request->target_forms_id; + } + + // スパムリストの追加 + SpamList::create([ + 'target_plugin_name' => 'forms', + 'target_id' => $target_id, + 'block_type' => $request->block_type, + 'block_value' => $request->block_value, + 'memo' => $request->memo, + ]); + + // スパムリスト一覧画面に戻る + return redirect("/manage/spam")->with('flash_message', 'スパムリストに追加しました。'); + } + + /** + * スパムリスト編集画面 + * + * @return view + * @method_title スパムリスト編集 + * @method_desc スパムリストを編集できます。 + * @method_detail + */ + public function edit($request, $id) + { + // スパムリストデータの呼び出し + $spam = SpamList::findOrFail($id); + + // フォーム一覧を取得(適用範囲選択用) + $forms = Forms::orderBy('forms_name')->get(); + + // 画面の呼び出し + return view('plugins.manage.spam.edit', [ + "function" => __FUNCTION__, + "plugin_name" => "spam", + "spam" => $spam, + "forms" => $forms, + ]); + } + + /** + * スパムリスト更新処理 + */ + public function update($request, $id) + { + // httpメソッド確認 + if (!$request->isMethod('post')) { + abort(403, '権限がありません。'); + } + + // 項目のエラーチェック + $validator = Validator::make($request->all(), [ + 'block_value' => ['required', 'max:255'], + 'target_forms_id' => ['required_if:scope_type,form'], + ], [ + 'target_forms_id.required_if' => '適用範囲で特定フォームを選択した場合、フォームを選択してください。', + ]); + $validator->setAttributeNames([ + 'block_value' => '値', + 'target_forms_id' => 'フォーム', + ]); + + // エラーがあった場合は入力画面に戻る。 + if ($validator->fails()) { + return redirect('manage/spam/edit/' . $id) + ->withErrors($validator) + ->withInput(); + } + + // スパムリストデータの呼び出し + $spam = SpamList::findOrFail($id); + + // 適用範囲の処理 + $target_id = null; + if ($request->scope_type === 'form' && $request->filled('target_forms_id')) { + $target_id = $request->target_forms_id; + } + + // 更新 + $spam->target_id = $target_id; + $spam->block_value = $request->block_value; + $spam->memo = $request->memo; + $spam->save(); + + // スパムリスト一覧画面に戻る + return redirect("/manage/spam")->with('flash_message', 'スパムリストを更新しました。'); + } + + /** + * スパムリスト削除処理 + */ + public function destroy($request, $id) + { + // httpメソッド確認 + if (!$request->isMethod('post')) { + abort(403, '権限がありません。'); + } + + // 削除 + SpamList::where('id', $id)->delete(); + + // スパムリスト一覧画面に戻る + return redirect("/manage/spam")->with('flash_message', 'スパムリストから削除しました。'); + } + + /** + * CSVダウンロード + */ + public function downloadCsv($request) + { + // スパムリストを取得 + $spam_lists = SpamList::orderBy('block_type') + ->orderBy('created_at', 'desc') + ->get(); + + // フォーム一覧を取得 + $forms = Forms::pluck('forms_name', 'id'); + + // CSVデータの作成 + $csv_data = ''; + + // ヘッダー行 + $csv_data .= '"種別","値","適用範囲","メモ","登録日時"' . "\n"; + + // データ行 + foreach ($spam_lists as $spam) { + $scope_name = is_null($spam->target_id) ? '全体' : ($forms[$spam->target_id] ?? '不明'); + $csv_data .= '"' . SpamBlockType::getDescription($spam->block_type) . '",'; + $csv_data .= '"' . str_replace('"', '""', $spam->block_value) . '",'; + $csv_data .= '"' . $scope_name . '",'; + $csv_data .= '"' . str_replace('"', '""', $spam->memo ?? '') . '",'; + $csv_data .= '"' . $spam->created_at . '"' . "\n"; + } + + // 文字コード変換(UTF-8 BOM付き) + $csv_data = "\xEF\xBB\xBF" . $csv_data; + + // ファイル名 + $filename = 'spam_list_' . date('Ymd_His') . '.csv'; + + // レスポンス + return response($csv_data) + ->header('Content-Type', 'text/csv') + ->header('Content-Disposition', 'attachment; filename="' . $filename . '"'); + } +} diff --git a/app/Plugins/User/Forms/FormsPlugin.php b/app/Plugins/User/Forms/FormsPlugin.php index a007aa559..437d0cde6 100644 --- a/app/Plugins/User/Forms/FormsPlugin.php +++ b/app/Plugins/User/Forms/FormsPlugin.php @@ -20,6 +20,7 @@ use App\Models\User\Forms\FormsColumnsSelects; use App\Models\User\Forms\FormsInputs; use App\Models\User\Forms\FormsInputCols; +use App\Models\Common\SpamList; use App\Rules\CustomValiAlphaNumForMultiByte; use App\Rules\CustomValiCheckWidthForString; @@ -42,6 +43,7 @@ use App\Enums\FormsRegisterTargetPlugin; use App\Enums\FormStatusType; use App\Enums\PluginName; +use App\Enums\SpamBlockType; use App\Enums\Required; use App\Enums\StatusType; use App\Models\User\Bbses\Bbs; @@ -99,6 +101,7 @@ public function getPublicFunctions() 'listInputs', 'editInput', 'thanks', + 'editSpamFilter', ]; $functions['post'] = [ 'index', @@ -115,6 +118,10 @@ public function getPublicFunctions() 'registerOtherPlugins', 'updateSelectSequenceAll', 'updateColumnSequenceAll', + 'saveSpamFilter', + 'addSpamList', + 'deleteSpamList', + 'addToSpamListFromInput', ]; return $functions; } @@ -140,6 +147,11 @@ public function declareRole() $role_check_table["registerOtherPlugins"] = ['role_article']; $role_check_table['updateSelectSequenceAll'] = ['buckets.upColumnSequence', 'buckets.downColumnSequence']; $role_check_table['updateColumnSequenceAll'] = ['buckets.upColumnSequence', 'buckets.downColumnSequence']; + $role_check_table['editSpamFilter'] = ['frames.edit']; + $role_check_table['saveSpamFilter'] = ['frames.create']; + $role_check_table['addSpamList'] = ['frames.create']; + $role_check_table['deleteSpamList'] = ['frames.delete']; + $role_check_table['addToSpamListFromInput'] = ['frames.create']; return $role_check_table; } @@ -800,6 +812,14 @@ public function publicConfirm($request, $page_id, $frame_id, $id = null) ]); } + // スパムフィルタリングチェック + if ($this->isBlockedBySpamFilter($request, $form)) { + $spam_message = $form->spam_filter_message ?: '入力されたメールアドレス、または、IPアドレスからの送信は現在制限されています。'; + return $this->commonView('error_messages', [ + 'error_messages' => [$spam_message], + ]); + } + // フォームのカラムデータ $forms_columns = $this->getFormsColumns($form); @@ -1015,6 +1035,11 @@ public function publicStore($request, $page_id, $frame_id, $id = null) } } + // IPアドレスを記録(スパムフィルタリングが有効な場合のみ) + if ($form->use_spam_filter_flag) { + $forms_inputs->ip_address = $request->ip(); + } + $forms_inputs->save(); // フォームのカラムデータ @@ -2741,6 +2766,21 @@ public function listInputs($request, $page_id, $frame_id, $forms_id = null) ->orderBy('forms_inputs_id', 'asc')->orderBy('forms_columns_id', 'asc') ->get(); + // メールアドレス型カラムのID取得 + $email_column_ids = $columns->where('column_type', FormColumnType::mail)->pluck('id')->toArray(); + + // 各投稿のメールアドレス有無マップを作成 + $has_email_map = []; + foreach ($inputs as $input) { + $has_email = $input_cols->where('forms_inputs_id', $input->id) + ->whereIn('forms_columns_id', $email_column_ids) + ->filter(function ($col) { + return !empty($col->value); + }) + ->isNotEmpty(); + $has_email_map[$input->id] = $has_email; + } + // bucktsで開いていたページの保持 // $frame_page = "frame_{$frame_id}_buckets_page"; @@ -2750,6 +2790,7 @@ public function listInputs($request, $page_id, $frame_id, $forms_id = null) 'columns' => $columns, 'inputs' => $inputs, 'input_cols' => $input_cols, + 'has_email_map' => $has_email_map, ]); } @@ -3111,4 +3152,349 @@ public static function canDownload($request, Uploads $upload): array return [false, '対象ファイルに対する権限なし']; } } + + /** + * スパムフィルタリングによるブロック判定 + * + * @param \Illuminate\Http\Request $request リクエスト + * @param Forms $form フォームデータ + * @return bool ブロックする場合はtrue + */ + private function isBlockedBySpamFilter($request, $form) + { + // スパムフィルタリングが無効なら早期リターン + if (!$form->use_spam_filter_flag) { + return false; + } + + // 取得対象のスパムリスト(このフォーム用 + サイト全体用) + $spam_lists = SpamList::getFormsSpamLists($form->id); + + if ($spam_lists->isEmpty()) { + return false; + } + + // IPアドレスチェック + $client_ip = $request->ip(); + $ip_blocked = $spam_lists->where('block_type', SpamBlockType::ip_address) + ->where('block_value', $client_ip) + ->isNotEmpty(); + if ($ip_blocked) { + return true; + } + + // メールアドレス・ドメインチェック(メール型カラムの値を取得) + $email_columns = FormsColumns::where('forms_id', $form->id) + ->where('column_type', FormColumnType::mail) + ->pluck('id'); + + foreach ($email_columns as $column_id) { + $email = $request->forms_columns_value[$column_id] ?? null; + if (empty($email)) { + continue; + } + + // メールアドレス完全一致チェック + $email_blocked = $spam_lists->where('block_type', SpamBlockType::email) + ->where('block_value', $email) + ->isNotEmpty(); + if ($email_blocked) { + return true; + } + + // ドメインチェック + $domain = substr(strrchr($email, "@"), 1); + if ($domain) { + $domain_blocked = $spam_lists->where('block_type', SpamBlockType::domain) + ->where('block_value', $domain) + ->isNotEmpty(); + if ($domain_blocked) { + return true; + } + } + } + + return false; + } + + /** + * スパムフィルタリング設定画面 + * + * @method_title スパムフィルタリング + * @method_desc スパムフィルタリングの設定ができます。 + * @method_detail スパムリストの管理や、ブロック時のメッセージを設定できます。 + */ + public function editSpamFilter($request, $page_id, $frame_id) + { + // フォーム&フレームデータ + $form_frame = $this->getFormFrame($frame_id); + + // フォームデータ + $form = null; + if (!empty($form_frame->bucket_id)) { + $form = Forms::where('bucket_id', $form_frame->bucket_id)->first(); + } + + if (empty($form)) { + // ワーニング画面へ + return $this->view('forms_edit_warning_messages', [ + 'warning_messages' => ["フォーム選択から選択するか、フォーム作成で作成してください。"], + ]); + } + + // このフォームに適用されるスパムリスト(フォーム固有 + サイト全体) + $spam_lists = SpamList::getFormsSpamLists($form->id); + + // 表示テンプレートを呼び出す + return $this->view('forms_edit_spam_filter', [ + 'form' => $form, + 'spam_lists' => $spam_lists, + ]); + } + + /** + * スパムフィルタリング設定の保存 + */ + public function saveSpamFilter($request, $page_id, $frame_id, $forms_id) + { + // フォームデータ取得 + $form = Forms::find($forms_id); + if (empty($form)) { + abort(404, 'フォームが見つかりません。'); + } + + // 更新 + $form->use_spam_filter_flag = $request->input('use_spam_filter_flag', 0); + $form->spam_filter_message = $request->spam_filter_message; + $form->save(); + + // リダイレクト + return redirect("/plugin/forms/editSpamFilter/{$page_id}/{$frame_id}#frame-{$frame_id}") + ->with('flash_message', 'スパムフィルタリング設定を保存しました。'); + } + + /** + * スパムリストへの追加 + */ + public function addSpamList($request, $page_id, $frame_id, $forms_id) + { + // 項目のエラーチェック + $validator = Validator::make($request->all(), [ + 'block_type' => ['required', 'in:' . implode(',', SpamBlockType::getMemberKeys())], + 'block_value' => ['required', 'max:255'], + ]); + $validator->setAttributeNames([ + 'block_type' => '種別', + 'block_value' => '値', + ]); + + // エラーがあった場合は入力画面に戻る + if ($validator->fails()) { + return redirect("/plugin/forms/editSpamFilter/{$page_id}/{$frame_id}#frame-{$frame_id}") + ->withErrors($validator) + ->withInput(); + } + + // スパムリストの追加 + SpamList::create([ + 'target_plugin_name' => 'forms', + 'target_id' => $forms_id, + 'block_type' => $request->block_type, + 'block_value' => $request->block_value, + 'memo' => $request->memo, + ]); + + // リダイレクト + return redirect("/plugin/forms/editSpamFilter/{$page_id}/{$frame_id}#frame-{$frame_id}") + ->with('flash_message', 'スパムリストに追加しました。'); + } + + /** + * スパムリストからの削除 + */ + public function deleteSpamList($request, $page_id, $frame_id, $spam_id) + { + // スパムリストデータの取得 + $spam = SpamList::find($spam_id); + + // このフォーム専用のスパムリストのみ削除可能 + if ($spam && !$spam->isGlobalScope()) { + $spam->delete(); + } + + // リダイレクト + return redirect("/plugin/forms/editSpamFilter/{$page_id}/{$frame_id}#frame-{$frame_id}") + ->with('flash_message', 'スパムリストから削除しました。'); + } + + /** + * 投稿一覧からスパムリストへ追加 + */ + public function addToSpamListFromInput($request, $page_id, $frame_id, $inputs_id) + { + // 投稿データの取得 + $input = FormsInputs::find($inputs_id); + if (empty($input)) { + abort(404, '投稿データが見つかりません。'); + } + + $forms_id = $input->forms_id; + $added_count = 0; + $skipped_duplicate_count = 0; + $skipped_no_data_count = 0; + + // メモ + $memo = $request->input('memo'); + + // チェックボックスが1つも選択されていない場合 + $has_selection = $request->filled('add_ip_address') || $request->filled('add_email') || $request->filled('add_domain'); + if (!$has_selection) { + return redirect("/plugin/forms/listInputs/{$page_id}/{$frame_id}/{$forms_id}#frame-{$frame_id}") + ->with('flash_message', '追加する項目(IPアドレス、メールアドレス、ドメイン)を1つ以上選択してください。'); + } + + // IPアドレスをスパムリストに追加 + if ($request->filled('add_ip_address')) { + if (empty($input->ip_address)) { + $skipped_no_data_count++; + } else { + $scope_type = $request->input('scope_type', 'form'); + $target_id = ($scope_type === 'global') ? null : $forms_id; + + // 重複チェック + $exists = SpamList::where('target_plugin_name', 'forms') + ->where('target_id', $target_id) + ->where('block_type', SpamBlockType::ip_address) + ->where('block_value', $input->ip_address) + ->exists(); + + if ($exists) { + $skipped_duplicate_count++; + } else { + SpamList::create([ + 'target_plugin_name' => 'forms', + 'target_id' => $target_id, + 'block_type' => SpamBlockType::ip_address, + 'block_value' => $input->ip_address, + 'memo' => $memo, + ]); + $added_count++; + } + } + } + + // メールアドレスをスパムリストに追加 + if ($request->filled('add_email')) { + $email_columns = FormsColumns::where('forms_id', $forms_id) + ->where('column_type', FormColumnType::mail) + ->pluck('id'); + + $input_cols = FormsInputCols::where('forms_inputs_id', $inputs_id) + ->whereIn('forms_columns_id', $email_columns) + ->get(); + + $has_email_value = false; + foreach ($input_cols as $col) { + if (empty($col->value)) { + continue; + } + $has_email_value = true; + + $scope_type = $request->input('scope_type', 'form'); + $target_id = ($scope_type === 'global') ? null : $forms_id; + + // 重複チェック + $exists = SpamList::where('target_plugin_name', 'forms') + ->where('target_id', $target_id) + ->where('block_type', SpamBlockType::email) + ->where('block_value', $col->value) + ->exists(); + + if ($exists) { + $skipped_duplicate_count++; + } else { + SpamList::create([ + 'target_plugin_name' => 'forms', + 'target_id' => $target_id, + 'block_type' => SpamBlockType::email, + 'block_value' => $col->value, + 'memo' => $memo, + ]); + $added_count++; + } + } + if (!$has_email_value) { + $skipped_no_data_count++; + } + } + + // ドメインをスパムリストに追加 + if ($request->filled('add_domain')) { + $email_columns = FormsColumns::where('forms_id', $forms_id) + ->where('column_type', FormColumnType::mail) + ->pluck('id'); + + $input_cols = FormsInputCols::where('forms_inputs_id', $inputs_id) + ->whereIn('forms_columns_id', $email_columns) + ->get(); + + $has_domain_value = false; + foreach ($input_cols as $col) { + if (empty($col->value)) { + continue; + } + + $domain = substr(strrchr($col->value, "@"), 1); + if (empty($domain)) { + continue; + } + $has_domain_value = true; + + $scope_type = $request->input('scope_type', 'form'); + $target_id = ($scope_type === 'global') ? null : $forms_id; + + // 重複チェック + $exists = SpamList::where('target_plugin_name', 'forms') + ->where('target_id', $target_id) + ->where('block_type', SpamBlockType::domain) + ->where('block_value', $domain) + ->exists(); + + if ($exists) { + $skipped_duplicate_count++; + } else { + SpamList::create([ + 'target_plugin_name' => 'forms', + 'target_id' => $target_id, + 'block_type' => SpamBlockType::domain, + 'block_value' => $domain, + 'memo' => $memo, + ]); + $added_count++; + } + } + if (!$has_domain_value) { + $skipped_no_data_count++; + } + } + + // メッセージの組み立て + $messages = []; + if ($added_count > 0) { + $messages[] = "{$added_count}件をスパムリストに追加しました。"; + } + if ($skipped_duplicate_count > 0) { + $messages[] = "{$skipped_duplicate_count}件は既に登録済みのためスキップしました。"; + } + if ($skipped_no_data_count > 0) { + $messages[] = "{$skipped_no_data_count}件は該当データがないためスキップしました。"; + } + if (empty($messages)) { + $messages[] = 'スパムリストへの追加はありませんでした。'; + } + + // リダイレクト + return redirect("/plugin/forms/listInputs/{$page_id}/{$frame_id}/{$forms_id}#frame-{$frame_id}") + ->with('flash_message', implode(' ', $messages)); + } } diff --git a/database/migrations/2026_01_18_143634_create_spam_lists_table.php b/database/migrations/2026_01_18_143634_create_spam_lists_table.php new file mode 100644 index 000000000..cd3929939 --- /dev/null +++ b/database/migrations/2026_01_18_143634_create_spam_lists_table.php @@ -0,0 +1,45 @@ +increments('id'); + $table->string('target_plugin_name', 255)->comment('対象プラグイン名(forms等)'); + $table->integer('target_id')->nullable()->comment('対象ID(フォーム毎の場合はforms_id、全体の場合はnull)'); + $table->string('block_type', 50)->comment('ブロック種別: email, domain, ip_address'); + $table->string('block_value', 255)->comment('ブロック対象の値'); + $table->text('memo')->nullable()->comment('メモ'); + $table->integer('created_id')->nullable(); + $table->string('created_name', 255)->nullable(); + $table->timestamp('created_at')->nullable(); + $table->integer('updated_id')->nullable(); + $table->string('updated_name', 255)->nullable(); + $table->timestamp('updated_at')->nullable(); + $table->softDeletes(); + + $table->index(['target_plugin_name', 'target_id', 'block_type'], 'spam_lists_target_index'); + $table->index(['block_type', 'block_value'], 'spam_lists_block_index'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('spam_lists'); + } +} diff --git a/database/migrations/2026_01_18_143635_add_spam_filter_to_forms.php b/database/migrations/2026_01_18_143635_add_spam_filter_to_forms.php new file mode 100644 index 000000000..ed9af4903 --- /dev/null +++ b/database/migrations/2026_01_18_143635_add_spam_filter_to_forms.php @@ -0,0 +1,34 @@ +integer('use_spam_filter_flag')->default(0)->comment('スパムフィルタリング使用フラグ'); + $table->text('spam_filter_message')->nullable()->comment('スパムブロック時のメッセージ'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('forms', function (Blueprint $table) { + $table->dropColumn('use_spam_filter_flag'); + $table->dropColumn('spam_filter_message'); + }); + } +} diff --git a/database/migrations/2026_01_18_143636_add_ip_address_to_forms_inputs.php b/database/migrations/2026_01_18_143636_add_ip_address_to_forms_inputs.php new file mode 100644 index 000000000..acb787e2b --- /dev/null +++ b/database/migrations/2026_01_18_143636_add_ip_address_to_forms_inputs.php @@ -0,0 +1,32 @@ +string('ip_address', 255)->nullable()->comment('投稿者のIPアドレス'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('forms_inputs', function (Blueprint $table) { + $table->dropColumn('ip_address'); + }); + } +} diff --git a/resources/views/plugins/manage/menus_list.blade.php b/resources/views/plugins/manage/menus_list.blade.php index c2dbe5697..0a2b90eee 100644 --- a/resources/views/plugins/manage/menus_list.blade.php +++ b/resources/views/plugins/manage/menus_list.blade.php @@ -45,6 +45,13 @@ セキュリティ管理 @endif @endif + @if (Auth::user()->can('admin_site')) + @if (isset($plugin_name) && $plugin_name == 'spam') + スパム管理 + @else + スパム管理 + @endif + @endif @if (Auth::user()->can('admin_system')) @if (isset($plugin_name) && $plugin_name == 'plugin') プラグイン管理 diff --git a/resources/views/plugins/manage/spam/edit.blade.php b/resources/views/plugins/manage/spam/edit.blade.php new file mode 100644 index 000000000..a2d72cbb9 --- /dev/null +++ b/resources/views/plugins/manage/spam/edit.blade.php @@ -0,0 +1,100 @@ +{{-- + * スパム管理の編集テンプレート + * + * @author 井上 雅人 + * @copyright OpenSource-WorkShop Co.,Ltd. All Rights Reserved + * @category スパム管理 +--}} +@php +use App\Enums\SpamBlockType; +@endphp +{{-- 管理画面ベース画面 --}} +@extends('plugins.manage.manage') + +{{-- 管理画面メイン部分のコンテンツ section:manage_content で作ること --}} +@section('manage_content') + +
+
+ {{-- 機能選択タブ --}} + @include('plugins.manage.spam.spam_tab') +
+ +
+ + @include('plugins.common.errors_form_line') + +
+ {{ csrf_field() }} + +
+ +
+
+ {{ SpamBlockType::getDescription($spam->block_type) }} +
+ 種別は変更できません。 +
+
+ +
+ +
+ + @include('plugins.common.errors_inline', ['name' => 'block_value']) +
+
+ +
+ +
+
+ target_id) ? 'global' : 'form') == 'global') checked @endif> + +
+
+ target_id) ? 'global' : 'form') == 'form') checked @endif> + +
+ + @include('plugins.common.errors_inline', ['name' => 'target_forms_id']) +
+
+ +
+ +
+ +
+
+ +
+ +
+
+ {{ $spam->created_at->format('Y/m/d H:i:s') }} +
+
+
+ +
+
+ + キャンセル + + +
+
+
+ +
+
+ +@endsection diff --git a/resources/views/plugins/manage/spam/index.blade.php b/resources/views/plugins/manage/spam/index.blade.php new file mode 100644 index 000000000..d19762724 --- /dev/null +++ b/resources/views/plugins/manage/spam/index.blade.php @@ -0,0 +1,217 @@ +{{-- + * スパム管理のメインテンプレート + * + * @author 井上 雅人 + * @copyright OpenSource-WorkShop Co.,Ltd. All Rights Reserved + * @category スパム管理 +--}} +@php +use App\Enums\SpamBlockType; +@endphp +{{-- 管理画面ベース画面 --}} +@extends('plugins.manage.manage') + +{{-- 管理画面メイン部分のコンテンツ section:manage_content で作ること --}} +@section('manage_content') + +
+
+ {{-- 機能選択タブ --}} + @include('plugins.manage.spam.spam_tab') +
+ +
+ + {{-- 登録後メッセージ表示 --}} + @include('plugins.common.flash_message') + + @include('plugins.common.errors_form_line') + +
+ サイト全体で適用されるスパムリストを管理します。 +
+ + {{-- 検索フォーム --}} +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + クリア +
+
+
+ + {{-- スパムリスト一覧 --}} +
+ スパムリスト一覧 +
+ {{ csrf_field() }} + +
+
+ +
+ + + + + + + + + + + + + @forelse($spam_lists as $spam) + + + + + + + + + @empty + + + + @endforelse + +
種別適用範囲メモ登録日時操作
+ @if ($spam->block_type == SpamBlockType::email) + メールアドレス + @elseif ($spam->block_type == SpamBlockType::domain) + ドメイン + @else + IPアドレス + @endif + {{ $spam->block_value }} + @if (is_null($spam->target_id)) + 全体 + @else + @php + $form_name = $forms->where('id', $spam->target_id)->first()->forms_name ?? '不明'; + @endphp + {{ $form_name }} + @endif + {{ Str::limit($spam->memo, 30) }}{{ $spam->created_at->format('Y/m/d H:i') }} + + 編集 + +
+ {{ csrf_field() }} + +
+
スパムリストは登録されていません。
+
+ + {{-- ページング処理 --}} + {{ $spam_lists->links() }} + +
+ + {{-- スパムリスト追加フォーム --}} +
スパムリストへ追加
+ +
+ {{ csrf_field() }} + +
+ +
+ @foreach (SpamBlockType::getMembers() as $key => $value) +
+ + +
+ @endforeach + @include('plugins.common.errors_inline', ['name' => 'block_type']) + + ※ メールアドレス:完全一致でブロックします。
+ ※ ドメイン:メールアドレスの@以降と一致する場合にブロックします。
+ ※ メールアドレス・ドメインはフォームに「メールアドレス」型項目がある場合に有効です。
+ ※ IPアドレス:送信元IPアドレスと一致する場合にブロックします。 +
+
+
+ +
+ +
+ + @include('plugins.common.errors_inline', ['name' => 'block_value']) +
+
+ +
+ +
+
+ + +
+
+ + +
+ + @include('plugins.common.errors_inline', ['name' => 'target_forms_id']) + ※「全体」を選択すると、スパムフィルタリングを有効にしているすべてのフォームに適用されます。 +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+ +
+
+ +@endsection diff --git a/resources/views/plugins/manage/spam/spam_tab.blade.php b/resources/views/plugins/manage/spam/spam_tab.blade.php new file mode 100644 index 000000000..3c2e6ae32 --- /dev/null +++ b/resources/views/plugins/manage/spam/spam_tab.blade.php @@ -0,0 +1,31 @@ +{{-- + * スパム管理のタブ + * + * @author 井上 雅人 + * @copyright OpenSource-WorkShop Co.,Ltd. All Rights Reserved + * @category スパム管理 +--}} +
+ +
diff --git a/resources/views/plugins/user/forms/default/forms_edit_spam_filter.blade.php b/resources/views/plugins/user/forms/default/forms_edit_spam_filter.blade.php new file mode 100644 index 000000000..322d56392 --- /dev/null +++ b/resources/views/plugins/user/forms/default/forms_edit_spam_filter.blade.php @@ -0,0 +1,191 @@ +{{-- + * スパムフィルタリング設定画面テンプレート + * + * @author 井上 雅人 + * @copyright OpenSource-WorkShop Co.,Ltd. All Rights Reserved + * @category フォームプラグイン +--}} +@php +use App\Enums\SpamBlockType; +@endphp +@extends('core.cms_frame_base_setting') + +@section("core.cms_frame_edit_tab_$frame->id") + {{-- プラグイン側のフレームメニュー --}} + @include('plugins.user.forms.forms_frame_edit_tab') +@endsection + +@section("plugin_setting_$frame->id") + +{{-- 登録後メッセージ表示 --}} +@include('plugins.common.flash_message') + +@include('plugins.common.errors_form_line') + +
+ スパムフィルタリングの設定を行います。 +
+ +{{-- スパムフィルタリング設定フォーム --}} +
+ {{ csrf_field() }} + + +
+ +
+
+ + use_spam_filter_flag)) checked @endif> + +
+ + 本機能を有効にすると、スパムフィルタリングのために送信元のIPアドレスをフォーム投稿時に収集します。サイトのプライバシーポリシーにその旨を記載されることを推奨いたします。 + +
+
+ +
use_spam_filter_flag)) style="display: none;" @endif> +
+ +
+ + ※ 未入力の場合、デフォルトメッセージ「入力されたメールアドレス、または、IPアドレスからの送信は現在制限されています。」が表示されます。 +
+
+
+ + {{-- Submitボタン --}} +
+ +
+
+ +
use_spam_filter_flag)) style="display: none;" @endif> +
+ +{{-- 適用されるスパムリスト --}} +
適用されるスパムリスト
+ +
+ + + + + + + + + + + + @forelse($spam_lists as $spam) + + + + + + + + @empty + + + + @endforelse + +
種別適用範囲メモ操作
+ @if ($spam->block_type == SpamBlockType::email) + メールアドレス + @elseif ($spam->block_type == SpamBlockType::domain) + ドメイン + @else + IPアドレス + @endif + {{ $spam->block_value }} + @if ($spam->isGlobalScope()) + 全体 + @else + このフォーム + @endif + {{ $spam->memo }} + @if ($spam->isGlobalScope()) + + @else +
+ {{ csrf_field() }} + + +
+ @endif +
スパムリストは登録されていません。
+
+ +※ 適用範囲が「全体」のスパムリストはスパム管理から編集できます。 + +
+ +{{-- スパムリスト追加フォーム --}} +
スパムリストへ追加(このフォーム用)
+ +
+ {{ csrf_field() }} + + +
+ +
+ @foreach (SpamBlockType::getMembers() as $key => $value) +
+ + +
+ @endforeach + @include('plugins.common.errors_inline', ['name' => 'block_type']) + + ※ メールアドレス:完全一致でブロックします。
+ ※ ドメイン:メールアドレスの@以降と一致する場合にブロックします。
+ ※ メールアドレス・ドメインはフォームに「メールアドレス」型項目がある場合に有効です。
+ ※ IPアドレス:送信元IPアドレスと一致する場合にブロックします。 +
+
+
+ +
+ +
+ + @include('plugins.common.errors_inline', ['name' => 'block_value']) +
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+ + + +@endsection diff --git a/resources/views/plugins/user/forms/default/forms_list_inputs.blade.php b/resources/views/plugins/user/forms/default/forms_list_inputs.blade.php index c9f4749f8..a6d81a8ef 100644 --- a/resources/views/plugins/user/forms/default/forms_list_inputs.blade.php +++ b/resources/views/plugins/user/forms/default/forms_list_inputs.blade.php @@ -100,6 +100,8 @@ function submit_register_other_plugins(id) { @endif 登録ユーザ 登録日時 + IPアドレス + 操作 @@ -151,6 +153,58 @@ function submit_register_other_plugins(id) { {{$input->created_at}} + + {{$input->ip_address}} + + + +
+ + +
+ + @endforeach @@ -180,4 +234,18 @@ function submit_register_other_plugins(id) { + + + @endsection diff --git a/resources/views/plugins/user/forms/forms_frame_edit_tab.blade.php b/resources/views/plugins/user/forms/forms_frame_edit_tab.blade.php index 3e7b62461..230ad4738 100644 --- a/resources/views/plugins/user/forms/forms_frame_edit_tab.blade.php +++ b/resources/views/plugins/user/forms/forms_frame_edit_tab.blade.php @@ -46,6 +46,15 @@ 登録一覧 @endif +@if ($action == 'editSpamFilter') + +@else + +@endif @if ($action == 'listBuckets')