From bae0457dc88ded66165af19f682561fab44f246a Mon Sep 17 00:00:00 2001 From: gakigaki Date: Thu, 15 Jan 2026 12:26:35 +0900 Subject: [PATCH 01/44] feat(layout): add layout inheritance flag --- app/Enums/PageCvsIndex.php | 4 +- .../Controllers/Core/DefaultController.php | 19 ++- app/Models/Common/Page.php | 1 + app/Plugins/Manage/PageManage/PageManage.php | 8 + app/Plugins/Manage/SiteManage/SiteManage.php | 7 + config/connect.php | 3 + ...1_13_170722_add_base_layout_to_configs.php | 44 ++++++ ...add_layout_inherit_flag_to_pages_table.php | 44 ++++++ .../seeders/DefaultConfigsTableSeeder.php | 1 + database/seeders/DefaultPagesTableSeeder.php | 1 + database/seeders/ElementaryschoolSeeder.php | 5 + database/seeders/PTASeeder.php | 5 + .../views/plugins/manage/page/page.blade.php | 21 ++- .../plugins/manage/page/page_form.blade.php | 48 +++++- .../plugins/manage/page/page_import.blade.php | 1 - .../manage/site/pdf/base_main.blade.php | 12 ++ .../views/plugins/manage/site/site.blade.php | 141 ++++++++++++++++++ .../Core/DefaultControllerLayoutTest.php | 112 ++++++++++++++ 18 files changed, 464 insertions(+), 13 deletions(-) create mode 100644 database/migrations/2026_01_13_170722_add_base_layout_to_configs.php create mode 100644 database/migrations/2026_01_13_173712_add_layout_inherit_flag_to_pages_table.php create mode 100644 tests/Unit/Controllers/Core/DefaultControllerLayoutTest.php diff --git a/app/Enums/PageCvsIndex.php b/app/Enums/PageCvsIndex.php index cef26b78e..1deb24656 100755 --- a/app/Enums/PageCvsIndex.php +++ b/app/Enums/PageCvsIndex.php @@ -21,7 +21,8 @@ final class PageCvsIndex extends EnumsBase const header_color = 3; const theme = 4; const layout = 5; - const base_display_flag = 6; + const layout_inherit_flag = 6; + const base_display_flag = 7; /** key/valueの連想配列 */ const enum = [ @@ -31,6 +32,7 @@ final class PageCvsIndex extends EnumsBase self::header_color => 'header_color', self::theme => 'theme', self::layout => 'layout', + self::layout_inherit_flag => 'layout_inherit_flag', self::base_display_flag => 'base_display_flag', ]; } diff --git a/app/Http/Controllers/Core/DefaultController.php b/app/Http/Controllers/Core/DefaultController.php index 9ecfbd97d..adfd903bd 100644 --- a/app/Http/Controllers/Core/DefaultController.php +++ b/app/Http/Controllers/Core/DefaultController.php @@ -335,7 +335,7 @@ private function getTopPage($page, $languages = null) private function getLayout($page, $page_tree) { // レイアウトの初期値 - $layout_default = '1|1|0|1'; + $layout_default = config('connect.BASE_LAYOUT_DEFAULT'); // if (empty($this->page)) { if (empty($page)) { @@ -344,15 +344,24 @@ private function getLayout($page, $page_tree) // レイアウト $layout = null; - foreach ($page_tree as $tmp_page) { // レイアウト - if (empty($layout) && $tmp_page->layout) { - $layout = $tmp_page->layout; + if (empty($tmp_page->layout)) { + continue; + } + if ($tmp_page->id != $page->id && !is_null($tmp_page->layout_inherit_flag) && (int)$tmp_page->layout_inherit_flag === 0) { + // 祖先のレイアウトが「このページのみ」指定なら下層への継承対象から外す。 + continue; } + $layout = $tmp_page->layout; + break; + } + // 親も含めて空の場合は、基本レイアウトを使い、未設定なら初期値を返却 + if (empty($layout)) { + $layout = Configs::getSharedConfigsValue('base_layout', $layout_default); } - // 親も含めて空の場合は、初期値を返却 if (empty($layout)) { + // DBに不正な値が入るなどして予期せず空になった場合の安全弁 $layout = $layout_default; } return $layout; diff --git a/app/Models/Common/Page.php b/app/Models/Common/Page.php index 437f93341..548f17567 100644 --- a/app/Models/Common/Page.php +++ b/app/Models/Common/Page.php @@ -34,6 +34,7 @@ class Page extends Model 'header_color', 'theme', 'layout', + 'layout_inherit_flag', 'base_display_flag', 'membership_flag', 'container_flag', diff --git a/app/Plugins/Manage/PageManage/PageManage.php b/app/Plugins/Manage/PageManage/PageManage.php index 0aba57bad..d47894660 100644 --- a/app/Plugins/Manage/PageManage/PageManage.php +++ b/app/Plugins/Manage/PageManage/PageManage.php @@ -185,6 +185,7 @@ private function pageValidator($request, $page_id = null) 'ip_address' => ['nullable', new CustomValiTextMax()], 'othersite_url' => ['nullable', new CustomValiUrlMax()], 'class' => ['nullable', 'max:255'], + 'layout_inherit_flag' => ['nullable', Rule::in(['0', '1'])], 'meta_robots' => ['nullable', 'array', new CustomValiMetaRobots()], ]); $validator->setAttributeNames([ @@ -196,6 +197,7 @@ private function pageValidator($request, $page_id = null) 'ip_address' => 'IPアドレス制限', 'othersite_url' => '外部サイトURL', 'class' => 'メニュークラス名', + 'layout_inherit_flag' => 'レイアウトの適用範囲', 'meta_robots' => '検索避け設定', ]); return $validator; @@ -248,6 +250,7 @@ private function pageUploadValidatorRules() PageCvsIndex::header_color => ['required', 'max:255'], PageCvsIndex::theme => ['required'], PageCvsIndex::layout => ['required'], + PageCvsIndex::layout_inherit_flag => ['required', Rule::in(['0', '1'])], PageCvsIndex::base_display_flag => ['required', Rule::in(['0', '1'])], ]; @@ -285,6 +288,7 @@ public function store($request) $page->header_color = $request->header_color; $page->theme = $request->theme; $page->layout = $request->layout; + $page->layout_inherit_flag = $request->layout_inherit_flag ?? 1; $page->base_display_flag = (isset($request->base_display_flag) ? $request->base_display_flag : 0); $page->membership_flag = (isset($request->membership_flag) ? $request->membership_flag : 0); $page->container_flag = (isset($request->container_flag) ? $request->container_flag : 0); @@ -334,6 +338,7 @@ public function update($request, $page_id) 'header_color' => $request->header_color, 'theme' => $request->theme, 'layout' => $request->layout, + 'layout_inherit_flag' => $request->layout_inherit_flag ?? 1, 'base_display_flag' => (isset($request->base_display_flag) ? $request->base_display_flag : 0), 'membership_flag' => (isset($request->membership_flag) ? $request->membership_flag : 0), 'container_flag' => (isset($request->container_flag) ? $request->container_flag : 0), @@ -509,6 +514,7 @@ public function downloadCsvSample($request, $id = null) PageCvsIndex::header_color => 'NULL', PageCvsIndex::theme => 'NULL', PageCvsIndex::layout => 'NULL', + PageCvsIndex::layout_inherit_flag => '1', PageCvsIndex::base_display_flag => '1', ]; $csv_array[2] = [ @@ -518,6 +524,7 @@ public function downloadCsvSample($request, $id = null) PageCvsIndex::header_color => 'NULL', PageCvsIndex::theme => 'NULL', PageCvsIndex::layout => 'NULL', + PageCvsIndex::layout_inherit_flag => '1', PageCvsIndex::base_display_flag => '1', ]; @@ -736,6 +743,7 @@ public function upload($request, $page_id) 'header_color' => $csv_columns[PageCvsIndex::header_color], 'theme' => $csv_columns[PageCvsIndex::theme], 'layout' => $csv_columns[PageCvsIndex::layout], + 'layout_inherit_flag' => $csv_columns[PageCvsIndex::layout_inherit_flag], 'base_display_flag' => $csv_columns[PageCvsIndex::base_display_flag] ]); diff --git a/app/Plugins/Manage/SiteManage/SiteManage.php b/app/Plugins/Manage/SiteManage/SiteManage.php index 22d7ef1a4..c9f1f9110 100644 --- a/app/Plugins/Manage/SiteManage/SiteManage.php +++ b/app/Plugins/Manage/SiteManage/SiteManage.php @@ -317,6 +317,13 @@ public function update($request, $page_id = null) 'value' => $request->base_theme] ); + // 基本レイアウト + $configs = Configs::updateOrCreate( + ['name' => 'base_layout'], + ['category' => 'general', + 'value' => $request->base_layout] + ); + // 追加テーマ $configs = Configs::updateOrCreate( ['name' => 'additional_theme'], diff --git a/config/connect.php b/config/connect.php index 7e551c0a3..e7bf1a847 100644 --- a/config/connect.php +++ b/config/connect.php @@ -64,6 +64,9 @@ // 設定メニューの折り畳みcol 'CC_SETTING_EXPAND_COL' => 6, + // レイアウトの初期値 + 'BASE_LAYOUT_DEFAULT' => '1|1|0|1', + // Cache-Control 'CACHE_CONTROL' => env('CACHE_CONTROL', 'max-age=604800'), diff --git a/database/migrations/2026_01_13_170722_add_base_layout_to_configs.php b/database/migrations/2026_01_13_170722_add_base_layout_to_configs.php new file mode 100644 index 000000000..2df7cc192 --- /dev/null +++ b/database/migrations/2026_01_13_170722_add_base_layout_to_configs.php @@ -0,0 +1,44 @@ +where('name', 'base_layout')->exists()) { + return; + } + + $layout_default = config('connect.BASE_LAYOUT_DEFAULT'); + $top_page_layout = DB::table('pages')->orderBy('_lft', 'asc')->value('layout'); + $layout = $top_page_layout ?: $layout_default; + + DB::table('configs')->insert([ + 'name' => 'base_layout', + 'value' => $layout, + 'category' => 'general', + 'additional1' => null, + 'additional2' => null, + 'additional3' => null, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + DB::table('configs')->where('name', 'base_layout')->delete(); + } +} diff --git a/database/migrations/2026_01_13_173712_add_layout_inherit_flag_to_pages_table.php b/database/migrations/2026_01_13_173712_add_layout_inherit_flag_to_pages_table.php new file mode 100644 index 000000000..075daafcb --- /dev/null +++ b/database/migrations/2026_01_13_173712_add_layout_inherit_flag_to_pages_table.php @@ -0,0 +1,44 @@ +tinyInteger('layout_inherit_flag')->default(1)->after('layout'); + }); + + DB::table('pages') + ->whereNull('layout_inherit_flag') + ->update(['layout_inherit_flag' => 1]); + + $top_page_id = DB::table('pages')->orderBy('_lft', 'asc')->value('id'); + if ($top_page_id) { + DB::table('pages') + ->where('id', $top_page_id) + ->update(['layout_inherit_flag' => 0]); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('pages', function (Blueprint $table) { + $table->dropColumn('layout_inherit_flag'); + }); + } +} diff --git a/database/seeders/DefaultConfigsTableSeeder.php b/database/seeders/DefaultConfigsTableSeeder.php index 1403e944e..78d36829a 100644 --- a/database/seeders/DefaultConfigsTableSeeder.php +++ b/database/seeders/DefaultConfigsTableSeeder.php @@ -32,6 +32,7 @@ private function getDefaultConfigs() 'base_touch_callout' => ['value' => '0'], 'base_header_login_link' => ['value' => '1'], 'base_theme' => ['value' => null], + 'base_layout' => ['value' => config('connect.BASE_LAYOUT_DEFAULT')], 'smartphone_menu_template' => ['value' => SmartphoneMenuTemplateType::opencurrenttree], 'base_login_password_reset' => ['value' => 1], 'use_mypage' => ['value' => 1], diff --git a/database/seeders/DefaultPagesTableSeeder.php b/database/seeders/DefaultPagesTableSeeder.php index 9920c0770..dce15482f 100644 --- a/database/seeders/DefaultPagesTableSeeder.php +++ b/database/seeders/DefaultPagesTableSeeder.php @@ -22,6 +22,7 @@ public function run() 'page_name' => 'home', 'permanent_link' => '/', 'base_display_flag' => '1', + 'layout_inherit_flag' => 0, 'depth' => 0, // bugfix: tree構造で _lft, _rgtが両方0は間違った値で、上から2番目のページを上に移動するとNode must exists.エラーになるため修正 // '_lft'=>'0', diff --git a/database/seeders/ElementaryschoolSeeder.php b/database/seeders/ElementaryschoolSeeder.php index aca1ef6ee..3e7cf0daf 100644 --- a/database/seeders/ElementaryschoolSeeder.php +++ b/database/seeders/ElementaryschoolSeeder.php @@ -683,6 +683,7 @@ private function insertGetPageId($row) $header_color = (isset($row['header_color'])) ? $row['header_color'] : NULL; $theme = (isset($row['theme'])) ? $row['theme'] : NULL; $layout = (isset($row['layout'])) ? $row['layout'] : NULL; + $layout_inherit_flag = (isset($row['layout_inherit_flag'])) ? $row['layout_inherit_flag'] : NULL; $base_display_flag = (isset($row['base_display_flag'])) ? $row['base_display_flag'] : 1;// 1:表示する $membership_flag = (isset($row['membership_flag'])) ? $row['membership_flag'] : 0; $ip_address = (isset($row['ip_address'])) ? $row['ip_address'] : NULL; @@ -694,6 +695,9 @@ private function insertGetPageId($row) $_lft = (isset($row['_lft'])) ? $row['_lft'] : NULL; $_rgt = (isset($row['_rgt'])) ? $row['_rgt'] : NULL; $parent_id = (isset($row['parent_id'])) ? $row['parent_id'] : NULL; + if (is_null($layout_inherit_flag)) { + $layout_inherit_flag = ($permanent_link === '/') ? 0 : 1; + } return DB::table('pages')->insertGetId([ 'page_name' => $page_name, 'permanent_link' => $permanent_link, @@ -701,6 +705,7 @@ private function insertGetPageId($row) 'header_color' => $header_color, 'theme' => $theme, 'layout' => $layout, + 'layout_inherit_flag' => $layout_inherit_flag, 'base_display_flag' => $base_display_flag, 'membership_flag' => $membership_flag, 'ip_address' => $ip_address, diff --git a/database/seeders/PTASeeder.php b/database/seeders/PTASeeder.php index 02238615a..ea6a632bd 100644 --- a/database/seeders/PTASeeder.php +++ b/database/seeders/PTASeeder.php @@ -843,6 +843,7 @@ private function insertGetPageId($row) $header_color = (isset($row['header_color'])) ? $row['header_color'] : NULL; $theme = (isset($row['theme'])) ? $row['theme'] : NULL; $layout = (isset($row['layout'])) ? $row['layout'] : NULL; + $layout_inherit_flag = (isset($row['layout_inherit_flag'])) ? $row['layout_inherit_flag'] : NULL; $base_display_flag = (isset($row['base_display_flag'])) ? $row['base_display_flag'] : 1;// 1:表示する $membership_flag = (isset($row['membership_flag'])) ? $row['membership_flag'] : 0; $ip_address = (isset($row['ip_address'])) ? $row['ip_address'] : NULL; @@ -854,6 +855,9 @@ private function insertGetPageId($row) $_lft = (isset($row['_lft'])) ? $row['_lft'] : NULL; $_rgt = (isset($row['_rgt'])) ? $row['_rgt'] : NULL; $parent_id = (isset($row['parent_id'])) ? $row['parent_id'] : NULL; + if (is_null($layout_inherit_flag)) { + $layout_inherit_flag = ($permanent_link === '/') ? 0 : 1; + } return DB::table('pages')->insertGetId([ 'page_name' => $page_name, 'permanent_link' => $permanent_link, @@ -861,6 +865,7 @@ private function insertGetPageId($row) 'header_color' => $header_color, 'theme' => $theme, 'layout' => $layout, + 'layout_inherit_flag' => $layout_inherit_flag, 'base_display_flag' => $base_display_flag, 'membership_flag' => $membership_flag, 'ip_address' => $ip_address, diff --git a/resources/views/plugins/manage/page/page.blade.php b/resources/views/plugins/manage/page/page.blade.php index 4c07d84a5..2db1eb14f 100644 --- a/resources/views/plugins/manage/page/page.blade.php +++ b/resources/views/plugins/manage/page/page.blade.php @@ -8,6 +8,13 @@ @php use App\Enums\PageMetaRobots; use App\Models\Common\Page; +use App\Models\Core\Configs; + +$layout_default = config('connect.BASE_LAYOUT_DEFAULT'); +$base_layout = Configs::getSharedConfigsValue('base_layout', $layout_default); +$base_layout = $base_layout ?: $layout_default; +$base_layout_page = new Page(); +$base_layout_page->layout = $base_layout; @endphp {{-- 管理画面ベース画面 --}} @@ -374,7 +381,14 @@ function select_page(source_id, page_name) { @if ($page_item->getSimpleLayout()) -
+ @php + $layout_inherit_flag = (string)($page_item->layout_inherit_flag ?? '1'); + $layout_scope_label = ($layout_inherit_flag === '0') ? 'このページのみ' : '下層にも適用'; + @endphp +
+ +
{{$layout_scope_label}}
+
@else @php @@ -382,6 +396,9 @@ function select_page(source_id, page_name) { // 自分及び先祖ページを遡る foreach ($page_tree as $page_tmp) { if ($page_tmp->getSimpleLayout()) { + if (!is_null($page_tmp->layout_inherit_flag) && (int)$page_tmp->layout_inherit_flag === 0) { + continue; + } $layout_page_parent = $page_tmp; break; } @@ -390,7 +407,7 @@ function select_page(source_id, page_name) { @if ($layout_page_parent->getSimpleLayout())
@else -
+
@endif @endif diff --git a/resources/views/plugins/manage/page/page_form.blade.php b/resources/views/plugins/manage/page/page_form.blade.php index 75704585d..3b9e91487 100644 --- a/resources/views/plugins/manage/page/page_form.blade.php +++ b/resources/views/plugins/manage/page/page_form.blade.php @@ -11,6 +11,7 @@ @php use App\Enums\PageMetaRobots; use App\Models\Common\Page; +use App\Models\Core\Configs; @endphp {{-- 共通エラーメッセージ 呼び出し --}} @@ -26,6 +27,12 @@ @php // 検索避け設定で継承表示に使うため、自分自身から親ページを順に取得 $page_tree = $page->getPageTreeByGoingBackParent(null); + $layout_default = config('connect.BASE_LAYOUT_DEFAULT'); + $base_layout = Configs::getSharedConfigsValue('base_layout', $layout_default); + $base_layout = $base_layout ?: $layout_default; + $base_layout_page = new Page(); + $base_layout_page->layout = $base_layout; + $layout_inherit_flag_value = old('layout_inherit_flag', $page->layout_inherit_flag ?? 1); @endphp @@ -466,20 +473,53 @@ // 自分及び先祖ページを遡る foreach ($page_tree as $page_tmp) { if ($page_tmp->getSimpleLayout()) { + if (!is_null($page_tmp->layout_inherit_flag) && (int)$page_tmp->layout_inherit_flag === 0) { + continue; + } $layout_page_parent = $page_tmp; break; } } @endphp {{-- 公開設定が公開以外&親ページありなら --}} - @if (!$page->getSimpleLayout() && $layout_page_parent->getSimpleLayout()) -
- 未設定 のため、親ページ「{{$layout_page_parent->page_name}} 」のレイアウト を継承しています。
-
+ @if (!$page->getSimpleLayout()) + @if ($layout_page_parent->getSimpleLayout()) +
+ 未設定 のため、親ページ「{{$layout_page_parent->page_name}} 」のレイアウト を継承しています。
+
+ @else +
+ 未設定 のため、基本レイアウト を適用しています。
+ サイト基本設定 で変更できます。 +
+ @endif @endif +
+ +
+
+ @if ((string)$layout_inherit_flag_value === '0') + + @else + + @endif + +
+
+ @if ((string)$layout_inherit_flag_value === '1') + + @else + + @endif + +
+ レイアウトが未設定の場合は、親ページまたは基本レイアウトを継承します。 +
+
+
diff --git a/resources/views/plugins/manage/page/page_import.blade.php b/resources/views/plugins/manage/page/page_import.blade.php index c90d749a7..7d57bf644 100644 --- a/resources/views/plugins/manage/page/page_import.blade.php +++ b/resources/views/plugins/manage/page/page_import.blade.php @@ -63,7 +63,6 @@ function submit_download_csv_format_shift_jis_sample() {
- {{-- インポート画面(入力フォーム) --}}
{{csrf_field()}} diff --git a/resources/views/plugins/manage/site/pdf/base_main.blade.php b/resources/views/plugins/manage/site/pdf/base_main.blade.php index ce59ba173..6fbd7517c 100644 --- a/resources/views/plugins/manage/site/pdf/base_main.blade.php +++ b/resources/views/plugins/manage/site/pdf/base_main.blade.php @@ -13,6 +13,14 @@

サイト基本設定

+@php + $layout_default = config('connect.BASE_LAYOUT_DEFAULT'); + $base_layout_value = Configs::getConfigsValue($configs, 'base_layout', $layout_default); + $base_layout_value = $base_layout_value ?: $layout_default; + $base_layout_page = new \App\Models\Common\Page(); + $base_layout_page->layout = $base_layout_value; + $base_layout_title = $base_layout_page->getLayoutTitle() ?? $base_layout_value; +@endphp @@ -34,6 +42,10 @@ + + + + diff --git a/resources/views/plugins/manage/site/site.blade.php b/resources/views/plugins/manage/site/site.blade.php index 6b3a3d45e..d1508d81f 100644 --- a/resources/views/plugins/manage/site/site.blade.php +++ b/resources/views/plugins/manage/site/site.blade.php @@ -71,6 +71,147 @@ 基本テーマとは別のテーマを追加で読み込みます。スタイルの読み込み順は追加テーマの方が後になる為、スタイル競合時は追加テーマのものが優先されます。 + @php + $layout_default = config('connect.BASE_LAYOUT_DEFAULT'); + $base_layout_value = Configs::getConfigsValue($configs, 'base_layout', $layout_default); + $base_layout_value = $base_layout_value ?: $layout_default; + @endphp + {{-- 基本レイアウト --}} +
+ +
+
+ @if (old('base_layout', $base_layout_value) == '0|0|0|0') + + @else + + @endif + +
+
+ @if (old('base_layout', $base_layout_value) == '0|0|0|1') + + @else + + @endif + +
+
+ @if (old('base_layout', $base_layout_value) == '0|0|1|0') + + @else + + @endif + +
+
+ @if (old('base_layout', $base_layout_value) == '0|0|1|1') + + @else + + @endif + +
+
+ @if (old('base_layout', $base_layout_value) == '0|1|0|0') + + @else + + @endif + +
+
+ @if (old('base_layout', $base_layout_value) == '0|1|0|1') + + @else + + @endif + +
+
+ @if (old('base_layout', $base_layout_value) == '0|1|1|0') + + @else + + @endif + +
+
+ @if (old('base_layout', $base_layout_value) == '0|1|1|1') + + @else + + @endif + +
+
+ @if (old('base_layout', $base_layout_value) == '1|0|0|0') + + @else + + @endif + +
+
+ @if (old('base_layout', $base_layout_value) == '1|0|0|1') + + @else + + @endif + +
+
+ @if (old('base_layout', $base_layout_value) == '1|0|1|0') + + @else + + @endif + +
+
+ @if (old('base_layout', $base_layout_value) == '1|0|1|1') + + @else + + @endif + +
+
+ @if (old('base_layout', $base_layout_value) == '1|1|0|0') + + @else + + @endif + +
+
+ @if (old('base_layout', $base_layout_value) == '1|1|0|1') + + @else + + @endif + +
+
+ @if (old('base_layout', $base_layout_value) == '1|1|1|0') + + @else + + @endif + +
+
+ @if (old('base_layout', $base_layout_value) == '1|1|1|1') + + @else + + @endif + +
+
+ 全ページに適用する基本レイアウトです。各ページのレイアウト設定で上書きできます。 +
+
@php $placeholder_message = 'HTMLカラーコードを入力'; diff --git a/tests/Unit/Controllers/Core/DefaultControllerLayoutTest.php b/tests/Unit/Controllers/Core/DefaultControllerLayoutTest.php new file mode 100644 index 000000000..4ce610bdc --- /dev/null +++ b/tests/Unit/Controllers/Core/DefaultControllerLayoutTest.php @@ -0,0 +1,112 @@ +attributes->set('configs', collect($configs)); + } + + protected function newConfig(string $name, ?string $value): Configs + { + $config = new Configs(); + $config->name = $name; + $config->value = $value; + return $config; + } + + protected function callGetLayout(Page $page, $page_tree): string + { + $controller = new DefaultController(); + $method = new \ReflectionMethod(DefaultController::class, 'getLayout'); + $method->setAccessible(true); + + return $method->invoke($controller, $page, $page_tree); + } + + protected function tearDown(): void + { + $this->setSharedConfigs([]); + parent::tearDown(); + } + + /** + * 祖先の layout_inherit_flag=0 を継承対象から除外すること。 + */ + public function testGetLayoutSkipsAncestorsWithLayoutInheritFlagOff(): void + { + $current = new Page(); + $current->id = 3; + $current->layout = null; + $current->layout_inherit_flag = 1; + + $parent = new Page(); + $parent->id = 2; + $parent->layout = '1|1|1|1'; + $parent->layout_inherit_flag = 0; + + $grandparent = new Page(); + $grandparent->id = 1; + $grandparent->layout = '0|1|0|1'; + $grandparent->layout_inherit_flag = 1; + + $this->setSharedConfigs([ + $this->newConfig('base_layout', '1|0|0|1'), + ]); + + $layout = $this->callGetLayout($current, collect([$current, $parent, $grandparent])); + + $this->assertSame('0|1|0|1', $layout); + } + + /** + * ツリー内にレイアウトが無い場合は基本レイアウトを採用すること。 + */ + public function testGetLayoutFallsBackToBaseLayoutWhenNoLayoutInTree(): void + { + $current = new Page(); + $current->id = 1; + $current->layout = null; + + $parent = new Page(); + $parent->id = 2; + $parent->layout = null; + + $this->setSharedConfigs([ + $this->newConfig('base_layout', '1|0|0|1'), + ]); + + $layout = $this->callGetLayout($current, collect([$current, $parent])); + + $this->assertSame('1|0|0|1', $layout); + } + + /** + * 基本レイアウトが空なら初期値にフォールバックすること。 + */ + public function testGetLayoutFallsBackToDefaultWhenBaseLayoutEmpty(): void + { + $current = new Page(); + $current->id = 1; + $current->layout = null; + + $this->setSharedConfigs([ + $this->newConfig('base_layout', ''), + ]); + + $layout = $this->callGetLayout($current, collect([$current])); + + $this->assertSame(config('connect.BASE_LAYOUT_DEFAULT'), $layout); + } +} From 49b49ec6b503431898f13272d18e0529decbe98b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 20 Jan 2026 06:46:33 +0000 Subject: [PATCH 02/44] chore: Bump version to v1.40.0 --- config/version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/version.php b/config/version.php index d4e985021..a04129191 100644 --- a/config/version.php +++ b/config/version.php @@ -12,7 +12,7 @@ | */ - 'cc_version' => '1.39.0', + 'cc_version' => '1.40.0', 'show_cc_version' => true, ]; From cc6e59947d041884226802d8e6aeb37fe0cab8d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 23:50:15 +0000 Subject: [PATCH 03/44] chore(deps-dev): bump lodash from 4.17.21 to 4.17.23 Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23) --- updated-dependencies: - dependency-name: lodash dependency-version: 4.17.23 dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- package-lock.json | 42 ++++++++++++++++++++++-------------------- package.json | 2 +- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 92e359f69..85376d31c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "dayjs": "^1.11.13", "jquery": "^3.7.0", "laravel-mix": "^6.0.49", - "lodash": "^4.17.19", + "lodash": "^4.17.23", "popper.js": "^1.16.1", "resolve-url-loader": "^5.0.0", "sass": "^1.62.1", @@ -76,7 +76,6 @@ "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -2426,7 +2425,6 @@ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "dev": true, "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -3258,7 +3256,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3985,7 +3982,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001716", "electron-to-chromium": "^1.5.149", @@ -4147,6 +4143,23 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -6975,7 +6988,6 @@ "integrity": "sha512-33AmZ+xjZhg2JMCe+vDf6a9mzWukE7l+wAtesjE7KyteqqKjzxv7aVQeWnul1Ve26mWvEQqyPwl0OctNBfSR9w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "file-type": "^12.0.0", "globby": "^10.0.0", @@ -7383,8 +7395,7 @@ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/js-tokens": { "version": "4.0.0", @@ -7751,9 +7762,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, @@ -8952,7 +8963,6 @@ "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", "dev": true, "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -8988,7 +8998,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -11322,7 +11331,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -11829,7 +11837,6 @@ "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/compiler-sfc": "3.5.13", @@ -11982,7 +11989,6 @@ "integrity": "sha512-CNqKBRMQjwcmKR0idID5va1qlhrqVUKpovi+Ec79ksW8ux7iS1+A6VqzfZXgVYCFRKl7XL5ap3ZoMpwBJxcg0w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -12031,7 +12037,6 @@ "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", @@ -12104,7 +12109,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -12222,7 +12226,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -12325,7 +12328,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", diff --git a/package.json b/package.json index 6f5bc94c3..b37dda843 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "dayjs": "^1.11.13", "jquery": "^3.7.0", "laravel-mix": "^6.0.49", - "lodash": "^4.17.19", + "lodash": "^4.17.23", "popper.js": "^1.16.1", "resolve-url-loader": "^5.0.0", "sass": "^1.62.1", From 548d3e5c94c335a64d12a37f3c3485503ef04c4e Mon Sep 17 00:00:00 2001 From: masaton0216 Date: Thu, 22 Jan 2026 10:21:29 +0900 Subject: [PATCH 04/44] =?UTF-8?q?add:=20=E3=82=B9=E3=83=91=E3=83=A0?= =?UTF-8?q?=E3=83=95=E3=82=A3=E3=83=AB=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0?= =?UTF-8?q?=E6=A9=9F=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0=EF=BC=88=E3=82=B9?= =?UTF-8?q?=E3=83=91=E3=83=A0=E7=AE=A1=E7=90=86=EF=BC=8B=E3=83=95=E3=82=A9?= =?UTF-8?q?=E3=83=BC=E3=83=A0=E3=83=97=E3=83=A9=E3=82=B0=E3=82=A4=E3=83=B3?= =?UTF-8?q?=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() }} + + +
+ +
+
設定項目追加テーマ {{Configs::getConfigsValue($configs, 'additional_theme', null)}}
基本レイアウト{{$base_layout_title}}
背景色 {{Configs::getConfigsValue($configs, 'base_background_color', null)}}
+ + + + + + + + + + + + @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') + @if ($function == "edit")