# 画面設計書 42-CMS管理

## 概要

本ドキュメントは、Shopware 6 Administration（管理画面）における「CMS管理」画面の設計書です。この画面はショップページのレイアウト編集を行うためのドラッグ&ドロップエディタを提供するコンテンツ管理のメイン画面です。

### 本画面の処理概要

**業務上の目的・背景**：現代のECサイトでは、柔軟なページレイアウトとコンテンツ管理が必要不可欠です。技術的な知識がなくても、マーケティング担当者や店舗運営者がページを直感的に作成・編集できることが求められます。本画面は、CMSページ（ショッピングエクスペリエンス）の一覧管理、作成、編集機能を提供し、カテゴリページ、商品詳細ページ、ランディングページなど様々なタイプのレイアウトを管理します。

**画面へのアクセス方法**：管理画面にログイン後、左側メニューの「コンテンツ」→「ショッピングエクスペリエンス」をクリックすることでアクセスできます。URLパスは `/admin#/sw/cms/index` となります。`cms.viewer` 権限を持つユーザーのみがアクセス可能です。

**主要な操作・処理内容**：
1. CMSページ一覧の表示 - グリッドビューまたはリストビューでレイアウトを一覧表示
2. ページの検索 - キーワード検索によるレイアウトの絞り込み
3. ページタイプでのフィルタリング - カテゴリ、商品詳細、ランディングページなどタイプ別に絞り込み
4. レイアウトの作成 - 新規CMSページの作成
5. レイアウトの編集 - 既存レイアウトの編集（詳細画面へ遷移）
6. レイアウトの複製 - 既存レイアウトをコピーして新規作成
7. レイアウトの削除 - 未割当のレイアウトを削除
8. プレビュー画像の設定 - レイアウトのサムネイル画像を設定
9. デフォルトレイアウトの設定 - カテゴリ/商品ページのデフォルトレイアウトを指定
10. レイアウト名の変更 - インラインでの名前変更

**画面遷移**：
- 遷移元: ダッシュボード、メニュー
- 遷移先: CMS詳細画面（sw.cms.detail）、CMS作成画面（sw.cms.create）

**権限による表示制御**：
- `cms.viewer`: 閲覧権限 - 一覧の表示が可能
- `cms.creator`: 作成権限 - 新規作成ボタンが表示
- `cms.editor`: 編集権限 - 編集、名前変更、プレビュー変更が可能
- `cms.deleter`: 削除権限 - 削除ボタンが表示（未割当レイアウトのみ）

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 62 | CMSページ表示 | 主機能 | ショップページのレイアウト編集 |

## 画面種別

一覧（グリッドビュー/リストビュー切替可能）

## URL/ルーティング

- **パス**: `/admin#/sw/cms/index`
- **コンポーネント**: `sw-cms-list`
- **ルート名**: `sw.cms.index`
- **必要権限**: `cms.viewer`

## 入出力項目

| 項目名 | 種別 | 型 | 必須 | 説明 |
|--------|------|-----|------|------|
| term | 入力 | string | - | 検索キーワード |
| page | 入力 | number | - | ページ番号 |
| limit | 入力 | number | - | 1ページあたりの表示件数（グリッド: 9、リスト: 10） |
| sortBy | 入力 | string | - | ソート対象カラム（デフォルト: createdAt） |
| sortDirection | 入力 | string | - | ソート方向（デフォルト: DESC） |
| currentPageType | 入力 | string | - | フィルターするページタイプ |
| listMode | 入力 | string | - | 表示モード（grid/list） |

## 表示項目

| 項目名 | カラム名 | 説明 | 編集可否 |
|--------|----------|------|----------|
| ページ名 | name | レイアウトの名称 | string（インライン編集可） |
| ページタイプ | type | レイアウトの種類（カテゴリ、商品詳細など） | - |
| 割当数 | assignments | カテゴリ・商品・ランディングページへの割当総数 | - |
| 割当ページ名 | assignedPages | 割り当てられたページの名前一覧（デフォルト非表示） | - |
| 作成日時 | createdAt | レイアウト作成日時 | - |
| 更新日時 | updatedAt | レイアウト最終更新日時（デフォルト非表示） | - |
| プレビュー画像 | previewMedia | レイアウトのサムネイル画像（グリッドビュー時） | - |

## イベント仕様

### 1-ページ読み込み（createdComponent）

画面初期表示時にCMSページ一覧を取得する。

**処理フロー**:
1. サイドバーを折りたたむ（`adminMenu.collapseSidebar()`）
2. ユーザー設定から表示モード、ソート設定を読み込み
3. デフォルトレイアウト設定を取得（`getDefaultLayouts()`）
4. メディアフォルダIDを取得（`setPageContext()`）
5. `getList()` でCMSページ一覧を取得

### 2-検索実行（onSearch）

検索ボックスにキーワードを入力し検索を実行。

**処理フロー**:
1. 検索キーワードを `term` にセット
2. `resetList()` でページ1にリセット
3. `getList()` で検索結果を取得

### 3-ページタイプフィルタ（onSortPageType）

ページタイプドロップダウンで絞り込み。

**処理フロー**:
1. 選択されたページタイプを `currentPageType` にセット
2. `resetList()` でリスト再取得

### 4-レイアウト作成（onCreateNewLayout）

新規作成ボタンをクリック。

**処理フロー**:
1. `$router.push({ name: 'sw.cms.create' })` で作成画面へ遷移

### 5-レイアウト編集（onListItemClick）

レイアウト行/カードをクリック。

**処理フロー**:
1. 選択されたページのIDを取得
2. `$router.push({ name: 'sw.cms.detail', params: { id } })` で詳細画面へ遷移

### 6-レイアウト複製（onDuplicateCmsPage）

コンテキストメニューの複製をクリック。

**処理フロー**:
1. 複製時の上書き設定（新しい名前 = 元の名前 + " - Copy"）を構築
2. `pageRepository.clone()` でレイアウトを複製
3. 成功時、リストを再取得
4. 失敗時、エラー通知を表示

### 7-レイアウト削除（onConfirmPageDelete）

削除確認モーダルで確定。

**処理フロー**:
1. `pageRepository.delete(page.id)` でレイアウトを削除
2. 成功時、リストを再取得
3. 失敗時、エラー通知を表示（割り当て中のレイアウトは削除不可）

### 8-プレビュー画像変更（onPreviewImageChange）

メディアモーダルで画像を選択。

**処理フロー**:
1. 選択された画像IDを `currentPage.previewMediaId` にセット
2. `saveCmsPage()` でページを保存
3. `currentPage.previewMedia` を更新して表示に反映

### 9-デフォルトレイアウト設定（onConfirmLayoutSetAsDefault）

デフォルトに設定を選択。

**処理フロー**:
1. ページタイプに応じて設定キーを決定（product/category）
2. `systemConfigApiService.saveValues()` でシステム設定を更新
3. `defaultCategoryId` または `defaultProductId` を更新

### 10-表示モード切替（onListModeChange）

グリッド/リストビューの切替ボタンをクリック。

**処理フロー**:
1. `listMode` を切り替え（grid ↔ list）
2. 表示件数を更新（グリッド: 9件、リスト: 10件）
3. リストを再取得
4. ユーザー設定を保存

## データベース更新仕様

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| ページ一覧取得 | cms_page | SELECT | CMSページデータの取得 |
| ページ名変更 | cms_page | UPDATE | name の更新 |
| プレビュー画像変更 | cms_page | UPDATE | preview_media_id の更新 |
| ページ複製 | cms_page, cms_section, cms_block, cms_slot | INSERT | 全関連データの複製 |
| ページ削除 | cms_page | DELETE | CMSページの削除 |
| デフォルト設定 | system_config | UPDATE | core.cms.default_*_cms_page の更新 |

### テーブル別更新項目詳細

#### cms_page

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | ページネーション、ソート条件に基づく | categories, products, landingPages をアソシエーション |
| UPDATE | name | 入力値 | 名前変更時 |
| UPDATE | preview_media_id | 選択した画像のID | プレビュー画像変更時 |
| INSERT | * | 元レイアウトのコピー | 複製時 |
| DELETE | - | id指定 | 未割当のページのみ削除可能 |

#### system_config

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | configuration_value | configuration_key = 'core.cms.*' | デフォルトレイアウト取得 |
| UPDATE | configuration_value | ページID | デフォルトレイアウト設定時 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|--------------|----------|
| sw-cms.components.cmsListItem.notificationDeleteErrorMessage | エラー | 削除に失敗しました | 削除失敗時 |
| global.notification.unspecifiedSaveErrorMessage | エラー | 保存に失敗しました | 複製失敗時 |
| sw-cms.general.deleteDisabledToolTip | 警告 | 割当中のため削除できません | 削除不可時のツールチップ |

## 例外処理

| 例外ケース | 処理内容 |
|-----------|---------|
| 検索結果0件 | 空状態のメッセージを表示 |
| 割当中レイアウトの削除試行 | 削除ボタンを無効化、ツールチップで理由を表示 |
| 複製失敗 | エラー通知を表示、ローディング解除 |
| 削除失敗 | エラー通知を表示、ローディング解除 |
| 権限不足 | 該当機能のボタンを非表示/無効化 |

## 備考

- ページタイプは `cmsPageTypeService` で管理され、拡張可能
- グリッドビューでは最大3件の割当ページ名を表示、それ以上はツールチップで表示
- ユーザー設定（表示モード、ソート）は `user_config` テーブルに保存される
- デフォルトレイアウトは `core.cms.default_category_cms_page` / `core.cms.default_product_cms_page` で管理
- リンクされたレイアウト（割当あり）の判定は集約クエリで効率化されている

---

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

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

### 推奨読解順序

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

CMSページエンティティとその関連構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | cms_page | `src/Core/Content/Cms/CmsPageDefinition.php` | CMSページエンティティの定義 |
| 1-2 | cms_section | `src/Core/Content/Cms/Aggregate/CmsSection/CmsSectionDefinition.php` | セクション構造 |

**読解のコツ**: CMSはcms_page → cms_section → cms_block → cms_slot の4階層構造になっている。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `src/Administration/Resources/app/administration/src/module/sw-cms/index.ts` | モジュール登録、ルート定義 |

**主要処理フロー**:
1. **39-99行**: `Module.register('sw-cms', ...)` でモジュールを登録
2. **49-76行**: ルート定義（index, detail, create）
3. **78-96行**: ナビゲーション定義

#### Step 3: 一覧画面コンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | sw-cms-list/index.js | `src/Administration/Resources/app/administration/src/module/sw-cms/page/sw-cms-list/index.js` | Vue.jsコンポーネントの定義 |

**主要処理フロー**:
- **28-62行**: `data()` - コンポーネントの状態定義（pages, listMode等）
- **104-122行**: `listCriteria` - 検索条件の構築
- **222-250行**: `getList()` - CMSページデータ取得処理
- **526-563行**: `getColumnConfig()` - テーブルカラム定義

#### Step 4: ページタイプサービスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | cms-page-type.service.js | `src/Administration/Resources/app/administration/src/module/sw-cms/service/cms-page-type.service.js` | ページタイプの管理 |

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

```
sw-cms-list (Vue Component)
    │
    ├─ createdComponent()
    │      ├─ adminMenu.collapseSidebar()
    │      ├─ loadGridUserSettings()
    │      │      └─ getUserSettings()
    │      ├─ getDefaultLayouts()
    │      │      └─ systemConfigApiService.getValues()
    │      ├─ setPageContext()
    │      │      └─ getDefaultFolderId()
    │      └─ getList()
    │
    ├─ getList()
    │      ├─ addQueryScores()
    │      └─ pageRepository.search(criteria)
    │             └─ Shopware API (POST /api/search/cms_page)
    │
    ├─ onDuplicateCmsPage(page)
    │      └─ pageRepository.clone(id, behavior)
    │             └─ Shopware API (POST /api/_action/clone/cms_page/{id})
    │
    └─ onConfirmPageDelete()
           └─ pageRepository.delete(id)
                  └─ Shopware API (DELETE /api/cms_page/{id})
```

### データフロー図

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

検索キーワード ─────────┐
                        │
ページタイプ ───────────┼──▶ sw-cms-list Component
                        │         │
表示モード ─────────────┘         ├──▶ listCriteria構築
                                  │         │
                                  │         ▼
                                  │    Criteria API
                                  │         │
                                  │         ▼
                                  │    pageRepository.search()
                                  │         │
                                  │         ▼
                                  │    Shopware API
                                  │         │
                                  │         ▼
                                  └──▶ pages データ
                                           │
                                           ├──▶ グリッドビュー表示
                                           └──▶ リストビュー表示
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `src/Administration/Resources/app/administration/src/module/sw-cms/index.ts` | ソース | モジュール定義 |
| sw-cms-list/index.js | `src/Administration/Resources/app/administration/src/module/sw-cms/page/sw-cms-list/index.js` | ソース | 一覧画面コンポーネント |
| sw-cms-list.html.twig | `src/Administration/Resources/app/administration/src/module/sw-cms/page/sw-cms-list/sw-cms-list.html.twig` | テンプレート | 画面テンプレート |
| sw-cms-list.scss | `src/Administration/Resources/app/administration/src/module/sw-cms/page/sw-cms-list/sw-cms-list.scss` | スタイル | 画面スタイル |
| cms-page-type.service.js | `src/Administration/Resources/app/administration/src/module/sw-cms/service/cms-page-type.service.js` | ソース | ページタイプサービス |
| CmsPageDefinition.php | `src/Core/Content/Cms/CmsPageDefinition.php` | ソース | エンティティ定義 |
