# 通知設計書 10-フォーラム投稿通知（テーマ購読者向け）

## 概要

本ドキュメントは、QuickerSiteシステムにおけるフォーラム投稿通知機能（テーマ購読者向け）の設計を記述する。フォーラム（テーマ/トピック）に新しい投稿やコメントがあった際に、購読者にメールで通知する機能である。

### 本通知の処理概要

フォーラム投稿通知は、テーマ（フォーラム）またはトピックを購読しているユーザーに対して、新しい投稿やコメント（返信）があった際に自動的にメール通知を送信する機能である。テーマ購読者には新規トピック作成時に、トピック購読者には新規コメント作成時に通知される。

**業務上の目的・背景**：フォーラムやブログのコミュニティ活動を活性化するため。ユーザーが興味のあるテーマやトピックをフォローし、新しい投稿を見逃さないようにする。能動的にフォーラムをチェックする必要をなくし、ユーザーエンゲージメントを向上させる。

**通知の送信タイミング**：新しいトピックまたはコメント（返信）が保存（Save）された直後。isNewフラグがtrueの場合のみ送信される。投稿の更新時（編集時）には送信されない。

**通知の受信者**：
- テーマ購読者（tblThemeSubscription）：新規トピック作成時に通知
- トピック購読者（tblThemeTopicSubscription）：新規コメント作成時に通知
- 投稿者自身は除外される（iContactID<>logon.contact.iId）
- sEmailが空でないユーザーのみ対象

**通知内容の概要**：投稿者のニックネーム、投稿件名、投稿本文、投稿へのリンク。テーマ設定のテンプレート（sBodyNotification/sTopicBodyNotification）を使用。

**期待されるアクション**：受信者はメール内のリンクをクリックしてフォーラムにアクセスし、新しい投稿を確認する。必要に応じて返信やコメントを行う。

## 通知種別

メール

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（ループ処理） |
| 優先度 | 中 |
| リトライ | 無 |

### 送信先決定ロジック

#### テーマ購読者への通知（新規トピック作成時）

1. iSubLevel=QS_theme_sublevel_theme（テーマレベル購読許可）の場合のみ
2. tblThemeSubscriptionから該当テーマの購読者を取得
3. 投稿者自身を除外（iContactID<>logon.contact.iId）
4. sEmailが空でないユーザーに送信

#### トピック購読者への通知（新規コメント作成時）

1. iSubLevel>QS_theme_sublevel_none（トピックレベル購読許可）の場合
2. iPostID<>0（返信である）の場合のみ
3. tblThemeTopicSubscriptionから該当トピックの購読者を取得
4. 投稿者自身を除外（iContactID<>logon.contact.iId）
5. sEmailが空でないユーザーに送信

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | システムデフォルト（customer.webmasterEmail） |
| 送信元名称 | システムデフォルト（customer.siteName） |
| 件名 | theme.sSubjectNotification（コメント通知）/ theme.sTopicSubjectNotification（トピック通知） |
| 形式 | HTML |

### 本文テンプレート（コメント通知）

```html
<p>You are receiving this email because you requested to be notified when somebody responded to the topic <em>[QS_theme:postsubject]</em>.</p>
<p><strong>[QS_theme:Replyer]</strong> has replied.</p>
<p><i>[QS_theme:reply]</i></p>
<p>Click the link below or paste it into your browser to read the reply online.</p>
<p><a href='[QS_theme:postlink]'>[QS_theme:postlink]</a></p>
```

### 本文テンプレート（トピック通知）

```html
<p>You are receiving this email because you requested to be notified when somebody posted a new topic in the theme <em>[QS_theme:Name]</em>.</p>
<p><strong>[QS_theme:Poster]</strong> has posted the topic <em>[QS_theme:postsubject]</em>.</p>
<p><i>[QS_theme:post]</i></p>
<p>Click the link below or paste it into your browser to read the topic online.</p>
<p><a href='[QS_theme:postlink]'>[QS_theme:postlink]</a></p>
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | フォーラム通知には添付ファイルを含まない |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| [QS_theme:postsubject] | 投稿件名 | sSubject / parentTopic.sSubject | Yes |
| [QS_theme:Replyer] | コメント投稿者 | sAnName / logon.contact.sNickname | Yes |
| [QS_theme:Poster] | トピック投稿者 | sAnName / logon.contact.sNickname | Yes |
| [QS_theme:reply] | コメント本文 | sBody + sFileLink | Yes |
| [QS_theme:post] | トピック本文 | sBody + sFileLink | Yes |
| [QS_theme:postlink] | 投稿へのリンク | prepareLink() | Yes |
| [QS_theme:Name] | テーマ名 | theme.sName | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| データ更新 | 新規トピック保存 | isNew=true, iPostID=0, iSubLevel=theme | テーマ購読者に通知 |
| データ更新 | 新規コメント保存 | isNew=true, iPostID<>0, iSubLevel>none | トピック購読者に通知 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| isNew=false | 投稿の更新時は通知しない |
| iSubLevel=none | 購読が無効なテーマでは通知しない |
| 投稿者自身 | 自分の投稿は自分に通知しない |
| sEmailが空 | メールアドレスが設定されていないユーザーはスキップ |
| テーマ購読者へのトピック通知 | テーマを購読済みの場合はトピック購読通知を除外 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[投稿保存 post.Save] --> B{isNew?}
    B -->|No| Z[通知なし]
    B -->|Yes| C{iSubLevel?}
    C -->|none| Z
    C -->|theme| D[テーマ購読者取得]
    D --> E[購読者ループ]
    E --> F{投稿者自身?}
    F -->|Yes| G[スキップ]
    F -->|No| H{sEmail有効?}
    H -->|No| G
    H -->|Yes| I{iPostID=0?}
    I -->|Yes| J[トピック通知送信]
    I -->|No| K[コメント通知送信]
    J --> L[次の購読者へ]
    K --> L
    G --> L
    L --> M{次の購読者?}
    M -->|Yes| E
    M -->|No| N{iSubLevel>none?}
    C -->|topic以上| N
    N -->|No| Z
    N -->|Yes| O{iPostID<>0?}
    O -->|No| Z
    O -->|Yes| P[トピック購読者取得]
    P --> Q[購読者ループ]
    Q --> R{投稿者自身?}
    R -->|Yes| S[スキップ]
    R -->|No| T{sEmail有効?}
    T -->|No| S
    T -->|Yes| U[コメント通知送信]
    U --> V[次の購読者へ]
    S --> V
    V --> W{次の購読者?}
    W -->|Yes| Q
    W -->|No| X[モデレーター通知チェック]
    X --> Y[処理完了]
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| tblThemeSubscription | テーマ購読者の取得 | iThemeID, iContactID |
| tblThemeTopicSubscription | トピック購読者の取得 | iPostID, iContactID |
| tblContact | 購読者の連絡先取得 | sEmail |
| tblTheme | テーマ設定の取得 | 通知テンプレート、iSubLevel |
| tblPost | 投稿情報の取得 | sSubject, sBody |

### テーブル別参照項目詳細

#### tblThemeSubscription

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| iContactID | 購読者ID | WHERE iThemeID=対象テーマ |

#### tblThemeTopicSubscription

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| iContactID | 購読者ID | WHERE iPostID=対象トピック |

#### tblTheme

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| iSubLevel | 購読レベル設定 | 購読可否判定 |
| sSubjectNotification | コメント通知件名 | メール件名 |
| sBodyNotification | コメント通知本文 | メール本文 |
| sTopicSubjectNotification | トピック通知件名 | メール件名 |
| sTopicBodyNotification | トピック通知本文 | メール本文 |
| bForwardPostsToModerator | モデレーター転送フラグ | モデレーター通知 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| なし | - | フォーラム通知ではDBを更新しない |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 購読者取得エラー | DBアクセスエラー | エラー処理なし（通知スキップ） |
| 送信失敗 | SMTPエラー等 | エラー処理なし（次の購読者へ継続） |
| テンプレート変数エラー | 変数が見つからない | 空文字で置換 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0回（リトライなし） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし（同期処理） |
| 1日あたり上限 | 制限なし |

### 配信時間帯

投稿保存に連動するため、24時間いつでも送信可能。

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

- 投稿者除外：自分の投稿は自分に通知しない
- 購読制御：iSubLevelでテーマ/トピック単位の購読可否を制御
- 匿名投稿対応：sAnNameが設定されている場合はそちらを使用
- XSS対策：quotrep関数でHTMLエスケープ、filterJS関数でJS除去
- スマイリー変換：addSmilies関数でスマイリーをイメージに変換
- 検証待ち投稿：bNeedsToBeValidated=trueの場合は専用メッセージを追加

## 備考

- iSubLevelの定義：
  - QS_theme_sublevel_none (0)：購読無効
  - QS_theme_sublevel_authortopic：投稿者のみトピック購読可
  - QS_theme_sublevel_topic：全員トピック購読可
  - QS_theme_sublevel_theme：テーマ購読可
- テーマ購読者はトピック単位の購読から自動的に除外される
- モデレーター通知（bForwardPostsToModerator）は別途処理される
- アバター対応：customer.bUseAvatarsがtrueの場合、メール本文にアバター画像を含む
- 添付ファイルリンク：sFileLink関数でダウンロードリンクを生成

---

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

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

### 推奨読解順序

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

投稿保存時の通知送信処理。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | post.asp | `asp/includes/post.asp` | Save関数内の通知処理（行168-233） |

**主要処理フロー**:
1. **行170**: isNew判定（新規投稿のみ通知）
2. **行175-196**: テーマ購読者への通知（iSubLevel=theme）
3. **行176-180**: テーマ購読者取得SQL
4. **行182-195**: 購読者ループと送信処理
5. **行199-217**: トピック購読者への通知（iPostID<>0）
6. **行201-205**: トピック購読者取得SQL
7. **行207-216**: 購読者ループと送信処理
8. **行219-233**: モデレーター通知

#### Step 2: 通知テンプレート処理を理解する

テンプレート変数の置換処理。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | post.asp | `asp/includes/post.asp` | preparenotifyReply関数（行729-748） |
| 2-2 | post.asp | `asp/includes/post.asp` | preparenotifyTopic関数（行749-773） |

**主要処理フロー**:
- **行730-744**: コメント通知の変数置換
- **行750-768**: トピック通知の変数置換
- **行745-747, 770-772**: 検証待ちメッセージの追加

#### Step 3: 購読管理を理解する

テーマ・トピック購読の仕組み。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | theme.asp | `asp/includes/theme.asp` | subscribeToTheme関数（行697-726） |
| 3-2 | theme.asp | `asp/includes/theme.asp` | subscribeToTopic関数（行658-683） |

**主要処理フロー**:
- **行697-726**: テーマ購読登録（tblThemeSubscriptionにINSERT）
- **行658-683**: トピック購読登録（tblThemeTopicSubscriptionにINSERT）

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

```
post.Save() [新規投稿保存]
    |
    +-- isNew=true判定
           |
           +-- テーマ購読者通知（iSubLevel=theme）
           |      |
           |      +-- SQL: tblThemeSubscription JOIN tblContact
           |      |
           |      +-- 購読者ループ
           |             |
           |             +-- cls_mail_message作成
           |             |
           |             +-- preparenotifyTopic() [トピック通知]
           |             |      |
           |             |      +-- [QS_theme:Name]置換
           |             |      +-- [QS_theme:Poster]置換
           |             |      +-- [QS_theme:postsubject]置換
           |             |      +-- [QS_theme:post]置換
           |             |      +-- [QS_theme:postlink]置換
           |             |
           |             +-- preparenotifyReply() [コメント通知]
           |             |      |
           |             |      +-- [QS_theme:Replyer]置換
           |             |      +-- [QS_theme:postsubject]置換
           |             |      +-- [QS_theme:reply]置換
           |             |      +-- [QS_theme:postlink]置換
           |             |
           |             +-- send() [メール送信]
           |
           +-- トピック購読者通知（iSubLevel>none, iPostID<>0）
           |      |
           |      +-- SQL: tblThemeTopicSubscription JOIN tblContact
           |      |
           |      +-- 購読者ループ
           |             |
           |             +-- cls_mail_message作成
           |             +-- preparenotifyReply()
           |             +-- send()
           |
           +-- モデレーター通知（bForwardPostsToModerator）
                  |
                  +-- theme.contact.sEmail
                  +-- preparenotifyReply/Topic() + getVisitorDetails()
                  +-- send()
```

### データフロー図

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

投稿保存                   post.Save()
(sSubject, sBody,         |
 iThemeID, iPostID)  ->   |
                          v
                          isNew判定
                          |
                          v
tblTheme             ->   購読レベル判定
(iSubLevel)               |
                          v
tblThemeSubscription ->   テーマ購読者取得
tblContact                |
(sEmail)                  v
                          テンプレート置換
                          (preparenotifyTopic/Reply)
                          |
                          v
                          cls_mail_message.send()  ->  購読者メールボックス
                          |
                          v
tblThemeTopicSub     ->   トピック購読者取得
tblContact                |
(sEmail)                  v
                          テンプレート置換
                          (preparenotifyReply)
                          |
                          v
                          cls_mail_message.send()  ->  購読者メールボックス
                          |
                          v
theme.contact        ->   モデレーター通知
(sEmail)                  |
                          v
                          cls_mail_message.send()  ->  モデレーターメールボックス
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| post.asp | `asp/includes/post.asp` | ソース | 投稿クラス・通知送信処理 |
| theme.asp | `asp/includes/theme.asp` | ソース | テーマクラス・購読管理 |
| mail_message.asp | `asp/includes/mail_message.asp` | ソース | メール送信クラス定義 |
| contact.asp | `asp/includes/contact.asp` | ソース | コンタクトクラス定義 |
| logon.asp | `asp/includes/logon.asp` | ソース | ログオンクラス（認証情報） |
