WooCommerce納品書印刷でFTPエラーが起きた時の原因と直し方

WooCommerce納品書印刷でFTPエラーが起きた時の原因と直し方

WooCommerce納品書印刷でFTPエラーが起きた時の原因と直し方

WooCommerceのマイアカウント画面から「納品書を印刷」や「領収書を印刷」をクリックした瞬間に「このサイトで重大なエラーが発生しました」と表示されてページが落ちる場合、原因はプラグインがFTPの認証情報なしにファイルシステムへアクセスしようとしたことにある。PHP 8環境で発生しやすいこの問題は、WP_Filesystem()の呼び出し方を修正すれば直る。

エラーの原因は何か

エラーの原因は何か

この問題は「Print Invoice & Delivery Notes for WooCommerce」などの納品書プラグインが、フロントエンドからWP_Filesystem()を呼び出す際にFTPの認証情報を渡していないことが根本原因だ。WP_Filesystem()はWordPressがサーバー上のファイルを操作するためのAPIで、通常は管理画面から操作するときに使われる。しかしプラグインのコードがこのAPIをバックグラウンドで実行しようとしたとき、必要なFTP接続情報が揃わず、接続オブジェクトがnullのままになってしまう。

PHP 8では関数の引数の型チェックが厳格化されたため、nullの接続オブジェクトをftp_nlist()などの関数に渡すと即座に致命的なTypeErrorが発生する。これが「Uncaught TypeError: ftp_nlist(): Argument #1 ($ftp) must be of type FTP\Connection, null given」というエラーの中身だ。

エラー発生時の状態
プラグインが WP_Filesystem() を引数なしで呼び出す
FTP認証情報がないため接続オブジェクトが null になる
PHP 8の型チェックで TypeError が発生し画面がクラッシュ
修正後の正しい流れ
まず request_filesystem_credentials() で認証情報を取得
取得した認証情報を WP_Filesystem( $credentials ) に渡す
FTP接続が正常に行われ、ファイル操作が完了する
エラー発生時  修正後

上の図で見るとわかるように、修正前は認証情報の取得ステップが丸ごと抜け落ちている。WordPressが用意している標準的な手順は「まず認証情報を集め、それからファイルシステムを初期化する」という2段階だ。プラグインがこの流れを省略したことで、FTP接続が確立されないまま後続の処理が走り、致命的エラーに至っている。

自分のサイトでエラーが発生しているか確認する方法

自分のサイトでエラーが発生しているか確認する方法

まずはエラーの詳細を把握するためにWordPressのデバッグモードを有効にしよう。wp-config.phpに以下の行を追加する。

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

この設定をすると、エラーの内容が/wp-content/debug.logに記録されるようになる。ページが真っ白になる現象は本番環境では特に厄介だが、ログを見ればスタックトレースが残っているため原因を特定できる。スタックトレースの中にwp-admin/includes/class-wp-filesystem-ftpext.phpwoocommerce-delivery-notesというパスが見つかれば、今回のケースに該当する可能性が高い。

プラグインのコードを修正してエラーを止める手順

プラグインのコードを修正してエラーを止める手順

根本的な修正はプラグイン本体のコードを書き換えることだが、これはプラグインが更新されるたびに変更が上書きされてしまう一時しのぎの対策だ。それでも今すぐエラーを止めたい場合には有効なので、まずは直接修正する手順を説明する。

修正対象のファイルとコードの場所

対象ファイルはプラグインディレクトリ内の includes/helpers/class-utils.php にある get_filesystem() メソッドだ。このメソッドが WP_Filesystem() を引数なしで呼び出している箇所が問題の中心になる。

// 修正前のコード
public static function get_filesystem() {
    global $wp_filesystem;
    if ( ! $wp_filesystem ) {
        require_once ABSPATH . 'wp-admin/includes/file.php';
        WP_Filesystem(); // ← 引数がないためFTP接続に失敗する
    }
    return $wp_filesystem;
}

修正後のコード

以下のように書き換える。request_filesystem_credentials() であらかじめ認証情報を取得し、それを WP_Filesystem() に渡す形にする。さらに、request_filesystem_credentials() が認証情報を問い合わせるHTMLフォームを出力してしまうのを防ぐために、出力バッファリングで囲んでおく。

// 修正後のコード
public static function get_filesystem() {
    global $wp_filesystem;
    if ( ! function_exists( 'WP_Filesystem' ) ) {
        require_once ABSPATH . 'wp-admin/includes/file.php';
    }
    if ( ! $wp_filesystem ) {
        ob_start(); // バッファリング開始
        $credentials = request_filesystem_credentials( '' );
        ob_end_clean(); // バッファを捨てる(フォーム出力を抑制)
        WP_Filesystem( $credentials ); // 認証情報を渡す
    }
    return $wp_filesystem;
}
STEP 1 FTPまたはサーバーのファイルマネージャーでプラグインファイルにアクセス
STEP 2 includes/helpers/class-utils.php をテキストエディタで開く
STEP 3 get_filesystem() メソッドを見つけてコードを差し替え
STEP 4 ファイルを保存してアップロードし、動作を確認

この修正を施すと、wp-config.phpに定義されたFTP定数(FTP_HOSTFTP_USERFTP_PASS)や、WordPressがデータベースに保存している認証情報が自動的に使われるようになる。結果としてFTP接続が正常に確立され、exists()などのファイル操作メソッドが問題なく動作する。

プラグイン更新で修正が消えないようにする恒久対策

プラグイン更新で修正が消えないようにする恒久対策

プラグインのコアファイルを直接編集する方法は、アップデートがあるたびに上書きされてしまうため本番運用には不向きだ。より持続的な対策として、以下のいずれかの方法を選ぶとよい。

子テーマのfunctions.phpでフックを使って上書きする

プラグインが提供しているフィルターフックやアクションフックを利用し、テンプレートのレンダリング時にファイルシステムアクセスが発生する処理を迂回する方法だ。ただし、このプラグインではフックが十分に用意されていない可能性が高いため、テーマのCSSやテンプレートの上書きだけで対応しきれないこともある。

プラグインのIssueトラッカーやサポートフォーラムに修正を依頼する

今回の修正はすでにWordPress.orgのサポートフォーラムにも報告されている。プラグインの開発者がこの修正を取り込めば、次回以降のアップデートで公式に問題が解決される。開発者の対応を待つ間は、前述のファイル直接編集でしのぎつつ、アップデートのたびに修正を再適用する運用になる。

プラグイン全体をフォークして独自バージョンを使う

どうしても自前で管理したい場合は、プラグインのコードをコピーして別のプラグインとしてインストールし直す方法もある。ただし今後のアップデートやセキュリティパッチの追従をすべて自分で行う必要があるため、開発リソースに余裕がある場合に限った選択肢だ。

同じエラーが別のプラグインで出る場合の一般的な対処法

同じエラーが別のプラグインで出る場合の一般的な対処法

今回のエラーは「Print Invoice & Delivery Notes for WooCommerce」に限らず、フロントエンドから WP_Filesystem() を不用意に呼び出しているあらゆるプラグインで発生しうる。バックアップ系、インポート系、PDF生成系のプラグインで似たような「ftp_nlist()」を含むTypeErrorが出た場合は、以下の共通チェックポイントで原因を絞り込める。

  • スタックトレースの2〜3行目に表示されているプラグインのパスを特定する
  • 該当プラグインのファイルシステム呼び出し部分を探す(WP_Filesystem() または $wp_filesystem を grep する)
  • 認証情報の取得が行われているか確認する(request_filesystem_credentials() の有無)
  • PHPのバージョンを確認する(PHP 8.0未満では暗黙の型変換でエラーが表面化しないことがある)
エラーパターンの見分け方
パターンA 管理画面では動くがフロントエンドでだけ落ちる → FTP認証情報の未取得が原因
パターンB PHP 7.xでは問題なかったがPHP 8に上げたら発生 → 型チェック厳格化の影響
パターンC 特定のサーバー環境(ftpext方式)でのみ発生 → ファイルシステム方式の不一致

特に「ftpext」というファイルシステム方式を使用しているサーバー環境で顕著に発生する。多くのレンタルサーバーでは「direct」方式が使われるため問題が起きにくいが、FTP経由でファイル操作を行う設定になっているとこのエラーに遭遇しやすい。

よくある質問

エラーが出ているのに管理画面にはアクセスできるのはなぜか

管理画面ではWordPressがWP_Filesystem()を呼び出す前に自動的に認証情報を収集する仕組みが働くため、正常に動作する。フロントエンドではその仕組みが起動しないため、プラグインが自前で認証情報を取得しない限り接続に失敗する。これが「管理画面では動くのにマイアカウント画面では落ちる」という現象の理由だ。

出力バッファリングをしないとどうなるのか

request_filesystem_credentials() は認証情報が不足している場合にFTPのホスト名やユーザー名を入力するHTMLフォームを画面に直接出力してしまう。フロントエンドのページに突然フォームが表示されると、サイトのレイアウトが崩れたり、ユーザーを混乱させたりする。出力バッファリングでこのフォーム出力を捕捉して破棄することで、見た目に影響を与えずに認証情報だけを取得できる。

wp-config.phpにFTP定数を設定するだけで直らないのか

FTP定数(FTP_HOSTFTP_USERFTP_PASS)を定義していても、プラグインがWP_Filesystem()を引数なしで呼び出している限り、WordPressは認証情報を探しに行かない。定数はあくまで「情報の置き場所」であり、「その情報を取りに行く処理(request_filesystem_credentials())」が実行されなければ意味がない。コードの修正が不可欠な理由はここにある。

PHP 8にアップグレードした直後から発生したのだが関係あるのか

大いに関係がある。PHP 8.0から関数の引数と戻り値の型が厳密にチェックされるようになり、それまで暗黙的に許容されていたnullの受け渡しがTypeErrorとして検出されるようになった。PHP 7.x時代は同じコードでも警告で済んでいたか、あるいはエラーが発生しても表面的に無視されていた可能性が高い。

この修正でセキュリティ上の問題は起きないのか

起きない。request_filesystem_credentials()はWordPressのコア関数であり、管理画面で日常的に使われている安全な方法だ。認証情報はwp-config.phpの定数やデータベースに保存された情報から取得され、フロントエンドの訪問者にFTPのパスワードが表示されることはない。出力バッファリングでフォームの表示を抑制するのも、余計な情報を露出させないための適切な処置だ。

この記事のポイント

  • WooCommerceの納品書印刷で重大エラーが発生するのは、プラグインがWP_Filesystem()を認証情報なしで呼び出しているのが原因
  • PHP 8の厳格な型チェックにより、FTP接続オブジェクトがnullのまま関数に渡されてTypeErrorが起きる
  • プラグインのclass-utils.phpにあるget_filesystem()メソッドを修正すれば即座に直る
  • request_filesystem_credentials()で認証情報を先に取得し、出力バッファリングでフォーム表示を防ぐのが正しい修正手順
  • プラグインのアップデートで修正が消えるため、恒久対応は開発者による公式修正を待つかフォークしての運用が必要
佐々木 太陽

・ Reddit、Stack Overflow、WordPress.org フォーラムを日々巡回し、現場の悩みを拾い上げて記事化 ・ WordPress、WooCommerce、Next.js などモダンWeb制作領域のトラブルシューティングが専門 ・ 「検索しても答えが見つからなかった」を一つでも減らすことが目標 ・ エラーメッセージから根本原因にたどり着く粘り強い調査が得意 ・ 初心者がつまずきやすい箇所を先回りで解決する記事作りを心がけている

メッセージを残す