phpで画像ダウンロードボタンを作ってみる

この記事はだいぶ前に書かれたものなので情報が古いかもしれません

画像にカーソルを合わせて右クリックすると、コンテキストメニューの中に「名前をつけて画像を保存」みたいなのがありますよね。

まあ、それがあるのだから画像を自分のPCに落としたかったら右クリックで名前をつけて画像を保存すればいいわけなんですが、今回はそれをあえてプログラム組んでダウンロード用のボタンを作ってみましょうと、そんな企画です。

企画といっても、別に難しいことをするわけではないです。実際ソースで書いたら5,6行くらいなもんですからね。


ボタンの設置

とりあえずは、ダウンロードボタンを設置してみましょう。

<a href="/download.php">download</a>

ボタンっつってもリンクですね。サイト内にdownload.phpというページがあるとここは仮定してください。


ダウンロード処理

で、そのdownload.phpの中はこんな感じ。

//画像のパスとファイル名
$fpath = '/images/sample.jpg';
$fname = 'sample';

//画像のダウンロード
header('Content-Type: application/octet-stream');
header('Content-Length: '.filesize($fpath));
header('Content-disposition: attachment; filename="'.$fname.'"');
readfile($fpath);

これで完成です。じっさい動かしてみれば、右クリックで画像を保存しようとしたときと同じようなアラートが出てくるはずです。

Content-Type: application/octet-streamっていうのは、え~……なんだろ。

なんだろう……? 説明しろと言われると困るな。


そもそもContent-Typeって何さ?

Content-Typeというのは、ファイルの種類を示すものです。このContent-Typeを任意に指定してやることによって、ブラウザはどんな挙動をすればいいか判断するわけですね。

「text/html」ってなってたら、ブラウザは「こいつはHTMLファイルか。おっけ、任せろ。ページオープン!」と思うです。

で、上記のように「application/octet-stream」ってなってたら、ブラウザは「よし、任せろ。行くぜ! 必殺!! 滅びのオクテットストリーーーーーーム!!!」とはならずに、「よっしゃ、ダウンロード処理開始じゃ」と判断するわけです。

このContent-Typeさえ指定してあればダウンロード処理はできると思うのですが、たとえばダウンロードするファイルに任意に名前をつけたかったりしたら、Conten-dispositionを用いればいいようです。


この辺りの詳しい理解を深めたかったら、僕はいつものようにGoogleさんに丸投げするので、ググるのがいいと思います。


Content-Type

Content-Length

Content-disposition


どんなときに使おうかしら?

そもそも右クリックでダウンロードできるんだからいらないんじゃね? ってなことはさっきも言いましたが、じゃあどんなときにこの手間をかけたらいいのかと言うと、主にどの画像がどれくらいダウンロードされたかの統計を取りたいとか、そんなときじゃないかと思います。

実際、今回僕がこの機能を仕事で使ったのも、サイト内でファイルごとのダウンロード数を集計する必要があったからです。

といっても、右クリックでのダウンロードを禁止しているわけではないので、そっちでダウンロードされたら集計はされないし、厳密なカウントはできないんですけどね。クライアントにも、そこは厳密にやらなくていいと言われたし……。


でっどふぉ~る(落とし穴)

ただちょっと問題があって、そのダウンロードされた回数を取得するには、当然ながらダウンロードボタンが押された後に対象のファイルのダウンロードを回数をインクリメントするわけなんですけども、ダウンロードが完了した後にインクリメント処理を入れるのは、たぶんなんですけど無理っぽいんですよね。

//画像のパスとファイル名
$fpath = '/images/sample.jpg';
$fname = 'sample';

//画像のダウンロード
header('Content-Type: application/octet-stream');
header('Content-Length: '.filesize($fpath));
header('Content-disposition: attachment; filename="'.$fname.'"');
readfile($fpath);

//ダウンロード回数をDBに登録する処理をここに書く

登録処理自体は省略しますけど、例えばこんな感じで書いても、実際に登録処理は行われない。普通にファイルをダウンロードして終わりです。

↓なら登録処理もできます。

//ダウンロード回数をDBに登録する処理をここに書く

//画像のパスとファイル名
$fpath = '/images/sample.jpg';
$fname = 'sample';

//画像のダウンロード
header('Content-Type: application/octet-stream');
header('Content-Length: '.filesize($fpath));
header('Content-disposition: attachment; filename="'.$fname.'"');
readfile($fpath);

先に登録処理を終えてしまうやり方ですね。これならインクリメント処理も実装できる。

できるのですが、ただこれ、実際にファイルをダウンロードしようがしまいが、このdownload.phpにアクセスした時点で、つまり自分で設置したダウンロードリンクが押された時点でインクリメントしちゃうんですね。

右クリックしたときもそうですけど、名前をつけて画像を保存するときって、保存ボタンとキャンセルボタンがあるでしょ? キャンセルボタンを押せば当然ながらファイルはダウンロードされないです。

この処理だと、そうやってキャンセルされた場合でも、ダウンロード回数は増えてしまうんですね。

でも、たぶんですが、これは仕方ないっぽいです。本当にダウンロードされたかキャンセルされたかのフラグをphp側で受け取るのは無理っぽいので。



もしそれが可能だってことを知ってる人がいたら、ぜひご一報ほしいですね。

まだコメントはいただけてないみたい……
もしかしたら何か関連しているかも?