# 機能設計書 18-ユーザー編集

## 概要

本ドキュメントは、LEGACY CMSにおける既存ユーザー情報の編集機能について記述する。この機能は管理画面において、登録済みユーザーのプロフィール情報、連絡先情報、所属情報、およびメーリングリスト購読設定を更新する機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：システム管理者がユーザーアカウントの情報を最新の状態に維持するため、ユーザー情報の修正・更新機能が必要である。組織変更、連絡先変更、ロール変更などの運用上の要請に対応する。

**機能の利用シーン**：管理者がユーザー一覧画面からユーザーを選択し、編集画面に遷移する。ユーザーの基本情報（名前、エイリアス、メールアドレス）、個人情報（性別、生年月日）、連絡先情報（電話番号）、所属情報（組織、役職）、住所情報（住所、市区町村、国、郵便番号）、メーリングリスト購読設定を編集・保存する。

**主要な処理内容**：
1. ユーザーの権限チェック（uusers + uview権限の複合確認）
2. 対象ユーザーの既存情報取得（users + users_profiles + users_roles結合）
3. メーリングリストグループ一覧の取得
4. 編集フォーム表示（タブ形式：User / Subscriptions）
5. 入力バリデーション実行
6. usersテーブル更新（エイリアス、メールアドレス、ロール）
7. users_profilesテーブル更新（プロフィール情報）
8. mail_subscriptions購読設定の更新（削除 → 再登録）

**関連システム・外部連携**：メーリングリスト管理機能との連携（mail_groups, mail_subscriptionsテーブル）

**権限による制御**：
- 編集画面表示：`uusers` + `uview` リソースへのアクセス権限が必要
- 保存処理実行：`uusers` + `uedit` リソースへのアクセス権限が必要

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 42 | ユーザー編集画面 | 主画面 | ユーザー情報の編集フォーム表示 |
| 41 | ユーザー管理画面 | 遷移元画面 | 一覧から編集画面への遷移 |
| 44 | ユーザー詳細画面 | 関連画面 | 編集画面内でAjax読み込み表示 |

## 機能種別

CRUD操作（Update）/ フォーム処理 / Ajax通信 / タブUI

## 入力仕様

### 入力パラメータ（編集画面表示時）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | integer | Yes | 編集対象ユーザーID | 数値 |

### 入力パラメータ（保存処理時）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | integer | Yes | 対象ユーザーID | 数値 |
| first | string | Yes | 名 | NotEmpty |
| last | string | Yes | 姓 | NotEmpty |
| alias | string | Yes | エイリアス | NotEmpty, Alnum, 重複チェック（自身除外） |
| email | string | Yes | メールアドレス | NotEmpty, EmailAddress, 重複チェック（自身除外） |
| role | integer | Yes | ロールID | NotEmpty, Digits |
| country | string | Yes | 国コード | NotEmpty |
| gender | string | No | 性別 | N/F/M |
| dob | string | No | 生年月日 | YYYY-MM-DD形式 |
| organisation | string | No | 組織名 | - |
| position | string | No | 役職 | - |
| address | string | No | 住所 | - |
| city | string | No | 市区町村 | - |
| phone | string | No | 電話番号 | - |
| postcode | string | No | 郵便番号 | - |
| lists[] | array | No | 購読メーリングリストID配列 | - |

### 入力データソース

- URLパラメータ（ユーザーID）
- POSTデータ（フォーム入力値）
- セッション（ログインユーザー情報、ACL情報）

## 出力仕様

### 出力データ（編集画面表示時）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| userArray | array | 対象ユーザーの現在情報（users + users_profiles + users_roles結合データ） |
| listsArray | array | メーリングリストグループ一覧 |

### 出力データ（保存処理完了時）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| HTML | string | 成功メッセージとCloseボタン |

### 出力先

- 編集画面：`application/modules/admin/views/scripts/users/edit.phtml`
- 保存処理：Ajax応答（HTML直接出力）

## 処理フロー

### 処理シーケンス（編集画面表示）

```
1. editAction()が呼び出される
   └─ 権限チェック（uusers + uview）
      ├─ 権限なし → privileges画面へフォワード
      └─ 権限あり → 次のステップへ

2. レイアウト設定
   └─ setLayout() でadminレイアウトを設定

3. ユーザー情報取得
   ├─ パラメータからuser_id取得
   ├─ SELECT FROM users
   │   JOIN users_profiles ON upro_userid = user_id
   │   JOIN users_roles ON role_id = user_role
   │   WHERE user_id = ?
   └─ 結果が0件の場合 → manageActionへリダイレクト

4. メーリングリスト一覧取得
   └─ SELECT FROM mail_groups ORDER BY mgroup_title ASC

5. ビューへのデータ設定
   ├─ userArray: ユーザー情報
   └─ listsArray: メーリングリスト一覧

6. edit.phtml描画
   ├─ detailsResponse: Ajax読み込みでユーザー詳細表示
   ├─ Userタブ: Identity/Personal/Contact/Organisation/Location fieldset
   └─ Subscriptionsタブ: メーリングリスト購読設定
```

### 処理シーケンス（保存処理）

```
1. saveAction()が呼び出される
   └─ 権限チェック（uusers + uedit）
      ├─ 権限なし → privileges画面へフォワード
      └─ 権限あり → 次のステップへ

2. レイアウト/ビュー無効化
   ├─ disableLayout()
   └─ setNoRender(true)

3. パラメータ検証
   └─ idパラメータ存在チェック
      ├─ なし → エラーメッセージ出力
      └─ あり → 次のステップへ

4. バリデーション実行
   ├─ first: NotEmpty
   ├─ last: NotEmpty
   ├─ alias: NotEmpty, Alnum, 重複チェック（自身除外）
   ├─ email: NotEmpty, EmailAddress, 重複チェック（自身除外）
   ├─ role: NotEmpty, Digits
   └─ country: NotEmpty

5. バリデーション結果判定
   ├─ 失敗 → エラーメッセージ出力
   └─ 成功 → 次のステップへ

6. usersテーブル更新
   └─ UPDATE users SET user_alias, user_email, user_role WHERE user_id = ?

7. users_profilesテーブル更新
   └─ UPDATE users_profiles SET upro_first, upro_last, ... , upro_date=NOW() WHERE upro_userid = ?

8. mail_subscriptions更新
   ├─ DELETE FROM mail_subscriptions WHERE msub_user = ?
   └─ 購読リストがある場合
      └─ foreach: INSERT INTO mail_subscriptions (msub_group, msub_user)

9. 成功メッセージ出力
   └─ "User Saved" + Closeボタン
```

### フローチャート

```mermaid
flowchart TD
    A[開始: editAction] --> B{uusers+uview権限あり?}
    B -->|No| C[privileges画面へフォワード]
    B -->|Yes| D[パラメータからuser_id取得]
    D --> E[SELECTクエリ実行 users+profiles+roles]
    E --> F{ユーザー存在する?}
    F -->|No| G[manageActionへリダイレクト]
    F -->|Yes| H[メーリングリスト一覧取得]
    H --> I[ビューへデータ設定]
    I --> J[edit.phtml描画]
    J --> K[終了]
    C --> K
    G --> K

    L[開始: saveAction] --> M{uusers+uedit権限あり?}
    M -->|No| N[privileges画面へフォワード]
    M -->|Yes| O{idパラメータあり?}
    O -->|No| P[エラーメッセージ出力]
    O -->|Yes| Q[バリデーション実行]
    Q --> R{バリデーション成功?}
    R -->|No| S[エラーメッセージ出力]
    R -->|Yes| T[usersテーブル更新]
    T --> U[users_profilesテーブル更新]
    U --> V[mail_subscriptions削除]
    V --> W{購読リストあり?}
    W -->|Yes| X[mail_subscriptionsへINSERT]
    W -->|No| Y[成功メッセージ出力]
    X --> Y
    Y --> K
    N --> K
    P --> K
    S --> K
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | エイリアス一意性 | エイリアスは他ユーザーと重複不可（自身は除外） | 保存時 |
| BR-02 | メールアドレス一意性 | メールアドレスは他ユーザーと重複不可（自身は除外） | 保存時 |
| BR-03 | プロフィール更新日時 | 保存時にupro_dateをNOW()で更新 | 保存時 |
| BR-04 | 購読設定全置換 | 保存時に既存購読を全削除後、新規に登録し直す | 保存時 |
| BR-05 | 権限分離 | 閲覧権限（uview）と編集権限（uedit）を分離 | 画面表示/保存時 |

### 計算ロジック

特になし

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ユーザー情報取得 | users | SELECT | 基本情報取得 |
| ユーザー情報取得 | users_profiles | SELECT | プロフィール情報結合取得 |
| ユーザー情報取得 | users_roles | SELECT | ロール情報結合取得 |
| メーリングリスト取得 | mail_groups | SELECT | グループ一覧取得 |
| ユーザー情報更新 | users | UPDATE | エイリアス、メール、ロール更新 |
| プロフィール更新 | users_profiles | UPDATE | プロフィール情報更新 |
| 購読設定削除 | mail_subscriptions | DELETE | 既存購読設定全削除 |
| 購読設定登録 | mail_subscriptions | INSERT | 新規購読設定登録 |

### テーブル別操作詳細

#### usersテーブル

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | user_id, user_alias, user_email, user_role等 | WHERE user_id = ? | JOINで取得 |
| UPDATE | user_alias, user_email, user_role | 入力値 | WHERE user_id = ? |

#### users_profilesテーブル

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | upro_first, upro_last, upro_country等 | WHERE upro_userid = user_id | JOINで取得 |
| UPDATE | upro_first, upro_last, upro_country, upro_gender, upro_dob, upro_organisation, upro_position, upro_address, upro_city, upro_phone, upro_postcode, upro_date | 入力値 / NOW() | WHERE upro_userid = ? |

#### mail_subscriptionsテーブル

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| DELETE | - | WHERE msub_user = ? | 全削除 |
| INSERT | msub_group, msub_user | グループID, ユーザーID | 購読リストごとに実行 |

#### mail_groupsテーブル

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | mgroup_id, mgroup_title, mgroup_description, mgroup_open | ORDER BY mgroup_title ASC | フィルタUI用 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 権限エラー | uusers/uview/uedit権限なし | privileges画面へフォワード |
| - | パラメータエラー | idパラメータ未指定 | "User Not Specified!"メッセージ表示 |
| - | データなし | 指定ユーザーが存在しない | manage画面へリダイレクト |
| - | バリデーションエラー | first/last/alias/email/role/countryが不正 | RenderMessagesでエラー表示 |
| - | 重複エラー | エイリアスまたはメールアドレスが他ユーザーと重複 | 該当フィールドにエラー表示 |

### リトライ仕様

特になし（ユーザー操作による再試行）

## トランザクション仕様

**注意**：現在の実装ではトランザクション管理が行われていない。
- usersテーブル更新、users_profilesテーブル更新、mail_subscriptions削除・登録が個別のクエリとして実行される
- 処理途中でエラーが発生した場合、データの整合性が損なわれる可能性がある

**推奨改善**：
- Zend_Db_Adapterのbegin Transaction/commit/rollbackを使用した一括処理

## パフォーマンス要件

- 編集画面表示時に3テーブルJOIN + メーリングリスト取得の2クエリを実行
- 保存処理時に2テーブルUPDATE + 購読数分のINSERT実行
- 大量の購読リストがある場合、INSERTの回数が増加

## セキュリティ考慮事項

- ACLによる権限チェック（uusers + uview/ueditリソース）
- Zend_Validate_Db_NoRecordExistsによる重複チェック時の自身除外
- Zend_Filter_Inputによる入力フィルタリング
- XSS対策：ビューでのエスケープ処理（$this->escape()）
- SQLインジェクション対策：Zend_Db_Selectによるプリペアドステートメント使用

## 備考

- タブ形式UI（User / Subscriptions）でDojo TabContainerを使用
- ユーザー詳細表示（detailsAction）をAjaxで編集画面内に読み込み
- メンバーシップタブは`user_member='Y'`の場合のみ表示
- 各メーリングリストの購読状態はMSubStatusAllヘルパーで判定

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

ユーザー関連テーブルとメーリングリスト関連テーブルの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | database.sql | `database.sql` | usersテーブル（347-361行目）、users_profiles（457-477行目）、mail_groups、mail_subscriptionsの定義 |

**読解のコツ**: users、users_profiles、mail_subscriptionsの関連を確認する。

#### Step 2: エントリーポイントを理解する

コントローラーのeditActionとsaveActionを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | UsersController.php | `application/modules/admin/controllers/UsersController.php` | editAction（513-554行目）、saveAction（345-507行目） |

**主要処理フロー（editAction）**:
1. **515行目**: uusers + uview権限チェック
2. **517行目**: setLayout()でadminレイアウト設定
3. **519行目**: idパラメータからuser_id取得
4. **525-530行目**: users + users_profiles + users_roles JOIN SELECTクエリ構築
5. **538-543行目**: mail_groups一覧取得
6. **545-547行目**: ユーザー未存在時のリダイレクト

**主要処理フロー（saveAction）**:
1. **347行目**: uusers + uedit権限チェック
2. **349-350行目**: レイアウト/ビュー無効化
3. **356-409行目**: バリデータ定義（重複チェック時に自身除外）
4. **419-426行目**: usersテーブル更新
5. **429-445行目**: users_profilesテーブル更新
6. **447-467行目**: mail_subscriptions削除と再登録

#### Step 3: ビューテンプレートを理解する

編集フォームのUI実装を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | edit.phtml | `application/modules/admin/views/scripts/users/edit.phtml` | ユーザー編集フォーム |

**主要処理フロー**:
- **39行目**: detailsResponse（Ajax読み込みでユーザー詳細表示）
- **43-274行目**: Userタブ（Identity/Personal/Contact/Organisation/Location fieldset）
- **280-295行目**: Subscriptionsタブ（メーリングリスト購読設定）
- **286-290行目**: foreachでメーリングリストをチェックボックスで表示

### プログラム呼び出し階層図

```
UsersController::editAction()
    │
    ├─ ACL::isAllowed() [権限チェック uusers+uview]
    │
    ├─ setLayout() [adminレイアウト設定]
    │
    ├─ Zend_Db_Select [ユーザー情報取得]
    │      └─ users + users_profiles + users_roles JOIN
    │
    ├─ Zend_Db_Select [メーリングリスト取得]
    │      └─ mail_groups
    │
    └─ View: edit.phtml
           ├─ Ajax: /admin/users/details
           ├─ URoleSelect() [ロール選択肢]
           ├─ countriesOptions() [国選択肢]
           └─ MSubStatusAll() [購読状態確認]

UsersController::saveAction()
    │
    ├─ ACL::isAllowed() [権限チェック uusers+uedit]
    │
    ├─ disableLayout() / setNoRender()
    │
    ├─ Zend_Filter_Input [バリデーション]
    │      ├─ Zend_Validate_NotEmpty
    │      ├─ Zend_Validate_Alnum
    │      ├─ Zend_Validate_EmailAddress
    │      └─ Zend_Validate_Db_NoRecordExists [重複チェック 自身除外]
    │
    ├─ $registry->db->update('users') [users更新]
    │
    ├─ $registry->db->update('users_profiles') [profiles更新]
    │
    ├─ $registry->db->delete('mail_subscriptions') [購読削除]
    │
    └─ $registry->db->insert('mail_subscriptions') [購読登録]
```

### データフロー図

```
[入力]                    [処理]                         [出力]

URLパラメータ ───────▶ UsersController        ───▶ ビューへ変数設定
(id)                     ├─ ACL権限チェック            ├─ userArray
                         └─ SELECTクエリ構築           └─ listsArray
                                   │
                                   ▼
                         edit.phtml ───▶ HTML出力
                                   │
                                   ▼ (フォーム送信)
POSTパラメータ ─────▶ UsersController::saveAction
(first,last,alias,       ├─ ACL権限チェック
 email,role,country,     ├─ Zend_Filter_Input
 gender,dob,...)         │
                         ├─ UPDATE users
                         ├─ UPDATE users_profiles
                         ├─ DELETE mail_subscriptions
                         └─ INSERT mail_subscriptions
                                   │
                                   ▼
                         Ajax応答 ───▶ "User Saved" + Closeボタン
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| UsersController.php | `application/modules/admin/controllers/UsersController.php` | コントローラー | ユーザー管理のリクエスト処理 |
| edit.phtml | `application/modules/admin/views/scripts/users/edit.phtml` | ビュー | 編集フォームテンプレート |
| details.phtml | `application/modules/admin/views/scripts/users/details.phtml` | ビュー | ユーザー詳細表示（Ajax読み込み） |
| URoleSelect.php | `application/modules/admin/views/helpers/URoleSelect.php` | ヘルパー | ロール選択肢生成 |
| countriesOptions.php | `application/modules/admin/views/helpers/CountriesOptions.php` | ヘルパー | 国選択肢生成 |
| MSubStatusAll.php | `application/modules/admin/views/helpers/MSubStatusAll.php` | ヘルパー | 購読状態確認 |
| database.sql | `database.sql` | スキーマ | users, users_profiles, mail_groups, mail_subscriptionsテーブル定義 |
