# 機能設計書 7-記事編集

## 概要

本ドキュメントは、LEGACY CMSにおける記事編集機能の設計を記述したものである。既存記事の内容を編集・更新する仕様を定義する。

### 本機能の処理概要

**業務上の目的・背景**：作成済みの記事を修正・更新するための機能である。誤字脱字の修正、内容の追加・変更、カテゴリの変更など、記事のライフサイクル全般にわたって使用される重要な機能である。公開済みの記事を更新した場合は、検索インデックスも自動的に更新される。

**機能の利用シーン**：管理者またはコンテンツ編集者が記事一覧画面から編集対象の記事を選択し、編集画面で内容を修正する。タイトル、本文、ティーザー（導入文）、カテゴリ、コメント設定、スティッキー設定などを変更可能。

**主要な処理内容**：
1. 記事編集画面の表示（記事データの取得）
2. 編集内容の入力受付
3. 入力値のバリデーション
4. 記事レコードの更新
5. 公開済み記事の場合は検索インデックスの更新

**関連システム・外部連携**：Zend_Search_Luceneを使用した検索インデックスの更新（公開済み記事の場合）。

**権限による制御**：`aarticles`（記事モジュールアクセス）および`aarticleedit`（記事編集）権限が必要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 36 | 記事編集画面 | 主画面 | 記事の編集・更新処理 |

## 機能種別

CRUD操作（Update） / データ入力

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | int | Yes | 記事ID（URL） | isset, is_numeric |
| title | string | Yes | 記事タイトル | NotEmpty |
| category | int | Yes | カテゴリID | NotEmpty |
| introduction | string | Yes | ティーザー（導入文） | NotEmpty |
| content | string | No | 本文 | allowEmpty |
| comments | string | No | コメント許可フラグ（Y/N） | allowEmpty |
| moderate | string | No | コメント承認制フラグ（Y/N） | allowEmpty |
| sticky | string | No | スティッキー（固定表示）フラグ（Y/N） | allowEmpty |

### 入力データソース

- URLパラメータ（id）
- フォーム入力（POSTリクエスト）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| articleArray | array | 編集対象の記事データ |
| HTML | string | Ajaxダイアログ用HTML（保存結果） |

### 出力先

- 画面表示（編集フォーム）
- データベース更新（articlesテーブル）
- 検索インデックス更新（公開済み記事の場合）
- Ajaxダイアログ（保存結果）

## 処理フロー

### 処理シーケンス

```
1. 権限チェック
   └─ aarticles + aarticleedit 権限を確認
2. 記事IDの取得
3. 記事データの取得
   └─ fetchArticle()で記事情報を取得
4. 記事存在チェック
   └─ 存在しない場合は一覧へリダイレクト
5. 編集フォームの表示
   └─ 取得した記事データでフォームを初期化

--- 保存処理（saveAction） ---
6. POSTデータのバリデーション
7. 記事レコードの更新
   └─ updateArticle()でDBを更新
8. 検索インデックスの更新
   └─ 公開済み記事の場合のみ
9. 保存完了ダイアログの表示
```

### フローチャート

```mermaid
flowchart TD
    A[記事編集リクエスト] --> B{権限チェック}
    B -->|権限なし| C[権限エラー画面]
    B -->|権限あり| D[記事ID取得]
    D --> E[記事データ取得]
    E --> F{記事存在?}
    F -->|No| G[記事一覧へリダイレクト]
    F -->|Yes| H[編集フォーム表示]
    H --> I[ユーザー編集]
    I --> J[保存リクエスト]
    J --> K[バリデーション]
    K --> L{バリデーション成功?}
    L -->|No| M[エラーメッセージ表示]
    L -->|Yes| N[記事更新]
    N --> O{公開済み?}
    O -->|Yes| P[検索インデックス更新]
    O -->|No| Q[保存完了ダイアログ]
    P --> Q
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | 編集日時自動更新 | article_editに現在日時を設定 | 保存時 |
| BR-002 | フラグデフォルト値 | コメント/モデレート/スティッキーは未設定時'N' | 保存時 |
| BR-003 | 検索インデックス更新 | 公開済み記事は検索インデックスも更新 | article_status='published'の場合 |
| BR-004 | HTMLエンティティデコード | 本文とティーザーはhtml_entity_decodeして保存 | 保存時 |

### 計算ロジック

検索インデックス用URL生成: `/articles/article/{id}/{urlencode(title)}/`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 記事取得 | articles, articles_categories, users | SELECT | 編集対象の記事を取得 |
| 記事更新 | articles | UPDATE | 記事内容を更新 |

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

#### articles（SELECT）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | article_id | パラメータのidと一致 | 検索条件 |
| SELECT | 全カラム | - | 編集フォーム初期化用 |

#### articles（UPDATE）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | article_title | 入力されたタイトル | 必須 |
| UPDATE | article_category | 選択されたカテゴリID | 必須 |
| UPDATE | article_intro | html_entity_decode(入力値) | 必須 |
| UPDATE | article_content | html_entity_decode(入力値) | 任意 |
| UPDATE | article_comments | 'Y' または 'N' | デフォルト'N' |
| UPDATE | article_moderate | 'Y' または 'N' | デフォルト'N' |
| UPDATE | article_sticky | 'Y' または 'N' | デフォルト'N' |
| UPDATE | article_edit | NOW() | 自動設定 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 権限エラー | aarticles または aarticleedit 権限がない | 権限エラー画面へフォワード |
| - | 記事未存在 | 指定IDの記事が存在しない | 記事一覧へリダイレクト |
| IS_EMPTY (title) | バリデーションエラー | タイトル未入力 | 'Title is required'を表示 |
| IS_EMPTY (category) | バリデーションエラー | カテゴリ未選択 | 'Category is required'を表示 |
| IS_EMPTY (introduction) | バリデーションエラー | ティーザー未入力 | 'Teaser is required'を表示 |
| - | ID未指定 | 記事IDがパラメータにない | 'Article Not Specified!'を表示 |

### リトライ仕様

特になし

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

明示的なトランザクション制御は実装されていない。UPDATE後に検索インデックス更新が失敗した場合、整合性が崩れる可能性がある。

## パフォーマンス要件

- レスポンス時間: 1秒以内（検索インデックス更新含む）

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

- 管理者権限（aarticles + aarticleedit）が必要
- html_entity_decodeを使用（XSS対策はビュー側で必要）
- SQLインジェクション対策: プレースホルダを使用

## 備考

- FCKEditorを使用したリッチテキスト編集
- Ajaxダイアログで保存結果を表示

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | database.sql | `database.sql` | articlesテーブル（5-20行目）の全カラムを確認 |

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ArticlesController.php | `application/modules/admin/controllers/ArticlesController.php` | editAction()（55-73行目）、saveAction()（123-212行目） |

**主要処理フロー（editAction）**:
1. **57行目**: 権限チェック（`aarticles` + `aarticleedit`）
2. **59行目**: 記事IDの取得
3. **61-62行目**: Articlesモデルで記事データ取得
4. **64-66行目**: 記事存在チェック

**主要処理フロー（saveAction）**:
1. **125行目**: 権限チェック
2. **138-158行目**: バリデータの設定
3. **160行目**: バリデーション実行
4. **162-173行目**: 記事更新

#### Step 3: モデル層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | Articles.php | `application/models/Articles.php` | fetchArticle()（181-196行目）、updateArticle()（314-369行目） |

**主要処理フロー**:
- **181-196行目**: fetchArticle() - JOINで記事詳細を取得
- **314-369行目**: updateArticle() - 記事更新と検索インデックス更新

#### Step 4: 検索インデックス更新を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | Search.php | `application/models/Search.php` | updateEntry()（116-124行目） |

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

```
Admin_ArticlesController::editAction()
    │
    ├─ ACL権限チェック
    │
    └─ Articles::fetchArticle($id)
           └─ SELECT articles JOIN articles_categories JOIN users

Admin_ArticlesController::saveAction()
    │
    ├─ ACL権限チェック
    │
    ├─ Zend_Filter_Input (バリデーション)
    │
    └─ Articles::updateArticle($params)
           │
           ├─ UPDATE articles
           │
           └─ (公開済みの場合)
                  └─ Search::updateEntry($params)
                         ├─ Search::deleteEntry($key)
                         └─ Search::createEntry($params)
```

### データフロー図

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

id (記事ID)    ───▶ fetchArticle()       ───▶ articleArray
                    (記事データ取得)           (編集フォーム表示)

title, category,
introduction,  ───▶ Zend_Filter_Input    ───▶ 検証済みデータ
content, etc.       (バリデーション)
                           │
                           ▼
                    updateArticle()
                           │
                    ┌──────┴──────┐
                    ▼             ▼
              UPDATE articles   (公開済みの場合)
                                Search::updateEntry()
                                      │
                                      ▼
                                Luceneインデックス
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ArticlesController.php | `application/modules/admin/controllers/ArticlesController.php` | コントローラー | 編集処理（55-73行目: editAction, 123-212行目: saveAction） |
| Articles.php | `application/models/Articles.php` | モデル | 記事データの取得・更新（181-196行目: fetchArticle, 314-369行目: updateArticle） |
| Search.php | `application/models/Search.php` | モデル | 検索インデックス更新（116-124行目: updateEntry） |
| database.sql | `database.sql` | DDL | articlesテーブル定義 |
