Skip to content

Commit 57b7529

Browse files
authored
Merge pull request #2297 from opensource-workshop/feature/user-columns-conditional-display
[ユーザ管理] ユーザー項目に条件付き表示機能を追加しました
2 parents 2df2ea6 + 90b49dc commit 57b7529

File tree

12 files changed

+1949
-14
lines changed

12 files changed

+1949
-14
lines changed

app/Enums/ConditionalOperator.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace App\Enums;
4+
5+
/**
6+
* 条件演算子
7+
*/
8+
class ConditionalOperator extends EnumsBase
9+
{
10+
// 定数メンバ
11+
const equals = 'equals';
12+
const not_equals = 'not_equals';
13+
const is_empty = 'is_empty';
14+
const is_not_empty = 'is_not_empty';
15+
16+
// key/valueの連想配列
17+
const enum = [
18+
self::equals => '次の値と一致する',
19+
self::not_equals => '次の値と一致しない',
20+
self::is_empty => '未入力である',
21+
self::is_not_empty => '未入力でない',
22+
];
23+
}

app/Http/Controllers/Auth/RegistersUsers.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ trait RegistersUsers
4242

4343
/**
4444
* Show the application registration form.
45-
* ユーザー登録画面表示自動登録
45+
* ユーザー登録画面表示(自動登録)
4646
*
4747
* @param \Illuminate\Http\Request $request
4848
* @return \Illuminate\Http\Response
@@ -99,6 +99,9 @@ public function showRegistrationForm(Request $request)
9999
// カラムの登録データ
100100
$input_cols = null;
101101

102+
// 条件付き表示の設定を取得
103+
$conditional_display_settings = UsersTool::getConditionalDisplaySettings($columns_set_id);
104+
102105
// サイトテーマ詰込
103106
$tmp_configs = Configs::getSharedConfigs();
104107
$base_theme = Configs::getConfigsValue($tmp_configs, 'base_theme', null);
@@ -119,6 +122,7 @@ public function showRegistrationForm(Request $request)
119122
'users_columns' => $users_columns,
120123
'users_columns_id_select' => $users_columns_id_select,
121124
'input_cols' => $input_cols,
125+
'conditional_display_settings' => $conditional_display_settings,
122126
'themes' => $themes,
123127
'sections' => Section::orderBy('display_sequence')->get(),
124128
'user_section' => new UserSection(),

app/Models/Core/UsersColumns.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ class UsersColumns extends Model
3636
'rule_regex',
3737
'rule_word_count',
3838
'display_sequence',
39+
'conditional_display_flag',
40+
'conditional_trigger_column_id',
41+
'conditional_operator',
42+
'conditional_value',
3943
];
4044

4145
/**

app/Plugins/Manage/UserManage/UserManage.php

Lines changed: 133 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Plugins\Manage\UserManage;
44

5+
use App\Enums\ConditionalOperator;
56
use App\Enums\CsvCharacterCode;
67
use App\Enums\EditType;
78
use App\Enums\Required;
@@ -2635,6 +2636,14 @@ public function editColumns($request, $id)
26352636
// ユーザーのカラム
26362637
$columns = UsersTool::getUsersColumns($id);
26372638

2639+
// トリガー項目として使用されている項目IDを取得
2640+
$trigger_column_ids = UsersColumns::where('columns_set_id', $id)
2641+
->where('conditional_display_flag', ShowType::show)
2642+
->whereNotNull('conditional_trigger_column_id')
2643+
->distinct()
2644+
->pluck('conditional_trigger_column_id')
2645+
->toArray();
2646+
26382647
foreach ($columns as &$column) {
26392648
if (UsersColumns::isSelectColumnType($column->column_type)) {
26402649
// 選択肢
@@ -2646,6 +2655,9 @@ public function editColumns($request, $id)
26462655
} else {
26472656
$column->selects = collect();
26482657
}
2658+
2659+
// トリガー項目として使用されているかのフラグを追加
2660+
$column->is_used_as_trigger = in_array($column->id, $trigger_column_ids);
26492661
}
26502662

26512663
return view('plugins.manage.user.edit_columns', [
@@ -2741,23 +2753,39 @@ public function updateColumn($request, $id)
27412753
$column->column_type = $request->$str_column_type;
27422754
$message = '項目【 '. $column->column_name .' 】を更新しました。';
27432755

2756+
$messages = [];
2757+
27442758
if (UsersColumns::isShowOnlyColumnType($column->column_type)) {
27452759
$column->required = Required::off;
2746-
$message = '項目【 '.$column->column_name.' 】を更新し、表示のみの型のため、必須入力を【 off 】に設定しました';
2760+
$messages[] = '表示のみの型のため、必須入力を【 off 】に設定しました';
27472761
} else {
27482762
// 通常
27492763
$column->required = $request->$str_required ? Required::on : Required::off;
2764+
2765+
// 必須ONに変更した場合、条件付き表示をOFFにする
2766+
if ($column->required == Required::on && $column->conditional_display_flag == ShowType::show) {
2767+
$column->conditional_display_flag = ShowType::not_show;
2768+
$column->conditional_trigger_column_id = null;
2769+
$column->conditional_operator = null;
2770+
$column->conditional_value = null;
2771+
$messages[] = '必須入力ONのため、条件付き表示を【 OFF 】に設定しました';
2772+
}
27502773
}
27512774

27522775
// 固定項目以外
27532776
if (!UsersColumns::isFixedColumnType($column->column_type)) {
27542777
// 必須入力
27552778
if ($column->required == Required::on) {
27562779
$column->is_show_auto_regist = ShowType::show;
2757-
$message = '項目【 '.$column->column_name.' 】を更新し、必須入力のため、自動登録時の表示指定【 '.ShowType::getDescription($column->is_show_auto_regist).' 】を設定しました';
2780+
$messages[] = '必須入力のため、自動登録時の表示指定【 '.ShowType::getDescription($column->is_show_auto_regist).' 】を設定しました';
27582781
}
27592782
}
27602783

2784+
// メッセージの生成
2785+
if (!empty($messages)) {
2786+
$message = '項目【 '.$column->column_name.' 】を更新し、' . implode('。また、', $messages) . '';
2787+
}
2788+
27612789
$column->save();
27622790

27632791
// 編集画面を呼び出す
@@ -2835,8 +2863,30 @@ public function deleteColumn($request, $id)
28352863
// 明細行から削除対象の項目名を抽出
28362864
$str_column_name = "column_name_"."$request->column_id";
28372865

2838-
// 所属型の関連テーブルを削除
28392866
$users_column = UsersColumns::findOrFail($request->column_id);
2867+
2868+
// この項目をトリガーにしている項目がないかチェック
2869+
$dependent_columns = UsersColumns::where('columns_set_id', $request->columns_set_id)
2870+
->where('conditional_display_flag', ShowType::show)
2871+
->where('conditional_trigger_column_id', $request->column_id)
2872+
->get();
2873+
2874+
if ($dependent_columns->count() > 0) {
2875+
// トリガーとして使用されている場合は削除不可(HTMLエスケープ)
2876+
$dependent_names_escaped = $dependent_columns->pluck('column_name')
2877+
->map(function ($name) {
2878+
return e($name);
2879+
})
2880+
->toArray();
2881+
2882+
$error_message = '項目【 '. e($request->$str_column_name) .' 】は以下の項目のトリガーとして使用されているため削除できません。<br>';
2883+
$error_message .= '先に以下の項目の条件付き表示をOFFにしてから削除してください。<br>';
2884+
$error_message .= '' . implode('<br>・', $dependent_names_escaped);
2885+
2886+
return redirect()->back()->with('errors_flash_message', $error_message);
2887+
}
2888+
2889+
// 所属型の関連テーブルを削除
28402890
if ($users_column->column_type === UserColumnType::affiliation) {
28412891
UserSection::query()->delete();
28422892
Section::query()->delete();
@@ -2879,14 +2929,24 @@ public function editColumnDetail($request, $id)
28792929
$selects = UsersColumnsSelects::where('users_columns_id', $column->id)->orderby('display_sequence')->get();
28802930
$select_agree = $selects->first() ?? new UsersColumnsSelects();
28812931

2932+
// トリガー候補の項目を取得
2933+
// 条件:自分自身を除く(システム固定項目・カスタム必須項目も含める)
2934+
// ただし、登録フォームに表示されない項目(登録日時、更新日時)は除外
2935+
$trigger_columns = UsersColumns::where('columns_set_id', $column->columns_set_id)
2936+
->where('id', '!=', $id) // 自分自身を除外
2937+
->whereNotIn('column_type', UserColumnType::showOnlyColumnTypes())
2938+
->orderBy('display_sequence')
2939+
->get();
2940+
28822941
return view('plugins.manage.user.edit_column_detail', [
2883-
"function" => __FUNCTION__,
2884-
"plugin_name" => "user",
2885-
'columns_set' => $columns_set,
2886-
'column' => $column,
2887-
'selects' => $selects,
2888-
'select_agree' => $select_agree,
2889-
'sections' => Section::orderBy('display_sequence')->get(),
2942+
"function" => __FUNCTION__,
2943+
"plugin_name" => "user",
2944+
'columns_set' => $columns_set,
2945+
'column' => $column,
2946+
'selects' => $selects,
2947+
'select_agree' => $select_agree,
2948+
'sections' => Section::orderBy('display_sequence')->get(),
2949+
'trigger_columns' => $trigger_columns,
28902950
]);
28912951
}
28922952

@@ -2932,6 +2992,55 @@ public function updateColumnDetail($request, $id)
29322992
$validator_attributes['variable_name'] = '変数名';
29332993
}
29342994

2995+
// カラム取得
2996+
$column = UsersColumns::where('id', $request->column_id)->where('columns_set_id', $request->columns_set_id)->first();
2997+
if (!$column) {
2998+
abort(404, 'カラムデータがありません。');
2999+
}
3000+
3001+
// システム固定項目または必須項目は条件付き表示を設定できない
3002+
if (UsersColumns::isFixedColumnType($column->column_type) || $column->required == Required::on) {
3003+
// 強制的に条件付き表示をOFFにする
3004+
$request->merge(['conditional_display_flag' => ShowType::not_show]);
3005+
}
3006+
3007+
// 条件付き表示のバリデーション
3008+
if ($request->conditional_display_flag == ShowType::show) {
3009+
$validator_values['conditional_trigger_column_id'] = ['required'];
3010+
$validator_values['conditional_operator'] = ['required'];
3011+
3012+
// 空白チェック(is_empty, is_not_empty)以外の場合のみ条件の値を必須にする
3013+
if ($request->conditional_operator !== ConditionalOperator::is_empty &&
3014+
$request->conditional_operator !== ConditionalOperator::is_not_empty) {
3015+
$validator_values['conditional_value'] = ['required', 'string', 'max:255'];
3016+
}
3017+
3018+
$validator_attributes['conditional_trigger_column_id'] = 'トリガーとなる項目';
3019+
$validator_attributes['conditional_operator'] = '表示する条件';
3020+
$validator_attributes['conditional_value'] = '条件の値';
3021+
3022+
// トリガー項目の追加バリデーション
3023+
$validator_values['conditional_trigger_column_id'][] = function ($attribute, $value, $fail) use ($column) {
3024+
if ($value) {
3025+
$trigger_column = UsersColumns::find($value);
3026+
if ($trigger_column) {
3027+
// 自分自身をトリガーにできない
3028+
if ($trigger_column->id == $column->id) {
3029+
$fail('トリガーとなる項目に自分自身は設定できません。');
3030+
}
3031+
// 同じ項目セットに属していることを確認
3032+
if ($trigger_column->columns_set_id != $column->columns_set_id) {
3033+
$fail('トリガーとなる項目は同じ項目セットに属している必要があります。');
3034+
}
3035+
// 循環依存チェック(A→B→C→Aのような循環参照を防止)
3036+
if (UsersTool::hasCyclicDependency($column->id, $value, $column->columns_set_id)) {
3037+
$fail('この設定により循環依存が発生します。トリガーとなる項目の依存関係を確認してください。');
3038+
}
3039+
}
3040+
}
3041+
};
3042+
}
3043+
29353044
// エラーチェック
29363045
if ($validator_values) {
29373046
$validator = Validator::make($request->all(), $validator_values);
@@ -2942,9 +3051,6 @@ public function updateColumnDetail($request, $id)
29423051
}
29433052
}
29443053

2945-
2946-
$column = UsersColumns::where('id', $request->column_id)->where('columns_set_id', $request->columns_set_id)->first();
2947-
29483054
// 項目の更新処理
29493055
$column->caption = $request->caption;
29503056
if ($request->caption_color) {
@@ -2973,6 +3079,20 @@ public function updateColumnDetail($request, $id)
29733079
// 正規表現
29743080
$column->rule_regex = $request->rule_regex;
29753081

3082+
// 条件付き表示設定の更新
3083+
$column->conditional_display_flag = $request->conditional_display_flag ?? ShowType::not_show;
3084+
3085+
if ($column->conditional_display_flag == ShowType::show) {
3086+
$column->conditional_trigger_column_id = $request->conditional_trigger_column_id;
3087+
$column->conditional_operator = $request->conditional_operator;
3088+
$column->conditional_value = $request->conditional_value;
3089+
} else {
3090+
// OFFの場合はクリア
3091+
$column->conditional_trigger_column_id = null;
3092+
$column->conditional_operator = null;
3093+
$column->conditional_value = null;
3094+
}
3095+
29763096
// 保存
29773097
$column->save();
29783098

app/Plugins/Manage/UserManage/UsersTool.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,4 +429,97 @@ private static function getOptionClass(): ?string
429429
}
430430
return null;
431431
}
432+
433+
/**
434+
* 条件付き表示の設定情報を取得
435+
*
436+
* @param int $columns_set_id カラムセットID
437+
* @return array 条件付き表示の設定情報の配列
438+
*/
439+
public static function getConditionalDisplaySettings($columns_set_id)
440+
{
441+
$conditional_columns = UsersColumns::where('columns_set_id', $columns_set_id)
442+
->where('conditional_display_flag', ShowType::show)
443+
->whereNotNull('conditional_trigger_column_id')
444+
->whereNotNull('conditional_operator')
445+
->get();
446+
447+
// トリガー項目を一括取得(N+1クエリ対策)
448+
$trigger_ids = $conditional_columns->pluck('conditional_trigger_column_id')->unique();
449+
$trigger_columns = UsersColumns::whereIn('id', $trigger_ids)->get()->keyBy('id');
450+
451+
$settings = [];
452+
foreach ($conditional_columns as $column) {
453+
$trigger_column = $trigger_columns->get($column->conditional_trigger_column_id);
454+
455+
$settings[] = [
456+
'target_column_id' => $column->id,
457+
'trigger_column_id' => $column->conditional_trigger_column_id,
458+
'trigger_column_type' => $trigger_column ? $trigger_column->column_type : null,
459+
'operator' => $column->conditional_operator,
460+
'value' => $column->conditional_value,
461+
];
462+
}
463+
464+
return $settings;
465+
}
466+
467+
/**
468+
* 循環依存をチェックする
469+
*
470+
* 指定された項目をトリガーに設定した場合、循環依存が発生しないかをチェックします。
471+
* 例: A→B→C→A のような循環参照を検出
472+
*
473+
* @param int $column_id 条件付き表示を設定する項目のID
474+
* @param int $trigger_column_id トリガーとして設定しようとしている項目のID
475+
* @param int $columns_set_id 項目セットID
476+
* @return bool 循環依存がある場合true、ない場合false
477+
*/
478+
public static function hasCyclicDependency($column_id, $trigger_column_id, $columns_set_id)
479+
{
480+
// トリガー項目が設定されていない場合は循環しない
481+
if (empty($trigger_column_id)) {
482+
return false;
483+
}
484+
485+
// 訪問済みノードを記録(無限ループ防止)
486+
$visited = [];
487+
488+
// 探索スタック(深さ優先探索)
489+
$stack = [$trigger_column_id];
490+
491+
// 同一項目セット内の条件付き表示設定を一度に取得(パフォーマンス最適化)
492+
$conditional_columns = UsersColumns::where('columns_set_id', $columns_set_id)
493+
->where('conditional_display_flag', ShowType::show)
494+
->whereNotNull('conditional_trigger_column_id')
495+
->get()
496+
->keyBy('id');
497+
498+
while (!empty($stack)) {
499+
$current_id = array_pop($stack);
500+
501+
// 自分自身に到達したら循環依存を検出
502+
if ($current_id == $column_id) {
503+
return true;
504+
}
505+
506+
// 既に訪問済みの場合はスキップ
507+
if (in_array($current_id, $visited)) {
508+
continue;
509+
}
510+
511+
// 訪問済みとしてマーク
512+
$visited[] = $current_id;
513+
514+
// 現在のノードがトリガーとして設定されているか確認
515+
$current_column = $conditional_columns->get($current_id);
516+
if ($current_column && $current_column->conditional_trigger_column_id) {
517+
// 次のトリガーをスタックに追加
518+
$stack[] = $current_column->conditional_trigger_column_id;
519+
}
520+
}
521+
522+
// 循環依存なし
523+
return false;
524+
}
432525
}

0 commit comments

Comments
 (0)