# 機能設計書 73-お問い合わせフォーム

## 概要

本ドキュメントは、Shopwareストアフロントにおけるお問い合わせフォーム機能の設計を記載する。顧客がショップに対してお問い合わせメッセージを送信できる機能であり、ニュースレター購読管理機能も含む。

### 本機能の処理概要

お問い合わせフォーム機能は、顧客がECサイトの運営者に対してお問い合わせを行うための機能である。商品に関する質問、注文に関する問い合わせ、サポートリクエストなど、様々な用途で利用される。また、ニュースレターの購読・解除も同じコントローラーで処理される。

**業務上の目的・背景**：ECサイトにおいて、顧客とショップ運営者のコミュニケーションは顧客満足度向上の重要な要素である。お問い合わせフォームは電話やメールに代わる主要なコミュニケーション手段として、24時間受付可能で、内容を記録に残せるメリットがある。また、ニュースレター機能はマーケティング活動の基盤となる。

**機能の利用シーン**：
- 顧客が商品について質問を送信する
- 顧客が注文に関するサポートを依頼する
- 顧客がショップへのフィードバックを送信する
- 顧客がニュースレターに登録する
- 顧客がニュースレターの購読を解除する

**主要な処理内容**：
1. フォーム入力データのバリデーション
2. CAPTCHA検証（設定されている場合）
3. レート制限の確認
4. 受信者メールアドレスの特定
5. お問い合わせイベントの発行（メール送信トリガー）
6. 成功/エラーレスポンスの返却

**関連システム・外部連携**：
- メール送信サービス（イベント経由）
- CMS（お問い合わせフォームの配置）
- CAPTCHA検証サービス
- レート制限サービス

**権限による制御**：
- 認証不要：誰でもお問い合わせフォームを送信可能
- CAPTCHA：ボット防止のために設定可能
- レート制限：同一IPからの過剰なリクエストを制限

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 26 | CMSページ（ウィジェット） | 主画面 | お問い合わせフォームウィジェットを配置 |
| 27 | CMSページ（フル） | 主画面 | お問い合わせ専用ページ |

## 機能種別

データ連携 / バリデーション

## 入力仕様

### 入力パラメータ

#### お問い合わせフォーム

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| salutationId | string (UUID) | Yes | 敬称ID | UUIDフォーマット、存在確認 |
| firstName | string | Yes | 名 | 空文字不可 |
| lastName | string | Yes | 姓 | 空文字不可 |
| email | string | Yes | メールアドレス | メールアドレス形式 |
| phone | string | No | 電話番号 | 文字列 |
| subject | string | Yes | 件名 | 空文字不可 |
| comment | string | Yes | お問い合わせ内容 | 空文字不可 |
| slotId | string (UUID) | No | CMSスロットID | UUIDフォーマット |
| navigationId | string (UUID) | No | ナビゲーションID | UUIDフォーマット |
| entityName | string | No | エンティティ種別 | product/landing_page/category |

#### ニュースレター

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| option | string | Yes | 操作種別 | subscribe/unsubscribe |
| email | string | Yes | メールアドレス | メールアドレス形式 |
| storefrontUrl | string | No | ストアフロントURL | 自動設定 |

### 入力データソース

- POSTリクエストボディ：フォーム入力値
- セッション：CAPTCHA検証用
- SalesChannelContext：販売チャネル情報、顧客情報

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| type | string | メッセージタイプ（success/danger/info） |
| alert | string | 表示メッセージまたはHTML |

### 出力先

JSONレスポンス（フロントエンドでアラート表示）

## 処理フロー

### 処理シーケンス（お問い合わせ）

```
1. リクエスト受信
   └─ FormController::sendContactForm がPOSTリクエストを受け取る

2. CAPTCHA検証
   └─ ルート属性で ATTRIBUTE_CAPTCHA = true が設定されている

3. バリデーション
   └─ contactFormValidationFactory で入力値を検証

4. レート制限確認
   └─ RateLimiter で同一IPからの送信頻度を確認

5. 受信者特定
   ├─ slotIdとnavigationIdから受信者を取得
   ├─ CMSスロットの設定から受信者を取得
   └─ フォールバック：システム設定のメールアドレス

6. イベント発行
   └─ ContactFormEvent をディスパッチ（メール送信トリガー）

7. レスポンス生成
   └─ 成功メッセージまたはエラーメッセージをJSON返却
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B{CAPTCHA有効?}
    B -->|Yes| C[CAPTCHA検証]
    B -->|No| D[バリデーション]
    C -->|成功| D
    C -->|失敗| E[エラーレスポンス]
    D -->|成功| F[レート制限確認]
    D -->|失敗| G[バリデーションエラー]
    F -->|OK| H[受信者メール取得]
    F -->|制限中| I[レート制限エラー]
    H --> J[ContactFormEventディスパッチ]
    J --> K[成功レスポンス]
    G --> E
    I --> E
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-73-01 | 認証不要 | お問い合わせフォームはログイン不要で利用可能 | 全てのお問い合わせ |
| BR-73-02 | CAPTCHA | ボット防止のためCAPTCHA検証を設定可能 | CAPTCHA有効時 |
| BR-73-03 | レート制限 | 同一IPからの過剰な送信を制限 | 全てのお問い合わせ |
| BR-73-04 | 受信者優先順位 | 1.スロット設定→2.CMS設定→3.システム設定の順で受信者を特定 | お問い合わせ送信時 |
| BR-73-05 | メールIDN変換 | 国際化ドメイン名(IDN)のメールアドレスをエンコード | メールアドレス処理時 |
| BR-73-06 | カスタム成功メッセージ | CMSスロット設定で成功メッセージをカスタマイズ可能 | お問い合わせ成功時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 敬称取得 | salutation | SELECT | salutationIdで敬称を取得 |
| CMSスロット取得 | cms_slot | SELECT | 受信者設定を取得 |
| カテゴリ取得 | category | SELECT | スロット設定を取得 |
| ランディングページ取得 | landing_page | SELECT | スロット設定を取得 |
| 商品取得 | product | SELECT | スロット設定を取得 |

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

#### cms_slot

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, config (mailReceiver, confirmationText) | WHERE id = :slotId | 受信者とメッセージ取得 |

#### salutation

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | WHERE id = :salutationId | 敬称情報取得 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ConstraintViolationException | バリデーションエラー | 入力値が不正 | エラーメッセージ一覧を表示 |
| RateLimitExceededException | レート制限エラー | 送信頻度が制限を超過 | 待機時間を表示 |
| CAPTCHA_INVALID | CAPTCHA エラー | CAPTCHA検証に失敗 | CAPTCHAをリロードして再入力を促す |

### リトライ仕様

- レート制限：待機時間経過後に再送信可能
- CAPTCHA失敗：新しいCAPTCHAを取得して再入力

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

- 読み取り専用の処理が主でトランザクション不要
- ニュースレター登録時のみDB更新が発生（別ルートで処理）

## パフォーマンス要件

- フォーム送信：2秒以内
- メール送信：非同期処理（即時レスポンス返却）

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

- **CAPTCHA**：ボットによる自動送信を防止
- **レート制限**：DoS攻撃やスパム送信を防止
- **入力検証**：XSS、SQLインジェクション対策
- **メールIDN変換**：国際化ドメイン名の適切なエンコード
- **受信者保護**：受信者メールアドレスはフロントエンドに公開しない

## 備考

- メール送信はContactFormEventを通じて非同期で実行される
- CMSで複数のお問い合わせフォームを異なるページに配置可能
- 各フォームで異なる受信者を設定可能

---

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

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

### 推奨読解順序

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

まず、お問い合わせフォームのリクエストデータとイベント構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ContactFormEvent.php | `src/Core/Content/ContactForm/Event/ContactFormEvent.php` | イベントの構造（受信者、送信者、データ） |
| 1-2 | ContactFormRouteResponse.php | `src/Core/Content/ContactForm/SalesChannel/ContactFormRouteResponse.php` | レスポンスの構造 |

**読解のコツ**: ContactFormEventはメール送信のトリガーとなるイベント。MailRecipientStructで受信者を定義。

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

処理の起点となるコントローラーを特定。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | FormController.php | `src/Storefront/Controller/FormController.php` | ストアフロント側のエントリーポイント |

**主要処理フロー**:
1. **44-52行目**: ルート定義（`/form/contact`）
2. **47-49行目**: XmlHttpRequest、CAPTCHA属性の設定
3. **53-93行目**: `sendContactForm`メソッド
4. **57-69行目**: 正常系処理（contactFormRoute呼び出し、成功メッセージ）
5. **70-90行目**: 例外ハンドリング（バリデーションエラー、レート制限）

#### Step 3: Store-APIルートを理解する

実際のお問い合わせ処理ロジックはStore-APIルートに実装されている。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | AbstractContactFormRoute.php | `src/Core/Content/ContactForm/SalesChannel/AbstractContactFormRoute.php` | 抽象クラス定義 |
| 3-2 | ContactFormRoute.php | `src/Core/Content/ContactForm/SalesChannel/ContactFormRoute.php` | 実装クラス |

**主要処理フロー**:
- **66-114行目**: `load`メソッド
- **69行目**: メールIDN変換
- **71行目**: バリデーション
- **73-75行目**: レート制限確認
- **77行目**: メール設定取得
- **79-84行目**: 敬称取得
- **86-88行目**: 受信者のフォールバック処理
- **96-106行目**: ContactFormEventのディスパッチ

#### Step 4: ニュースレター処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | FormController.php | `src/Storefront/Controller/FormController.php`（95-115行目） | ニュースレターハンドリング |

**主要処理フロー**:
- **95-115行目**: `handleNewsletter`メソッド
- **106行目**: optionによる分岐（subscribe/unsubscribe）
- **120-162行目**: `handleSubscribe`メソッド
- **167-194行目**: `handleUnsubscribe`メソッド

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

```
FormController::sendContactForm (Storefront)
    │
    ├─ ContactFormRoute::load (Store-API)
    │      │
    │      ├─ EmailIdnConverter::encodeDataBag
    │      │      └─ IDNメールアドレスのエンコード
    │      │
    │      ├─ validateContactForm
    │      │      └─ contactFormValidationFactory::create
    │      │
    │      ├─ RateLimiter::ensureAccepted
    │      │      └─ IPアドレスによるレート制限
    │      │
    │      ├─ getMailConfigs
    │      │      ├─ getSlotConfig (Category/LandingPage/Product)
    │      │      └─ cmsSlotRepository::search
    │      │
    │      ├─ salutationRepository::search
    │      │
    │      └─ eventDispatcher::dispatch(ContactFormEvent)
    │             └─ メール送信サービスへ連携
    │
    └─ 例外ハンドリング
           ├─ ConstraintViolationException → バリデーションエラー表示
           └─ RateLimitExceededException → 待機時間表示
```

### データフロー図

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

salutationId ───────┐
firstName ──────────┤
lastName ───────────┤
email ──────────────┼──▶ FormController    ───▶ ContactFormRoute
phone ──────────────┤         │                         │
subject ────────────┤         │                         ▼
comment ────────────┤         │                  バリデーション
slotId ─────────────┤         │                         │
navigationId ───────┘         │                         ├──▶ RateLimiter
                              │                         │
                              │                         ├──▶ DB: cms_slot, salutation
                              │                         │      (SELECT)
                              │                         │
                              │                         └──▶ ContactFormEvent
                              ▼                                    │
                         JsonResponse ◀───────────────────────────┘
                    (success/danger/info)              メール送信キュー
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| FormController.php | `src/Storefront/Controller/FormController.php` | コントローラー | ストアフロントのエントリーポイント |
| AbstractContactFormRoute.php | `src/Core/Content/ContactForm/SalesChannel/AbstractContactFormRoute.php` | 抽象クラス | ルートの抽象定義 |
| ContactFormRoute.php | `src/Core/Content/ContactForm/SalesChannel/ContactFormRoute.php` | ルート | Store-APIルート実装 |
| ContactFormEvent.php | `src/Core/Content/ContactForm/Event/ContactFormEvent.php` | イベント | メール送信トリガー |
| ContactFormRouteResponse.php | `src/Core/Content/ContactForm/SalesChannel/ContactFormRouteResponse.php` | レスポンス | レスポンス構造 |
| ContactFormRouteResponseStruct.php | `src/Core/Content/ContactForm/SalesChannel/ContactFormRouteResponseStruct.php` | DTO | レスポンスデータ構造 |
| EmailIdnConverter.php | `src/Core/Checkout/Customer/Service/EmailIdnConverter.php` | サービス | IDNメールアドレス変換 |
| RateLimiter.php | `src/Core/Framework/RateLimiter/RateLimiter.php` | サービス | レート制限 |
| AbstractNewsletterSubscribeRoute.php | `src/Core/Content/Newsletter/SalesChannel/AbstractNewsletterSubscribeRoute.php` | ルート | ニュースレター購読 |
| AbstractNewsletterUnsubscribeRoute.php | `src/Core/Content/Newsletter/SalesChannel/AbstractNewsletterUnsubscribeRoute.php` | ルート | ニュースレター解除 |
