# 機能設計書 12-ページ複製

## 概要

本ドキュメントは、QuickerSite CMSにおけるページ複製機能の設計について記述する。この機能により、管理者は既存のページを元に新しいページを作成でき、コンテンツの再利用とサイト構築の効率化を実現する。

### 本機能の処理概要

ページ複製機能は、既存のページ情報をコピーして新規ページとして保存する機能である。リストページの場合は、そのページに含まれるリストアイテムも併せて複製される。

**業務上の目的・背景**：Webサイトの運用において、類似した構造や内容のページを複数作成する必要がある場面は多い。例えば、製品紹介ページやイベント告知ページなど、フォーマットが共通のページを複数作成する際に、毎回ゼロから作成するのは非効率である。本機能により、既存のページをテンプレートとして活用し、効率的にコンテンツを展開できる。

**機能の利用シーン**：
- 既存の製品ページを複製して新製品のページを作成する場合
- 過去のイベントページを元に新しいイベントページを作成する場合
- 複雑なレイアウトのページを再利用したい場合
- リストページ（ブログ、ニュースなど）の構造ごと複製したい場合

**主要な処理内容**：
1. 複製元ページの情報を取得
2. 新規ページとしてタイトルに「(copy)」を付加
3. ホームページフラグ、ユーザーフレンドリーURL、ページコードをリセット
4. 新規IDを割り当てて保存
5. リストページの場合、関連するリストアイテムも再帰的に複製

**関連システム・外部連携**：外部システムとの連携はなし。

**権限による制御**：セカンドアドミン権限の`bPagesAdd`により、ページ追加（複製含む）機能へのアクセスが制御される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 5 | バックサイトホーム | 主画面 | ページ一覧から複製アイコンをクリック |
| 149 | サイトコピー | 関連機能 | サイト全体のコピー機能 |

## 機能種別

CRUD操作（Create） / データ複製

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| iId | Integer（暗号化） | Yes | 複製元ページのID | 数値であること、存在するページIDであること |
| btnaction | String | Yes | アクション種別（Copy） | "Copy"であること |
| QSSEC | String | Yes | CSRFトークン | 有効なセキュリティコードであること |

### 入力データソース

- 画面入力：ページ一覧画面からのコピーアイコンクリック
- セッションデータ：ログイン管理者情報
- データベース：tblPageテーブルからの既存ページ情報

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 新規ページ | cls_page | 複製された新しいページオブジェクト |
| リダイレクト先 | URL | 処理完了後の遷移先（bs_default.asp または bs_intranet.asp） |

### 出力先

- 画面表示：処理完了後、ページ一覧画面にリダイレクト
- データベース：tblPageテーブルに新規レコードが挿入

## 処理フロー

### 処理シーケンス

```
1. CSRF検証
   └─ checkCSRF()関数でセキュリティトークンを検証
2. ページ情報取得
   └─ cls_pageクラスでiIdに基づいて複製元ページ情報をロード
3. 複製フラグ設定
   └─ bCopy=trueを設定
4. copy()メソッド呼び出し
   ├─ リストアイテムの取得（リストページの場合）
   ├─ iIdをnullに設定（新規レコード化）
   ├─ bHomepageをfalseに設定
   ├─ sUserFriendlyURLを空に設定
   ├─ sCodeを空に設定
   ├─ タイトルに「コピー」を付加
   ├─ save()で新規ページを保存
   └─ リストアイテムを再帰的に複製
5. メニューキャッシュのクリア
6. リダイレクト
   └─ イントラネットフラグに応じて適切な画面へ遷移
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B[CSRF検証]
    B --> C[複製元ページ情報取得]
    C --> D[bCopy = true 設定]
    D --> E[copy メソッド呼び出し]
    E --> F{リストページ?}
    F -->|Yes| G[リストアイテム一覧取得]
    F -->|No| H[iId = null 設定]
    G --> H
    H --> I[bHomepage = false]
    I --> J[sUserFriendlyURL = 空]
    J --> K[sCode = 空]
    K --> L[タイトルに コピー 付加]
    L --> M[save 実行]
    M --> N{保存成功?}
    N -->|No| O[エラー]
    N -->|Yes| P{リストアイテムあり?}
    P -->|Yes| Q[各リストアイテムを複製]
    Q --> R[リストアイテムのiListPageIDを新ページIDに設定]
    R --> S[リストアイテムのcopy呼び出し]
    S --> T[次のリストアイテム]
    T --> P
    P -->|No| U[メニューキャッシュクリア]
    U --> V[ページ一覧へリダイレクト]
    V --> W[終了]
    O --> W
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-12-01 | ホームページ設定リセット | 複製されたページはホームページとして設定されない | 常に適用 |
| BR-12-02 | ユーザーフレンドリーURLリセット | 複製されたページのユーザーフレンドリーURLは空になる | 常に適用 |
| BR-12-03 | ページコードリセット | 複製されたページのsCodeは空になる（重複防止） | 常に適用 |
| BR-12-04 | タイトル変更 | 複製されたページのタイトルに「コピー」が付加される | 常に適用 |
| BR-12-05 | リストアイテム連動複製 | リストページを複製した場合、関連するリストアイテムも全て複製される | リストページの場合 |
| BR-12-06 | 新規ランク付与 | 複製されたページは同一階層の最後尾に配置される | 常に適用 |

### 計算ロジック

**新規ランク計算**：
- 複製されたページは新規ページとして扱われ、同一階層の既存ページ数 + 1 のランクが設定される
- 計算式（save()メソッド内）: `iRang = getRang + 1`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ページ複製 | tblPage | INSERT | 新規ページレコードを挿入 |
| リストアイテム複製 | tblPage | INSERT | リストアイテムの新規レコードを挿入 |

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

#### tblPage

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | iId | 自動採番 | 新規ID |
| INSERT | sTitle | 元のタイトル + " (copy)" | 複製を示す接尾辞 |
| INSERT | bHomepage | false | ホームページ設定はリセット |
| INSERT | sUserFriendlyURL | 空文字 | 重複防止のためリセット |
| INSERT | sCode | 空文字 | 重複防止のためリセット |
| INSERT | iRang | 同一階層の最大ランク + 1 | 新規ランク |
| INSERT | createdTS | 現在日時 | 作成日時 |
| INSERT | updatedTS | 現在日時 | 更新日時 |
| INSERT | その他全カラム | 元ページの値をコピー | 内容の複製 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | CSRF検証エラー | セキュリティトークンが無効 | エラーメッセージを表示し処理中断 |
| - | ページ不存在エラー | 指定されたiIdに対応するページが存在しない | 処理をスキップ |
| - | 保存エラー | データベースへの保存に失敗 | エラーメッセージを表示 |

### リトライ仕様

リトライは実装されていない。エラー発生時は処理を中断し、ユーザーに再操作を促す。

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

本機能では明示的なトランザクション制御は行われていない。リストページの場合、親ページと各リストアイテムの保存は個別に実行される。全体の整合性を保証するためのロールバック機構は実装されていない。

## パフォーマンス要件

- レスポンス時間: 通常のページ複製で1秒以内
- リストページ複製: リストアイテム数に比例（1アイテムあたり約0.1秒）
- 大量のリストアイテムを持つページの複製では、処理時間が長くなる可能性がある

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

1. **CSRF対策**: checkCSRF()によるトークン検証を実施
2. **権限チェック**: セカンドアドミン権限（bPagesAdd）によるアクセス制御
3. **パラメータ暗号化**: ページIDはencrypt/decrypt関数で暗号化して受け渡し
4. **確認ダイアログ**: JavaScriptで複製実行前に確認ダイアログを表示

## 備考

- 複製されたページのコンテンツ（sValue）は元のページと完全に同一
- 画像やファイルへの参照パスはそのまま複製される（ファイル自体は複製されない）
- 複製されたページは編集状態で保存されるため、公開状態（bOnline）は元ページの設定が継承される
- メニューキャッシュは複製後にクリアされ、次回アクセス時に再構築される

---

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

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

### 推奨読解順序

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

ページオブジェクトの構造と複製に関連するプロパティを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | page.asp | `asp/includes/page.asp` | cls_pageクラスのプロパティ定義（4-11行目）、特にbCopy、sCode、sUserFriendlyURL |

**読解のコツ**: bCopyプロパティはコピー処理中のフラグとして使用され、新規ページ保存時の特別な処理（ユーザーフレンドリーURL自動生成のスキップなど）を制御する。

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

複製アクションの起点となる処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | bs_process.asp | `asp/bs_process.asp` | Copyアクションの処理（65-73行目） |

**主要処理フロー**:
1. **65行目**: Copyアクションのcase文
2. **66行目**: checkCSRF()によるトークン検証
3. **67行目**: bCopy=trueの設定
4. **68行目**: copy()メソッドの呼び出し
5. **69-73行目**: イントラネットフラグに応じたリダイレクト処理

#### Step 3: 複製ロジックを理解する

copy()メソッドの詳細な実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | page.asp | `asp/includes/page.asp` | copy()関数の実装（960-977行目） |

**主要処理フロー**:
- **961行目**: iIdの存在チェック
- **962-963行目**: リストアイテムの取得（listitems(false)）
- **964行目**: iIdをnullに設定（新規レコード化）
- **965行目**: bHomepageをfalseに設定
- **966行目**: sUserFriendlyURLを空文字に設定
- **967行目**: sCodeを空文字に設定
- **968行目**: タイトルに「コピー」を付加
- **969行目**: save()呼び出し
- **970-973行目**: リストアイテムの再帰的複製
- **975行目**: メニューキャッシュクリア

#### Step 4: メニュー表示との連携を理解する

複製アイコンがどのように表示されるかを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | menu.asp | `asp/includes/menu.asp` | getBOMenu関数内のコピーアイコン生成（189-190行目） |

**主要処理フロー**:
- **189行目**: secondAdmin.bPagesAdd権限チェック
- **190行目**: コピーアイコンのHTML生成、confirmダイアログ付きリンク

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

```
bs_default.asp（コピーアイコンクリック）
    │
    ├─ bs_process.asp（Copyアクション）
    │      ├─ checkCSRF()
    │      ├─ cls_page.pick()
    │      ├─ bCopy = true
    │      └─ cls_page.copy()
    │             ├─ listitems(false) [リストアイテム取得]
    │             ├─ iId = null
    │             ├─ bHomepage = false
    │             ├─ sUserFriendlyURL = ""
    │             ├─ sCode = ""
    │             ├─ sTitle = sTitle & " (copy)"
    │             ├─ save()
    │             │      └─ db.execute() [INSERT into tblPage]
    │             ├─ [リストアイテムのループ]
    │             │      └─ リストアイテム.copy()
    │             └─ clearMenuCache()
    │
    └─ リダイレクト（bs_default.asp / bs_intranet.asp）
```

### データフロー図

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

ページID(iId)    ───▶  bs_process.asp           ───▶  新規ページ（tblPage INSERT）
Copyアクション   ───▶  (Copyアクション判定)      ───▶  メニューキャッシュクリア
CSRFトークン     ───▶  cls_page.copy()          ───▶  リダイレクトURL
                        │
                        ▼
                 tblPage
                 (SELECT → INSERT)
                        │
                        ▼
                 リストアイテム複製
                 (ループ処理)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| bs_process.asp | `asp/bs_process.asp` | ソース | Copyアクションのルーティング（65-73行目） |
| page.asp | `asp/includes/page.asp` | ソース | copy()メソッド、save()メソッドの実装 |
| menu.asp | `asp/includes/menu.asp` | ソース | コピーアイコンの表示制御 |
| bs_default.asp | `asp/bs_default.asp` | ソース | ページ一覧画面（操作元・リダイレクト先） |
| bs_intranet.asp | `asp/bs_intranet.asp` | ソース | イントラネット画面（リダイレクト先） |
| bs_listPage.asp | `asp/bs_listPage.asp` | ソース | リストページ画面（リストアイテムのコピー機能） |
