投稿日:2024年1月19日

先日、下記のような相談がありました。

フォーム設定済みのPDFをダウンロード。必要事項を入力してもらい、アップロードすることができるか?

検証してみました。

PDFをダウンロードするソースコードはよく書きますが、アップロードの実装はWordPressContact Form 7でしか行ったことがありません。

<a href="xxxx.pdf" download>PDFをダウンロード</a>

▲download属性を書けばブラウザで開かずにダウンロードすることができます。

 仕様

アップロードできるファイルはPDFのみにする。
ボタンクリック、もしくはドラッグ&ドロップでファイルを選択する。
アップロード後のファイル名は「タイムスタンプ+ランダムな英数字」にする。
※タイムスタンプだけだと、同時にアップロードした際にどちらかが上書きされる恐れがあるのでランダムな英数字を付与することにします。

最初にお見せします。今回作ったものはこちらです。

▲ボタンクリック、またはファイルドロップでファイルを選択できます。
仕様通りPDFのみ選択可能です。
ボタンクリックでは下記のソースコードの6行目に記載のある accept="application/pdf" にあるようにPDFしか選択できないようになっています。
ファイルドロップ時、PDF以外のファイルではアラートが表示されるようになっています。
ファイルを選択するとファイル名が表示されます。
上のフォームだとformタグに onsubmit="return false;" を記述しているので送信ボタンをクリックしても送信されません。

ソースコードです。

 HTML

<form action="upload.php" method="post" enctype="multipart/form-data">
  <div class="areaWrap">
    <p>ここに記入したPDFをドロップ、または選択してください。</p>
    <div class="fileBtnsWrap">
      <!-- ▼ ファイルを扱うのでtype="file"と記述 CSSで非表示にします。-->
      <input type="file" name="file" accept="application/pdf" class="fileUpload">
      <!-- ▼ こちらを表示させて上のinput要素と同じ位置に配置します。見かけ上のボタンになります。-->
      <p class="choiceFileBtn">ファイルを選択する</p>
    </div>
  </div>
  <!-- ▼ JSでファイル名を取得しspan内に表示します。-->
  <p class="displayFileName">選択したファイル名:<span></span></p>
  <div class="submitBtn">
    <input type="submit" value="送信する">
  </div>
</form>

▲6、8行目はCSSで下図のような位置調整を行います。

▲双方position:absolute; で天地左右、親要素の中央に配置します。
上部のinput要素の「ファイルを選択」ボタンは opacity:0; で非表示(0%)にします。
visibility:hidden; だとクリックできなくなります。

 CSS

// 囲み罫
.areaWrap {
  position: relative;
  border: 2px dashed #aaa;
  width: 70%;
  height: 250px;
  margin: 1rem auto;
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-items: center;
  > p {
    position: absolute;
    top: 5%;
  }
}

.fileBtnsWrap {
  position: relative;
}

// inputボタンと「ファイルを選択」ボタン
.fileUpload,
.choiceFileBtn {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 250px;
  height: 50px;
  padding: 0.5rem;
}

// inputボタン
.fileUpload {
  z-index: 100;
  display: block;
  opacity: 0;
  cursor: pointer;
}

// 「ファイルを選択」のボタン
.choiceFileBtn {
  background-color: #aaa;
  color: #fff;
  border-radius: 10px;
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.25rem;
}

// 送信ボタンとファイル名表示テキスト
.submitBtn,
.displayFileName {
  text-align: center;
  margin-bottom: 1rem;
  font-size: 1.25rem;
}

 JavaScript

// クリックでファイル選択
// ファイル名を表示
$('.fileUpload').change(function () {
  let file = $(this).prop('files')[0]; // ファイル名取得
  let fileName = file.name; // ファイル名取得
  $('.displayFileName span').text(fileName); // ファイル名表示
});

// ドラッグ&ドロップでファイル選択
$(document).on('drop', '.areaWrap', function (e) {
  $(this).find('.fileUpload')[0].files = e.originalEvent.dataTransfer.files;
  $(this).find('.fileUpload').trigger('change');
});

$(document).on('change', '.areaWrap .fileUpload', function (e) {
  let fileReader = new FileReader();
  fileReader.onload = function () {
    let file = $(this).prop('files')[0]; // ファイル名取得
    let fileName = file.name; // ファイル名取得
    $('.displayFileName span').text(fileName); // ファイル名表示
    e.preventDefault(); // 動作をキャンセル
  };
  fileReader.readAsDataURL(e.target.files[0]);
});

// ドロップ可能領域以外はドロップ無効
$(document).on('dragenter dragover drop', function (e) {
  e.stopPropagation(); // バブリングフェーズで親要素への伝播を止める
  e.preventDefault(); // 動作をキャンセル
});

// ドラッグ時PDFファイルでなかったら
$('.fileUpload').change(function () {
  if (!$('.displayFileName span').text().match(/\.pdf/)) { // ファイル名に「.pdf」が含まれてなかったら
    if (!alert('PDFファイルを選択してください。')) {
      $('.displayFileName span').text(''); // ファイル名表示を空欄にする
      return;
    }
  }
});

▲コメント通りです。
33〜40行目ですがファイル名に拡張子「.pdf」が含まれてなかったらアラートを表示する仕様にしています。
色々調べたのですが、inputのaccept属性のようにフィルターをかけることはできないようです。

次にformのactionで指定したupload.php。
送信される内容を処理するPHPです。

 PHP

<?php
// ファイルへのパス
$path = './xxxxxxx/';

ini_set('date.timezone', 'Asia/Tokyo');
$time_stamp = time(); // UNIXタイムスタンプ取得
$save_file_name = date("Ymd_His", $time_stamp); // 書式指定 例:20240119_140020 のようになります

$string_array = 'abcdefghijklmnopqrstuvwxyz0123456789'; // ランダムに出力する文字列
$random_string = substr(str_shuffle($string_array), 0, 6); // 6桁のランダムな文字列を出力する

// ファイルがアップロードされているか、POSTでアップロードされたかを確認
if(!empty($_FILES['file']['tmp_name']) && is_uploaded_file($_FILES['file']['tmp_name']) ) {

	// ファイルを保存する。ファイル名は6桁のランダム英数字+タイムスタンプ
	if(move_uploaded_file($_FILES['file']['tmp_name'], $path . $random_string . "_" . $save_file_name. '.pdf') ) {
		echo 'ファイルをアップロードしました。';
		exit;
	} else {
		echo 'ファイルのアップロードに失敗しました。';
	}
}

▲9行目の文字列はファイル名に含むことができる記号類を含むとより効果的でしょう。

最後にアップロードされたファイルを確認するページです。
▼アクセスするとこのように表示されます。

$result = glob('./xxxxxxx/*'); // glob関数
echo "<h1>アップロードPDF</h1>";
for($i=0; $i<count($result); $i++){
 $file_name = preg_replace("/\.\/xxxxxxx\//","",$result[$i]);
 echo "<p><a href='xxxxxxx/".$file_name."' target='_blank'>".$file_name."</a></p>";
}

1行目のglob関数はファイルを検索するPHPの関数。
「*」はワイルドカードでディレクトリxxxxxxx内の全てのファイルを取得します。
./xxxxxxx/xxxx.pdf のようにディレクトリ名を含んで取得します。
4行目のpreg_replace でディレクトリ名を削除(空白に置換)しています。
3行目の繰り返し処理内でリンク付きのファイル名を表示しています。

まとめ

色々と検証しての作業だったので、できた後の手応えを感じています。
普段使っているWebの機能をいざ自分で作るとなるとまだまだ敷居が高いことばかりです。

誰かが作っているのだから自分にもできる!

と思いながら手を動かしています。

これからも引き続き精進したいと思います。
なお、本記事で紹介しているソースコードはご自由にお使いいただいても構いませんが、支障が生じても責任は負いませんのでご了承いただきますようお願いいたします。

最後までお読みいただき、ありがとうございます。

Pocket