実現したいこと
■作成したいもの
WordPressでホームページを作成しています。 作成中のページ上で、ボタンを押下すると特定のファイル操作が行われて ZIPファイルをクライアントがダウンロードできる仕組みを作成したいです。
■お伺いしたいこと
今回ケースのエラーが起こる可能性があると思われる原因、
そもそも今回は後述しますが恐らくPHPでheader情報の付与が上手くいっていないと思われるのですが
もし同じご意見であれば、その付与が上手くいかない原因、ご意見をお伺いさせていただきたいです。
異なるご見解であれば、そのご意見もお伺いしたいです。
補足:
itagagakiさんのご助言があって、
headers_sent関数で確認したところ既にヘッダ送信済みのため、
headerのcontent-typeの設定が上手くいっていないことが
分かりました。
しかし、該当の既に送信を行ったモジュールはテーマ共通のPHPファイルであったため、
なぜPOST後のレスポンス送信時のエラーで差異が出るのかが不明です。
(既存テーマの(Cocoon)ではうまくいっています。)
このヘッダ送信済みとなってしまう "契機" の観点で 問題のありそうな部分があれば
ご助言いただきたいです…。
## 前提 前提として、WordPressのテーマは既存の物では無くて、一から自分で作成した テーマを使っています。 一から自分で作成したテーマについては、ほぼプレーンな状態で、特別な設定は 特に行っていない状態です。
現象が起きているページは、
WordPress の 固定ページ(page.php)です。
クライアントのダウンロード方法は一般的でブラウザを使ってページにアクセスし、 ボタン押下でエクスプローラーから保存する経由になります。 サーバの使用言語はPHPです。
発生している問題・エラーメッセージ
既存のWordPressのテーマ(Cocoonなど)であれば作成したPHPのコードで
ZIPファイルが正常にダウンロードできています。
しかし一から自分で作成したテーマでPHPのコードを走らせると、ページ上に ZIPファイルの中身の文字列が表示されてしまいます。
(以下の画像参照です。)
文字列が表示されるケースでは、ページ遷移が一度走って、
遷移後のページで以下のようなページが表示されます。
(URLは遷移前と遷移後で同じ)

エラーメッセージは特に表示されていません。
また、デベロッパーツールを使ってPOSTの結果を確認いたしました。
上手くいっているテーマの時と上手くいっていないテーマの時のPOSTの結果差分は以下の通りです。
(黒塗りのところについては差分は出ていません。)
上手くいっているケースではPOSTしたときは
content-type: application/zip となっていましたが、
上手くいかなかったときは
content-type: text/html; charaset=UTF-8 となっていました。
そのため、以下にコードを載せますが
恐らくPHPの header が上手く処理できておらず、readfileが行われている可能性が
あると個人的には考えております。
該当のソースコード(既存・今回作成したテーマともに同じコード)
HTML
1<form method="POST" action="" enctype="multipart/form-data"> 2 <div class="file_button"> 3 <input class="input-file" type="file" name="upimg" accept="image/png"> 4 </div></br></br> 5 <button type="submit" name="add" class="section1-content-button" onsubmit="return check_blank(this)" >ダウンロード</button > 6<!-- 省略 -->
PHP
1 // !!! OPEN処理とZIPファイルダウンロード処理部以外は、長くなるので割愛させていただきます。 !!! 2 // ファイルダウンロードさせるための処理定義 3 function download($pPath, $pMimeType = null) { 4 //-- Content-Typeとして送信するMIMEタイプ 5 $mimeType = (isset($pMimeType)) ? $pMimeType 6 : (new finfo(FILEINFO_MIME_TYPE))->file($pPath); 7 8 //-- 適切なMIMEタイプが得られない時は、未知のファイルを示すapplication/octet-streamとする 9 if (!preg_match('/\A\S+?/\S+/', $mimeType)) { 10 $mimeType = 'application/octet-stream'; 11 } 12 13 //-- Content-Type 14 header('Content-Type: ' . $mimeType); 15 16 //-- ウェブブラウザが独自にMIMEタイプを判断する処理を抑止する 17 header('X-Content-Type-Options: nosniff'); 18 19 //-- ダウンロードファイルのサイズ 20 header('Content-Length: ' . filesize($pPath)); 21 22 //-- ダウンロード時のファイル名 23 header('Content-Disposition: attachment; filename="' . basename($pPath) . '"'); 24 25 //-- keep-aliveを無効にする 26 header('Connection: close'); 27 28 //-- readfile()の前に出力バッファリングを無効化する 29 while (ob_get_level()) { ob_end_clean(); } 30 31 //-- 出力 32 readfile($pPath); 33 34 // 作成したzipは削除しておく 35 unlink($pPath); 36 37 //-- 最後に終了させる 38 exit; 39 } 40 41 // POSTされたときの処理開始 42 if(isset($_POST['add'])) { 43 44 // !!! 中略 45 46 // $zip_tmp_path、$zip_nameはそれぞれサーバ上のZIPファイルの場所と名前 47 $res = $zip->open($zip_tmp_path.'/'.$zip_name, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE); 48 49 // !!! 中略(ここからファイルダウンロードさせるための処理実行) !!! 50 51 // $pMimeTypeの引数は未指定で実行 52 download($zip_tmp_path.'/'.$zip_name); 53 }
試したこと
■ 調査を行って現象として、以下記事に近いと感じたため文字コードの改善を試みました。
「【PHP】headerでContent-Typeを指定したのに効かない場合の対処法」
https://mementoo.info/archives/2292
1.一から作成したテーマのPHPファイルをUTF-8(BOMなし)に変換して再配置
2.該当ページに対して charset=utf-8 を指定するようにHTMLを編集
■ type を 無条件に zip 指定のコードで現象起こるかどうか
ご助言いただき、PHPコードにて無条件で
header('Content-Type: application/zip')
を指定してダウンロード処理を実行してみました。
結果は変わりませんでした。以下試したダウンロード処理部のコードです。
(引数に正しくzipファイルのパスを渡しています。)
PHP
1function download($pPath) { 2 3 //-- Content-Type 4 header('Content-Type: application/zip'); 5 6 //-- ウェブブラウザが独自にMIMEタイプを判断する処理を抑止する 7 header('X-Content-Type-Options: nosniff'); 8 9 //-- ダウンロードファイルのサイズ 10 header('Content-Length: ' . filesize($pPath)); 11 12 //-- ダウンロード時のファイル名 13 header('Content-Disposition: attachment; filename="' . basename($pPath) . '"'); 14 15 //-- keep-aliveを無効にする 16 header('Connection: close'); 17 18 //-- readfile()の前に出力バッファリングを無効化する ※詳細は後述 19 while (ob_get_level()) { ob_end_clean(); } 20 21 //-- 出力 22 readfile($pPath); 23 24 // 作成したzipは削除しておく 25 unlink($pPath); 26 27 //-- 最後に終了させるのを忘れない 28 exit; 29}
■ header関数を呼ぶ前にheaders_sent関数で確認してみました。
ご助言いただき、headers_sent関数で既にヘッダが送信されているか確認しました。
以下コードです。
PHP
1function download($pPath) { 2 3 if (!headers_sent($filename, $linenum)) { 4 5 //-- Content-Type 6 header('Content-Type: application/zip'); 7 8 //-- ウェブブラウザが独自にMIMEタイプを判断する処理を抑止する 9 header('X-Content-Type-Options: nosniff'); 10 11 //-- ダウンロードファイルのサイズ 12 header('Content-Length: ' . filesize($pPath)); 13 14 //-- ダウンロード時のファイル名 15 header('Content-Disposition: attachment; filename="' . basename($pPath) . '"'); 16 17 //-- keep-aliveを無効にする 18 header('Connection: close'); 19 20 //-- readfile()の前に出力バッファリングを無効化する ※詳細は後述 21 while (ob_get_level()) { ob_end_clean(); } 22 23 //-- 出力 24 readfile($pPath); 25 26 // 作成したzipは削除しておく 27 unlink($pPath); 28 29 //-- 最後に終了させるのを忘れない 30 exit; 31 } 32 else { 33 echo "$filename の $linenum 行目でヘッダがすでに送信されています。\n" . 34 "リダイレクトできません。代わりにこの <a " . 35 "href=\"http://www.example.com\">リンク</a> をクリックしてください。\n"; 36 exit; 37 } 38}
確認したところ、同じPHPコードで Wordpressのテーマ Cocoon では既にヘッダ送信済み判定ではなかったのですが、
自作テーマでは既にヘッダ送信済み判定となりました。
実行結果:
/home/c1221591/public_html/[質問者の取得しているドメイン].com/wp-includes/formatting.php の 5740 行目で
ヘッダがすでに送信されています。 リダイレクトできません。代わりにこの リンク をクリックしてください。
該当の送信済みとなったformatting.phpのコードは以下の箇所でした。
※注釈つけています。
該当箇所:
<!-- ↓↓↓ ここが 5740行目 ↓↓↓ -->
window._wpemojiSettings = <?php echo wp_json_encode( $settings ); ?>;
参考文献:
https://developer.wordpress.org/reference/functions/print_emoji_detection_script/
PHP
1/** 2 * Prints inline Emoji detection script. 3 * 4 * @ignore 5 * @since 4.6.0 6 * @access private 7 */ 8function _print_emoji_detection_script() { 9 $settings = array( 10 <!-- 文字制限により中略 --> 11 <script<?php echo $type_attr; ?>> 12 13 14 <!-- ↓↓↓ ここが 5740行目 ↓↓↓ --> 15 window._wpemojiSettings = <?php echo wp_json_encode( $settings ); ?>; 16 } 17}
■ header関数使う前に、ob_cleanで出力バッファを意図的にクリアして、出力の終わりにob_flushする
ご助言いただき、ob_●● を試してみました。
PHP
1function download($pPath) { 2 if (!ob_clean()) { 3 echo <<<EOM 4 <script>alert('ob_cleanの失敗');</script> 5 EOM; 6 exit; 7 } 8 9 //-- Content-Type 10 header('Content-Type: application/zip'); 11 12 //-- ウェブブラウザが独自にMIMEタイプを判断する処理を抑止する 13 header('X-Content-Type-Options: nosniff'); 14 15 //-- ダウンロードファイルのサイズ 16 header('Content-Length: ' . filesize($pPath)); 17 18 //-- ダウンロード時のファイル名 19 header('Content-Disposition: attachment; filename="' . basename($pPath) . '"'); 20 21 //-- keep-aliveを無効にする 22 header('Connection: close'); 23 24 if (!ob_flush()) { 25 echo <<<EOM 26 <script>alert('ob_flushの失敗');</script> 27 EOM; 28 exit; 29 } 30 31 //-- 出力 32 readfile($pPath); 33 34 // 作成したzipは削除しておく 35 unlink($pPath); 36 37 //-- 最後に終了させるのを忘れない 38 exit; 39}
上記のように、header 処理の前に、出力バッファをクリアしてからフラッシュして
readfileしましたが、結果は不具合結果と同じでした。
ob_cleanした後に、headers_sent()で送信済みかどうか確認してみると既に送信済み
判定となっているため、同不具合が発生していると思われます。
試したこと最新(CHERRYさんからいただいた方針です。こちらで期待動作となりました。)
<?php の次行からダウンロードの処理を記載して、テーマの処理が始まる前にダウンロード処理するコードを記載することを 試しました。以下コードです。 (問題をシンプルにするために header関数を使わないようにしました。) ``` PHP <!-- page.php --> <?php define('WP_DEBUG', false); function download($pPath) { //-- Content-Type header('Content-Type: application/zip'); //-- ウェブブラウザが独自にMIMEタイプを判断する処理を抑止する header('X-Content-Type-Options: nosniff'); //-- ダウンロードファイルのサイズ header('Content-Length: ' . filesize($pPath)); //-- ダウンロード時のファイル名 header('Content-Disposition: attachment; filename="' . basename($pPath) . '"'); //-- keep-aliveを無効にする header('Connection: close'); //-- readfile()の前に出力バッファリングを無効化する while (ob_get_level()) { ob_end_clean(); } //-- 出力 readfile($pPath); // 作成したzipは削除しておく unlink($pPath); //-- 最後に終了させるのを忘れない exit; } if(isset($_POST['add'])) { // zip 作成処理 // 出力処理 ※”$zip_tmp_path.'/'.$zip_name” は zipファイルのパス download($zip_tmp_path.'/'.$zip_name); } ?> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"/> <meta name="referrer" content="no-referrer-when-downgrade"/> </head> <?php if(have_posts()){ while(have_posts()){ the_post(); the_content(); } } ?>``` HTML <!-- 記事内容 --> <!-- 省略 --> <form method="POST" action="" enctype="multipart/form-data"> <div class="file_button"> <input class="input-file" type="file" name="upimg" accept="image/png"> </div></br></br> <button type="submit" name="add" class="section1-content-button" onsubmit="check_blank(this);" >ダウンロード</button > </form> <!-- 省略 -->
結果、期待通りに download 処理を行うことができました。
補足情報(FW/ツールのバージョンなど)
OS:Windows 10 pro(64bit)
GoogleChrome:バージョン 90.0.4430.93(Official Build) (64 bit)

回答2件
あなたの回答
tips
プレビュー