Skip to content

Latest commit

 

History

History
679 lines (516 loc) · 24.2 KB

File metadata and controls

679 lines (516 loc) · 24.2 KB

OD Structured Data プラグイン設計・仕様

目的

OD Structured Data は、WordPress が保持している投稿・固定ページ・著者・画像・サイト・ブロック情報を標準 API 経由で解決し、利用者が明示した意味情報と統合して、Schema.org 準拠の JSON-LD を出力する WordPress プラグインである。

このプラグインは、Schema を利用者に一から入力させるフォームではない。WordPress の既存データに意味を追加し、検索エンジン、AI クローラー、外部システムへ標準形式で伝える変換レイヤーとして設計する。

現在のリポジトリ前提

  • Repository: od-structured-data
  • Plugin directory: od-structured-data
  • Main file: od-structured-data.php
  • Plugin name: OD Structured Data
  • Text domain: od-structured-data
  • PHP package: OD_Structured_Data
  • PHP constant prefix: OD_STRUCTURED_DATA_
  • Target WordPress: Requires at least: 6.8
  • Tested WordPress: Tested up to: 7.0.2
  • Target PHP: Requires PHP: 7.4
  • Local development: @wordpress/env

コマンドは既存の package.json を優先する。

npm run env:start
npm run env:stop
npm run env:logs
npm run wp -- --info
npm test

基本方針

  • WordPress 標準 API、標準フック、ブロックエディター拡張ポイントを使う。
  • テーマや WordPress コアを改変しない。
  • 投稿本文の編集体験を壊さない。
  • ページ全体の Schema 設定は、本文ブロックではなく投稿メタと投稿設定 UI で扱う。
  • 構造化データの完成 JSON 全体を保存しない。保存するのは有効状態、Schema タイプ、上書き値、明示的な意味指定などの設定差分だけにする。
  • JSON-LD はサーバー側で生成し、フロントエンドの wp_head から出力する。
  • SEO プラグインがすでに出力している構造化データを強制的に削除・停止しない。競合の可能性を検知し、このプラグイン側の出力を制御する。
  • Schema.org の全種類を最初から対象にしない。WordPress の標準データと結びつけやすく、実務上よく使うものから段階的に対応する。
  • Schema.org のデータモデルと WordPress 依存処理を分離し、テストしやすい構造にする。

MVP の範囲

最初の MVP は Article 系 Schema の管理と出力に絞る。

  1. 設定画面で選択された投稿タイプに投稿メタを正式に登録する。
  2. ブロックエディターの投稿設定サイドバーに構造化データ設定 UI を追加する。
  3. ArticleBlogPosting に対応する。
  4. 投稿タイトル、URL、公開日時、更新日時、著者、アイキャッチ画像、サイト情報を WordPress から自動解決する。
  5. 利用者が必要な項目だけ上書きできる。
  6. 新規コンテンツの初期有効化をサイト設定で選択できる。
  7. 既存コンテンツを対象投稿タイプ単位で一括有効化できる。
  8. 編集画面で検証結果を表示する。
  9. JSON-LD プレビューを表示する。
  10. フロントエンドの wp_head に JSON-LD を出力する。
  11. 既存 SEO プラグインなどによる Article 系出力との重複可能性を警告する。
  12. PHP フィルターで生成データを拡張できる。

MVP では FAQPage の自動生成、Organization の詳細設定画面、SEO プラグイン個別連携の深い制御までは必須にしない。ただし、将来追加しやすい境界で設計する。

現在の実装状況

初期 MVP として、以下を実装済み。

  • 設定画面で選択された投稿タイプへの _od_structured_data 投稿メタ登録
  • 新規コンテンツのデフォルト有効化設定
  • 既存コンテンツの一括有効化操作
  • REST API へ公開する投稿メタ schema
  • 投稿メタのサニタイズと Schema タイプの許可リスト検証
  • PluginDocumentSettingPanel による投稿設定パネル
  • Article / BlogPosting の切り替え
  • 見出し、説明文、画像、著者名の上書き保存
  • メディア選択 UI による画像上書き
  • 投稿タイトル、URL、日時、著者、アイキャッチ画像、サイト情報のサーバー側解決
  • Article 系 JSON-LD の @graph 生成
  • WebPage / WebSite / Organization の最小 graph ノード生成
  • 独自 REST endpoint によるサーバー生成プレビュー
  • 編集画面プレビューとサーバー側生成結果の一致
  • PHP Schema_Validator による編集画面の検証表示
  • wp_head からの JSON-LD 出力
  • Yoast SEO、Rank Math、All in One SEO の存在に基づく重複可能性の警告
  • od_structured_data_* フィルターの初期セット
  • PHPUnit による MVP 中心ロジックの自動テスト
  • 検証結果のエラー、推奨項目、整合性警告への分離
  • 投稿タイプ既定方針と投稿単位の継承・有効・無効
  • Schema ノードごとの出力担当設定
  • Organization / Person / 出力なしのサイト主体設定
  • サイト主体の名称、URL、ロゴ、sameAs 管理
  • canonical URL、更新日方針、取得元、最終出力値の診断
  • 未保存のタイトル、抜粋、画像を使う REST プレビュー
  • BreadcrumbList とパンくず項目フィルター

現時点で未実装、または今後の改善対象。

  • E2E テスト
  • SEO プラグイン個別 API との深い連携
  • LocalBusiness、Event など追加 Schema

対応 Schema の優先順位

Phase 1

  • Article
  • BlogPosting
  • WebPage
  • WebSite
  • Organization
  • Person

MVP では ArticleBlogPosting を実装対象とし、WebPageWebSiteOrganizationPerson はグラフ設計上の参照先として後から追加できる構造にする。

Phase 2

  • BreadcrumbList
  • LocalBusiness
  • Event

BreadcrumbList は実装済み。FAQPage は AI 検索対策として扱わず、ページ上の質問と回答を継続的に管理できる具体的な利用要件が生じるまで追加しない。

Phase 3

  • Product
  • Review
  • Course
  • HowTo
  • 業種別 Schema

Article 系 Schema

MVP で扱う Schema タイプは以下とする。

  • Article
  • BlogPosting

将来的に NewsArticle を追加できるようにするが、MVP の必須対象にはしない。

主なプロパティ

  • @context
  • @type
  • @id
  • headline
  • description
  • image
  • datePublished
  • dateModified
  • author
  • publisher
  • mainEntityOfPage

WordPress からの自動取得候補

Schema プロパティ WordPress 取得元
headline 投稿タイトル
description 抜粋、または将来の SEO メタ連携
image アイキャッチ画像
datePublished 公開日時
dateModified 更新日時
author 投稿者情報
mainEntityOfPage 投稿 URL
publisher サイト共通設定、将来的には Organization 設定

自動取得値と上書き値は UI 上でも内部データ上でも区別する。出力時は「上書き値が有効なら上書き値、未設定なら自動取得値」を使う。

投稿メタ仕様

投稿単位の設定は post_meta に保存する。対象投稿タイプは管理画面の設定値を基準に明示的に登録する。

初期状態では対象投稿タイプは未選択とする。利用者が 設定 > OD Structured Data で投稿、固定ページ、公開カスタム投稿タイプから必要なものを選択すると、その投稿タイプに _od_structured_data メタを登録する。

od_structured_data_post_types フィルターは、設定画面で選択された投稿タイプ配列を受け取り、コード側で追加・調整できる拡張ポイントとして維持する。

メタキー案:

_od_structured_data

保存する値は完成 JSON-LD ではなく設定差分とする。

{
  "enabled": true,
  "schemaType": "BlogPosting",
  "overrides": {
    "headline": "",
    "description": "",
    "imageId": 0,
    "authorName": ""
  },
  "features": {
    "article": true,
    "faqFromBlocks": false
  }
}

登録方針

  • register_post_meta() を使う。
  • show_in_rest を有効にする。
  • REST schema を定義し、additionalProperties は原則 false にする。
  • sanitize_callback を必ず指定する。
  • auth_callback では投稿編集権限を確認する。
  • 可能な限り投稿タイプごとに登録する。

サイト設定

サイト全体の設定は option として保存する。

Option 初期値 内容
od_structured_data_post_types string[] [] 構造化データ設定を有効にする投稿タイプ
od_structured_data_default_enabled bool false 新規コンテンツで出力トグルを初期有効化するか

既存コンテンツの一括有効化は永続的な option ではなく、管理画面フォームから実行する一回限りの操作とする。実行時は選択済み投稿タイプに属する既存コンテンツの _od_structured_data.enabled だけを true にし、Schema タイプや上書き値は維持する。

実装時は以下の考え方を守る。

register_post_meta(
	'post',
	'_od_structured_data',
	array(
		'type'              => 'object',
		'single'            => true,
		'show_in_rest'      => array(
			'schema' => array(
				'type'                 => 'object',
				'additionalProperties' => false,
			),
		),
		'sanitize_callback' => '...',
		'auth_callback'     => '...',
	)
);

エディター UI

ブロックエディターの投稿設定 UI に、構造化データ用のパネルを追加する。

小さな設定は PluginDocumentSettingPanel を使う。検証結果、JSON-LD プレビュー、詳細な上書き UI が増える場合は PluginSidebar を併用する。

基本 UI

  • 構造化データを有効にするトグル
  • Schema タイプ選択
  • 自動取得値の確認
  • 上書き値の入力
  • 検証結果
  • JSON-LD プレビュー
  • 既存 Schema との重複警告

表示方針

自動取得値と上書き値を混ぜて表示しない。

例:

見出し
自動取得: 投稿タイトル
上書き: 未設定
出力値: 投稿タイトル

利用者が Schema.org の仕様を詳しく知らなくても、現在何が出力されるか分かる UI にする。

JSON-LD 出力

JSON-LD はフロントエンドで wp_head から出力する。

基本条件:

  • is_admin() では出力しない。
  • is_singular() のときだけ処理する。
  • 対象投稿 ID が取得できない場合は出力しない。
  • 投稿メタで無効化されている場合は出力しない。
  • 検証上、最低限必要なデータが揃わない場合は出力しない、または不完全なプロパティを除外する。
  • 空値、不正値、型が合わない値は出力しない。
  • wp_json_encode() を使う。
  • JSON 文字列を手動連結で組み立てない。

出力時の考え方:

add_action( 'wp_head', 'od_structured_data_render_json_ld', 20 );
printf(
	'<script type="application/ld+json">%s</script>' . "\n",
	wp_json_encode(
		$graph,
		JSON_UNESCAPED_UNICODE |
		JSON_UNESCAPED_SLASHES |
		JSON_HEX_TAG |
		JSON_HEX_AMP
	)
);

JSON-LD 全体に esc_html() をかけない。値の検証・サニタイズを行ったうえで wp_json_encode() に任せる。

@graph 設計

将来的には複数 Schema を 1 つの @graph にまとめる設計を優先する。

{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "WebSite",
      "@id": "https://example.com/#website"
    },
    {
      "@type": "Organization",
      "@id": "https://example.com/#organization"
    },
    {
      "@type": "Article",
      "@id": "https://example.com/example/#article"
    }
  ]
}

Article から Organization、WebSite、WebPage、Person を @id で参照できるようにする。

MVP では Article 単体の出力から始めてもよいが、内部の Builder は @graph へ拡張できる形にする。

@id 生成方針

安定した URL ベースの ID を生成する。

  • WebSite: home_url( '/' ) . '#website'
  • Organization: home_url( '/' ) . '#organization'
  • WebPage: get_permalink( $post_id ) . '#webpage'
  • Article: get_permalink( $post_id ) . '#article'
  • Person: 著者アーカイブ URL またはユーザー ID ベースの安定 ID

URL の末尾スラッシュやフラグメントの重複に注意し、ID 生成処理は専用クラスまたは専用関数に分離する。

内部アーキテクチャ

Schema.org のデータモデルと WordPress 依存処理を分離する。

推奨構成:

Plugin
├─ Domain
│  ├─ Schema
│  │  ├─ ArticleSchema
│  │  ├─ WebPageSchema
│  │  └─ FAQPageSchema
│  ├─ Graph
│  │  ├─ GraphBuilder
│  │  └─ IdGenerator
│  └─ Validation
│     ├─ SchemaValidator
│     └─ ValidationResult
│
├─ WordPress
│  ├─ Metadata
│  │  ├─ PostMetaRegistrar
│  │  └─ OptionsRegistrar
│  ├─ Resolver
│  │  ├─ PostResolver
│  │  ├─ AuthorResolver
│  │  ├─ ImageResolver
│  │  └─ BlockResolver
│  ├─ Editor
│  │  ├─ DocumentSettingPanel
│  │  └─ SchemaSidebar
│  └─ Frontend
│     └─ JsonLdRenderer
│
└─ Integration
   ├─ YoastIntegration
   ├─ RankMathIntegration
   └─ GenericConflictDetector

MVP ではこの全ファイルを一度に作らなくてよい。ただし、責務境界はこの構成を基準にする。

実装原則

  • メインプラグインファイルはヘッダーと最小限の bootstrap に留める。
  • hook 登録は loader またはサービスクラスへ寄せる。
  • admin/editor 用処理は admin/editor の hook 配下に置く。
  • frontend 出力処理は wp_head に閉じ込める。
  • Domain 層から get_the_title() などの WordPress 関数を直接呼ばない。
  • WordPress 依存データは Resolver が取得し、Schema Builder に渡す。

悪い例:

$schema = array(
	'headline' => get_the_title( $post_id ),
);

望ましい例:

$post_data = $post_resolver->resolve( $post_id );
$schema    = $article_schema->build( $post_data, $settings );

デフォルト値解決

WordPress の既存データから Schema に使える値を取得する。

主な解決対象:

  • 投稿タイトル
  • 投稿 URL
  • 公開日時
  • 更新日時
  • 抜粋
  • アイキャッチ画像
  • 投稿者名
  • 投稿者 URL
  • サイト名
  • サイト URL
  • サイトアイコン
  • 将来の Organization 設定

日付は ISO 8601 形式を優先する。WordPress のタイムゾーン設定を尊重し、不明確な UTC 変換を勝手に行わない。

新規コンテンツの有効化初期値

投稿メタの保存済み値が存在しない既存コンテンツまで勝手に有効扱いしないため、Post_Settings::defaults() 自体の enabledfalse を維持する。

od_structured_data_default_enabled が有効な場合は、ブロックエディターで新規投稿を開いたタイミングで _od_structured_data.enabled の初期値を true にする。これにより、新規コンテンツの作成体験だけを変え、既存コンテンツの保存済み設定には影響させない。

バリデーション

Schema ごとに検証ルールを持つ。

検証結果は、エラーと警告を分ける。

  • 必須項目不足
  • 推奨項目不足
  • 型や形式の誤り
  • 自動取得できなかった項目
  • 他プラグインとの重複可能性

Google のリッチリザルト対象として十分かどうかと、Schema.org として構造が妥当かどうかは分けて扱う。

例:

  • headline が空ならエラー
  • datePublished が空ならエラーまたは強い警告
  • image がない場合は Article としては警告
  • SEO プラグインによる Article 出力の可能性がある場合は警告

セキュリティ

WordPress の基本原則に従い、入力時に検証・サニタイズし、出力時に適切にエスケープする。

権限

投稿単位の設定変更は、その投稿を編集できるユーザーだけに許可する。

current_user_can( 'edit_post', $post_id );

汎用的な edit_posts だけで済ませず、可能な場所では対象投稿 ID に対する edit_post を使う。

サニタイズ

処理例
Schema タイプ 許可リストとの厳密比較
URL esc_url_raw()
一行テキスト sanitize_text_field()
複数行テキスト sanitize_textarea_field()
HTML を含む回答 wp_kses_post()
真偽値 (bool) への正規化
日付 ISO 8601 として検証
投稿 ID・画像 ID absint()

Schema タイプは任意文字列を許可しない。

$allowed_types = array( 'Article', 'BlogPosting' );

if ( ! in_array( $value, $allowed_types, true ) ) {
	$value = 'Article';
}

Nonce と REST

  • REST 経由で保存するメタは、register_post_meta()auth_callback と REST API の標準権限に従う。
  • 独自 REST endpoint を追加する場合は permission_callback を必ず定義する。
  • 管理画面フォームを追加する場合は nonce と capability check を必ず組み合わせる。
  • 既存コンテンツの一括有効化は manage_options と nonce を確認してから実行する。

SEO プラグインとの関係

Yoast SEO、Rank Math、All in One SEO などがすでに構造化データを出力している可能性がある。

基本方針:

  1. 既存出力の可能性を検知する。
  2. 編集画面で警告する。
  3. このプラグイン側の重複出力を止められるようにする。
  4. 利用者が明示的に選択した場合だけ重複可能性のある Schema を出力する。
  5. 外部連携は Integration クラスへ隔離する。

禁止方針:

  • 他プラグインの hook を無断で外さない。
  • 他プラグインの JSON-LD を勝手に削除・改変しない。
  • フロントエンドに出力された HTML を実行時に無理に解析して競合判定しない。

既知プラグインについては、公式 API や提供されているフィルターがある場合のみ Integration として対応する。それ以外は「競合の可能性」として通知する程度に留める。

ブロック解析と FAQPage(保留)

FAQPage の自動生成は現在のロードマップでは保留する。実装を再検討する場合は、本文に配置されたコアブロックから意味情報を取得し、画面上の情報との一致を前提とする。

代表例は、アコーディオンまたは Details 系ブロックから FAQPage を生成する機能である。

基本処理:

  1. 投稿本文を parse_blocks() で解析する。
  2. ブロックを再帰的に走査する。
  3. 対象ブロックを Resolver で判定する。
  4. 質問と回答を抽出する。
  5. 空の質問または回答はスキップする。
  6. FAQPage 形式の JSON-LD を生成する。
  7. @graph に統合する。

出力例:

{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "質問文",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "回答文"
      }
    }
  ]
}

重要な制約

  • コアブロックの表示自体は変更しない。
  • コアブロックの複製版を作らない。
  • 想定外のブロック構造でも Fatal Error にしない。
  • ネストされたブロックも再帰的に解析する。
  • コアブロックの内部構造に強く依存しない。
  • 解析処理は Resolver として分離する。
  • ユーザーが明示的に FAQ として扱う指定をできるようにする。

単に「アコーディオンがあるから FAQ」と自動判定してはいけない。アコーディオンは FAQ 以外にも、利用規約、補足説明、プロフィール、ナビゲーションなどに使われるためである。

MVP では投稿メタ側に以下のような明示設定を持たせる方針を優先する。

このページのアコーディオンを FAQ として扱う
- 扱わない
- 対象を選択する
- すべて FAQ として扱う

コアブロックへ独自属性を追加する方式は、ブロック検証や将来互換性を確認してから採用する。

Block Resolver

ブロック解析は Resolver Registry として拡張できるようにする。

interface Schema_Block_Resolver {
	public function supports( array $block ): bool;

	public function resolve( array $block, int $post_id ): array;
}

Resolver 一覧はフィルターで拡張できるようにする。

$resolvers = apply_filters(
	'od_structured_data_block_resolvers',
	$default_resolvers
);

PHP フィルター

外部拡張を前提に、以下のようなフィルターを用意する。

候補:

  • od_structured_data_post_types
  • od_structured_data_allowed_article_types
  • od_structured_data_post_settings
  • od_structured_data_resolved_post_data
  • od_structured_data_article_schema
  • od_structured_data_graph
  • od_structured_data_should_render
  • od_structured_data_block_resolvers

フィルター名は od_structured_data_ を接頭辞とする。

非採用方針

以下は少なくとも MVP では採用しない。

  • 本文内に、フロント表示されない Article 設定専用ブロックを置く方式。
  • 完成済み JSON-LD 全体を投稿メタへ保存する方式。
  • SEO プラグインの出力を無断で停止・削除する方式。
  • Schema.org の全タイプを網羅しようとする方式。
  • コアブロックを複製して独自 FAQ ブロックを作る方式。
  • JSON-LD を文字列連結で組み立てる方式。

テスト・検証方針

最低限確認すること:

  • プラグインが有効化できる。
  • 設定画面で選択された投稿タイプのメタが REST API に正しく公開される。
  • 権限のないユーザーが投稿メタを更新できない。
  • 保存値が期待通りサニタイズされる。
  • 対象投稿タイプが未選択の場合は編集画面アセットを読み込まない。
  • 新規コンテンツのデフォルト有効化設定が編集画面へ渡される。
  • 既存コンテンツの一括有効化が対象投稿タイプだけを更新し、既存設定を維持する。
  • Article / BlogPosting の JSON-LD が is_singular() でだけ出力される。
  • 無効化された投稿では出力されない。
  • 空値や不正値が JSON-LD に混入しない。
  • wp_json_encode() による出力が HTML を壊さない。
  • 編集画面の UI で自動取得値、上書き値、出力値が区別できる。

現時点のリポジトリでは以下を基本確認コマンドとする。

npm test

WordPress 互換性は、下限の 6.8 系と最新安定版の両方で確認する。2026-07-24 時点では、WordPress 6.8.6 と WordPress 7.0.2 で npm test が通ることを確認済み。

wp-env を使う機能確認が必要な場合:

npm run env:start
npm run wp -- plugin status od-structured-data

PHPUnit は導入済み。PHPCS、Jest、Playwright などを追加した場合も、既存の package.json または composer.json に定義されたスクリプトを優先して実行する。

開発時の Codex 向け注意

  • まずこのドキュメントを読み、MVP 範囲か将来フェーズかを判断する。
  • 既存ファイル、既存命名、既存ツールを優先する。
  • 変更前に README.mdod-structured-data.phppackage.json を確認する。
  • WordPress 依存処理を Domain 層に混ぜない。
  • ユーザー入力を扱う場合は、サニタイズ、権限、REST schema、出力安全性を同時に確認する。
  • UI を作る場合は WordPress 標準コンポーネントと SlotFill を優先する。
  • Schema 出力を追加する場合は、必ず重複出力と空値除外を考慮する。
  • 実装後は利用可能なテストまたは wp-env コマンドで確認する。