タグアーカイブ WordPress

WordPressをサブフォルダにインストールするとREST APIが404になる原因と直し方

WordPressをサブフォルダにインストールしている環境で、REST APIのエンドポイントが404エラーを返す場合は、プラグインがサブフォルダを考慮せずにAPIのURLを生成しているバグが原因だ。該当プラグインを最新版に更新するか、パーマリンク設定のリフレッシュで解決する。

なぜサブフォルダ環境でプラグインのAPIが404になるのか

なぜサブフォルダ環境でプラグインのAPIが404になるのか

WordPressをドキュメントルート直下ではなく/blog/siteのようなサブフォルダにインストールした場合、REST APIのベースURLはhttps://example.com/subfolder/wp-json/となる必要がある。ところが一部のプラグインは、内部でAPIのURLを組み立てる際にこのサブフォルダを考慮しておらず、https://example.com/wp-json/...のようにルート直下を指してしまう。その結果、実在しないパスへのリクエストとなり404が返る。

今回のケースでは、プラグインが独自に追加したエンドポイント/profeedwp/v1/linkedin/company-posts/smartに対して、サブフォルダを含まない不完全なURLでリクエストを発行していた。同様の問題は、テーマや他のプラグインがrest_url()関数を正しく使わずにハードコードしたパスを参照している場合にも起こる。

解決手順

解決手順

まず簡単かつ即効性のある方法として、問題のプラグインを最新版へ更新する。次に、WordPressのパーマリンク設定をリセットし、REST APIのルートURLが正しく再構築されるか確認する。これで直らない場合は、手動でrest_url()が返す値を検証し、他のプラグインとの競合を調べる。

STEP 1 問題のプラグインを最新版に更新する
STEP 2 パーマリンク設定をリセットして API ルートを再構築する
STEP 3 rest_url() の戻り値を検証し、サブフォルダが含まれるか確認する
STEP 4 全プラグインを無効化して競合を切り分ける

プラグインを最新版に更新する

本件ではバージョン1.6.10で修正が行われている。管理画面の「プラグイン」→「インストール済みプラグイン」から対象プラグインを確認し、更新が表示されていれば適用する。更新が出ていない場合は、一度プラグインを削除して再インストールするか、開発元の公式ページから修正版がリリースされていないか確認する。

パーマリンク設定をリセットする

プラグインの更新で直らなかった場合、パーマリンク構造の再保存でWordPress内部のルーティングをリフレッシュできる。「設定」→「パーマリンク」を開き、現在選択されている設定をそのままの状態で「変更を保存」をクリックする。これにより.htaccessの再生成と、REST APIのルート定義が再構築される。サブフォルダ環境では特に、リライトルールが正しくサブフォルダをプレフィックスとして含む必要があるため、この一手順で解決するケースが多い。

rest_url() の戻り値を検証する

根本原因がプラグイン側のURL組み立てにあるかどうかを切り分けるには、WordPressが正しいREST APIのルートURLを返しているかを確認する。テーマのfunctions.phpなどに次のようなテストコードを一時的に追加する。

add_action('wp_footer', function() {
    echo '<!-- REST URL: ' . esc_url(rest_url()) . ' -->';
});

サイトのフッター部分のHTMLソースに出力されたURLがhttps://example.com/subfolder/wp-json/の形式になっていれば、WordPress本体の認識は正しい。もし/subfolderが欠落している場合は、wp-config.phpWP_HOMEWP_SITEURLが正しくサブフォルダを含んだ値で定義されているか確認する。

全プラグインを無効化して競合を切り分ける

それでも404が解消しない場合、別のプラグインがREST APIのルーティングに干渉している可能性がある。すべてのプラグインを一括で無効化し、問題のエンドポイントにアクセスして200番台のレスポンスが返るかテストする。正常動作が確認できたら、プラグインを1つずつ有効化して原因のプラグインを特定する。キャッシュ系プラグインやセキュリティプラグインは、REST APIへのリクエストをブロックしたり、URLを書き換えたりする設定項目を持つことがあるため、該当するプラグインの設定もあわせて確認する。

よくある質問

サブフォルダにインストールする際にwp-config.phpで注意すべき点は?

WP_HOMEWP_SITEURLの定数をhttps://example.com/subfolderのようにサブフォルダを含めて明示的に定義しておくと、サイトURLの誤認識を防げる。wp-config.phpに記述しなければならないわけではないが、マルチサーバー構成やリバースプロキシの背後で運用する場合は特に有効だ。

REST APIの404エラーはどのようにデバッグすればいいか?

ブラウザのデベロッパーツールのネットワークタブで、実際に送信されたリクエストURLを確認する。サブフォルダが欠落したURLでリクエストが発生している場合は、呼び出し元のJavaScriptファイルやPHPコードでURLの組み立て方をチェックする。rest_url()を使わずにハードコードされたパスが原因であることが多い。

プラグインを更新しても問題が再発する場合は?

修正パッチが適用されたバージョンでも、キャッシュの残存やデータベースに保存された古い設定値が原因で再発することがある。プラグインを完全に削除したあと、wp_optionsテーブルに残っている該当プラグインのオプションを手動で削除し、最新版を再インストールすると改善する場合がある。

サブフォルダ環境でなくてもAPIが404になる原因は?

パーマリンク設定が「基本」になっているとREST APIが動作しない。また、セキュリティプラグインが/wp-json/へのアクセスを制限しているケースもある。.htaccessのリライトルールが破損している場合も404になるため、パーマリンク設定の再保存でリフレッシュするのが初手として有効だ。

この記事のポイント

  • サブフォルダ環境でプラグインのAPIが404になるのは、URLにサブフォルダが含まれない不完全なパスが原因
  • 問題のプラグインを最新版に更新し、パーマリンク設定を再保存するのが解決の基本手順
  • rest_url() の戻り値とwp-config.phpの設定を確認し、WordPress本体のURL認識が正しいか検証する
  • 全プラグインの無効化で競合を切り分け、キャッシュやセキュリティ系プラグインの干渉を疑う
WP-CLIとREST APIとAbilities API、WordPressインターフェースの選び方

WP-CLIとREST APIとAbilities API、WordPressインターフェースの選び方

3つのインターフェースの全体像

3つのインターフェースの全体像

WordPressには外部からデータをやり取りするための主要なインターフェースが3つ存在する。WP-CLI、REST API、Abilities APIだ。それぞれが異なる距離感でWordPressと向き合い、異なる呼び出し元に対応する。これらを競合関係と捉えるのは誤りで、実際には階層構造をなしている。

WP-CLIはサーバー上で動作し、REST APIはHTTPを介して通信する。そしてAbilities APIは、そのさらに上位に位置し、AIエージェントが何をすべきかを判断する層になる。どのレイヤーがどこに位置するのかを理解すれば、タスクに応じた最適な選択はおのずと見えてくる。

  • WP-CLI:サーバー上で直接PHPを実行(またはSSH経由)。一括操作、移行、デプロイ、メンテナンス向き
  • REST API:wp-jsonへのHTTPリクエスト。ブラウザ、モバイルアプリ、外部サービスからコンテンツの読み書きに使用
  • Abilities API:RESTとMCPで公開される名前付きPHPケイパビリティ。AIエージェントが安全に操作を行えるように設計
Abilities API(AIエージェント層)
AIエージェントが安全にWordPressを操作するためのケイパビリティ定義
「何ができるか」を記述し、許可された操作のみを公開する
REST API(HTTP層)
ブラウザ、アプリ、外部サービスからのHTTP通信
「どんなデータがあるか」を公開し、認証付きで読み書き可能に
WP-CLI(コマンドライン層)
サーバー上での直接PHP実行、SSH経由の一括操作
HTTP往復なし、認証トークン不要、最高速での実行が可能
■ 上位層ほど「自律性」が高く「説明的」 ■ 下位層ほど「高速」で「直接操作」

3つのインターフェースは、下位ほど呼び出し元がサイトに近く、信頼度も高い。上位になるほど、呼び出し元は自律的で遠隔地に位置する。この構造を理解すれば、「どれを使うべきか」の判断はシンプルになる。

WP-CLI:サーバー上のコマンドライン

WP-CLI:サーバー上のコマンドライン

WP-CLIはWordPressのインストール環境に対して直接PHPを実行する。コマンド例としては wp post createwp plugin updatewp search-replacewp db export などがある。実行にはサーバーへのシェルアクセス(SSH)が前提だが、その分HTTPの往復も認証トークンの管理も不要になる。

WP-CLIが最も威力を発揮するのは、サイトを完全に制御できる状況だ。1000件の投稿を移行する、データベース全体でドメインを置換する、定期メンテナンスをスクリプト化する、あるいはデプロイの自動化など、スピードが求められる一括操作では他の追随を許さない。

WP-CLIが適さないケース
ブラウザやモバイルアプリからのアクセス、リモートサービスとの連携には使えない
WP-CLIが最も輝く場面
一括移行、データベース操作、定期メンテナンスの自動化、デプロイスクリプト

WP-CLIはシェルアクセスが前提のため、ブラウザやモバイルアプリ、外部サービスがサイトと通信する手段にはなりえない。しかし開発者がサイト全体を制御できる状況では、WP-CLIは圧倒的な速度と柔軟性を提供する。ターミナルからすべてを操作するワークフローが浸透している開発現場も多く、管理画面(wp-admin)をほとんど開かない運用も可能だ。

REST API:HTTP越しのWordPress

REST API:HTTP越しのWordPress

REST APIはWordPressサイトを、あらゆるHTTPクライアントが読み書きできる状態に変換する。エンドポイントは /wp-json/wp/v2/ 配下に存在し、認証にはアプリケーションパスワード、Cookieとnonce、あるいはOAuthを用いる。ブラウザ、モバイルアプリ、外部サービスがインターネット越しにコンテンツを取得・更新できるようになる。

ヘッドレスCMS構成のWordPressは、このREST APIを基盤に動作する。AstroやNext.jsで構築したフロントエンドがREST経由でコンテンツを取得し、モバイルアプリが投稿を行い、サードパーティ連携がデータを同期する。呼び出し元がサーバー外にいる場合、REST APIがほぼ唯一の通信経路となる。

REST APIの構造と制約
公開するもの
投稿、ユーザー、タクソノミー、設定といった「リソース」
公開しないもの
「誰が何をしたいのか」という意図や操作の文脈
人間の開発者ならドキュメントを読んで適切なリクエストを組み立てられるが、AIエージェントにはハードルが高い

REST APIには重要な限界がある。公開するのは「データの構造」であり、「そのデータで何をしたいのか」という操作の意図までは記述しない。どのエンドポイントが存在し、どうリクエストを組み立てるべきかは、呼び出し元が自ら理解する必要がある。人間の開発者であれば問題ないが、AIエージェントにとっては推論すべき情報が多すぎるという課題が残る。

Abilities API:AIエージェントのためのケイパビリティ層

Abilities API:AIエージェントのためのケイパビリティ層

Abilities APIはWordPress 6.9でコアに導入された最新のインターフェースだ(それ以前のバージョン向けにはプラグインも提供されている)。REST APIが残した「AIエージェントが何を許可されているのかをどう知るか」という課題を解決するために設計された。

Abilities APIでは、生のリソースを公開する代わりに、プラグインやテーマが「名前付きケイパビリティ(能力)」を登録する。各アビリティは、一意のID、人間が読めるラベル、説明文、入力・出力のスキーマ、権限チェックのコールバック、そして実行コールバックを備えた独立した操作単位となる。

add_action( 'wp_abilities_api_init', function () {
    wp_register_ability( 'my-plugin/publish-draft', [
        'label'             => '下書きを公開',
        'description'       => 'IDを指定して既存の下書き投稿を公開する',
        'category'          => 'my-plugin',
        'input_schema'      => [ /* 期待する入力のJSON Schema */ ],
        'output_schema'     => [ /* 結果のJSON Schema */ ],
        'permission_callback' => 'my_plugin_can_publish',
        'execute_callback'  => 'my_plugin_publish_draft',
        'meta'              => [ 'show_in_rest' => true ],
    ] );
} );
REST API
データの構造を公開する
「どんなリソースがあるか」に答える
Abilities API
操作の意図と許可を公開する
「何ができるか」「誰が許可されているか」に答える
アビリティは「これが実行可能な操作であり、必要な入力と許可条件はこれだ」という契約をAIエージェントに提示する

meta.show_in_rest をtrueに設定すると、そのアビリティは wp-json/wp-abilities/v1/abilities で公開され、クライアントが検出できるようになる。JavaScript側では @wordpress/abilities パッケージを介して利用する。

Abilities APIの最大の価値は、エージェントが安全に行動するために必要な「契約」を提供することだ。操作の定義、必要な入力形式、実行許可の条件が明示されるため、AIエージェントがサイトを壊すリスクを最小限に抑えられる。複数のエージェントが共通の語彙で協調動作するマルチエージェント構成でも、Abilities APIが基盤になりつつある。

3つのインターフェースの積み重なり方

3つのインターフェースの積み重なり方

3つのインターフェースは互いに積み重なる関係にある。Abilities APIは多くの場合REST APIの上に構築され、REST APIはWP-CLIが直接駆動するPHPの上で動作する。すべての基盤にあるのは、同じWordPressコア、同じデータベース、同じ関数群だ。

したがって問うべきは「どれが最善か」ではない。「呼び出し元がサイトからどれだけ離れているか」「操作の意図をどこまで明示する必要があるか」という視点で選択することが本質になる。呼び出し元が近く信頼できるほど下位層を、自律的で遠隔にあるほど上位層を使う。

距離:最短 サーバー上(SSH) WP-CLI 一括操作・最高速
距離:中程度 HTTP越し(リモート) REST API データの読み書き
距離:最長 AIエージェント(自律的) Abilities API 安全な操作定義

上位層になるほど「記述性」と「安全性」が重視され、下位層ほど「速度」と「直接制御」に優れる。これらは設計上、相補的な関係にあり、実際のプロジェクトではすべてを併用するのが理想的な構成だ。

各インターフェースの使い分け方

各インターフェースの使い分け方

日常的なタスクにおける選択指針を整理する。

  • 自分が制御するサイトに対して、一括かつ高速に操作したい → WP-CLI。移行、デプロイ、定期ジョブ、データベース操作が該当する
  • ブラウザ、アプリ、外部サービスがコンテンツを読み書きする必要がある → REST API。ヘッドレスフロントエンド、モバイルアプリ、外部連携が該当する
  • AIエージェントにサイトを壊さず操作させたい → Abilities API。許可したい操作をスキーマと権限付きで登録し、エージェントに発見させる
STEP 1 呼び出し元の「距離」を確認する(サーバー内か、リモートか、自律エージェントか)
STEP 2 操作に必要な「明示性」を判断する(データ構造だけで足りるか、操作意図の記述が必要か)
STEP 3 最適なレイヤーを選ぶ(多くの場合、複数レイヤーの併用が正解)

実際のプロジェクトでは、この3つを排他的に使うことはまれだ。むしろそれぞれの得意領域を活かして組み合わせるのが、効率的なWordPress運用の鍵になる。

3つを組み合わせた実践的な構成

3つを組み合わせた実践的な構成

WP Mayorの記事では、実際に3つのインターフェースを併用している構成例が紹介されている。まず、公開運用と日常的な運用作業はSSH経由のWP-CLIで実行される。新規投稿、メディアのインポート、プラグイン更新、キャッシュクリアといった操作をターミナルから完結させ、管理画面(wp-admin)をほとんど開かない運用が行われている。

フロントエンドはヘッドレス構成で、REST API越しにコンテンツを取得する。Astroで構築されたサイトが wp-json 経由でWordPressからデータを取得し、高速な静的ページとして配信する。訪問者はWordPressテーマに触れることなく、WordPressはバックエンドのエンジンとして機能し、REST APIがそのパイプ役を担う。

エージェント向けの機能はAbilities APIを通じて提供される。AIエージェントに限定的なタスクを任せたい場合、関連プラグインがその操作をアビリティとして登録する。権限チェックとスキーマを伴うため、シェルアクセスを丸ごと渡したり、大量の生エンドポイントをエージェントに解析させたりする必要がなくなる。

WP-CLI(運用層)
投稿・メディア・プラグイン管理をターミナルから一括実行。シェルアクセス可能なAIアシスタントも直接駆動できる
REST API(配信層)
ヘッドレスフロントエンドがコンテンツを取得。Astroが静的ページとして配信し、訪問者はWordPressテーマに触れない
Abilities API(エージェント層)
AIエージェント向けにスコープ付き操作を登録。権限チェックとスキーマにより安全な自動化を実現
各レイヤーがそれぞれの得意領域を担当し、互いに補完し合う構成

WP-CLIは速度と一括処理能力で、REST APIは外部連携の柔軟性で、Abilities APIはAIエージェントの安全性で優位性を持つ。1つのインターフェースに別の役割を強制しようとするところから問題は始まる。3つのレイヤーを適材適所で使い分けることが、WordPress自動化の効率を最大化する道筋だ。

この記事のポイント

  • WP-CLI、REST API、Abilities APIは競合ではなく、呼び出し元の距離に応じた階層構造をなす
  • WP-CLIはサーバー上の直接操作に最適で、一括処理と速度が求められる場面で選ぶ
  • REST APIはHTTP越しのデータ読み書きを担い、ヘッドレス構成やモバイルアプリ連携の基盤となる
  • Abilities APIはAIエージェントに操作の安全な契約を提供し、マルチエージェント構成でも威力を発揮する
  • 実際のプロジェクトでは3つを組み合わせ、各レイヤーの得意領域を活かすのが理想的な運用だ
Gravity Forms PDFで合計金額が倍になる原因と修正方法

Gravity Forms PDFで合計金額が倍になる原因と修正方法

Gravity Forms で作成したフォームから PDF を出力するプラグイン「PDF Invoices for Gravity Forms」を使っていて、テンプレート内で get_total() メソッドを複数回呼び出すと合計金額が呼び出し回数に応じて倍々に膨らんでしまう現象は、静的変数 self::$total が各呼び出しのたびに加算され続ける設計になっているのが原因だ。直すにはヘルパークラスを子テーマから拡張し、get_total() の内部で毎回リセットして再計算させる変更を加える。

合計金額が倍になる現象はどのようなときに起こるのか

合計金額が倍になる現象はどのようなときに起こるのか

たとえば請求書のテンプレートに「小計」と「総合計」を別々の位置に表示したい場合、PDF_Invoices_For_GravityForms_Helpers::get_total() を2回呼ぶことになる。ところがイベント参加登録や商品注文フォームなどで実際にこの処理を通すと、2回目の呼び出し時には1回目に加算された値にさらに同じ計算が上乗せされ、本来 10,000 円のところが 20,000 円になるといった不具合が起きる。

なぜ get_total() を複数回呼ぶと値が積み上がるのか

なぜ get_total() を複数回呼ぶと値が積み上がるのか

問題の根本は class-pcafe-gfpi-helpers.php ファイル内の get_total() メソッドにある。このメソッドは静的変数 self::$total を使い、内部で次のように加算している。

public static function get_total(){
    self::$total += self::get_subtotal();
    self::$total += self::$shipping;
    return self::$total;
}

静的変数はリクエストの間ずっと値を保持するため、同じ処理中に get_total() が呼ばれるたびに前回の合計に小計と送料が足されていく。2回呼べば「小計 + 送料」が2倍になり、3回なら3倍になる。通常、こうした合計取得メソッドは毎回ゼロから計算し直すべきであり、内部で継ぎ足す構造は意図したものでない可能性が高い。

Before(問題のある状態)
1回目呼び出し: 小計=10,000 + 送料=500 → total=10,500
変数 self::$total が 10,500 を保持したまま
2回目呼び出し: 10,500 + 10,000 + 500 → total=21,000
After(修正後のあるべき動作)
1回目呼び出し: 小計=10,000 + 送料=500 → total=10,500
2回目呼び出し: 変数をリセット後 再計算 → total=10,500
静的変数が保持され加算される問題  毎回リセットして再計算する修正後

上の図のように、2回目で合計が倍になる。特に「小計」「消費税」「総合計」など複数の金額を PDF テンプレートに配置する場合にこの問題が顕在化しやすい。

get_total() 修正の基本的な考え方

get_total() 修正の基本的な考え方

プラグイン本体のファイルを直接編集してしまうと、アップデートのたびに修正が上書きされて消える。そのため子テーマの functions.php を使い、プラグインのヘルパークラスを拡張した独自クラスを用意する方法をとる。拡張クラスでは get_total() メソッドをオーバーライドし、計算前に self::$total を強制的に 0 にリセットしてから小計と送料を加算する。

子テーマでヘルパークラスを拡張して修正する手順

子テーマでヘルパークラスを拡張して修正する手順

独自ヘルパークラスを作成する

まず子テーマの functions.php に、プラグインのヘルパークラスを継承したクラスを定義する。子テーマがない場合は、Code Snippets プラグインを使うか、wp-content/themes/(現在のテーマ)/functions.php に追記する形でもよい。コードは以下のようになる。

class Custom_GFPI_Helpers extends PDF_Invoices_For_GravityForms_Helpers {

    public static function get_total() {
        self::$total = 0; // 計算前に必ずリセットする
        self::$total += self::get_subtotal();
        self::$total += self::$shipping;
        return self::$total;
    }
}

テンプレート内でカスタムクラスを呼び出す

拡張クラスを作っただけでは既存のテンプレートには反映されない。PDF テンプレート内で PDF_Invoices_For_GravityForms_Helpers::get_total() を呼んでいる箇所を、先ほど定義した Custom_GFPI_Helpers::get_total() に置き換える。

テンプレートファイルは多くの場合 wp-content/uploads/pdf-invoices-for-gravity-forms/templates/ 以下にカスタムテンプレートとして配置されている。該当の .php ファイルを開き、以下のように書き換える。

<?php
// 修正前
// echo PDF_Invoices_For_GravityForms_Helpers::get_total();

// 修正後
echo Custom_GFPI_Helpers::get_total();
?>

これでテンプレート内のどの場所から呼び出しても、毎回リセット後に計算が走るため合計が積み上がることはなくなる。

変更後にキャッシュと動作を確認する

変更を加えたあとは、必ず PDF を生成し直して合計金額が正しいか確認する。Gravity Forms のエントリーから「PDFを表示」ボタンで実際の請求書を開き、同じ合計が要求された位置すべてに正しく表示されているかをチェックする。サイトでキャッシュプラグインを使っている場合は、キャッシュを全削除してから確認すると確実だ。

STEP 1 子テーマの functions.php にカスタムクラスを定義する
STEP 2 PDF テンプレート内の呼び出しを Custom_GFPI_Helpers に変更する
STEP 3 キャッシュを削除し、PDF を生成し直して合計金額を確認する

よくある質問

プラグイン本体のファイルを直接修正してもよいか

推奨しない。プラグインがアップデートされるたびに修正が上書きされ、その都度同じ変更を加えなければならなくなる。子テーマや Code Snippets を使う方法なら、アップデートに影響されず継続的に動作する。

他の金額表示(税額や値引き額)も倍増している場合の対処は

同じヘルパークラス内で定義されている get_tax()get_discount() にも同様の静的変数の加算構造がある可能性が高い。それらのメソッドも同じ要領でカスタムクラス内にオーバーライドし、内部で該当の静的変数をリセットする処理を加えるとよい。

子テーマを使っていない場合でも対応できるか

子テーマがない場合は Code Snippets プラグインが便利だ。スニペットとしてクラス定義を追加すれば、テーマに依存せず同じ修正を適用できる。テンプレートの書き換えは手動で行う必要があるが、クラスの読み込み自体はスニペット経由で問題なく動作する。

修正後に PDF が真っ白になる場合の確認点は

クラス名やメソッド名のスペルミス、オートローダーがカスタムクラスを見つけられていないケースが考えられる。まず PHP のエラーログを確認し、クラスが見つからないという趣旨の致命的エラーが出ていないか調べる。出ている場合はクラス定義の記述ミスか、定義のタイミングが早すぎる可能性があるため、init フックなどで定義を遅らせると改善することがある。

この記事のポイント

  • get_total() の多重呼び出しで合計が倍増するのは、静的変数 self::$total が加算され続ける設計のため
  • プラグイン本体を直接修正せず、子テーマの functions.php でカスタムクラスを定義する
  • カスタムクラス内で get_total() をオーバーライドし、計算前に self::$total = 0; でリセットする
  • PDF テンプレート側の呼び出しをカスタムクラスに差し替え、キャッシュ削除後に動作確認する
  • 同様の構造を持つ get_tax()get_discount() も併せて修正を検討する
WooCommerce 11.0でget_queried_object()がショップページでもWP_Postを返す改善

WooCommerce 11.0でget_queried_object()がショップページでもWP_Postを返す改善

WooCommerce 11.0より、ショップページで get_queried_object() を呼び出した際の戻り値の型が WP_Post_Type から WP_Post に統一される。これまでショップページだけが例外的に商品の投稿タイプオブジェクトを返していたが、今回の変更でWordPress標準の挙動と一貫性が保たれることになる。

この修正は、WooCommerceが内部的に管理するクエリの取り扱いをWordPressコアに合わせるもので、テーマやプラグインの開発者が「ショップページかどうか」を意識せずに get_queried_object() を扱えるようにする狙いがある。フロントページにショップページを設定している場合も同様の挙動となる。

WooCommerce 11.0のショップページ改善

WooCommerce 11.0のショップページ改善

get_queried_object() は現在のWordPressクエリに対応するオブジェクトを取得する標準関数だ。通常の固定ページや投稿ページでは WP_Post オブジェクトを返すが、これまでのWooCommerceではショップページに限り WP_Post_Type オブジェクト、つまり商品(product)の投稿タイプ情報を返していた。この不一致が開発者にとって混乱の元となっていた。

WooCommerce Developer Blogの説明によれば、ショップページで get_queried_object() を呼び出して「商品アーカイブであること」を判定するコードを書いていた場合、この変更の影響を受ける可能性がある。逆に言えば、今回の修正で is_shop() のような条件分岐タグと get_queried_object() の戻り値の関係が整理され、より直感的なコードが書けるようになる。

WooCommerce 10.x までの挙動(Before)
Shopページ WP_Post_Type (商品の投稿タイプ情報)
その他固定ページ WP_Post (ページの投稿オブジェクト)
Shopページだけが例外で、他のページと異なるオブジェクト型を返していた
WooCommerce 11.0 以降の統一された挙動(After)
Shopページ WP_Post (ショップ固定ページの投稿オブジェクト)
その他固定ページ WP_Post (ページの投稿オブジェクト)
すべてのページで一貫して WP_Post を返すように統一された

この変更の背景には、WordPressの「投稿ページ」設定と同様にショップページでも WP_Post を返すべき、という設計上の判断がある。WooCommerce 11.0ではこの長年の不一致が解消され、より予測しやすいAPIへと改善された。

影響を受けるコードの判断方法

影響を受けるコードの判断方法

自作のテーマやプラグインでショップページのクエリオブジェクトを参照している場合、以下のいずれかの関数やプロパティを使用していないか確認する必要がある。

  • get_queried_object() を呼び出している
  • get_queried_object_id() を呼び出している
  • $query->queried_object に直接アクセスしている
  • $query->queried_object_id に直接アクセスしている

これらのコードがショップページ上で実行され、戻り値として WP_Post_Type オブジェクトを期待しているなら、WooCommerce 11.0へのアップデート後に動作が変わる可能性が高い。とくに、queried_object->labels->name などのプロパティに依存している場合は要注意だ。

Before / After コードの比較

具体的なコードの違いを見てみよう。以下はショップページでの get_queried_object() の戻り値の変化を示している。

// WooCommerce 10.x まで Shopページのみ例外
get_queried_object();          // → WP_Post_Type
get_post_type_object( 'product' ); // → WP_Post_Type

// その他の固定ページ
get_queried_object();          // → WP_Post
get_post_type_object( 'product' ); // → WP_Post_Type
// WooCommerce 11.0 以降 Shopページも含めて統一
get_queried_object();          // → WP_Post
get_post_type_object( 'product' ); // → WP_Post_Type

この変更の影響を受けないケース

次のような状況では、WooCommerce 11.0の変更による影響はなく、既存のコードはそのまま動作する。

  • 単一の商品ページ(single-product)では引き続き商品の WP_Post オブジェクトが返る
  • 商品カテゴリやタグ、ブランド、属性などのタクソノミーページでは WP_Term オブジェクトが返る
  • その他の投稿タイプアーカイブや個別ページはもともと変更の対象外
  • is_shop()is_archive()is_post_type_archive( 'product' ) といった条件分岐関数の挙動は従来どおり変わらない

開発者が取るべき具体的な対応

開発者が取るべき具体的な対応

もし既存のコードがショップページで get_queried_object() の戻り値を WP_Post_Type として扱っている場合、WooCommerce 11.0へのアップデートに備えて改修が必要だ。修正の基本方針は「オブジェクトの型をチェックしてからプロパティにアクセスする」ことにある。

商品の投稿タイプ情報を取得する推奨方法

商品の WP_Post_Type オブジェクトが必要な場合は、get_post_type_object() を使うのが安全で推奨される方法だ。この関数はWooCommerceのバージョンに関係なく常に正しいオブジェクトを返す。

$product_post_type = get_post_type_object( 'product' );

if ( $product_post_type instanceof WP_Post_Type ) {
    // 商品のWP_Post_Typeオブジェクトは
    // get_post_type_object() から引き続き取得できる
    $singular_name = $product_post_type->labels->singular_name;
}

ショップページのWP_Post情報を扱うコード例

ショップページ自体の情報(ページタイトルやスラッグなど)を取得したい場合は、WooCommerce 11.0以降は get_queried_object() から直接 WP_Post としてアクセスできるようになる。以下はその典型的な使用例だ。

$shop_page = get_queried_object();

if ( $shop_page instanceof WP_Post ) {
    // WooCommerce 11.0以降、ショップページでも
    // WP_Postとして扱える
    $shop_title = $shop_page->post_title;
    $shop_slug  = $shop_page->post_name;
}
修正の流れと対応手順
STEP 1 ショップページで get_queried_object() / $query->queried_object を使っている箇所を特定する
STEP 2 戻り値を WP_Post_Type として使っているか確認する( instanceof や型チェックをしていない場合)
STEP 3 get_post_type_object( 'product' ) で商品の投稿タイプ情報を個別に取得し、既存コードを書き換える
STEP 4 テスト環境でWooCommerce 11.0にアップデートし、ショップページが正しく表示されるか検証する

重要なのは、get_queried_object() の戻り値に依存した条件分岐を書く前に、必ず instanceof でオブジェクトの型をチェックする習慣をつけることだ。これにより、WooCommerceの将来のアップデートや他のプラグインとの競合にも強いコードになる。

この記事のポイント

  • WooCommerce 11.0ではショップページの get_queried_object()WP_Post を返すように統一される
  • 商品の投稿タイプ情報が欲しい場合は get_post_type_object( 'product' ) を使用する
  • is_shop() などの条件分岐関数の挙動は変わらないため、ページ判定ロジックの修正は不要
  • 影響を受けるコードは、おもにショップページでクエリオブジェクトのプロパティに直接アクセスしている箇所
  • アップデート前に instanceof による型チェックを追加し、テスト環境で検証するのが安全な移行手順
WordPress 7.0.1でクラシックエディタ保存できない時の原因と直し方

WordPress 7.0.1でクラシックエディタ保存できない時の原因と直し方

WordPress 7.0.1 更新後にクラシックエディタや WPBakery などのページビルダーで投稿や固定ページを保存できない、下書きが消えるといった症状は、PHP 8.4 との互換性問題が原因だ。PHP バージョンを 8.3 以下に切り替えればこの問題は解消する。

クラシックエディタで保存できずリダイレクトされる現象の正体

クラシックエディタで保存できずリダイレクトされる現象の正体

WordPress 7.0.1 に更新した直後から、クラシックエディタプラグインを有効化していると「公開」「下書き保存」をクリックしても保存されず、投稿一覧にリダイレクトされてしまう。保存したはずの記事や固定ページは一覧からも消え、下書きにも残らない。

さらにややこしいのは、ブロックエディタ(Gutenberg)では問題なく保存できるという点だ。クラシックエディタのプラグインを無効化せずとも、ブロックエディタ側で開いて操作すれば保存は成功する。このため一見すると「特定のプラグインだけが壊れている」ように見えるが、実際にはクラシックエディタ系や WPBakery など旧来の編集画面に依存するツール全般で同じ症状が出る。

この現象はプラグイン側の不具合ではなく、WordPress コアが動作する PHP のバージョンに起因する。特に PHP 8.4 環境で顕在化しやすい。

なぜ PHP 8.4 でクラシックエディタが動作しなくなるのか

なぜ PHP 8.4 でクラシックエディタが動作しなくなるのか

根本原因は PHP 8.4 で廃止または挙動が変更された関数や構文が、クラシックエディタの編集画面や保存処理の中で使われていることにある。古いエディタ画面は WordPress コアの一部として動作するが、その内部処理が新しい PHP の厳格な型チェックや廃止予定の警告(Deprecated)に引っかかり、画面遷移やデータ保存に失敗する。

たとえば、PHP 8.4 では暗黙の nullable 型宣言が非推奨になり、引数のデフォルト値や型宣言の扱いが厳密化された。WordPress の管理画面まわりには長い歴史を持つコードが多く、こうした細かな PHP の変更に対してすべてのプラグインやテーマが追随できているわけではない。

ブロックエディタが正常に動作するのは、ブロックエディタの保存処理が REST API を経由し、比較的新しいコードベースで実装されているため、PHP 8.4 の影響を受けにくいからだ。

PHP バージョンを 8.3 に切り替えて問題を解消する

PHP バージョンを 8.3 に切り替えて問題を解消する

現時点で最も確実な対処法は、サーバーの PHP バージョンを 8.3 にダウングレードすることだ。PHP 8.4 はリリースされたばかりで、WordPress エコシステム全体の完全な互換性が確保されるまでは安定動作が見込める 8.3 を使うほうが無難である。

PHP 8.4
クラシックエディタ保存不可 → 一覧にリダイレクト → 記事消失
STEP 1 サーバー管理画面から PHP 設定を開く
STEP 2 PHP 8.3 を選択して適用する
PHP 8.3
クラシックエディタ正常保存・下書きも残る

多くのレンタルサーバーではコントロールパネル(cPanel や独自管理画面)から簡単に PHP バージョンを切り替えられる。

サーバー管理画面での具体的な操作

cPanel を使っている場合は「PHP の選択」または「MultiPHP Manager」といったメニューを探す。対象ドメインを選択し、ドロップダウンメニューから「PHP 8.3」を選んで保存するだけだ。変更は数分以内に反映される。

独自の管理画面を提供しているサーバーでも、多くの場合「PHP 設定」「PHP バージョン管理」といった項目がある。もし見つからなければサーバー運営会社のサポートに「PHP バージョンを 8.3 に変更したい」と伝えれば対応してくれるケースが多い。

変更前に現在の PHP バージョンを確認する

WordPress 管理画面の「ツール」→「サイトヘルス」→「情報」タブを開き、「サーバー」セクションを見ると現在の PHP バージョンが表示されている。ここで 8.4 以上であれば今回の問題に該当する可能性が高い。

PHP 8.3 への切り替えでも直らない場合の追加確認

PHP 8.3 への切り替えでも直らない場合の追加確認

ごくまれに、PHP バージョンを下げても問題が続くことがある。そんなときは次の3点を順に確かめる。

ブラウザキャッシュと WordPress キャッシュのクリア

PHP の変更後にブラウザのキャッシュが残っていると、古い JavaScript や CSS で画面が正しく動作しないことがある。ブラウザのキャッシュを削除するか、シークレットウィンドウで管理画面を開き直す。また、WordPress 側でキャッシュプラグインを使っている場合はそのキャッシュもすべて削除する。

管理画面で JavaScript エラーが出ていないか調べる

ブラウザの開発者ツール(F12 キー)を開き、「コンソール」タブで赤いエラーが出ていないか確認する。保存ボタンを押した瞬間に何らかの JavaScript エラーが記録されていれば、それが原因の手がかりになる。

WordPress のデバッグモードでログを取得する

wp-config.php に以下のコードを追加してデバッグモードを有効にすると、保存時のエラーが wp-content/debug.log に記録される。

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);

ログに「Deprecated」や「Fatal error」が記録されていれば、それが直接の原因を示している。ただし、大半のケースでは PHP 8.3 への切り替えだけで問題が解消するため、ログを調べるのはレアケースの最終手段と考えてよい。

よくある質問

PHP 8.3 に戻すとセキュリティ面で問題はないか

PHP 8.3 は現在もアクティブサポートが継続しており、セキュリティ修正は提供され続けている。WordPress 公式の推奨バージョンでもあるため、本番環境で使っても安全だ。無理に最新の 8.4 に上げるよりも、安定した 8.3 で運用するほうが結果的にリスクが低い。

PHP 8.3 に変更したのにクラシックエディタがまだ使えない

PHP の変更がサーバーに完全に反映されるまで数分かかることがある。まずは数分待ってから再度試す。それでも改善しない場合は、一度プラグインを無効化して再度有効化してみる。また、前述のキャッシュクリアも忘れずに行う。

WPBakery や他のページビルダーでも同じ症状が出るのか

出る。クラシックエディタだけでなく、WPBakery をはじめとする旧型の編集画面を使うページビルダー全般が PHP 8.4 の影響を受ける。これらもブロックエディタと異なり、内部の保存処理が古いコードに依存しているためだ。

PHP バージョンを自由に変更できないサーバーではどうすればいいか

サーバー管理画面に PHP バージョンの変更オプションがない場合は、サーバー運営会社のサポートに連絡して「PHP 8.3 への切り替えを依頼したい」と伝える。ほとんどの会社は対応してくれる。もし変更ができないと言われた場合は、PHP 8.4 の環境でも動作するように WordPress のアップデートを待つか、ブロックエディタに一時的に切り替えて運用するしかない。

この記事のポイント

  • WordPress 7.0.1 でクラシックエディタ保存不可になる原因は PHP 8.4 の互換性問題
  • PHP バージョンを 8.3 に下げると問題は即座に解消する
  • ブロックエディタは PHP 8.4 でも正常に動作するため一時的な回避策になる
  • 変更後はブラウザキャッシュと WordPress キャッシュを忘れずにクリアする
  • PHP 8.3 は公式推奨でありセキュリティ面でも安全に運用できる
データベース接続確立エラーの原因と復旧手順

データベース接続確立エラーの原因と復旧手順

「データベース接続確立エラー」でサイトがダウンした場合、まずはサーバー会社に連絡してデータベースサーバーの稼働状況を確認し、wp-config.php の接続情報を照合するのが最短の復旧手順だ。自分でできる対処は限られているため、慌てずに切り分けを進める。

なぜ「データベース接続確立エラー」が突然発生するのか

なぜ「データベース接続確立エラー」が突然発生するのか

このエラーは WordPress がデータベースに接続できないときに表示される。日本語環境では「データベース接続確立エラー」というメッセージが画面に表示され、サイト全体が表示できなくなる。原因は大きく4つに分けられる。

  • wp-config.php 内のデータベース名・ユーザー名・パスワード・ホスト名のいずれかが誤っている
  • データベースサーバー自体がダウンしている(サーバー障害・メンテナンス)
  • データベースサーバーは稼働しているが、負荷集中で応答不能になっている
  • データベースが破損している(テーブルクラッシュなど)

サイトを長期間触っていなかったのに突然エラーが出た場合は、サーバー側で MySQL や MariaDB のバージョンアップ、パスワード変更、セキュリティ設定の変更が行われた可能性が高い。WordPress 側の設定ファイルは変わらないまま、サーバー側の接続条件だけが変わることで不一致が起きる。

Before(エラー発生中)
ブラウザに「データベース接続確立エラー」が表示される
wp-config.php とデータベースの認証情報が不一致、または DB サーバーが停止
After(復旧後)
サイトが正常表示される
正しい接続情報で WordPress がデータベースにアクセスできる状態
エラー状態  復旧後

エラーが出たらまず試す3つの切り分け

エラーが出たらまず試す3つの切り分け
STEP 1 サーバー会社にデータベースサーバーの稼働状況を確認する
STEP 2 wp-config.php の接続情報をサーバー管理画面の値と照合する
STEP 3 phpMyAdmin などでデータベースに直接接続できるか試す

サーバー会社にデータベースサーバーの状態を問い合わせる

最も確実で早いのが、利用しているサーバー会社のサポートに連絡することだ。データベースサーバーがダウンしていれば、自分で何をしても復旧しない。管理画面にログインできなくても、サーバー会社のコントロールパネル(cPanel や独自管理画面)にアクセスできれば、そこからデータベースの状態を確認できる場合もある。

特に共有サーバー(複数ユーザーで1台のサーバーを共有するプラン)では、他のユーザーの影響でデータベースサーバーに負荷がかかり、一時的に応答しなくなることがある。この場合もサーバー会社側で対処が必要になる。

wp-config.php の接続情報を確認する

FTP ソフトやサーバーのファイルマネージャーで WordPress のインストールディレクトリにある wp-config.php を開き、以下の4つの定数を確認する。

define( 'DB_NAME', 'database_name_here' );
define( 'DB_USER', 'username_here' );
define( 'DB_PASSWORD', 'password_here' );
define( 'DB_HOST', 'localhost' );

これらの値がサーバーのデータベース管理画面(phpMyAdmin やサーバー会社のコントロールパネル)で設定した値と完全に一致しているか確認する。サーバー会社がパスワードをリセットした場合や、セキュリティアップデートでホスト名が localhost から mysqlcluster2.example.com のような専用ホスト名に変更されるケースがある。

wp-config.php に一時的な確認コードを入れる

接続情報が正しいかどうかを切り分けるには、wp-config.php に以下のテストコードを追加する方法も有効だ。エラーメッセージの詳細が表示され、単なる認証エラーなのか、サーバー自体に到達できないのかが判別できる。

$link = mysqli_connect( DB_HOST, DB_USER, DB_PASSWORD, DB_NAME );
if ( ! $link ) {
    die( '接続失敗: ' . mysqli_connect_error() );
}
echo '接続成功';
die();

このコードを wp-config.php/* That's all, stop editing! Happy blogging. */ より上に追記し、サイトにアクセスする。「接続失敗」と表示されれば認証情報かサーバー到達性の問題、「接続成功」と表示されれば WordPress 本体やプラグイン側の別の要因が疑われる。確認が終わったら必ずこのコードを削除する。

phpMyAdmin からデータベースに直接接続する

サーバー会社のコントロールパネルから phpMyAdmin にアクセスし、該当のデータベースを開けるか確認する。開ければ、データベースサーバーは稼働しており、認証情報も正しいことがわかる。開けない場合は、ユーザー名・パスワードが誤っているか、そのユーザーにデータベースへのアクセス権限が付与されていない。

phpMyAdmin 自体が開けない、または読み込みに極端に時間がかかる場合は、データベースサーバーの高負荷やダウンが原因だ。この場合もサーバー会社への連絡が必要になる。

データベースの修復が必要なケース

データベースの修復が必要なケース

wp-config.php の情報が正しく、データベースサーバーも稼働しているのに接続エラーが出る場合、データベースのテーブルが破損している可能性がある。この修復は WordPress の自動修復機能で対応できる。

wp-config.php に以下の1行を追加する。

define( 'WP_ALLOW_REPAIR', true );

その後、ブラウザで https://あなたのサイトのURL/wp-admin/maint/repair.php にアクセスすると、データベース修復画面が表示される。「データベースを修復」または「データベースを修復して最適化」ボタンをクリックすれば修復が実行される。修復完了後は、セキュリティのために必ず追加した行を削除する。

管理画面にログインできない場合の対処

管理画面にログインできない場合の対処

「データベース接続確立エラー」が出ている間は、WordPress の管理画面(/wp-admin)にもアクセスできない。この状態では wp-config.php の確認や修正を WordPress の管理画面から行うことはできず、必ずサーバー側のファイルマネージャーか FTP ソフトを使う必要がある。

FTP の接続情報がわからない場合も、サーバー会社のサポートに連絡すれば、コントロールパネルへのログイン方法やファイルマネージャーの使い方を案内してもらえる。WordPress のログイン情報よりも先に、サーバーの管理画面にアクセスできる状態を確保することが復旧の第一歩だ。

再発を防ぐための日常的な対策

再発を防ぐための日常的な対策

データベース接続エラーは突然発生し、サイト全体が完全に停止するため、予防と早期発見の仕組みを整えておくことが重要だ。

  • サーバー会社のデータベース稼働状況を定期的にチェックする(障害通知メールの設定)
  • データベースの定期バックアップを自動化する(サーバー側のバックアップ機能やプラグインを利用)
  • wp-config.php のバックアップを手元に保管し、接続情報をメモしておく
  • サーバー会社のコントロールパネルと FTP のログイン情報を常に最新に保つ

特にレンタルサーバーの共有プランを利用している場合、サーバー会社がメンテナンスやセキュリティアップデートでデータベースの接続設定を変更することがある。変更の予告メールを見逃さないよう、サーバー会社からのメールは確実に受信できるアドレスに設定しておく。

よくある質問

データベース接続エラーと「重大なエラー」は別のものか

別のエラーだ。「データベース接続確立エラー」はデータベースとの通信そのものができない状態で、サイト全体が表示されない。「このサイトで重大なエラーが発生しました」は WordPress 本体やプラグインの PHP エラーで、管理画面にメールが届く場合もある。後者はデータベースに接続できていることが前提になる。

wp-config.php を修正したのに直らない場合はどうすればよいか

データベースサーバー自体が停止しているか、MySQL のサービスが落ちている可能性が高い。サーバー会社のコントロールパネルで MySQL の状態を確認し、停止していれば再起動を試みる。操作権限がない場合はサーバー会社に依頼する。

データベースのユーザー名やパスワードを忘れた場合はどうするか

サーバーのコントロールパネル(cPanel の「MySQL データベース」など)から確認または再設定できる。WordPress の管理画面からは確認できないため、必ずサーバー側の管理画面を使う。パスワードをリセットした場合は、wp-config.php の DB_PASSWORD も新しい値に更新する必要がある。

データベースの修復でデータが消えることはあるか

WP_ALLOW_REPAIR による修復は、破損したテーブルの構造を修復する機能で、保存されている投稿や設定データを削除することはない。ただし、修復作業の前には必ずデータベースのバックアップを取得しておくことが望ましい。

エラーが断続的に発生する場合の原因は何か

データベースサーバーの負荷が一時的に高まっているか、同時接続数の上限に達している可能性がある。アクセス集中時だけエラーが出る場合は、サーバースペックやプランの見直しを検討する。また、プラグインが非効率なデータベースクエリを大量に発行していないかも確認する。

この記事のポイント

  • データベース接続エラーは wp-config.php の誤りかサーバー側の障害が主因
  • エラー発生時は管理画面にログインできないため、FTP やサーバー管理画面で対応する
  • サーバー会社に連絡してデータベースサーバーの稼働状態を確認するのが最短の復旧手段
  • テーブル破損が疑われる場合は WP_ALLOW_REPAIR で修復を試みる
  • 日常的にバックアップと接続情報の控えを取っておくことで復旧時間を短縮できる
WordPress会員データが大量削除された時の原因と復旧手順

WordPress会員データが大量削除された時の原因と復旧手順

WordPress サイトで会員データが突然数千件単位で削除され、WP Activity Log に「Unknown user」として記録される事態は、データベースへの直接アクセスか管理者権限の乗っ取りによる攻撃の可能性が高い。即座にサイトをメンテナンスモードに切り替え、被害拡大を防ぎつつバックアップから復旧し、侵入経路を特定して再発を防ぐ必要がある。

なぜ突然会員データが大量削除されたのか

この事象の最大の特徴は、削除を実行したユーザーが「Unknown user」と表示される点にある。通常、WordPress の操作ログにはユーザー名かユーザー ID が記録されるが、ここに名前が出ないということは、wp_users テーブルを経由しない削除が行われていることを示している。考えられる原因は大きく3つに絞られる。

データベースへの直接操作

最も深刻なケースとして、攻撃者が SQL インジェクションや phpMyAdmin への不正アクセス、あるいはサーバーに仕込んだマルウェア経由でデータベースに直接 DELETE 文を実行している状況だ。この場合、WordPress のアクションやフィルターフックを一切通過しないため、WP Activity Log を含むどの監査プラグインもユーザーを特定できず「Unknown」となる。

管理者アカウントの乗っ取り

管理者権限を持つアカウントが乗っ取られ、攻撃者がそのアカウントで MemberPress の会員一括操作機能やデータベースクリーニング系プラグインを実行したケースだ。ただし、この場合は通常ユーザー名がログに残る。残っていないということは、攻撃者が操作後にユーザーごと削除したか、別の手段を使った可能性が高い。

REST API や XML-RPC の悪用

WordPress の REST API や XML-RPC に認証の弱点があると、外部から会員データの削除リクエストを送信される場合がある。特に、API 経由で直接データベース操作を行うカスタムエンドポイントがテーマやプラグインに存在すると、認証をすり抜けて大量削除が実行される危険がある。

大量削除が発生する3つの経路
A. データベース直接操作 SQL インジェクションやサーバー侵入により、DELETE 文を直接実行。WordPress のログを一切通過しないため「Unknown」になる。
B. 管理者アカウント乗っ取り 乗っ取った管理者で管理画面から一括削除。通常はユーザー名が残るが、アカウントごと削除された可能性もある。
C. REST API や XML-RPC の悪用 認証の脆弱性を突き、API 経由で削除リクエストを送信。プラグイン独自のエンドポイントに注意が必要。
最も深刻な経路  緊急調査が必要な経路

同時に多数のプラグイン更新が表示され、WordPress 7.0.1 への更新も同日にリリースされたという状況は、実際に重大な脆弱性が公表され、各開発者が一斉にパッチを配布している可能性を示唆している。更新が突如10件以上積み上がるのは、テーマやプラグインに含まれる共通ライブラリに脆弱性が見つかった場合によく見られるパターンだ。

攻撃を受けた直後にとるべき緊急対応の手順

攻撃を受けた直後にとるべき緊急対応の手順

被害の発覚から時間が経つほど、データ消失や情報流出の範囲は広がる。以下の手順を発見後30分以内に実行することで、二次被害を最小限に抑えられる。

STEP 1 サイトをメンテナンスモードに切り替え、外部からの全アクセスを遮断する
STEP 2 全プラグインとテーマを強制無効化し、攻撃者が仕込んだマルウェアの実行を止める
STEP 3 WP Activity Log をエクスポートし、攻撃元 IP と操作内容の全記録を保全する
STEP 4 直近の正常なバックアップからデータベースを復元する

メンテナンスモードでアクセスを遮断する

まず、攻撃が継続している可能性を想定し、サイト全体をメンテナンスモードに切り替える。管理画面にアクセスできる状態であれば、.maintenance ファイルを手動で作成する方法が最も確実だ。WordPress のルートディレクトリに .maintenance ファイルを配置し、以下の内容を記述すると、全訪問者にメンテナンス画面が表示される。

<?php
$upgrading = time();
?>

FTP やサーバーのファイルマネージャーでアクセスできない場合は、サーバー管理パネルから Web サーバー(Apache や Nginx)自体を停止するか、.htaccess に IP 制限をかけて特定の IP 以外をすべて拒否する方法も有効だ。

プラグインとテーマを強制的に無効化する

管理画面からプラグインを一括停止できない、あるいは管理画面自体が攻撃者にロックされている場合、FTP で /wp-content/plugins/ ディレクトリごとリネームする(例: plugins_backup)。これにより、WordPress は全プラグインを無効化した状態で起動する。同様に、/wp-content/themes/ から現在のテーマを除く全テーマを別ディレクトリに退避し、標準テーマ(Twenty Twenty-Five 等)のみを残す。

ログを保全して侵入経路を特定する

WP Activity Log のデータは攻撃者に消去される前にエクスポートする。CSV または JSON でダウンロードし、ローカルに保存する。このとき、サーバーのアクセスログ(access.log)とエラーログ(error.log)も必ず取得する。ログから得るべき情報は「攻撃元 IP」「削除が実行された正確な日時」「リクエスト URL(特に POST リクエスト)」「User-Agent」の4点だ。

「Unknown user」による削除操作が大量に記録されている場合、リクエスト URL が wp-admin/admin-ajax.phpwp-json/ を含んでいないかを重点的に確認する。これらが含まれていれば、AJAX や REST API 経由の攻撃である可能性が高い。

バックアップからの復旧とデータ整合性の確認

バックアップからの復旧とデータ整合性の確認

攻撃発生前の正常な状態がわかっているバックアップがあれば、それをリストアする。このケースのように2日前のバックアップが存在する場合、消失した会員データはその時点のものに戻るが、2日間に新規登録した会員や更新されたデータは失われるため、復旧後に差分を手動で補完する必要がある。

復旧の Before / After
Before(攻撃を受けた状態)
MemberPress 会員テーブルが数千件消失し「Unknown user」の削除ログのみが残っている。プラグイン更新が20件積み上がり、WP 7.0.1 が未適用。
After(復旧完了後)
バックアップから会員データをリストア。全プラグインと WP 本体を最新に更新し、不正ファイルを除去した状態でサイトを再公開。
攻撃直後の状態  復旧後の安全な状態

データベースをリストアする手順

バックアップが SQL ダンプファイル(.sql または .sql.gz)の場合、phpMyAdmin またはサーバーのコマンドラインからインポートする。WordPress のデータベース全体を置き換える場合は、まず現在の全テーブルを削除(DROP)してからインポートする。この操作は取り返しがつかないため、必ず現在のデータベースも別名でエクスポートしてから実行する。

# コマンドラインでのリストア例
mysql -u ユーザー名 -p データベース名 < backup.sql

リストア後、MemberPress の会員数が期待通りに戻っているか、会員のサブスクリプション状況や支払い履歴が正しく関連付けられているかを必ず確認する。特に、WooCommerce と連携している場合は wp_usermeta テーブルの整合性もチェックする必要がある。

再発を防ぐための恒久対策

再発を防ぐための恒久対策

復旧が完了したら、同じ攻撃を二度と受けないようにするための対策を即座に実施する。サーバー侵入を許した原因が残ったままだと、再度同じ経路から攻撃される危険が極めて高い。

全ファイルの改ざんチェックとマルウェアスキャン

WordPress のコアファイル、プラグイン、テーマのすべてについて、オリジナルの配布ファイルと比較して改ざんされていないかを確認する。WordPress の管理画面から「サイトヘルス」→「情報」→「ファイルの整合性」でコアファイルのチェックが可能だが、プラグインとテーマは手動かセキュリティプラグイン(Wordfence や Sucuri 等)を使ってスキャンする。特に、wp-content/uploads/ 内に .php ファイルが存在していないかを重点的に調べる。

管理者アカウントの総点検とパスワード再設定

すべての管理者アカウントについて、覚えのないユーザーが追加されていないか、権限が勝手に昇格されていないかを確認する。wp_users テーブルと wp_usermeta を直接確認し、不審な管理者を削除する。その上で、全管理者と編集者のパスワードを強制的にリセットし、可能であれば二要素認証(2FA)を導入する。WordPress 6.8 以降は標準でパスキー認証が利用できるため、セキュリティキーを使ったログインも有効な選択肢だ。

データベースのアクセス制限と監査の強化

データベースへの外部からの直接アクセスを遮断するため、phpMyAdmin などの管理ツールを公開ディレクトリに置かない、または IP 制限と HTTP 認証をかける。また、wp-config.php に以下の定数を追加して、WordPress のファイル編集機能を無効化しておくことで、管理画面を乗っ取られた場合でもテーマやプラグインのコードが書き換えられることを防げる。

define('DISALLOW_FILE_EDIT', true);
define('DISALLOW_FILE_MODS', true); // 必要な場合のみ

XML-RPC と REST API のアクセス制限

XML-RPC を使用していない場合は .htaccess でブロックするか、プラグインで完全に無効化する。REST API は多くのプラグインが依存しているため完全には無効化できないが、認証が必要なエンドポイントに対して適切な権限チェックが実装されているかを確認する。特に、MemberPress やカスタムプラグインが提供する REST API エンドポイントは、開発元のドキュメントでセキュリティ設定を再確認する。

よくある質問

WP Activity Log に「Unknown user」と表示されるのは必ずハッキングか

必ずしもそうとは限らないが、高い確率で不正アクセスが疑われる。プラグインのバグやデータベースの不整合でも発生しうるが、同時に大量のデータが削除されている場合は攻撃の可能性を第一に考えるべきだ。特に、管理者権限を持たない IP からの操作が記録されている場合はほぼ確実に侵入されている。

プラグインの更新が突然大量に表示されたのはなぜか

共通ライブラリやフレームワークに重大な脆弱性が見つかり、複数のプラグインが一斉にセキュリティアップデートをリリースした可能性が高い。WordPress 本体の緊急アップデートが同日にリリースされていることも、何らかの広範囲に影響する脆弱性が公表されたことを示唆している。これらの更新は速やかに適用すべきだ。

バックアップから復旧した後、消えたデータは完全に戻るのか

バックアップ取得時点のデータは戻るが、それ以降に追加・変更されたデータは復元されない。MemberPress の会員情報の場合、新規登録者やサブスクリプションの変更履歴が失われるため、復旧後に会員からの問い合わせをもとに手動で補完する必要がある。決済情報は決済代行サービス側に残っていることが多いため、そちらを参照して復元できる場合もある。

WP 7.0.1 への更新はすぐに適用すべきか

重大なセキュリティ修正を含むマイナーアップデートの場合、速やかな適用が推奨される。ただし、大量削除が発生した環境では、まずバックアップからの復旧と侵入経路の遮断を完了させてから更新を行う。更新前に全ファイルの改ざんチェックを済ませ、マルウェアが残っていない状態で適用するのが安全だ。

この記事のポイント

  • 「Unknown user」による大量削除はデータベース直接操作か管理者乗っ取りが原因の可能性が高い
  • 発見後は即座にメンテナンスモードでサイトを遮断し、プラグインを強制無効化して攻撃を止める
  • WP Activity Log とサーバーログを直ちにエクスポートし、攻撃元 IP と侵入経路を特定する
  • バックアップからデータベースをリストアし、復旧後に全ファイルの改ざんチェックとマルウェアスキャンを実施する
  • XML-RPC の無効化、REST API の権限確認、二要素認証の導入で再発を防ぐ
myCred Toolkit Pro アップデート後の致命的エラーと復旧手順

myCred Toolkit Pro アップデート後の致命的エラーと復旧手順

WordPress サイトで myCred Toolkit Pro をアップデートした直後に「Class MWP_Module not found」という致命的エラーが発生し管理画面にもアクセスできなくなった場合は、最新バージョンで修正済みの可能性が高い。修正がまだ提供されていない場合でも、クラスファイルの読み込み順序を確認し手動で修正すれば復旧できる。

MWP_Module クラスが見つからない致命的エラーはなぜ起こるのか

MWP_Module クラスが見つからない致命的エラーはなぜ起こるのか

このエラーは、myCred Toolkit Pro 内の WooCommerce Plus 改修版アドオンが読み込まれる際、ベースクラス(親クラス)である MWP_Module がまだ定義されていない状態で、子クラスが呼び出されるために発生する。具体的には class-mwp-module-coupons.phpMWP_Module を継承しようとするが、その元となる抽象クラスファイルが先に読み込まれていないのだ。

本来なら abstract/class-mwp-abstract-module.php が先にロードされるべきところ、プラグインのアップデート時にファイルの欠落が起きたり、オートローダーの設定不備で読み込み順序が狂ったりすると、このエラーが表面化する。PHP は未定義クラスへの継承を許さないため、サイト全体が致命的エラーで停止してしまう。

アップデート直後にサイトがダウンした場合の緊急復旧手順

アップデート直後にサイトがダウンした場合の緊急復旧手順

エラーによって WordPress 管理画面にも入れなくなった状態では、FTP クライアントやサーバーのファイルマネージャを使ってプラグインを強制的に無効化する必要がある。データベースを直接操作する方法もあるが、ファイル名の変更がもっとも手軽で確実だ。

STEP 1 FTP クライアントでサーバーに接続し、/wp-content/plugins/ ディレクトリへ移動する
STEP 2 mycred-toolkit-pro フォルダを右クリックし、「名前の変更」で末尾に _disabled を付ける
STEP 3 ブラウザでサイトにアクセスし、管理画面 /wp-admin/ へログインできるか確認する
STEP 4 管理画面に入れたら、プラグイン一覧で Toolkit Pro が「無効」になっていることを確認し、旧バージョンへ差し替える準備をする

上の手順でプラグインを無効化すれば、サイトは正常に表示されるようになる。続いて、動作していた旧バージョン(例として 1.0.4)を再インストールして有効化するか、修正版が配布されているかを確認する。

手動でクラスファイルの読み込み順序を確認し修正する方法

手動でクラスファイルの読み込み順序を確認し修正する方法

開発元の修正版がまだ提供されていない、あるいは修正を待てない場合は、プラグインのファイルを手動で編集してクラスの読み込み順序を修正できる。修正の要点は、class-mwp-module-coupons.php に到達する前にベースクラスを確実に読み込ませることだ。

問題のファイルと修正箇所を特定する

エラーログに出力されたパスをもとに、wp-content/plugins/mycred-toolkit-pro/includes/addons/mycred-woocommerce-plus/mycred-woocommerce-plus-revamped/modules/class-mwp-module-coupons.php を開く。7 行目付近で MWP_Module を継承しているクラス定義があるはずだ。

ベースクラスのファイルは wp-content/plugins/mycred-toolkit-pro/includes/addons/mycred-woocommerce-plus/mycred-woocommerce-plus-revamped/abstract/class-mwp-abstract-module.php に存在する。これが読み込まれていないことが原因なので、子クラスのファイルの先頭で明示的に require_once を追加する。

修正前(エラー発生)
1 <?php
2
3 // エラー: 親クラスが不明
4 class MWP_Module_Coupons extends MWP_Module {
5
修正後(正常動作)
1 <?php
2 require_once plugin_dir_path( __FILE__ ) .
3 ../abstract/class-mwp-abstract-module.php‘;
4
5 class MWP_Module_Coupons extends MWP_Module {
6
修正前(親クラス未定義のまま実行)  修正後(require_once で事前に読み込み)

上記のように require_once を追加することで、クラス定義より前にベースクラスを確実に読み込める。ただし、この修正は自作テーマの functions.php に書くような一時しのぎとは異なり、プラグインのコアファイルを直接編集するため、アップデートで上書きされる可能性がある点を理解しておく必要がある。

オートローダーの確認と修正

モダンなプラグインは PHP のオートローダー(Composer の autoload など)を使ってクラスを動的に読み込む仕組みを採用している。myCred Toolkit Pro も Composer ベースのオートロードを利用している可能性が高く、composer.json の autoload 設定や名前空間のマッピングが正しいかを確認すると根本的な解決につながる。

プラグインディレクトリで composer dump-autoload -o を実行し、クラスマップを再生成するのも有効な手だ。ただし、サーバーに Composer がインストールされている必要があるため、ローカル環境で再生成してからファイルをアップロードする方法が現実的だ。

修正版がリリースされている場合の安全なアップデート手順

修正版がリリースされている場合の安全なアップデート手順

開発元から修正版が提供された場合、本番環境にいきなり適用するのは避け、最初にステージング環境で動作確認を行うのが鉄則だ。致命的エラーはサイト全体を巻き込むため、万が一のときに備えて必ずバックアップを取る。

STEP 1 本番サイトのデータベースと全ファイルをバックアップする(プラグイン「UpdraftPlus」やサーバー側のスナップショット機能を使う)
STEP 2 ステージング環境にバックアップを復元し、Toolkit Pro を最新版へアップデートする
STEP 3 WooCommerce のポイント付与やクーポン連携が正常に動くか、テスト注文を行って検証する
STEP 4 問題なければメンテナンス時間帯を設け、本番環境でも同様にアップデートする

ステージング環境がない場合は、本番環境の深夜帯などアクセスが少ない時間にメンテナンスモードへ切り替えてから実施する。アップデート後すぐに管理画面へアクセスできるか、フロントエンドにエラーが出ていないかを確認し、問題があればすぐに旧バージョンへロールバックできるよう、ファイルのバックアップを手元に残しておく。

よくある質問

myCred 本体をアップデートしていないと同様のエラーは起きるか

プラグインによっては、本体(myCred コア)とアドオン(Toolkit Pro)のバージョンに互換性が要求される。MWP_Module クラスは Toolkit Pro 内部の抽象クラスなので myCred コアのバージョンに直接は依存しないが、コアが古すぎると別の非互換エラーを引き起こす可能性がある。常に両方を最新に保つことが望ましい。

修正版が出るまでサイトを止めておくべきか

致命的エラーでサイトが完全に停止しているなら、前述の緊急復旧手順でプラグインを無効化するか旧バージョンへ巻き戻し、通常運用を再開してよい。ポイント機能が止まる影響を考慮し、必要なら代替手段を顧客に案内する。

エラーログはどこで確認できるか

WordPress のデバッグモードを有効にしていれば /wp-content/debug.log にエラー詳細が出力される。有効でない場合は wp-config.phpdefine( 'WP_DEBUG', true );define( 'WP_DEBUG_LOG', true ); を記述する。レンタルサーバーによってはエラーログが管理画面から閲覧できる場合もある。

子テーマの functions.php で読み込み順序を修正できるか

テーマの functions.php はプラグインより後に読み込まれるため、この方法では MWP_Module クラスを事前に定義することはできない。プラグインファイルの直接編集が必須になる。

この記事のポイント

  • myCred Toolkit Pro アップデート後の MWP_Module クラス欠落エラーは、読み込み順序の不備が主因
  • 緊急復旧は FTP でプラグインフォルダをリネームし、旧バージョンへ差し替える
  • 手動修正では子クラスのファイル冒頭に require_once を追加する
  • 開発元修正版を適用する際は必ずバックアップとステージング検証を行う
  • PHP のオートローダー設定や Composer のクラスマップ再生成も有効なアプローチ
Shield SecurityのcookieがNginxキャッシュを止める時の解決策

Shield SecurityのcookieがNginxキャッシュを止める時の解決策

Shield Security の icwp-wpsf-notbot cookie がサーバーのページキャッシュを妨害する問題は、Shield の設定で「silentCAPTCHA」の複雑度を「なし」にし、かつカスタムフィルターで匿名ユーザーへの cookie 送信を停止することで解決できる。

なぜ Shield Security が全ページでキャッシュを止めてしまうのか

なぜ Shield Security が全ページでキャッシュを止めてしまうのか

Shield Security はボット判定や silentCAPTCHA の動作のために icwp-wpsf-notbot という cookie をフロントエンドの全ページで発行する仕様になっている。Nginx のキャッシュ機構は原則として Set-Cookie ヘッダを含むレスポンスをキャッシュしないため、この cookie がすべてのページキャッシュを無効化してしまう。結果として x-proxy-cache: MISS が返り続け、サーバーへのリクエストが毎回発生し、レスポンスタイムが 1000ms を超える状況に陥る。

Before(Shield 有効時)
リクエストのたびに Set-Cookie が発生
Nginx はレスポンスをキャッシュせず破棄
レスポンスタイム 1000ms〜1600ms
After(cookie 停止後)
キャッシュ可能なレスポンスが返る
初回 MISS → 2回目以降 HIT で高速表示
レスポンスタイム 約100ms
cookie がキャッシュを妨害している状態  cookie 停止後

サーバー側で特定の cookie だけをキャッシュ対象から除外する設定ができない場合、プラグイン側でこの cookie を止めるのが唯一の現実的な解決策になる。

管理画面で silentCAPTCHA の複雑度を「なし」にする

管理画面で silentCAPTCHA の複雑度を「なし」にする

Shield Security の silentCAPTCHA は、ボット防御のためにフロントエンドのページにも cookie をセットする。設定を最小限に絞り込むには、まず管理画面から操作する。

STEP 1 WordPress 管理画面 → 左メニュー「Shield」→「設定」
STEP 2 「CAPTCHA」タブを開き「silentCAPTCHA」セクションを探す
STEP 3 「複雑度」を なし に変更して保存
STEP 4 「ログイン保護」「スパムボットブロック」などで不要な silentCAPTCHA の利用をすべてオフにする

これだけでは cookie の出力が止まらないケースが多い。Shield は silentCAPTCHA の設定に関わらず、フロントエンドの訪問者に対して一律に icwp-wpsf-notbot をセットする内部ロジックを持っているためだ。ここから先はコードレベルの対応が必要になる。

フィルターフックで匿名ユーザーへの cookie 送信を無効化する

フィルターフックで匿名ユーザーへの cookie 送信を無効化する

Shield Security は icwp-wpsf-notbot cookie を制御するための専用フィルターを提供している。テーマの functions.php に数行のコードを追加すれば、ログインしていない一般訪問者に対する cookie の送信だけを停止できる。

functions.php に追加するコード

以下のコードを子テーマの functions.php に追加する。子テーマを使用していない場合は、Code Snippets プラグインなどで追加してもよい。テーマの直接編集はアップデートで消えるため避ける。

/**
 * Shield Security の icwp-wpsf-notbot cookie をログインしていないユーザーには送信しない
 */
add_filter( 'icwp_shield_set_notbot_cookie', function( $set_cookie ) {
    if ( ! is_user_logged_in() ) {
        return false;
    }
    return $set_cookie;
} );

このフィルターは icwp-wpsf-notbot cookie をセットする直前に呼び出される。ログインしていないユーザーの場合は false を返して cookie の発行をブロックし、ログイン済みユーザーには通常通り cookie を許可する。管理画面のログイン保護や IP ブロック、ファイアウォールなどのコア機能には影響しない。

動作確認の手順

  • シークレットウィンドウでサイトにアクセスする
  • ブラウザの開発者ツール(F12)→「アプリケーション」タブ→「Cookie」で icwp-wpsf-notbot が存在しないことを確認する
  • レスポンスヘッダーから Set-Cookie が消えていることを確認する
  • 2回目以降のアクセスで x-proxy-cache: HIT が返ることを確認する

全プラグインを最新に保って不要な干渉を防ぐ

全プラグインを最新に保って不要な干渉を防ぐ

Shield Security はアップデートの頻度が高く、バージョンによって内部のフィルター名が変更されることがある。Shield 22.1.3 および WordPress 7.0.1、PHP 8.2 環境では上記のフィルターが有効だが、プラグインが更新された際にはフィルター名が維持されているかを確認する必要がある。

また、キャッシュ系プラグインや CDN を併用している場合は、cookie 停止後にキャッシュを完全にクリアしてからテストすること。古いキャッシュが残っていると HIT になっていてもレスポンスが遅いままに見える場合がある。

よくある質問

SilentCAPTCHA を無効にしただけでは cookie は消えないのか

多くの場合、管理画面の設定だけでは cookie の出力は止まらない。Shield は silentCAPTCHA が無効でもフロントエンドの全リクエストに cookie をセットする内部挙動を持っている。確実に止めるにはフィルターフックの追加が必要だ。

このフィルターでログイン保護やファイアウォールは機能しなくなるか

今回のコードは匿名ユーザーへの icwp-wpsf-notbot cookie 送信だけを止めるもので、IP ブロックやブルートフォース保護、ファイアウォールといった Shield の主要防御機能はすべて通常通り動作する。ログインしたユーザーには引き続き cookie がセットされる。

functions.php を直接編集するのはリスクがないか

テーマの functions.php を直接編集すると、テーマのアップデートで変更が失われる。必ず子テーマを作成するか、Code Snippets のようなコード管理プラグインを使用する。また、コード追加前にサイトのバックアップを取得しておくと安全だ。

フィルターが効かない場合の確認ポイントは

Shield のバージョンが古い、あるいは逆に新しすぎてフィルター名が変更されている可能性がある。Shield の公式ドキュメントや変更履歴を確認する。また、キャッシュ系プラグインでサーバー側のキャッシュとは別にページキャッシュが残っていると、cookie 停止後も古いレスポンスが返り続けるため、すべてのキャッシュをクリアしてからテストする。

レンタルサーバーのキャッシュ設定で cookie ごとの除外はできないのか

共用サーバーの Nginx キャッシュ設定はサーバー全体で一律に適用されることが多く、特定の cookie だけを除外する柔軟な設定は提供されないケースがほとんどだ。サーバー側で対応できない以上、プラグイン側で cookie を止めるのが最も確実な方法になる。

この記事のポイント

  • Shield Security の icwp-wpsf-notbot cookie が Nginx のページキャッシュを全面的に阻害する
  • 管理画面で silentCAPTCHA の複雑度を「なし」にしても cookie は止まらない
  • functions.php にカスタムフィルターを追加しログインしていないユーザーへの cookie 送信を停止する
  • フィルター追加後はシークレットウィンドウで Set-Cookie ヘッダーと x-proxy-cache の値を確認する
  • プラグインのアップデート後はフィルター名の変更に注意しキャッシュを完全クリアして再テストする
Elementorテキストエディタで段落が勝手に横並びになる時の直し方

Elementorテキストエディタで段落が勝手に横並びになる時の直し方

Elementorのテキストエディタウィジェットで複数段落を入力したとき、編集画面では問題ないのに公開ページで急に横並びになる現象は、WoodMartテーマが組み込むフレックスボックス(Flexbox)のグローバルスタイルや、テーマ側の段組み(カラム)レイアウト用CSSが誤って適用されていることが主な原因だ。

なぜElementorの段落が公開サイトで横並びになるのか

なぜElementorの段落が公開サイトで横並びになるのか

WoodMartテーマは、WPBakeryと並んでElementor対応を謳う多機能テーマだ。その内部ではグリッドレイアウトや商品カードの並びを柔軟に制御するため、.entry-content.elementor-widget-text-editor といったコンテナに対して display: flexflex-wrap: wrap をデフォルトCSSとして指定しているケースがある。

このような設定が有効だと、コンテナ直下の <p> タグはフレックスアイテムとして扱われ、利用可能な幅の中で自動的に横方向へ配置されるのだ。通常のブロック要素であれば改行されるため縦に積み重なるが、フレックスコンテナの子要素はこの規則から外れる。その結果、編集画面では普通に見えていても、テーマのグローバルCSSがロードされるフロントエンドでのみ崩れが発生するという、なかなか気づきにくいトラブルになる。

ほかにも、Elementorの「段組み」設定の競合や、意図せず有効化されたCSSの最適化機能が影響することもあるが、ほとんどはWoodMartのベーススタイルが起点だ。次の項で切り分け手順を確かめつつ修正していく。

問題の再現状況をデモで確認する

問題の再現状況をデモで確認する
Before(公開ページで横並び)

「会社概要についての本文がここにあります。WoodMartの特徴を活かしたレイアウトです。」

「サービス一覧のご案内です。Elementorを使って自由に編集した内容がここに入ります。」

2つの段落が横に並んでいる(フレックスアイテム化している)
After(CSS追加で縦並びに修正)

「会社概要についての本文がここにあります。WoodMartの特徴を活かしたレイアウトです。」

「サービス一覧のご案内です。Elementorを使って自由に編集した内容がここに入ります。」

各段落が改行され、通常のブロック要素として縦に積まれている
エラー状態(横並び)  修正後(縦並び)

フレックスコンテナの子要素だから横に並ぶ、という仕組みをこのデモで示している。原因のCSSを特定し、段落の並びをブロック表示に戻せば解決できる。

ElementorとWoodMartで段落が横並びになるCSSの特定と修正手順

ElementorとWoodMartで段落が横並びになるCSSの特定と修正手順

ここからは実際にサイトを修正するための手順を説明する。作業は大きく3ステップだ。修正用のCSSは数行で済むが、きちんと原因を突き止める手順を踏まないと、後日ほかのレイアウト崩れを引き起こす可能性がある。

ブラウザの検証ツールで適用されているスタイルを調べる

まずはChromeのデベロッパーツール(F12キー)を使って、公開ページ上のテキストエディタ部分を調べる。<p> タグを右クリックし「検証」を選択すると、スタイルパネルで各要素に適用されているCSSを確認できる。

ここで最も注目すべきは、テキストエディタのラッパー要素(たいていは .elementor-widget-text-editor か、WoodMartが生成する固有のクラスが付いたdiv)に対して display: flexdisplay: grid が指定されていないかどうかだ。仮に以下のようなCSSが表示された場合、これが横並びの直接的な原因になる。

.elementor-widget-text-editor {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
}

このCSSがWoodMartの親テーマ、または子テーマのスタイルシートから読み込まれている場合は、検証ツールの右上にファイル名と行番号が表示される。原因となるファイルが特定できたら、次の手順で上書きするCSSを追加しよう。

修正用CSSを追加する場所を選ぶ

原因となっている display: flex を打ち消すには、以下のようなCSSを適用すればよい。要素をブロック表示に戻すには display: block を指定し、内部の段落が確実に積み重なるようにする。

.elementor-widget-text-editor {
    display: block !important;
}
.elementor-widget-text-editor p {
    display: block;
    width: 100%;
}

このCSSは以下のいずれかの場所に追加する。優先順位順に記す。

  1. 管理画面の「外観」→「カスタマイズ」→「追加CSS」(最も手軽で、テーマに関係なく安全)
  2. WoodMartのテーマオプションにあるカスタムCSS欄
  3. Elementorのサイト設定内のカスタムCSS

注意点として、追加CSSに !important を使うのはどうしても優先度で負ける場合の最終手段だ。まずは !important なしで試し、効かなければ付与するという手順を踏むほうが、意図しないカスケード崩れを防げる。上記のコード例では !important を付記したが、自身の環境で不要なら省略して構わない。

Elementorとキャッシュのクリアを忘れずに行う

CSSを追加してもすぐに反映されない場合、Elementor固有のキャッシュや、サーバー側のキャッシュが影響している。Elementorの「ツール」メニューから「CSSとデータを再生成」を実行し、さらに「Elementor」→「設定」→「高度な設定」でCSSの出力方法を「外部ファイル」から「内部埋め込み」に切り替えて一時的に様子を見るのもひとつの手だ。

サーバーでLiteSpeed CacheやW3 Total Cacheなどのキャッシュ系プラグインを使っているなら、管理画面から全キャッシュを削除しておく。とくに「CSSの最適化」や「CSSの結合」をオンにしている場合、追加したCSSが適用されない原因になりやすい。キャッシュをクリアしたあとに、シークレットモードで公開ページを開いて検証する。

テーマをアップデートする際の注意点と恒久対策

テーマをアップデートする際の注意点と恒久対策

今回の現象はあくまでテーマの全体的なスタイル指定が原因であり、Elementorのバグではない。WoodMartが将来のアップデートでこのフレックスボックス指定を変更する可能性もゼロではないが、テーマのアップデートに依存するのはリスクが高い。

恒久的な対策としては、親テーマを直接編集せず、子テーマの style.css か「追加CSS」に上書きルールを残すことだ。もしテーマのバージョンアップ後に問題が再発したら、原因のCSSセレクタが変わっていないか検証ツールで再確認し、セレクタを合わせて更新すればすぐに直せる。

複数ページで同じテキストエディタウィジェットを使っている場合は、サイト全体に影響する「追加CSS」での対応が推奨だ。特定のページや投稿タイプでのみ発生しているなら、該当ページのElementor編集画面で「サイト設定」→「カスタムCSS」を使い、スコープを絞った指定をする手もある。

よくある質問

テキストエディタ以外のウィジェットでも同じ症状は出るのか

見出しウィジェットや画像ウィジェットなど、直下に複数のブロック要素がぶら下がらない種類のウィジェットでは、この現象はまず起きない。ただし「内部セクション」や「Flexboxコンテナ」などの新しめのコンテナ系ウィジェットを使っていると、似た横並びが発生することがある。

WoodMart以外のテーマでも同じことが起きるのか

汎用的なテーマでは稀だが、カラム多用型の多機能テーマ(Avada、The7、Flatsomeなど)でも同様の報告がある。いずれの場合も、根本原因はテーマが付与しているフレックスボックスやグリッドのグローバルCSSだ。

追加CSSで直したのにスマホ表示だけ直らない

デスクトップでは修正されても、モバイル用のメディアクエリ内で再度 flex-direction: row が指定されている可能性がある。検証ツールでデバイスモードに切り替え、同じテキストエディタで適用されているスタイルを再確認する。必要ならメディアクエリを追加して上書きしよう。

子テーマのstyle.cssに書いても効かないのはなぜか

読み込み順の問題がほとんどだ。親テーマのCSSが子テーマより後で読み込まれていると、詳細度が同じなら後勝ちで上書きされる。functions.phpで子テーマのCSSを依存関係付きで読み込んでいるか確認し、どうしても効かないなら「追加CSS」機能(wp_headの最後で出力される)を使うほうが確実だ。

この記事のポイント

  • Elementor編集画面では正常でもフロントエンドでテキスト段落が横並びになる場合、WoodMartが付与するflex指定が原因
  • ブラウザの検証ツールで適用されているdisplayプロパティを特定し、追加CSSでブロック表示に戻す
  • 修正CSSは外観カスタマイズの「追加CSS」がもっとも安全で確実
  • CSS追加後はElementorのCSS再生成とサーバーキャッシュのクリアをセットで行う
  • テーマアップデート後も再発しにくいよう、恒久対策として上書きルールを残しておく