[php] PHPでcsvとtar.gzファイルをダウンロードさせる

ZeroMailの管理画面から、ログと添付ファイルをダウンロード出来るようにした。
ログファイルはCSVで、添付ファイルはディレクトリごと一括してtar.gzです。

ダウンロードアラート

CSVはともかくtar.gzにてこずったのでメモっておくことにする。
※TarはPEARでなくコマンドでやっつけてます。

CSVのダウンロード

ZeroMailのログファイルはCSVと同じカンマ区切りだけど、ファイル自体はCSVではなく先頭にexit;したphpファイルで、ログはコメントアウトになってます。

<?php exit;/*
"2010/06/25 - 10:53:22","ああああ","ああああ","info@example.com","123-4567","穂気都参古区出藻町12-345","090-2134-5678","http://example.com/","先日購入したグレートソードについてですが、メタルスライムとの戦闘で刃先が欠けてしまいました。改修をお願いしたいのですが、修理が終わるまで何日くらいかかりますか?","電話","とにかく早く連絡が欲しい","","a50edc47.jpg (331.495kb)",""
*/?>

CSVなら1行目に項目のラベルがあった方がエクセルで開いた時とかに見やすい。
メールフォームのラベル設定がちょうど連想配列なので

$inputs = array(
	'name'=>'お名前',
	'name2'=>'ふりがな',
	'email'=>'E-Mail',
	'zipcode'=>'郵便番号',
	'address'=>'住所',
	'tel'=>'電話番号',
	'url'=>'URL',
	'message'=>'お問い合わせ内容',
	'cont'=>'ご連絡方法',
	'time'=>'ご連絡希望時間',
	'timetxt'=>'ご連絡希望時間(詳細)',
	'tmpfile'=>'添付ファイル',
	'tmpfile2'=>'添付ファイル2'
);

これをCSVらしくカンマ区切りにimplodeして使う。

$head = '"送信日時","'.implode('","',array_values($inputs))."\"\n";

ログはfileで取得してから先頭と最後のコメント行を削除すれば、CSVの行データが得られる

$data = file("logfilename.php");
array_shift($data);
array_pop($data);

あとはしかるべきヘッダを送信して$head$dataを出力すればCSVでダウンロード出来る。
(Content-Dispositionがダウンロードのためのヘッダ)
文字コードはShift-JISでないとエクセルとかで文字化けするんで、出力する時にmb_convert_encodingする。

header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=logfile.csv");
header("Content-Length: ".(filesize("logfilename.php")+strlen($head)));
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");

print(mb_convert_encoding($head, "SJIS", ”AUTO”));

foreach($data as $i => $line){
	print(mb_convert_encoding($line, "SJIS", "auto"));
}

exit;

IE6はContent-DispositionとCache-Controlの併用でバグが発生するらしい。

ログがまんまCSVだったりデータベースだったりしてもやることは同じです。

tar.gzのダウンロード

zipと平行してよく目にするtar.gz。
見れば圧縮ファイルなんだろうと推測は付くけど、拡張子が2つも続くのが謎だった。(でも特に必要がなかったから調べもしなかった)

@Tenderfeel: tarってどれくらい圧縮されるんだろう

@wokamoto: @Tenderfeel tar は無圧縮です。圧縮させるには gunzip を併用するのが一般的。

(wokamotoさん Replyありがとうございます 😀 )

Tarの解説に圧縮圧縮書いてあるんだけど、実際は無圧縮で複数のファイルを1つにまとめることしか出来ませんよ、と。
tarにするついでにzipで圧縮するから拡張子が2つ続くと。Tarのコマンドを見ると確かにそんなオプションがありました。

まずtarアーカイブを作るためにテンポラリファイルを作成する。

$tmp_file = tempnam("/tmp", "HOGE");

systemでtarコマンドを実行する。

$command  = "tar czf $tmp_file  [対象ディレクトリ]";
system($command);

$commandに入れてるコマンドはtarの次にあるのが重要で
c→新しいアーカイブを作成
z→アーカイブをgzipにフィルターする
f→指定したアーカイブ・ファイルまたはデバイスを使う
という意味がある。

コマンド実行で圧縮ファイルは出来るもののダウンロードさせるダイアログは出ないので、CSVと同じことをしなければならない。

まずはファイル関数で圧縮ファイルの内容を取得する。取得したら削除。

$fp = fopen($tmp_file, "r");
$contents = fread($fp, filesize($tmp_file));
fclose($fp);
			
unlink($tmp_file);

TarはMIMEタイプがapplication/x-tarなのでContent-typeはそのように設定する。

header("Content-type: application/x-tar");
header("Content-Disposition: attachment; filename="download.tar.gz"");
header("Content-Length: ".strlen($contents));
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");

echo $contents;

exit;

実際にやってみるとdownload.tar.gzがダウンロード出来るのだけど、
解凍したファイルはルートディレクトリからフォルダが作成されてしまうので、開いても開いてもフォルダが出てくるマトリョーシュカ状態になる。

解凍して出来るフォルダを対象ディレクトリだけにするには、コマンドに-C(指定ディレクトリにcdしてから動作を行なう)オプションをつける

$command  = "tar czf $tmp_file -C [対象の1つ上のディレクトリまでのルートパス] [対象ディレクトリ]";

仮に /root/user/www/public_html/test/uploads/ っていうパスで圧縮したいディレクトリがuploadsなら、コマンドは次のようになる。

$command  = "tar czf $tmp_file -C /root/user/www/public_html/test uploads/";

〆はexit;でなくflush()でもいいらしい。
ルートパスは$_SERVERのDOCUMENT_ROOTやSCRIPT_FILENAMEで取れます。

windowsにインストールされたxampp上ではうんともすんとも言わない。
tarはLinuxのコマンドだから当然だ…ってことでテストにはLinuxなサーバーが必要ですよ。

Leave a Comment.