以下は,課題提出画面を Drag & Drop + Ajax に対応させ,PHP側では PDO を使って提出情報を登録する解答例です。
この例は完成版の構成に合わせて common/common.php を読み込んでいます。したがって,$dbh はPDO接続オブジェクトであり,login_check() によってログイン中の会員情報を取得できます。
task_dragdrop.php では,ドロップ領域を用意し,JavaScriptでファイル選択・送信前チェック・Ajax送信を行います。PHPスクリプトへの直接リンクではなく,下のコードブロックで内容を確認してください。
task_dragdrop.php
<?php
// task_dragdrop.php: Drag & Drop + Ajaxによる課題提出画面
session_start();
require_once __DIR__ . '/common/common.php';
$member = login_check();
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>課題提出(Drag & Drop版)</title>
<style>
.drop-zone {
border: 3px dashed #0d6efd;
border-radius: 12px;
padding: 2rem;
text-align: center;
background: #f8f9fa;
cursor: pointer;
}
.drop-zone.dragover {
background: #e7f1ff;
border-color: #084298;
}
#message { margin-top: 1rem; }
</style>
</head>
<body>
<h1>課題提出(Drag & Drop版)</h1>
<form id="upload_form">
<p>課題名:<input type="text" name="name" id="name" required></p>
<p>コメント:<br>
<textarea name="word" id="word" rows="5" cols="50"></textarea>
</p>
<div id="drop_zone" class="drop-zone">
ここに pdf, zip, txt ファイルをドラッグ&ドロップしてください。<br>
またはクリックしてファイルを選択してください。<br>
<small>最大サイズ: 5 MB</small>
</div>
<input type="file" id="task_file" name="task_file" accept=".pdf,.zip,.txt" hidden>
<p id="file_info"></p>
<p><button type="submit">ファイル送信</button></p>
</form>
<div id="message"></div>
<script>
const dropZone = document.getElementById('drop_zone');
const fileInput = document.getElementById('task_file');
const fileInfo = document.getElementById('file_info');
const message = document.getElementById('message');
const form = document.getElementById('upload_form');
let selectedFile = null;
function showFile(file) {
const allowedExt = ['pdf', 'zip', 'txt'];
const maxSize = 5 * 1024 * 1024;
const ext = file.name.split('.').pop().toLowerCase();
if (!allowedExt.includes(ext)) {
selectedFile = null;
fileInfo.textContent = '';
alert('アップロードできるファイルは pdf, zip, txt のみです。');
return;
}
if (file.size > maxSize) {
selectedFile = null;
fileInfo.textContent = '';
alert('ファイルサイズは5MB以下にしてください。');
return;
}
selectedFile = file;
fileInfo.textContent = `${file.name} (${file.size} bytes)`;
}
dropZone.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', () => {
if (fileInput.files.length > 0) {
showFile(fileInput.files[0]);
}
});
['dragenter', 'dragover'].forEach(eventName => {
dropZone.addEventListener(eventName, event => {
event.preventDefault();
event.stopPropagation();
dropZone.classList.add('dragover');
});
});
['dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, event => {
event.preventDefault();
event.stopPropagation();
dropZone.classList.remove('dragover');
});
});
dropZone.addEventListener('drop', event => {
const files = event.dataTransfer.files;
if (files.length > 0) {
showFile(files[0]);
}
});
form.addEventListener('submit', async event => {
event.preventDefault();
if (!selectedFile) {
alert('提出するファイルを選択してください。');
return;
}
const formData = new FormData();
formData.append('name', document.getElementById('name').value);
formData.append('word', document.getElementById('word').value);
formData.append('task_file', selectedFile);
message.textContent = 'アップロード中です...';
try {
const response = await fetch('task_ajax_upload.php', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.ok) {
message.textContent = result.message;
form.reset();
selectedFile = null;
fileInfo.textContent = '';
} else {
message.textContent = result.message;
}
} catch (error) {
message.textContent = '通信エラーが発生しました。';
}
});
</script>
</body>
</html>
task_ajax_upload.php はAjaxから送られた FormData を受け取り,結果をJSONで返します。JavaScript側で確認済みであっても,PHP側でも必ずファイルサイズ・拡張子・MIMEタイプを確認します。
task_ajax_upload.php
<?php
// task_ajax_upload.php: Drag & Drop + Ajax用アップロード処理
session_start();
require_once __DIR__ . '/common/common.php';
$member = login_check();
header('Content-Type: application/json; charset=UTF-8');
function json_response(bool $ok, string $message): void
{
echo json_encode(['ok' => $ok, 'message' => $message], JSON_UNESCAPED_UNICODE);
exit();
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
json_response(false, '不正なリクエストです。');
}
if (empty($_POST['name'])) {
json_response(false, '課題名を入力してください。');
}
if (!isset($_FILES['task_file']) || $_FILES['task_file']['error'] !== UPLOAD_ERR_OK) {
json_response(false, 'ファイルが正しくアップロードされませんでした。');
}
$file = $_FILES['task_file'];
$max_size = 5 * 1024 * 1024;
$allowed_ext = ['pdf', 'zip', 'txt'];
$allowed_mime = [
'application/pdf',
'application/zip',
'application/x-zip-compressed',
'text/plain',
];
if ($file['size'] > $max_size) {
json_response(false, 'ファイルサイズは5MB以下にしてください。');
}
$original = basename($file['name']);
$ext = strtolower(pathinfo($original, PATHINFO_EXTENSION));
if (!in_array($ext, $allowed_ext, true)) {
json_response(false, 'アップロードできる拡張子は pdf, zip, txt のみです。');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
if (!in_array($mime, $allowed_mime, true)) {
json_response(false, '許可されていないファイル形式です。');
}
$upload_dir = __DIR__ . '/task_folder';
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0755, true);
}
$save_name = date('YmdHis') . '_' . bin2hex(random_bytes(4)) . '.' . $ext;
$save_path = $upload_dir . '/' . $save_name;
if (!move_uploaded_file($file['tmp_name'], $save_path)) {
json_response(false, 'ファイルの保存に失敗しました。');
}
$sql = 'INSERT INTO task (member, name, file, change_name, word, modified)
VALUES (:member, :name, :file, :change_name, :word, NOW())';
$stmt = $dbh->prepare($sql);
$stmt->bindValue(':member', (int)$member['id'], PDO::PARAM_INT);
$stmt->bindValue(':name', $_POST['name'], PDO::PARAM_STR);
$stmt->bindValue(':file', $original, PDO::PARAM_STR);
$stmt->bindValue(':change_name', $save_name, PDO::PARAM_STR);
$stmt->bindValue(':word', $_POST['word'] ?? '', PDO::PARAM_STR);
$stmt->execute();
json_response(true, 'アップロードが完了しました。');
event.preventDefault() によって,ファイルをブラウザで開いてしまう既定動作を止めている。event.dataTransfer.files から,ドロップされたファイルを取得している。FormData を使うことで,テキスト入力とファイルを同時に送信している。fetch() の結果を response.json() で読み取り,画面に結果メッセージを表示している。json_response() 関数により,成功時も失敗時もJSON形式で応答している。$dbh->prepare() と bindValue() を使って行っている。この解答例は教材用に理解しやすくするための構成です。実運用では,アップロード先ディレクトリをWeb公開領域から分離する,CSRF対策を追加する,ファイル名・MIMEタイプ・拡張子の扱いをさらに厳密にする,といった追加対策も検討してください。
表示専用ページでも確認できます:task_dragdrop.php の表示,task_ajax_upload.php の表示