WordPressマルチサイトでREST APIからタグが入らない問題と解決策

IT・WEB

REST APIを使って外部から記事を自動投稿するツールを作っていたところ、サブディレクトリ型のマルチサイトだけタグが付かないという問題に直面しました。

投稿自体はHTTP 201で成功するのに、レスポンスの tags フィールドは常に空配列。原因を調べてみると、WordPressマルチサイト特有のID管理の仕様に起因するものでした。同じ問題で詰まっている方のために、原因と解決策を共有します。

症状の確認

次のようなコードでタグ付きの投稿を試みます。

$post_data = [
    'title'      => '記事タイトル',
    'content'    => '本文',
    'status'     => 'publish',
    'categories' => [5],
    'tags'       => [853, 854],  // タグIDを配列で渡す
];

独立ドメインのWordPressでは問題なく動くのに、サブディレクトリ型マルチサイト(例:example.com/subsite)では次のレスポンスが返ります。

{
    "id": 595,
    "status": "publish",
    "categories": [5],
    "tags": []    // タグが空
}

HTTP 201が返るので一見成功しているように見えますが、投稿を確認するとタグが一つも付いていません。

原因:term_id と term_taxonomy_id のズレ

WordPressはタグ(taxonomy)を2つのテーブルで管理しています。

  • wp_terms テーブルの term_id:タグそのもの(名前・スラッグ)の識別子
  • wp_term_taxonomy テーブルの term_taxonomy_id:「どのサイトのどのタクソノミーか」を示す識別子

通常の単一WordPressでは term_idterm_taxonomy_id の値が一致することが多いため問題になりません。しかしマルチサイトでは、複数サイトが同じデータベースを共有するため、これらの値が必ずしも一致しません。

REST APIの tags フィールドは内部的に term_taxonomy_id で動作します。ところがタグ作成APIが返す idterm_id です。マルチサイトではこの2つが別の値になるため、渡したIDが無視されてしまいます。

さらに、/wp-json/wp/v2/tags?search=キーワード で既存タグを検索しても常に空配列が返るという問題も重なります。「タグが見つからない → 新規作成 → でも紐付かない」というループに陥ります。

なお、この問題はサブディレクトリ型マルチサイト固有です。独立ドメインのWordPressでは発生しません。

解決策:カスタムエンドポイントを使う

WordPressの内部関数 wp_set_post_tags() はタグ名を直接受け取り、IDの解決を内部で行います。この関数はREST APIのID変換を迂回するため、マルチサイトでも正確に動作します。

この関数をREST API経由で呼べるようにカスタムエンドポイントを追加することで解決できます。

WordPressにエンドポイントを追加する

タグを付けたいサブサイトで有効になっているテーマの functions.php の末尾に次のコードを追記します。

add_action('rest_api_init', function () {
    register_rest_route('asp/v1', '/set-tags', [
        'methods'             => 'POST',
        'callback'            => 'asp_set_post_tags_by_name',
        'permission_callback' => function () {
            return current_user_can('edit_posts');
        },
    ]);
});

function asp_set_post_tags_by_name(WP_REST_Request $request) {
    $post_id   = (int) $request->get_param('post_id');
    $tag_names = $request->get_param('tag_names');

    if (!$post_id || !is_array($tag_names) || empty($tag_names)) {
        return new WP_Error('invalid_params', 'post_id and tag_names are required', ['status' => 400]);
    }

    // wp_set_post_tags() はタグ名を直接受け付けるためIDのズレが発生しない
    $result = wp_set_post_tags($post_id, $tag_names, false);

    if (is_wp_error($result)) {
        return new WP_Error('tag_set_failed', $result->get_error_message(), ['status' => 500]);
    }

    $tags = wp_get_post_tags($post_id);
    $tag_data = array_map(function($tag) {
        return ['id' => $tag->term_id, 'name' => $tag->name, 'slug' => $tag->slug];
    }, $tags);

    return rest_ensure_response([
        'success'  => true,
        'post_id'  => $post_id,
        'tags_set' => $tag_data,
    ]);
}

外部スクリプト側の呼び出し方

投稿作成後に取得した post_id とタグ名の配列をカスタムエンドポイントに送ります。

// 通常通り投稿を作成(tags フィールドは不要)
$post_data = [
    'title'      => '記事タイトル',
    'content'    => '本文',
    'status'     => 'publish',
    'categories' => [5],
];

$res     = wp_rest_post($site_url, $post_data);
$post_id = $res['id'] ?? null;

// 投稿成功後にカスタムエンドポイントでタグをセット
if ($post_id && !empty($tag_names)) {
    set_tags_via_custom_endpoint($site_url, $auth, $post_id, $tag_names);
}

function set_tags_via_custom_endpoint(string $site_url, string $auth, int $post_id, array $tag_names): bool {
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => $site_url . '/wp-json/asp/v1/set-tags',
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            'Authorization: Basic ' . $auth,
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode([
            'post_id'   => $post_id,
            'tag_names' => $tag_names,
        ]),
        CURLOPT_TIMEOUT => 15,
    ]);
    $res  = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    $json = json_decode($res, true);
    return $code === 200 && !empty($json['success']);
}

動作確認

エンドポイントが正しく追加されているかはcurlで確認できます。

curl -X POST https://example.com/subsite/wp-json/asp/v1/set-tags \
  -H "Authorization: Basic your_base64_credentials" \
  -H "Content-Type: application/json" \
  -d '{"post_id": 100, "tag_names": ["テストタグ", "WordPress"]}'

成功すると次のレスポンスが返ります。

{
    "success": true,
    "post_id": 100,
    "tags_set": [
        { "id": 7,  "name": "テストタグ", "slug": "testtag" },
        { "id": 12, "name": "WordPress",  "slug": "wordpress" }
    ]
}

注意点

新規サブサイト追加時は必ずfunctions.phpに追記する

functions.phpはサイトごとに独立しています。新しいサブサイトを追加するたびに、そのサブサイトで有効なテーマのfunctions.phpへの追記が必要です。投稿自体は成功(HTTP 201)するのでタグが付いていないことに気づきにくい点に注意してください。

タグの上書きと追記の違い

wp_set_post_tags() の第3引数が動作を決めます。

  • false(デフォルト):既存のタグを置き換える
  • true:既存のタグに追記する

独立ドメインのWordPressには影響しない

独立ドメインのWordPressでは通常のREST APIで問題なく動作します。このカスタムエンドポイントを追加しても悪影響はないので、すべてのサイトで統一して使う構成にすることも可能です。

まとめ

サブディレクトリ型WordPressマルチサイトでREST APIのタグ投稿が効かない場合、原因は term_idterm_taxonomy_id のズレです。

  • REST APIの tags フィールドにIDを渡す方法はマルチサイトでは機能しない
  • タグ検索APIもマルチサイトでは正常に動作しないことがある
  • wp_set_post_tags() はタグ名で直接処理するためIDのズレ問題が発生しない
  • カスタムエンドポイントを経由することでマルチサイトでも確実にタグを付与できる
  • 新規サブサイト追加のたびにfunctions.phpへの追記が必要な点を忘れずに

コメント

タイトルとURLをコピーしました