拡張ポイント — filter リファレンス

本プラグインはテーマ・他プラグインからカスタマイズできる WordPress filter を多数提供しています。 本章では主要 11 個 + 補助 5 個の合計 16 個の filter を、関数シグネチャ・デフォルト値・上書きサンプルとともに解説します。

filter 全体一覧

本プラグインが提供する全 filter とその責務を一覧にします。

filter 名 定義ファイル 1 行説明
ksmd_md_scheme md-renderer.php md ホスト URL の scheme (http/https) を上書き
ksmd_subdir_mode_md_scheme v1.0.3 md-subdir-mode.php サブディレモード root URL の scheme を上書き (reverse proxy 環境向け)
ksmd_host md-renderer.php / core-functions.php md ホスト名を動的に取得 (multisite 対応等)
ksmd_home_intro_markdown md-route-renderers.php md ホームの導入 Markdown を上書き
ksmd_home_archive_links md-route-renderers.php md ホームに表示するアーカイブセクションのリンク配列
ksmd_home_recent_post_type md-route-renderers.php md ホームの「最近の記事」の post_type
ksmd_archive_default_post_types md-route-renderers.php archive route で対象とする post_type 配列のデフォルト
ksmd_term_archive_post_types md-route-renderers.php term archive で対象とする post_type
ksmd_archive_query_args md-route-renderers.php archive 出力時の WP_Query args (posts_per_page 等)
ksmd_schema_type md-schema-mapper.php / md-route-renderers.php post_type/route 別の Schema.org type を動的決定
ksmd_route_schema_header_lines md-route-renderers.php route 別の Schema.org header Markdown 行配列
ksmd_route_schema_footer_lines md-route-renderers.php route 別の Schema.org footer Markdown 行配列
ksmd_schema_header_lines md-schema-mapper.php singular post の Schema.org header Markdown 行配列
ksmd_schema_footer_lines md-schema-mapper.php singular post の Schema.org footer Markdown 行配列
ksmd_robots_txt_lines md-resolver.php md ホストの robots.txt 行配列
ksmd_test_renderer_sslverify settings-page-logic.php 診断タブの Test renderer 内 wp_remote_get の sslverify 設定
ksmd_bootstrap_sslverify md-bootstrap-installer.php Bootstrap verify 内 wp_remote_get の sslverify 設定

📝 Note

the_content filter は WordPress core のフィルタで本プラグインの拡張点ではありませんが、 singular renderer は apply_filters('the_content', $post->post_content) 経由で本文を取得するため、 the_content に登録した Markdown 系プラグイン (Markdown 入力プラグイン等) の出力もそのまま反映されます。

📝 Note (新機能の拡張点)

includes/md-alternate-link.php (alt-link) と includes/md-favicon-proxy.php (favicon proxy) は 現状 apply_filters() 拡張点なし。設定 toggle (enable_md_alternate_link / enable_favicon_proxy) と Content Targets 設定で挙動を制御します。将来的に「alt-link 出力対象 URI」「favicon リダイレクト先 URL」などを動的に変更したい ニーズが生じた場合、対応する filter を追加する余地があります。

ksmd_md_scheme

md ホスト URL の scheme を決定します。

シグネチャ

apply_filters( 'ksmd_md_scheme',
    string $scheme,    // 'http' or 'https' (home_url から自動算出)
    string $url,       // 書き換え対象の元 URL
    string $md_host    // md ホスト名 (例: md.example.com)
);
// 期待戻り値: 'http' or 'https' (それ以外は内部で 'https' に強制矯正)

デフォルト値

home_url() の scheme をそのまま継承します。本番 https サイトは https、 ローカル http (localhost / WSL2) は http になります。

$home_scheme = wp_parse_url( $home_url, PHP_URL_SCHEME );
$scheme      = ( $home_scheme === 'http' ) ? 'http' : 'https';

サンプル 1: 強制 https 化

ローカルでは http だが md ホストだけは常に https で配信したい場合 (証明書を md.* にだけ入れている等):

add_filter( 'ksmd_md_scheme', function ( $scheme, $url, $md_host ) {
    return 'https';
}, 10, 3 );

サンプル 2: 開発環境だけ http に固定

add_filter( 'ksmd_md_scheme', function ( $scheme, $url, $md_host ) {
    if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
        return 'http';
    }
    return $scheme;
}, 10, 3 );

サンプル 3: 特定パスだけ http

(運用上推奨はしないが、参考として)

add_filter( 'ksmd_md_scheme', function ( $scheme, $url, $md_host ) {
    if ( strpos( $url, '/internal-tool/' ) !== false ) {
        return 'http';
    }
    return $scheme;
}, 10, 3 );

ksmd_subdir_mode_md_scheme v1.0.3

サブディレモード root の md ホスト URL を構築するときの scheme を上書きできる filter です。reverse proxy 環境やメインホストと md ホストで scheme が異なる場合に有効です。サブディレモード機能専用のため、サブディレモードを使っていない場合は呼ばれません。

シグネチャ

apply_filters( 'ksmd_subdir_mode_md_scheme',
    string $scheme,    // 'http' or 'https' (home_url から自動算出、不正値時は is_ssl() フォールバック)
    string $md_host,   // md ホスト名 (例: md.example.com)
    array  $opts       // 全 ksmd_settings (subdir_mode_enabled / subdir_mode_type 等を含む)
);
// 期待戻り値: 'http' or 'https'

デフォルト値

wp_parse_url( home_url(), PHP_URL_SCHEME ) をそのまま使用。不正値 (空・想定外) のときのみ is_ssl() ベースの判定に fallback します。一般的な ksmd_md_scheme filter (個別ページの URL 用) とは独立して、subdir mode root の https://md.example.com/ 構築時にのみ呼ばれます。

サンプル 1: reverse proxy で常に https

上流 (Cloudflare 等) で TLS 終端し、内部リクエストが http で届く環境。 md ホスト root の canonical 用 URL を強制 https にしたい場合:

add_filter( 'ksmd_subdir_mode_md_scheme', function ( $scheme, $md_host, $opts ) {
    return 'https';
}, 10, 3 );

サンプル 2: メインと md で scheme が異なる

メインサイトは http (社内ステージング) だが md ホストには独立して https 証明書を入れている場合、md scheme だけ強制 https にできます:

add_filter( 'ksmd_subdir_mode_md_scheme', function ( $scheme, $md_host, $opts ) {
    if ( strpos( $md_host, 'md.staging.' ) === 0 ) {
        return 'https'; // ステージング md だけは https
    }
    return $scheme;
}, 10, 3 );

呼ばれるタイミング

サブディレモードの auto モード renderer (ksmd_render_subdir_root_auto()) で md ホスト URL (https://md.example.com/) を組み立てる前。canonical pointer / parallel mirror 宣言 / 「最近の Markdown コンテンツ」リンク / sitemap.xml URL の構築に影響します。

ksmd_host

md ホスト名を動的に取得・上書きします。

シグネチャ

apply_filters( 'ksmd_host',
    string $md_host  // 設定値の md_host (空なら ksmd_default_md_host() 算出値)
);
// 期待戻り値: ホスト名文字列 (例: 'md.example.com')

デフォルト値

設定タブの md_host 値、または空なら ksmd_default_md_host() (home_url() の host から www. を除去して md. を付与) が返ります。

サンプル 1: multisite で blog ID ごとに切替

add_filter( 'ksmd_host', function ( $md_host ) {
    if ( ! function_exists( 'get_current_blog_id' ) ) {
        return $md_host;
    }
    $blog_id = get_current_blog_id();
    $map = array(
        1 => 'md.example.com',
        2 => 'md.example.jp',
        3 => 'md.example.co.kr',
    );
    return isset( $map[ $blog_id ] ) ? $map[ $blog_id ] : $md_host;
}, 10, 1 );

サンプル 2: ステージング環境では別ホスト

add_filter( 'ksmd_host', function ( $md_host ) {
    if ( defined( 'WP_ENVIRONMENT_TYPE' )
         && WP_ENVIRONMENT_TYPE === 'staging' ) {
        return 'md-staging.example.com';
    }
    return $md_host;
}, 10, 1 );

サンプル 3: 言語別 md ホスト (Polylang/WPML 風)

add_filter( 'ksmd_host', function ( $md_host ) {
    if ( function_exists( 'pll_current_language' ) ) {
        $lang = pll_current_language( 'slug' );
        if ( $lang === 'en' ) {
            return 'md-en.example.com';
        }
    }
    return $md_host;
}, 10, 1 );

⚠ Warning

ksmd_host を上書きする場合、その新ホストでも index.php を配置し、 DNS / バーチャルホストを設定する必要があります。Bootstrap installer は設定値の md_host を見るため、 filter で上書きしたホストには対応しません。手動 SSH 配置になります。

ksmd_home_intro_markdown

md ホームの H1/Canonical 直後に挿入する導入 Markdown を上書きします。

シグネチャ

apply_filters( 'ksmd_home_intro_markdown',
    string $intro_raw,  // 設定値 home_intro_markdown
    array  $context     // home/front_page の context
);
// 期待戻り値: Markdown 文字列 (空なら blogdescription にフォールバック)

デフォルト値

設定値 home_intro_markdown の生値。空文字なら renderer 側で blogdescription (get_bloginfo('description')) にフォールバックします。

サンプル 1: 動的に当日の記事数を埋め込む

add_filter( 'ksmd_home_intro_markdown', function ( $intro, $context ) {
    $count = wp_count_posts( 'post' )->publish;
    return "本サイトには現在 **{$count} 本の記事** が公開されています。\n\n"
         . "AI クローラ向けの Markdown 並行版です。";
}, 10, 2 );

サンプル 2: 特定 locale だけ別 intro

add_filter( 'ksmd_home_intro_markdown', function ( $intro, $context ) {
    if ( get_locale() === 'en_US' ) {
        return "Welcome to the AI-optimized Markdown version of our site.";
    }
    return $intro;  // 既存値を尊重
}, 10, 2 );

サンプル 3: テーマ functions.php からの簡易上書き

add_filter( 'ksmd_home_intro_markdown', '__return_empty_string' );
// 強制的に空にして blogdescription だけ表示させる

ksmd_home_archive_links

md ホームの「サイトのセクション」に列挙するアーカイブリンク配列を上書きします。

シグネチャ

apply_filters( 'ksmd_home_archive_links',
    array $links,    // [['label' => string, 'url' => string, 'pt' => string], ...]
    array $context
);
// 期待戻り値: 同じ形式の配列 (label / url 必須、pt 任意)

デフォルト値

enabled_post_typeshas_archive=true な post_type の archive link を集めた配列。 pageattachment は除外。リンクは md_host 化済。

サンプル 1: お知らせ archive を先頭に追加

add_filter( 'ksmd_home_archive_links', function ( $links, $context ) {
    array_unshift( $links, array(
        'label' => 'お知らせ',
        'url'   => 'https://md.example.com/news/',
        'pt'    => 'news',
    ) );
    return $links;
}, 10, 2 );

サンプル 2: 特定 post_type を除外

add_filter( 'ksmd_home_archive_links', function ( $links, $context ) {
    return array_values( array_filter( $links, function ( $link ) {
        return $link['pt'] !== 'product';  // products archive を除外
    } ) );
}, 10, 2 );

サンプル 3: ラベルを日本語ローカライズ

add_filter( 'ksmd_home_archive_links', function ( $links, $context ) {
    $jp_map = array(
        'post'    => 'ブログ記事',
        'product' => '商品一覧',
        'event'   => 'イベント情報',
    );
    foreach ( $links as &$link ) {
        if ( isset( $jp_map[ $link['pt'] ] ) ) {
            $link['label'] = $jp_map[ $link['pt'] ];
        }
    }
    return $links;
}, 10, 2 );

ksmd_home_recent_post_type

md ホームの「最近の記事」セクションで使う post_type を上書きします。

シグネチャ

apply_filters( 'ksmd_home_recent_post_type',
    array|string $default,  // 計算済 post_type 配列 (page/attachment 除外済)
    array        $context
);
// 期待戻り値: post_type slug の string または string[] (両対応で後方互換)

デフォルト値

enabled_post_types から pageattachment を除外した配列。 設定で enabled_post_types が空なら空配列が渡され、renderer 側で「記事がありません」表示になります。

サンプル 1: post のみに固定 (CPT を含めたくない)

add_filter( 'ksmd_home_recent_post_type', function ( $default, $context ) {
    return 'post';  // string を返しても可、内部で配列化される
}, 10, 2 );

サンプル 2: お知らせ + ブログを最近の記事に

add_filter( 'ksmd_home_recent_post_type', function ( $default, $context ) {
    return array( 'post', 'news' );
}, 10, 2 );

サンプル 3: 特定 CPT を除外

add_filter( 'ksmd_home_recent_post_type', function ( $default, $context ) {
    if ( ! is_array( $default ) ) {
        $default = array( $default );
    }
    return array_values( array_diff( $default, array( 'product' ) ) );
}, 10, 2 );

ksmd_archive_default_post_types

archive route (author / date / search / feed) で対象とする post_type 配列のデフォルトを上書きします。

シグネチャ

apply_filters( 'ksmd_archive_default_post_types',
    array  $list,        // 計算済 post_type 配列 (route_type 別 exclude 適用済)
    string $route_type,  // 'author' | 'date' | 'search' | 'feed' | etc.
    array  $context
);
// 期待戻り値: post_type slug の配列

デフォルト値

enabled_post_types から以下を除外した配列:

search route だけ page を含めるのは、WP 標準の is_search() 時に WP_Querypost_type='any' に切り替えるため、page も検索結果に出るのが WP デフォルト動作だからです。

サンプル 1: feed に CPT も含める

add_filter( 'ksmd_archive_default_post_types',
    function ( $list, $route_type, $context ) {
        if ( $route_type === 'feed' ) {
            $list[] = 'product';
            $list[] = 'event';
        }
        return $list;
    }, 10, 3
);

サンプル 2: search から特定 CPT を除外

add_filter( 'ksmd_archive_default_post_types',
    function ( $list, $route_type, $context ) {
        if ( $route_type === 'search' ) {
            $list = array_values( array_diff( $list, array( 'private_post' ) ) );
        }
        return $list;
    }, 10, 3
);

サンプル 3: author 一覧は post のみ

add_filter( 'ksmd_archive_default_post_types',
    function ( $list, $route_type, $context ) {
        if ( $route_type === 'author' ) {
            return array( 'post' );
        }
        return $list;
    }, 10, 3
);

ksmd_term_archive_post_types

term archive (category / tag / custom taxonomy) で対象とする post_type を上書きします。

シグネチャ

apply_filters( 'ksmd_term_archive_post_types',
    array   $term_post_types,  // 計算済配列 (taxonomy.object_type ∩ enabled_post_types)
    WP_Term $term,             // 当該 term オブジェクト
    array   $context
);
// 期待戻り値: post_type slug の配列

デフォルト値

get_taxonomy($term->taxonomy)->object_typeenabled_post_types の積集合から attachment を除外した配列。page は WP 標準動作に合わせて含めます。

サンプル 1: 特定カテゴリは post のみ

add_filter( 'ksmd_term_archive_post_types',
    function ( $list, $term, $context ) {
        if ( $term->taxonomy === 'category'
             && $term->slug === 'news' ) {
            return array( 'post' );
        }
        return $list;
    }, 10, 3
);

サンプル 2: タグページから page を除外

add_filter( 'ksmd_term_archive_post_types',
    function ( $list, $term, $context ) {
        if ( $term->taxonomy === 'post_tag' ) {
            return array_values( array_diff( $list, array( 'page' ) ) );
        }
        return $list;
    }, 10, 3
);

サンプル 3: カスタム taxonomy で CPT を追加

add_filter( 'ksmd_term_archive_post_types',
    function ( $list, $term, $context ) {
        if ( $term->taxonomy === 'product_brand' ) {
            $list[] = 'product_review';
            return array_values( array_unique( $list ) );
        }
        return $list;
    }, 10, 3
);

ksmd_archive_query_args

archive 出力時に内部で使う WP_Query args を直前で上書きします。

シグネチャ

apply_filters( 'ksmd_archive_query_args',
    array  $args,        // WP_Query args (post_type, post_status, posts_per_page, paged, etc.)
    string $route_type,  // 'archive' | 'term' | 'author' | 'date' | 'search' | 'feed' | 'home' | 'front_page'
    array  $context
);
// 期待戻り値: WP_Query args 配列

デフォルト値

array(
    'post_type'      => $post_types,    // route 別に計算
    'post_status'    => 'publish',
    'posts_per_page' => 50,
    'paged'          => $paged,
    'orderby'        => 'date',
    'order'          => 'DESC',
    'has_password'   => false,
    'no_found_rows'  => true,
);

サンプル 1: posts_per_page を増やす

add_filter( 'ksmd_archive_query_args',
    function ( $args, $route_type, $context ) {
        if ( $route_type === 'archive' || $route_type === 'feed' ) {
            $args['posts_per_page'] = 100;
        }
        return $args;
    }, 10, 3
);

サンプル 2: 修正日順にソート

add_filter( 'ksmd_archive_query_args',
    function ( $args, $route_type, $context ) {
        if ( $route_type === 'archive' ) {
            $args['orderby'] = 'modified';
        }
        return $args;
    }, 10, 3
);

サンプル 3: meta_query で sticky を先頭に

add_filter( 'ksmd_archive_query_args',
    function ( $args, $route_type, $context ) {
        if ( $route_type === 'home' ) {
            $args['meta_key'] = '_pinned';
            $args['orderby']  = array(
                'meta_value' => 'DESC',
                'date'       => 'DESC',
            );
        }
        return $args;
    }, 10, 3
);

ksmd_schema_type

post_type / route 別の Schema.org type を動的に決定します。設定の schema_type_map を上書きする最終ゲートです。

シグネチャ

apply_filters( 'ksmd_schema_type',
    string $type,        // 設定値 schema_type_map[$post_type] or route 別デフォルト
    string $post_type_or_route,  // 'post' | 'page' | 'archive' | 'term' | 'author' | 'date' | 'home' | 'front_page' | 'search' | 'feed' | '404'
    array  $context      // singular: ['post' => WP_Post] / route: route context
);
// 期待戻り値: Schema.org type 文字列 (例: 'NewsArticle', 'BlogPosting')

デフォルト値

post_type / routeデフォルト type
postArticle
pageWebPage
archiveCollectionPage
term / category / tagCollectionPage
authorProfilePage
dateCollectionPage
home / front_pageWebSite
searchSearchResultsPage
404Thing
feedCollectionPage

サンプル 1: post を NewsArticle として宣言

add_filter( 'ksmd_schema_type',
    function ( $type, $key, $context ) {
        if ( $key === 'post' ) {
            return 'NewsArticle';
        }
        return $type;
    }, 10, 3
);

サンプル 2: カテゴリ別に type を変える

add_filter( 'ksmd_schema_type',
    function ( $type, $key, $context ) {
        if ( $key !== 'post' ) {
            return $type;
        }
        $post = isset( $context['post'] ) ? $context['post'] : null;
        if ( ! ( $post instanceof WP_Post ) ) {
            return $type;
        }
        if ( has_category( 'tutorial', $post ) ) {
            return 'TechArticle';
        }
        if ( has_category( 'news', $post ) ) {
            return 'NewsArticle';
        }
        return $type;
    }, 10, 3
);

サンプル 3: search ページを ItemList に

add_filter( 'ksmd_schema_type',
    function ( $type, $key, $context ) {
        if ( $key === 'search' ) {
            return 'ItemList';  // SearchResultsPage より具体的
        }
        return $type;
    }, 10, 3
);

ksmd_route_schema_header_lines

route 別の Schema.org ヘッダブロックの行配列を上書きします。

シグネチャ

apply_filters( 'ksmd_route_schema_header_lines',
    array  $lines,     // Markdown 行の配列 ('> **Schema.org/...**' 等)
    string $type,      // Schema.org type
    string $headline,  // タイトル
    string $url,       // seo の正規 URL (md_host 化前)
    array  $context
);
// 期待戻り値: Markdown 行の配列

デフォルト値

array(
    "> **Schema.org/{$type}**",
    "> - name: {$headline}",
    "> - inLanguage: {$bcp47}",
    "> - url: {$url}",       // seo URL
    ">",
    "> **Schema.org/BreadcrumbList**",
    "> - 1: {$site_name} ({$home_md})",     // md_host
    "> - 2: {$headline} ({$url_md})",       // md_host
);

サンプル 1: WebSite に SearchAction を追加

add_filter( 'ksmd_route_schema_header_lines',
    function ( $lines, $type, $headline, $url, $context ) {
        if ( $type !== 'WebSite' ) {
            return $lines;
        }
        $search_url = home_url( '/?s={query}' );
        $lines[] = '>';
        $lines[] = '> **Schema.org/SearchAction**';
        $lines[] = '> - target: ' . $search_url;
        $lines[] = '> - query-input: required name=query';
        return $lines;
    }, 10, 5
);

サンプル 2: BreadcrumbList を完全削除

add_filter( 'ksmd_route_schema_header_lines',
    function ( $lines, $type, $headline, $url, $context ) {
        $kept = array();
        $skip = false;
        foreach ( $lines as $line ) {
            if ( strpos( (string) $line, '**Schema.org/BreadcrumbList**' ) !== false ) {
                $skip = true;
                continue;
            }
            if ( $skip && preg_match( '/^>\s*-\s+\d+:/', (string) $line ) ) {
                continue;
            }
            $skip = false;
            $kept[] = $line;
        }
        return $kept;
    }, 10, 5
);

サンプル 3: author route で sameAs を追加

add_filter( 'ksmd_route_schema_header_lines',
    function ( $lines, $type, $headline, $url, $context ) {
        if ( $type !== 'ProfilePage' ) {
            return $lines;
        }
        $author = isset( $context['author'] ) ? $context['author'] : null;
        if ( ! ( $author instanceof WP_User ) ) {
            return $lines;
        }
        $twitter = get_user_meta( $author->ID, 'twitter', true );
        if ( $twitter ) {
            $lines[] = '> - sameAs: https://twitter.com/' . $twitter;
        }
        return $lines;
    }, 10, 5
);

ksmd_route_schema_footer_lines

route 別の Schema.org フッタブロック (Organization) の行配列を上書きします。

シグネチャ

apply_filters( 'ksmd_route_schema_footer_lines',
    array $lines  // フッタの Markdown 行配列 (---, > **Schema.org/Organization**, ...)
);
// 期待戻り値: Markdown 行の配列

デフォルト値

array(
    "---",
    "",
    "> **Schema.org/Organization**",
    "> - name: {$site_name}",
    "> - url: {$site_url}",     // seo URL
    "> - logo: {$logo_url}",    // get_site_icon_url() があれば
);

サンプル 1: Organization に sameAs を追加

add_filter( 'ksmd_route_schema_footer_lines', function ( $lines ) {
    $lines[] = '> - sameAs: https://twitter.com/your_handle';
    $lines[] = '> - sameAs: https://www.facebook.com/your_page';
    $lines[] = '> - sameAs: https://www.linkedin.com/company/your_co';
    return $lines;
}, 10, 1 );

サンプル 2: 連絡先を追加

add_filter( 'ksmd_route_schema_footer_lines', function ( $lines ) {
    $lines[] = '>';
    $lines[] = '> **Schema.org/ContactPoint**';
    $lines[] = '> - contactType: customer support';
    $lines[] = '> - email: support@example.com';
    $lines[] = '> - availableLanguage: ja, en';
    return $lines;
}, 10, 1 );

サンプル 3: フッタを完全に空にする

add_filter( 'ksmd_route_schema_footer_lines', '__return_empty_array' );

ksmd_schema_header_lines (singular)

singular post (post / page / CPT 単一) の Schema.org ヘッダ行を上書きします。route 用とは別 filter です。

シグネチャ

apply_filters( 'ksmd_schema_header_lines',
    array   $lines,
    WP_Post $post
);

デフォルト値

array(
    "> **Schema.org/{$type}**",   // Article / WebPage / 上書き型
    "> - headline: {$headline}",
    "> - author: {$author_name}",
    "> - datePublished: {$date_published}",
    "> - dateModified: {$date_modified}",
    "> - inLanguage: {$bcp47}",
    "> - url: {$permalink}",       // seo URL
    ">",
    "> **Schema.org/BreadcrumbList**",
    "> - 1: {$site_name} ({$home_md})",
    "> - 2: {$archive_label} ({$archive_md})",
    "> - 3: {$headline} ({$permalink_md})",
);

サンプル: 記事に keywords を追加

add_filter( 'ksmd_schema_header_lines',
    function ( $lines, $post ) {
        $tags = wp_get_post_tags( $post->ID, array( 'fields' => 'names' ) );
        if ( ! empty( $tags ) ) {
            $lines[] = '> - keywords: ' . implode( ', ', $tags );
        }
        return $lines;
    }, 10, 2
);

ksmd_schema_footer_lines (singular)

singular post の Schema.org フッタ (Person + Organization) を上書きします。

シグネチャ

apply_filters( 'ksmd_schema_footer_lines',
    array   $lines,
    WP_Post $post
);

サンプル: 著者の Twitter / GitHub を sameAs として追加

add_filter( 'ksmd_schema_footer_lines',
    function ( $lines, $post ) {
        $twitter = get_user_meta( $post->post_author, 'twitter', true );
        $github  = get_user_meta( $post->post_author, 'github', true );
        $extras  = array();
        if ( $twitter ) {
            $extras[] = '> - sameAs: https://twitter.com/' . $twitter;
        }
        if ( $github ) {
            $extras[] = '> - sameAs: https://github.com/' . $github;
        }
        if ( empty( $extras ) ) {
            return $lines;
        }
        // Person ブロックの末尾に sameAs を差し込む (簡易版: 最初の '>' 区切りの直前に挿入)
        $kept = array();
        $injected = false;
        foreach ( $lines as $line ) {
            if ( ! $injected && trim( (string) $line ) === '>' ) {
                foreach ( $extras as $extra ) {
                    $kept[] = $extra;
                }
                $injected = true;
            }
            $kept[] = $line;
        }
        return $kept;
    }, 10, 2
);

補助 filter

ksmd_robots_txt_lines

md ホストの /robots.txt 出力行を上書きします。

apply_filters( 'ksmd_robots_txt_lines', array $lines );

サンプル: 特定 UA を Disallow:

add_filter( 'ksmd_robots_txt_lines', function ( $lines ) {
    $lines[] = '';
    $lines[] = 'User-agent: BadBot';
    $lines[] = 'Disallow: /';
    return $lines;
}, 10, 1 );

ksmd_test_renderer_sslverify

診断タブ Test renderer の wp_remote_get 内 sslverify を制御します (オレオレ証明書のローカル検証用)。

apply_filters( 'ksmd_test_renderer_sslverify', bool $verify );
// ローカル開発のみ無効化
add_filter( 'ksmd_test_renderer_sslverify', function ( $verify ) {
    if ( defined( 'WP_ENVIRONMENT_TYPE' )
         && WP_ENVIRONMENT_TYPE === 'local' ) {
        return false;
    }
    return $verify;
}, 10, 1 );

ksmd_bootstrap_sslverify

Bootstrap installer の verify アクションで使う wp_remote_get の sslverify を制御します。

apply_filters( 'ksmd_bootstrap_sslverify', bool $verify );

複数 filter の組み合わせ例

例 1: multisite で md_host を動的化 + i18n を route 別に切替

/**
 * multisite + i18n: blog ID ごとに md_host と inLanguage を切り替え
 */

// (1) blog ID ごとに md_host
add_filter( 'ksmd_host', function ( $md_host ) {
    if ( ! function_exists( 'get_current_blog_id' ) ) {
        return $md_host;
    }
    $map = array(
        1 => 'md.example.com',     // ja
        2 => 'md-en.example.com',  // en
        3 => 'md-ko.example.com',  // ko
    );
    $bid = get_current_blog_id();
    return isset( $map[ $bid ] ) ? $map[ $bid ] : $md_host;
}, 10, 1 );

// (2) blog ID ごとに inLanguage を上書き (header lines に介入)
add_filter( 'ksmd_route_schema_header_lines',
    function ( $lines, $type, $headline, $url, $context ) {
        if ( ! function_exists( 'get_current_blog_id' ) ) {
            return $lines;
        }
        $lang_map = array(
            1 => 'ja-JP',
            2 => 'en-US',
            3 => 'ko-KR',
        );
        $bid  = get_current_blog_id();
        $lang = isset( $lang_map[ $bid ] ) ? $lang_map[ $bid ] : 'ja-JP';
        // inLanguage 行を置換
        foreach ( $lines as &$line ) {
            if ( strpos( (string) $line, '> - inLanguage:' ) === 0 ) {
                $line = '> - inLanguage: ' . $lang;
            }
        }
        return $lines;
    }, 10, 5
);

// (3) Singular でも同様
add_filter( 'ksmd_schema_header_lines',
    function ( $lines, $post ) {
        if ( ! function_exists( 'get_current_blog_id' ) ) {
            return $lines;
        }
        $lang_map = array(
            1 => 'ja-JP',
            2 => 'en-US',
            3 => 'ko-KR',
        );
        $bid  = get_current_blog_id();
        $lang = isset( $lang_map[ $bid ] ) ? $lang_map[ $bid ] : 'ja-JP';
        foreach ( $lines as &$line ) {
            if ( strpos( (string) $line, '> - inLanguage:' ) === 0 ) {
                $line = '> - inLanguage: ' . $lang;
            }
        }
        return $lines;
    }, 10, 2
);

例 2: ニュースサイト向け統合プリセット

/**
 * ニュースサイト最適化:
 * - post を NewsArticle 化
 * - 一覧は新着順 100 件
 * - Organization に sameAs を 3 件追加
 * - home の最近記事は post と news CPT
 */

add_filter( 'ksmd_schema_type', function ( $type, $key, $context ) {
    if ( $key === 'post' ) return 'NewsArticle';
    if ( $key === 'home' || $key === 'front_page' ) return 'NewsMediaOrganization';
    return $type;
}, 10, 3 );

add_filter( 'ksmd_archive_query_args',
    function ( $args, $route_type, $context ) {
        if ( in_array( $route_type, array( 'archive', 'home', 'feed' ), true ) ) {
            $args['posts_per_page'] = 100;
        }
        return $args;
    }, 10, 3
);

add_filter( 'ksmd_route_schema_footer_lines', function ( $lines ) {
    $lines[] = '> - sameAs: https://twitter.com/news_site';
    $lines[] = '> - sameAs: https://www.facebook.com/news_site';
    $lines[] = '> - sameAs: https://www.youtube.com/@news_site';
    return $lines;
}, 10, 1 );

add_filter( 'ksmd_home_recent_post_type', function ( $default, $context ) {
    return array( 'post', 'news' );
}, 10, 2 );

例 3: EC サイト向け統合プリセット

/**
 * EC サイト向け:
 * - product を Product として宣言
 * - product 一覧 archive を ItemList に
 * - 商品ブランド taxonomy で post_type を product のみに
 * - home に products archive を最優先で
 */

add_filter( 'ksmd_schema_type', function ( $type, $key, $context ) {
    if ( $key === 'product' ) return 'Product';
    if ( $key === 'archive' ) {
        $pt = isset( $context['post_type'] ) ? $context['post_type'] : '';
        if ( $pt === 'product' ) return 'ItemList';
    }
    return $type;
}, 10, 3 );

add_filter( 'ksmd_term_archive_post_types',
    function ( $list, $term, $context ) {
        if ( $term->taxonomy === 'product_brand' ) {
            return array( 'product' );
        }
        return $list;
    }, 10, 3
);

add_filter( 'ksmd_home_archive_links', function ( $links, $context ) {
    // products を先頭に
    usort( $links, function ( $a, $b ) {
        if ( $a['pt'] === 'product' ) return -1;
        if ( $b['pt'] === 'product' ) return 1;
        return 0;
    } );
    return $links;
}, 10, 2 );

filter 登録のタイミング

登録すべきタイミングは plugins_loaded 以降

本プラグインは plugins_loaded priority 0 で ksmd_bootstrap_plugin() を実行し、 その中で全モジュールを load します。filter は WordPress の filter システムに登録するだけなので、 実際にはどのタイミングで add_filter しても大丈夫です。ただし以下のガイドラインを推奨します:

典型的なミス

⚠ Warning

ミス例 1: plugins_loaded priority -1 等の超早期で登録すると、 本プラグインの ksmd_bootstrap_plugin() より前に動くため、定数 KSMD_OPTION_KEY 等が未定義のままコールバックが呼ばれる可能性がある。priority 1 以上を推奨。

⚠ Warning

ミス例 2: after_setup_theme 等のテーマ初期化 hook で get_post_types() を呼ぶと、init 後に登録される CPT がまだ未登録の可能性がある。 CPT を扱う filter コールバックは init priority 11 以降での登録を推奨。

register_post_type と filter の協調

// よくないパターン: priority 0 だと CPT 未登録
// add_action( 'plugins_loaded', function () {
//     add_filter( 'ksmd_archive_default_post_types', function ( $list ) {
//         return array_merge( $list, get_post_types( array( 'public' => true ) ) );
//     }, 10, 3 );
// }, 0 );

// 良いパターン: init priority 11 以降
add_action( 'init', function () {
    add_filter( 'ksmd_archive_default_post_types',
        function ( $list, $route_type, $context ) {
            $public = array_diff(
                get_post_types( array( 'public' => true ) ),
                array( 'attachment' )
            );
            return array_values( array_unique( array_merge( $list, $public ) ) );
        }, 10, 3
    );
}, 11 );

filter 起動タイミングの一覧

filter呼ばれるタイミング
ksmd_hostmd ホスト判定時 + URL 書き換え時
ksmd_md_schemeURL 書き換え時 (本文 / Schema 内 URL)
ksmd_home_intro_markdownhome/front_page renderer 実行時
ksmd_home_archive_linkshome renderer + ItemList 構築時
ksmd_home_recent_post_typehome renderer 「最近の記事」構築時
ksmd_archive_default_post_typesauthor/date/search/feed renderer の WP_Query 構築前
ksmd_term_archive_post_typesterm renderer の WP_Query 構築前
ksmd_archive_query_args各 route の WP_Query 直前
ksmd_schema_typeSchema.org block 構築開始時
ksmd_route_schema_header_linesroute Schema header 構築完了直後
ksmd_route_schema_footer_linesroute Schema footer 構築完了直後
ksmd_schema_header_linessingular Schema header 構築完了直後
ksmd_schema_footer_linessingular Schema footer 構築完了直後
ksmd_robots_txt_linesmd ホスト /robots.txt 構築時

priority と $accepted_args の使い分け

add_filter() の第 3 引数は $priority、第 4 引数は $accepted_args です。 本プラグインの全 filter について、最低限必要な $accepted_args 値は以下です:

filter引数の数第 4 引数
ksmd_md_scheme33
ksmd_host11 (省略可)
ksmd_home_intro_markdown22
ksmd_home_archive_links22
ksmd_home_recent_post_type22
ksmd_archive_default_post_types33
ksmd_term_archive_post_types33
ksmd_archive_query_args33
ksmd_schema_type33
ksmd_route_schema_header_lines55
ksmd_route_schema_footer_lines11 (省略可)
ksmd_schema_header_lines22
ksmd_schema_footer_lines22
ksmd_robots_txt_lines11 (省略可)

priority の指針

$accepted_args の指針

必要な引数の数を正確に指定すること。デフォルトは 1 なので、 2 つ目以降の引数 (例: $post, $context) を使いたい場合は明示する必要があります。 指定しないと第 2 引数以降が常に null になります。

// よくある間違い: $context を使いたいが第 4 引数を省略 → $context が null になる
// add_filter( 'ksmd_archive_default_post_types', function ( $list, $route_type, $context ) {
//     // $context が常に null
// } );  // ← デフォルト accepted_args=1 で $list しか渡されない

// 正しい記述
add_filter( 'ksmd_archive_default_post_types',
    function ( $list, $route_type, $context ) {
        // 全引数が利用可能
        return $list;
    },
    10,  // priority
    3    // accepted_args
);

テーマ functions.php に書く例

すべてのカスタマイズを 1 ファイル (テーマ functions.php もしくは独自プラグイン) に集約する書き方の例です。

<?php
/**
 * theme/functions.php — ksmd 拡張
 */

// ----------------------------------------------------------
// 1. md_host を動的化 (multisite 対応)
// ----------------------------------------------------------
add_filter( 'ksmd_host', 'mytheme_ksmd_host', 10, 1 );
function mytheme_ksmd_host( $md_host ) {
    if ( function_exists( 'get_current_blog_id' ) ) {
        $bid = get_current_blog_id();
        $map = array(
            1 => 'md.example.com',
            2 => 'md-en.example.com',
        );
        if ( isset( $map[ $bid ] ) ) {
            return $map[ $bid ];
        }
    }
    return $md_host;
}

// ----------------------------------------------------------
// 2. post を NewsArticle 扱いに
// ----------------------------------------------------------
add_filter( 'ksmd_schema_type', 'mytheme_ksmd_schema_type', 10, 3 );
function mytheme_ksmd_schema_type( $type, $key, $context ) {
    if ( $key === 'post' ) return 'NewsArticle';
    return $type;
}

// ----------------------------------------------------------
// 3. archive 一覧を 100 件に
// ----------------------------------------------------------
add_filter( 'ksmd_archive_query_args', 'mytheme_ksmd_archive_query_args', 10, 3 );
function mytheme_ksmd_archive_query_args( $args, $route_type, $context ) {
    if ( in_array( $route_type, array( 'archive', 'home', 'feed' ), true ) ) {
        $args['posts_per_page'] = 100;
    }
    return $args;
}

// ----------------------------------------------------------
// 4. Organization に sameAs を 3 件追加
// ----------------------------------------------------------
add_filter( 'ksmd_route_schema_footer_lines',
    'mytheme_ksmd_org_sameAs', 10, 1 );
add_filter( 'ksmd_schema_footer_lines',
    'mytheme_ksmd_org_sameAs_singular', 10, 2 );

function mytheme_ksmd_org_sameAs( $lines ) {
    $extras = array(
        '> - sameAs: https://twitter.com/your_handle',
        '> - sameAs: https://www.facebook.com/your_page',
    );
    foreach ( $extras as $line ) {
        $lines[] = $line;
    }
    return $lines;
}

function mytheme_ksmd_org_sameAs_singular( $lines, $post ) {
    return mytheme_ksmd_org_sameAs( $lines );
}

// ----------------------------------------------------------
// 5. home の intro を季節で動的に変える
// ----------------------------------------------------------
add_filter( 'ksmd_home_intro_markdown',
    'mytheme_ksmd_home_intro', 10, 2 );
function mytheme_ksmd_home_intro( $intro, $context ) {
    $month = (int) date_i18n( 'n' );
    if ( $month === 12 || $month === 1 ) {
        return "新年の特集ページを公開中です。\n\n" . $intro;
    }
    return $intro;
}

他プラグインからのアクセス

Cocoon との連携

Cocoon は the_content 経由でショートコードを展開しますが、本プラグインは the_content フィルタ通過後の HTML を Markdown 化するため、Cocoon のショートコード展開結果 (例: [toc] による目次) はそのまま反映されます。

ただし以下の挙動に注意:

Yoast SEO との連携

Yoast の canonical / sitemap 出力はメインサイト (www) で動作し、md ホストでは template_redirect priority -1 で本プラグインが先に exit するため Yoast は動きません。 つまり md ホストで Yoast の <head> 出力が混入することはありません。

md ホストの canonical は本プラグインが HTTP Link ヘッダで宣言します。 X-Robots-Tag: noindex, follow も同様に本プラグインが付与します。 つまり Yoast と本プラグインは「メインサイトの SEO 担当 = Yoast」「md ホストの canonical 宣言 = 本プラグイン」 という分業が自然に成立します。

RankMath との連携

RankMath も Yoast と同様、メインサイト側でだけ動作します。md ホストには影響しません。

ただし RankMath が the_content hook で BreadcrumbList の自動挿入をしている場合、 その HTML 出力が Markdown 化されて含まれる可能性があります。本プラグインの BreadcrumbList と重複しないよう、 必要なら以下の filter で対処してください:

// 例: RankMath の breadcrumb HTML が記事冒頭に出ている場合、md 経路だけ remove
add_action( 'template_redirect', function () {
    $host = isset( $_SERVER['HTTP_HOST'] )
        ? strtolower( $_SERVER['HTTP_HOST'] )
        : '';
    $opts = get_option( 'ksmd_settings', array() );
    $md_host = isset( $opts['md_host'] ) ? $opts['md_host'] : '';
    if ( $host !== strtolower( $md_host ) ) return;
    // RankMath の breadcrumb 関連 filter を remove
    remove_filter( 'the_content', 'rank_math_breadcrumbs_inject' );
}, -2 );  // ksmd の priority -1 より先

Polylang / WPML との連携

多言語プラグインを使う場合、md_host の言語別切替を ksmd_host filter で実装することが推奨です。 また inLanguage も言語別に上書きすると AI クローラに正しく多言語サイトとして認識されます。

add_filter( 'ksmd_host', function ( $md_host ) {
    if ( function_exists( 'pll_current_language' ) ) {
        $lang = pll_current_language( 'slug' );
        if ( $lang === 'en' ) return 'md-en.example.com';
        if ( $lang === 'ko' ) return 'md-ko.example.com';
    }
    return $md_host;
}, 10, 1 );

💡 Tip

言語別に md_host を切り替えた場合、それぞれの md_host について Bootstrap installer か手動 SSH で index.php を配置する必要があります。設定タブの md_host は単一値しか保存できないため、 Bootstrap installer は 1 ホストのみ対応。残りのホストは SSH で同じ index.php を配置してください。

WP-CLI との連携

WP-CLI から本プラグインの cache を flush するには:

wp eval 'ksmd_cache_flush_all();'

# transient だけ消したい場合
wp transient delete --all --network

WP-CLI は DOING_CRON/DOING_AJAX/REST_REQUEST 定数を立てないため、 md ホストへ wp eval --url=md.example.com/... でリクエストすることはできますが、 通常は不要です (テスト用)。

まとめ

本章では本プラグインが提供する 16 個の filter を解説しました:

これらを組み合わせれば、本プラグインのコードに一切手を入れずに以下のようなカスタマイズが可能です:

次章「トラブルシューティング」では、これらの filter を使っても解決しない問題への対処方法を解説します。