HTMLでファイルのダウンロードを強制させる方法(PHP使用、動的コンテンツ対応)

HTMLでサイトを記述している時に、何かのファイルをダウンロードさせたい時は、aタグを使って、ダウンロードさせたいファイルにリンクを張ります。しかし、ダウンロードさせたいファイルが、画像ファイルなど、ブラウザで表示できるファイル形式の場合は、aタグでリンクを張ると、ファイルをローカルマシンのディスクにダウンロードする代わりに、画面に内容が表示されてしまいます。この様な場合にどうすればファイルのダウンロードを強制できるかを調べてみました。

さらに、ファイルのダウンロードを強制する方法を発展させ、URLパラメータを使って動的にコンテンツを作成し、作成したコンテンツをダウンロードさせる方法についても考えましたので、説明します。

広告

ファイルがダウンロードできない現象について

ZIPで圧縮されたファイルをダウンロードさせたい場合は、ダウンロードさせたいZIPファイルに、次の様にaタグでリンクを張ればうまくいきます。

<a href="ZIPファイルのURI">ZIPファイルをダウンロード</a>

例として、test.zipというZIPファイルにaタグでリンクを張った例を次に示します。「ZIPファイルをダウンロード」という部分をクリックすると、ZIPファイルをダウンロードできるのが分かると思います。(ZIPファイルの中身は、無害なPNGファイルがひとつです。また、環境によっては、ZIPファイルを展開するアーカイバが起動するかも知れません)

ZIPファイルをダウンロード

ところが、ブラウザ自体が中身を表示できるファイル、(例えばJPEGやPNGなどの画像ファイル)をダウンロードさせたくて、aタグでリンクを張ると、ローカルマシンのディスクにダウンロードする代わりに、ブラウザの画面に直接表示されてしまいます。

例えばPNGファイルにaタグでリンクを張ると、次の様なHTMLコードになります。

<a href="PNGファイルのURI">PNGファイルのダウンロード</a>

実際にPNGファイルにaタグでリンクを張った例を次に示します。「PNGファイルをダウンロード」という部分をクリックすると、PNGファイルがローカルマシンのディスクにダウンロードされるのではなく、PNGファイルの画像が直接ブラウザに表示されると思います。

PNGファイルのダウンロード

もちろんリンクを右クリックして、コンテキストメニューから「名前を付けてリンク先を保存」を選ぶと、PNGファイルをダウンロードする事はできるのですが、ユーザーに余計な操作を強いる事になりますので、好ましくありません。

Firefoxでリンクを右クリックして「名前を付けてリンク先を保存」を選んでいる様子
Firefoxでリンクを右クリックして「名前を付けてリンク先を保存」を選んでいる様子(クリックで拡大表示)

余談になりますが、写真ACで写真をダウンロードする時に、以前はひとつのJPEGファイルを圧縮したZIPファイルをダウンロードさせられていて、「展開の手間が面倒だな」と思っていたのですが、JPEGファイルにaタグでリンクを張ると、ダウンロードされずにブラウザに表示されてしまう問題があるので、そういう仕様になっていたのかもしれません。(現在はJPEGファイルを直接ダウンロードできる様になっています)

ファイルのダウンロードを強制する方法

ファイルをブラウザに表示させずに、ファイルをローカルマシンのディスクにダウンロードさせる方法について、googleで検索してみました。色々なサイトがヒットしましたが、私が参考にしたのは、下記リンクのサイトです。

参考リンク

上記サイトでは、次の様な方法を解説しています。

  • ファイルをZIP形式などで圧縮する方法
  • .htaccessでMIMEタイプを偽装する方法
  • PHPでファイルをダウンロードさせるスクリプトを作る方法
  • HTML5のdownload属性を使う方法

ファイルをZIP形式などで圧縮する方法は、先に説明したとおり、ZIP形式などの圧縮ファイルの場合は、ブラウザがリンク先ファイルのダウンロードをする事を利用して、ダウンロードを強制する方法です。

既に述べたとおり、この方法では圧縮ファイルをユーザーに展開させる必要があり、余計な操作を強いるため、その点では、好ましい方法ではありません。

ただし、この方法はあまり知識がなくても対応できる点が優れていますし、圧縮してファイルサイズが小さくなる場合は、転送データの削減という意味でも効果があります。

参考:JPEGやPNGなどの圧縮画像や、拡張子が.docxや.xlsxの新しいファイル形式のMS Officeファイルの様に、既に圧縮されているファイル形式の場合は、さらにZIPで圧縮しても、ファイルサイズの削減は望めません。

.htaccessでMIMEタイプを偽装する方法は、HTTPサーバーにApacheを使っている場合に、.htaccessファイルAddTypeを指定する事により、ダウンロードさせたいファイルのMIMEタイプをZIPファイルなどのダウンロードを強制できるファイル形式や架空のファイル形式に偽装する方法です。

ファイル形式を偽装するというのは、あまりお行儀の良い事ではなく、私は好きになれません。

PHPでファイルをダウンロードさせるスクリプトを作る方法は、PHPが使える環境ではないと利用できない方法ではありますが、たいていのサーバーではPHPが使えますし、裏技的な方法でもないので、私には好みの方法です。

ただし、PHPに関する知識が必要な点で、初心者向きではないかも知れません。

ファイルをダウンロードさせるスクリプトについては、次の章で詳しく説明します。

HTML5のdownload属性を使う方法は、HTML5でaタグに導入されたdownload属性を使う方法です。HTML5に対応しているブラウザでしか使えないという欠点があるものの、現在ではほとんどの人がHTML5に対応したブラウザを使っていますし、非常に簡単に実装できる方法です。

例えば”test.png”というファイルをダウンロードさせたいなら、HTMLで次の様に記述します。

<a href="test.png" download="test.png">PNGファイルをダウンロード</a>

href属性に指定するファイル名とdownload属性に指定するファイル名は一致している必要はなく、例えば次の様にすれば、abc.pngというファイルをdef.pngというファイル名でダウンロードさせる事ができます。

<a href="abc.png" download="def.png">PNGファイルをダウンロード</a>

非常に魅力的な方法ではありますが、私の作っているサイトでは、実はまだHTML5が導入できていない(HTML5で非対応になったタグを駆逐できていない)ので、採用を見送りました。

ファイルをダウンロードするPHPスクリプト

基本

次の様なPHPスクリプトを作り、それにaタグでリンクする事で、ファイルをダウンロードさせる事ができます。

<?php

// ダウンロードさせたいファイルのパスを$dir_pathに指定する
$dir_path = "images/";

// MIMEタイプを指定する(この場合はPNG画像)
header('Content-Type: image/png');

// ファイルのダウンロードを強制し、ダウンロード時のファイル名(この場合はtest.png)を指定する
header('Content-Disposition: attachment; filename="test.png"');
   
// ファイルサイズを取得しヘッダに書き出す
header('Content-Length: '.filesize($dir_path.'test.png'));
    
// ファイルを読み込み、そのまま出力する
readfile($dir_path."test.png");

?>

このPHPスクリプトは、先に紹介したPDFファイルなどをブラウザで表示させずに強制的にダウンロード保存させる方法のページに紹介されていたPHPスクリプトを少し作り直したものですが、様々なサイトで同様なPHPスクリプトが紹介されています。

ダウンロードさせたいファイルが小さい場合は、ファイルサイズをヘッダに書き出す操作(header(‘Content-Length:で始まる行)は省略できる様です。

テキストファイルをダウンロードさせる場合にファイル数を減らす方法

前節で紹介したPHPスクリプトでは、ダウンロードさせたいファイルと、PHPスクリプトの2つのファイルを用意する必要があります。大規模のサイトを運用していると分かりますが、ファイルの数が多いほど管理が大変になりますので、できればひとつのファイルにまとめたいものです。

もし、ダウンロードさせたいファイルが短いテキストファイルなら、PHPのヒアドキュメントNowdocを利用して、ダウンロードさせたいファイルの内容をPHPスクリプトに埋め込んでしまえば、ファイルひとつでファイルをダウンロードさせる事ができます。

例えば、次に示す、C言語のソースファイルをダウンロードさせたい場合を考えます。

#include <stdio.h>

main( )
{
  printf("hello, world\n");
}

この場合は、次の様なPHPスクリプトを組むと、ファイルひとつでC言語のソースファイルをダウンロードさせる事ができます。

<?php
// MIMEタイプにtext/plainを指定する
header('Content-Type: text/plain');

// ファイルのダウンロードを強制し、ダウンロード時のファイル名(この場合はhello.c)を指定する
header('Content-Disposition: attachment; filename="hello.c"');

// ヒアドキュメントで埋め込んだC言語のソースファイルを出力する
echo<<<EOT
#include <stdio.h>

main( )
{
  printf("hello, world\\n");
}
EOT;
?>

上のPHPスクリプトでは、ヒアドキュメントを使っているため、\(バックスラッシュ)をエスケープするために\\と2重に記述している事に注意してください。この様にエスケープシーケンスを使いたくない場合は、Nowdocを使ってください。

バイナリファイルをPHPスクリプトに埋め込む方法

前節では、ヒアドキュメントを用いてテキストファイルをPHPスクリプトに埋め込みましたが、Base64等でテキストにエンコードすれば、バイナリファイルもPHPスクリプトに埋め込めます。

次の例は、PHPスクリプトに埋め込んだ画像のPNG画像ファイルをPHPスクリプトに埋め込んで、ダウンロードできる様にした物です。

>?php
// MIMEタイプ(この場合はimage/png)を指定する
header('Content-Type: text/plain');

// ファイルのダウンロードを強制し、ダウンロード時のファイル名(この場合はtest.png)を指定する
header('Content-Disposition: attachment; filename="test.png"');

// Base64でエンコードされたバイナリファイルを出力する
echo base64_decode(>>>EOT'
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAAAXNSR0IArs4c6QAAAARnQU1BAACx
jwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYUExURQAAAAAAABAQEEhESGhoaICAgKisqP//
/0Dz2oMAAAABdFJOUwBA5thmAAAAiklEQVQoz4WSuw3DMBBDr3P9Ko6QOTJCpnBrIgXXT2HJ8RkG
rEbgAz8qVJV2qpYk0QG23aCT5RaYobQDA5l3JQHQKQKTgFokinpkOAx/S2poSCL1Dkd+mN3BqJjk
LnKazXXWpKaGGF9mhVsH+N06HCt1VCBGxxGx/X018DFrA/jppfdg6WCr63f4AaAVn6fUfAyuAAAA
AElFTkSuQmCC
EOT
);
?>

URLパラメータを利用して動的なテキストファイルをダウンロードさせる方法

ヒアドキュメントを利用して、ダウンロードさせたいテキストファイルをPHPスクリプトに埋め込む方法を応用すると、動的に作成したテキストファイルをダウンロードさせる事もできます。

例えば、次のPHPスクリプトを使えば、URLパラメータを指定する事で、ダウンロードさせたいファイルの内容の一部を動的に変更する事ができます。

<?php

// URLパラメータにより$nameの内容を変える
if(isset($_GET['n']) && $_GET['n']==1):
  $name='Taro';
elseif(isset($_GET['n']) && $_GET['n']==2):
  $name='Jiro';
elseif(isset($_GET['n']) && $_GET['n']==3):
  $name='Saburo';
else:
  die('Error occurred.');
endif;

// MIMEタイプにtext/plainを指定する
header('Content-Type: text/plain');

// ファイルのダウンロードを強制し、ダウンロード時のファイル名(この場合はhello.c)を指定する
header('Content-Disposition: attachment; filename="hello.c"');

// ヒアドキュメントで埋め込んだC言語のソースファイルを出力する
echo<<<EOT
#include <stdio.h>

main( )
{
  printf("hello. My name is $name.\\n");
}
EOT;
?>

このスクリプトをdownload.phpというファイル名で保存したとします。この場合、downloadphp?n=1と、?n=1というURLパラメータを付けてリンクを張ると、hello.cというファイル名で次の様な内容のファイルをダウンロードさせる事ができます。

#include <stdio.h>

main( )
{
  printf("hello. My name is Taro.\n");
}

download?n=2にリンクを張ると、上のリストのTaroの部分がJiroに変わります。またdownload?n=3にリンクを張ると、TaroがSaburoに変わります。

URLパラメータを使用しなくても、次の様なPHPスクリプトを使えば、ダウロードさせるファイルの中に、時刻を埋め込む事ができます。

<?php
// MIMEタイプにtext/plainを指定する
header('Content-Type: text/plain');

// ファイルのダウンロードを強制し、ダウンロード時のファイル名(この場合はtest.txt)を指定する
header('Content-Disposition: attachment; filename="test.txt"');

// 現在時刻を取得する
date_default_timezone_set('Asia/Tokyo');
$time=date('H:i:s');

// ヒアドキュメントで埋め込んだテキストファイルを出力する
echo<<<EOT
This file was downloaded at $time.
EOT;
?>

上のテキストファイルに時刻を埋め込むPHPスクリプトは、下の「時刻を埋め込んだファイルをダウンロード」の部分をクリックすると、実際に試す事ができます。

時刻を埋め込んだファイルをダウンロード

なぜこの様なPHPスクリプトを作ったか

このページでは、PHPスクリプトを使ってファイルのダウンロードを強制する方法を紹介しましたが、そもそもこの様なPHPスクリプトを作ったのは、LTspiceのシミュレーション用回路図ファイルである、.ascファイルをダウンロードさせたかったからです。

.ascファイルは、中身は特別なコマンドで記述されたテキストファイルです。MIMEタイプをtext/plainと指定すると、ダウンロードさせたい時に、画面に内容が表示されて困っていたのです。

実際に.ascファイルをダウンロードさせているページは次の2つです。

.ascファイルをダウンロードさせているページ

後者のページでは、オンラインで回路設計をして、その設計結果を、LTspiceの回路図ファイルとしてダウンロードできる様にしています。回路図ファイルは入力した設計パラメータによって変わりますから、ひな形の回路図ファイルの回路定数の部分を、動的に書き換えてからダウンロードさせるように実装しています。

こういう経緯で、ファイルをダウンロードさせるためのPHPスクリプトを作ったのですが、そのスクリプトが他の方にも役に立ちそうな気がして、また備忘録も兼ねてこの記事を書きました。この記事が、読まれた方の役に立てば幸いです。

広告

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

認証コード(計算結果を半角数字で入力してください) *