メール認証第二弾です。ファイルや圧縮ファイル(zip)をダウンロードする際、安易なダウンロードを避けるためにメールで認証確認をして返信メールにてダウンロードするPHPコードの紹介です。

このメール認証は一般的にダウンロードサイト等でよく採用されていますが、レンタルサーバー等で小規模な運営を行っている管理者様が、ダウンロード可能な画像、PDF、ファイルなど過剰にアクセスされないように、メール認証で制御できるよう、管理者様自身でプログラムを構築できます。

以下に紹介するPHPコードや手順は、ほぼコピペで動作しますのでご参考にしてください

準備(レンタルサーバーXの例)
サーバーパネルのデータベース→MySQL設定
データベースを追加→〇〇_registrationdb データベース名を作成→追加
データベース→phpmyadminクリック→registrationsテーブル名作成
上部メニューの 「SQL」 をクリック
SQL画面に現在、SELECT * FROM registrations WHERE 1と書いてある場合は削除

SQLに貼りつけ

ALTER TABLE registrations
ADD COLUMN token VARCHAR(64) NULL AFTER email,
ADD COLUMN token_expires_at DATETIME NULL AFTER token,
ADD COLUMN verified TINYINT(1) NOT NULL DEFAULT 0 AFTER token_expires_at,
ADD COLUMN downloaded TINYINT(1) NOT NULL DEFAULT 0 AFTER verified,
ADD COLUMN verified_at DATETIME NULL AFTER downloaded,
ADD COLUMN downloaded_at DATETIME NULL AFTER verified_at;
ADD COLUMN download_started_at DATETIME NULL AFTER downloaded_at;

実行ボタン
メッセージに、返り値が空でした (行数 0)。 (クエリの実行時間: 0.0005 秒。)この表示がでたら成功
貼りつけた内容
貼りつけ内容
カラム用途
idINT管理番号
emailVARCHAR(255)メールアドレス
tokenVARCHAR(64)認証用トークン
verifiedTINYINT認証済みか
downloadedTINYINTダウンロード済みか
created_atDATETIME認証要求日時
verified_atDATETIME認証日時
downloaded_atDATETIMEダウンロード日時
download_started_atDATETIME通信エラー対策
emailをUNIQUE(重複禁止)

ファイルの階層
public_html/

├─ download/
│ ├─ index.php
│ ├─ send.php
│ ├─ verify.php
│ └─ download.php

│PHPから見た相対的パス
└─ momo.flower-processing/ ← アクセス制限があるフォルダまたは設定
   └─ download/
       └─ test.zip
HTMLのダウンロードリンクの作成
<a href="download/index.php">ダウンロード test.zip</a>
各コード内データベース名・ユーザー名・パスワード
$db_name = '○○_registrationdb';
$db_user = 'ここにXserverのDBユーザー名';
$db_pass = 'ここにXserverのDBパスワード';

全体の流れ
① HTMLの「ダウンロード test.zip」

② メールアドレス入力

③ 認証メール受信

④ メール内リンクをクリック

⑤ 認証成功

⑥ test.zip ダウンロード

⑦ 同じメールで再度アクセス

「このメールアドレスでは、すでにダウンロードされています。」
正常終了
10:00

ダウンロード開始

ZIPを最後まで送信

downloaded = 1

以後ダウンロード不可
通信が途中で切れた
10:00

ダウンロード開始

通信エラー

downloaded = 0 のまま

もう一度クリック

再ダウンロード
10分を超えた場合
10:00 ダウンロード開始

通信エラー

10:11 再試行

期限切れ
通信エラーなどで途中でダウンロードできなかった人を救済する処置で
PC端末に完全にダウンロードされた可否を保証するものではありません
index.php
<?php
// メールアドレス入力画面
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ダウンロード認証</title>

<style>
body {
    font-family: sans-serif;
    background: #f5f5f5;
    margin: 0;
    padding: 40px 20px;
}

.container {
    max-width: 500px;
    margin: 0 auto;
    background: #fff;
    padding: 30px;
    border-radius: 10px;
    box-shadow: 0 3px 15px rgba(0,0,0,.1);
}

h1 {font-size: 24px; margin-top: 0;}

p {line-height: 1.7;}

input[type="email"] {
    width: 100%;
    box-sizing: border-box;
    padding: 12px;
    font-size: 16px;
    border: 1px solid #ccc;
    border-radius: 6px;
    margin: 10px 0 15px;
}

button {
    width: 100%;
    padding: 13px;
    font-size: 16px;
    color: #fff;
    background: #333;
    border: 0;
    border-radius: 6px;
    cursor: pointer;
}

button:hover {opacity: .85;}

</style>
</head>

<body>

<div class="container">

    <h1>ダウンロード認証</h1>
    <!-- <h1>専用ページ認証</h1>  -->

    <p>ダウンロードするにはメールアドレスの認証が必要です。</p>
    <!-- <p>会員専用ページを表示するにはメールアドレスの認証が必要です。</p  -->

    <form action="send.php" method="post">

        <label for="email">メールアドレス</label>

        <input
            type="email"
            id="email"
            name="email"
            required
            autocomplete="email"
            placeholder="example@example.com"
        >

        <button type="submit">認証メールを送信</button>

    </form>
</div>
</body>
</html>
send.php
<?php
// ====データベース設定=============
$db_host = 'localhost';
$db_name = '〇〇_registrationdb';
$db_user = 'ここにXserverのDBユーザー名';
$db_pass = 'ここにXserverのDBパスワード';

// ===送信元メールアドレス==========
$from_email = 'admin@〇〇.com'; //任意のアドレス

// ====入力チェック================
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    exit('不正なアクセスです。');
}

$email = trim($_POST['email'] ?? '');

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    exit('正しいメールアドレスを入力してください。');
}

// ===データベース接続============
try {

    $pdo = new PDO(
        "mysql:host={$db_host};dbname={$db_name};charset=utf8mb4",
        $db_user,
        $db_pass,
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ]
    );

} catch (PDOException $e) {

    exit('データベース接続に失敗しました。');

}

// ====ダウンロード済みか確認========
$stmt = $pdo->prepare("
    SELECT *
    FROM registrations
    WHERE email = ?
    LIMIT 1
");

$stmt->execute([$email]);

$user = $stmt->fetch();

if ($user && (int)$user['downloaded'] === 1) {

    exit('
        <h2>このメールアドレスは使用済みです</h2>
        <p>このメールアドレスでは、すでにダウンロードされています。</p>
    ');

}

// ======認証トークンを作成=========
$token = bin2hex(random_bytes(32));

// 認証リンクの有効期限
$expires = date('Y-m-d H:i:s', time() + 600);

// =======DBへ登録・更新===========
if ($user) {

    // 既存ユーザーの場合
    $stmt = $pdo->prepare("
        UPDATE registrations
        SET
            token = ?,
            token_expires_at = ?,
            verified = 0,
            verified_at = NULL
        WHERE id = ?
    ");

    $stmt->execute([
        $token,
        $expires,
        $user['id']
    ]);

} else {

    // 新規の場合
    $stmt = $pdo->prepare("
        INSERT INTO registrations
        (
            registration_type,
            user_id,
            name,
            email,
            token,
            token_expires_at,
            verified,
            downloaded
        )
        VALUES
        (
            'download',
            '------',
            '',
            ?,
            ?,
            ?,
            0,
            0
        )
    ");

    $stmt->execute([
        $email,
        $token,
        $expires
    ]);

}

// =======認証URL==================
$verify_url =
    'https://〇〇.com/download/verify.php?token=' //任意のアドレスを
    . urlencode($token);

// ======メール本文================
$subject = 'ダウンロード認証';

$message = <<<TEXT

ダウンロード認証のご依頼を受け付けました。

以下のリンクをクリックして、メールアドレスの認証を完了してください。

{$verify_url}

この認証リンクの有効期限は10分です。

10分を過ぎた場合は、認証リンクが無効になります。

心当たりがない場合は、このメールを破棄してください。

TEXT;

// ========メールヘッダー==========
$headers = [];

$headers[] = 'From: ' . $from_email;
$headers[] = 'Reply-To: ' . $from_email;
$headers[] = 'Content-Type: text/plain; charset=UTF-8';

// =========メール送信============
$result = mail(
    $email,
    $subject,
    $message,
    implode("\r\n", $headers)
);

// ===========結果表示============
if ($result) {

    echo '
    <!DOCTYPE html>
    <html lang="ja">
    <head>
    <meta charset="UTF-8">
    <title>認証メールを送信しました</title>
    </head>
    <body>

    <h2>認証メールを送信しました</h2>

    <p>
    入力したメールアドレスに認証メールを送信しました。
    </p>

    <p>
    メールをご確認いただき、メール内のリンクをクリックしてください。
    </p>

    </body>
    </html>
    ';

} else {

    exit('メールの送信に失敗しました。');

}
verify.php
<?php

// ====データベース設定==============
$db_host = 'localhost';
$db_name = '〇〇_registrationdb';
$db_user = 'ここにXserverのDBユーザー名';
$db_pass = 'ここにXserverのDBパスワード';

// ====token確認====================
$token = $_GET['token'] ?? '';

if ($token === '') {
    exit('認証情報がありません。');
}

// ====データベース接続==============
try {

    $pdo = new PDO(
        "mysql:host={$db_host};dbname={$db_name};charset=utf8mb4",
        $db_user,
        $db_pass,
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ]
    );

} catch (PDOException $e) {

    exit('データベース接続に失敗しました。');

}

// =======token検索================
$stmt = $pdo->prepare("
    SELECT *
    FROM registrations
    WHERE token = ?
    LIMIT 1
");

$stmt->execute([$token]);

$user = $stmt->fetch();

// ===tokenが存在しない============
if (!$user) {

    exit('
        <h2>認証エラー</h2>
        <p>この認証リンクは無効です。</p>
    ');

}

// ===すでにダウンロード済み========
if ((int)$user['downloaded'] === 1) {

    exit('
        <h2>使用済みです</h2>
        <p>
        このメールアドレスはすでにダウンロード済みです。
        </p>
    ');

}

// =======有効期限確認============
if (
    empty($user['token_expires_at']) ||
    strtotime($user['token_expires_at']) < time()
) {

    exit('
        <h2>認証期限切れ</h2>
        <p>
        この認証リンクの有効期限が切れています。
        </p>
    ');

}

// ===メール認証完了==============
$stmt = $pdo->prepare("
    UPDATE registrations
    SET
        verified = 1,
        verified_at = NOW()
    WHERE id = ?
");

$stmt->execute([$user['id']]);

// ====認証成功==================
?>

<!DOCTYPE html>
<html lang="ja">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>認証完了</title>

<style>

body {font-family: sans-serif; background: #f5f5f5; margin: 0; padding: 40px 20px;}

.container {
    max-width: 500px;
    margin: 0 auto;
    background: #fff;
    padding: 30px;
    border-radius: 10px;
    box-shadow: 0 3px 15px rgba(0,0,0,.1);
}

h1 {font-size: 24px;}

.download-button {
    display: block;
    text-align: center;
    background: #333;
    color: #fff;
    text-decoration: none;
    padding: 14px;
    border-radius: 6px;
    margin-top: 25px;
}

.download-button:hover {opacity: .85;}

</style>

</head>

<body>
<div class="container">

<h1>メール認証が完了しました</h1>

<p>メールアドレスの認証が完了しました。</p>
<p>下のボタンからファイルをダウンロードしてください。</p>

<a class="download-button" href="download.php?id=<?php echo (int)$user['id']; ?>">test.zip をダウンロード</a>

        </div>
    </body>
</html>
download.php
<?php

// =======データベース設定================
$db_host = 'localhost';
$db_name = '〇〇_registrationdb';
$db_user = 'ここにXserverのDBユーザー名';
$db_pass = 'ここにXserverのDBパスワード';

// =======ZIPファイル====================
$zip_file =
    $_SERVER['DOCUMENT_ROOT']
    . '/momo.flower-processing/download/test.zip'; //アドレスは変更してください

// ======ダウンロードID==================
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;

if ($id <= 0) {
    exit('不正なアクセスです。');
}

// =======データベース接続===============
try {

    $pdo = new PDO(
        "mysql:host={$db_host};dbname={$db_name};charset=utf8mb4",
        $db_user,
        $db_pass,
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ]
    );

} catch (PDOException $e) {

    exit('データベース接続に失敗しました。');

}

// =======ユーザー情報取得===============
$stmt = $pdo->prepare("
    SELECT *
    FROM registrations
    WHERE id = ?
    LIMIT 1
");

$stmt->execute([$id]);

$user = $stmt->fetch();

if (!$user) {
    exit('認証情報が見つかりません。');
}

// ======メール認証確認==================
if ((int)$user['verified'] !== 1) {

    exit('
        <h2>認証が必要です</h2>
        <p>
        先にメールアドレスの認証を完了してください。
        </p>
    ');

}

// ======すでに正常ダウンロード済み=======
if ((int)$user['downloaded'] === 1) {

    exit('
        <h2>ダウンロード済みです</h2>
        <p>
        このメールアドレスでは、すでにダウンロードされています。
        </p>
    ');

}

// =======ZIPファイル確認===============
if (!is_file($zip_file)) {

    exit('
        <h2>ファイルエラー</h2>
        <p>
        ダウンロードファイルが見つかりません。
        </p>
    ');

}

// ======ダウンロード開始時刻===========
$now = time();

$download_started_at = $user['download_started_at'] ?? null;

// =====10分を超えた古いダウンロード試行======
if ($download_started_at !== null) {

    $started_time = strtotime($download_started_at);

    if ($started_time !== false) {

        $elapsed = $now - $started_time;

        if ($elapsed > 600) {

            exit('
                <h2>ダウンロード期限が切れました</h2>
                <p>
                ダウンロード開始から10分を超えています。
                </p>
            ');

        }

    }

}

// ======初回ダウンロード開始時刻を記録========
if ($download_started_at === null) {

    $stmt = $pdo->prepare("
        UPDATE registrations
        SET download_started_at = NOW()
        WHERE id = ?
          AND verified = 1
          AND downloaded = 0
    ");

    $stmt->execute([$id]);

}

// ======ダウンロード開始===========
$file_size = filesize($zip_file);

if ($file_size === false) {
    exit('ファイルサイズを取得できません。');
}

// 出力バッファを完全に停止
while (ob_get_level()) {
    ob_end_clean();
}

// HTTPヘッダー
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="test.zip"');
header('Content-Length: ' . $file_size);
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');

// =======ファイル送信==============
$handle = fopen($zip_file, 'rb');

if ($handle === false) {

    exit('ファイルを開けませんでした。');

}

$success = true;

while (!feof($handle)) {

    $data = fread($handle, 8192);

    if ($data === false) {

        $success = false;
        break;

    }

    echo $data;

    flush();

    // クライアントが接続を切断した場合
    if (connection_aborted()) {

        $success = false;
        break;

    }

}

fclose($handle);

// =====正常完了=================
if ($success) {

    $stmt = $pdo->prepare("
        UPDATE registrations
        SET
            downloaded = 1,
            downloaded_at = NOW()
        WHERE id = ?
          AND verified = 1
          AND downloaded = 0
    ");

    $stmt->execute([$id]);

}
// =====終了====================
exit;

オプション HTMLファイルのメール認証表示
└─ momo.flower-processing/ ← アクセス制限があるフォルダまたは設定
   └─ download/
      └─ test.html
verify.phpの変更
<a class="download-button" href="access.php?id=<?php echo (int)$user["id"]; ?>" > test.html を開く </a>
認証リンクだけ10分間有効、認証が成功すると、時間制限なし
access.php
<?php
// =====データベース設定================
$db_host = 'localhost';
$db_name = '〇〇_registrationdb';
$db_user = 'ここにXserverのDBユーザー名';
$db_pass = 'ここにXserverのDBパスワード';

// =======test.html の場所=============
$html_file =
    $_SERVER['DOCUMENT_ROOT']
    . '/momo.flower-processing/download/test.html';

// ======ID確認========================
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;

if ($id <= 0) {exit('不正なアクセスです。');}

// =====データベース接続================
try {

    $pdo = new PDO(
        "mysql:host={$db_host};dbname={$db_name};charset=utf8mb4",
        $db_user,
        $db_pass,
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ]
    );

} catch (PDOException $e) {

    exit('データベース接続に失敗しました。');

}

// =======ユーザー情報取得=============
$stmt = $pdo->prepare("
    SELECT *
    FROM registrations
    WHERE id = ?
    LIMIT 1
");

$stmt->execute([$id]);

$user = $stmt->fetch();

// =======ユーザーが存在しない=========
if (!$user) {

    exit('認証情報が見つかりません。');

}

// =======ール認証確認================
if ((int)$user['verified'] !== 1) {

    exit('
        <h2>メール認証が必要です</h2>
        <p> メール認証を完了してからアクセスしてください。</p>
    ');

}

// ======HTMLファイル確認============
if (!is_file($html_file)) {

    exit('
        <h2>ファイルエラー</h2>
        <p> HTMLファイルが見つかりません。</p>
    ');

}

// ======HTMLを表示================
header('Content-Type: text/html; charset=UTF-8');

readfile($html_file);

exit;
複数のファイルを構成した場合のアクセス制限対策
public_html/

├─ download/
│ ├─ index.php
│ ├─ send.php
│ ├─ verify.php
│ ├─ access.php ← 新規追加(test.htmlの読み込み)
│ └─ asset.php ← 新規追加

└─ momo.flower-processing/
└─ download/ ← Xserverアクセス制限
├─ test.html
├─ style.css
├─ script.js
└─ images/
├─ image1.jpg
└─ image2.png
test.htmlの呼び出しが下記の設定としたら

<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
<img src="images/photo.jpg">
     ↓
asset.php?file=style.css
asset.php?file=script.js
asset.php?file=images/photo.jpg
に自動に変更
PHPがサーバー内部からファイルを取得するので、ユーザー側にはメール認証だけを要求 なので、JSやCSSは見えません。
access.php 認証済みか確認して、test.html を表示する入口
<?php

// ==データベース設定=======================
$db_host = 'localhost';
$db_name = '〇〇_registrationdb';
$db_user = 'ここにXserverのDBユーザー名';
$db_pass = 'ここにXserverのDBパスワード';

// =====ID確認=======================--====
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;

if ($id <= 0) {
    exit('不正なアクセスです。');
}

// =====データベース接続====================
try {

    $pdo = new PDO(
        "mysql:host={$db_host};dbname={$db_name};charset=utf8mb4",
        $db_user,
        $db_pass,
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ]
    );

} catch (PDOException $e) {

    exit('データベース接続に失敗しました。');

}

// ======ーザー確認=========================
$stmt = $pdo->prepare("
    SELECT *
    FROM registrations
    WHERE id = ?
    LIMIT 1
");

$stmt->execute([$id]);

$user = $stmt->fetch();

// ======ユーザーが存在しない================
if (!$user) {

    exit('
        <h2>アクセスできません</h2>
        <p>認証情報が見つかりません。</p>
    ');

}

// =====メール認証確認======================
if ((int)$user['verified'] !== 1) {

    exit('
        <h2>メール認証が必要です</h2>
        <p>
        メール認証を完了してからアクセスしてください。
        </p>
    ');

}

// =====test.html の場所===================
$html_file =
    $_SERVER['DOCUMENT_ROOT']
    . '/momo.flower-processing/download/test.html';

// =====ファイル確認========================
if (!is_file($html_file)) {

    exit('
        <h2>ファイルエラー</h2>
        <p>
        test.html が見つかりません。
        </p>
    ');

}

//クッキー対策
setcookie(
    'download_user_id',
    (string)$id,
    [
        'expires' => time() + 60 * 60 * 24 * 365,
        'path' => '/download/',
        'secure' => true,
        'httponly' => true,
        'samesite' => 'Lax'
    ]
);

// =====HTML読み込み====================
$html = file_get_contents($html_file);

if ($html === false) {

    exit('HTMLファイルを読み込めませんでした。');

}

// ====CSS・JS・画像などをPHP経由に変更=======
// CSS
$html = preg_replace(
    '/(<link\b[^>]*?href=["\'])(?!https?:\/\/|\/\/|data:)([^"\']+)(["\'])/i',
    '$1asset.php?file=$2$3',
    $html
);

// JavaScript
$html = preg_replace(
    '/(<script\b[^>]*?src=["\'])(?!https?:\/\/|\/\/|data:)([^"\']+)(["\'])/i',
    '$1asset.php?file=$2$3',
    $html
);

// 画像
$html = preg_replace(
    '/(<img\b[^>]*?src=["\'])(?!https?:\/\/|\/\/|data:)([^"\']+)(["\'])/i',
    '$1asset.php?file=$2$3',
    $html
);

// ======HTMLを表示====================
header('Content-Type: text/html; charset=UTF-8');

echo $html;

exit;
asset.php
<?php

$db_host = 'localhost';
$db_name = '〇〇_registrationdb';
$db_user = 'ここにXserverのDBユーザー名';
$db_pass = 'ここにXserverのDBパスワード';

// ====ID確認========================
// access.php から呼ばれた場合はURLに認証IDを引き継ぐ必要があります。
// この場合はCookieを利用します。
$id = isset($_COOKIE['download_user_id'])
    ? (int)$_COOKIE['download_user_id']
    : 0;

if ($id <= 0) {

    http_response_code(403);

    exit('アクセスが拒否されました。');

}

// ====データベース接続======================
try {

    $pdo = new PDO(
        "mysql:host={$db_host};dbname={$db_name};charset=utf8mb4",
        $db_user,
        $db_pass,
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ]
    );

} catch (PDOException $e) {

    http_response_code(500);

    exit('データベース接続エラーです。');

}

// ====認証確認============================
$stmt = $pdo->prepare("
    SELECT verified
    FROM registrations
    WHERE id = ?
    LIMIT 1
");

$stmt->execute([$id]);

$user = $stmt->fetch();

if (!$user || (int)$user['verified'] !== 1) {

    http_response_code(403);

    exit('メール認証が必要です。');

}

// ====ファイル名取得=======================
$file = $_GET['file'] ?? '';

if ($file === '') {

    http_response_code(404);

    exit('ファイルが指定されていません。');

}

// ====URLデコード=========================
$file = rawurldecode($file);

// ====不正なパスを拒否=====================
if (
    str_contains($file, '..') ||
    str_contains($file, '\\') ||
    str_starts_with($file, '/')
) {

    http_response_code(403);

    exit('不正なファイル指定です。');

}

// ====許可する拡張子=======================
$allowed_extensions = [

    'css','js','jpg','jpeg','png','gif','webp','svg','woff','woff2','ttf'
];

$extension =
    strtolower(
        pathinfo($file, PATHINFO_EXTENSION)
    );

if (!in_array($extension, $allowed_extensions, true)) {

    http_response_code(403);

    exit('このファイルは取得できません。');

}

// ====ファイルパス=======================
$base_dir =
    realpath(
        $_SERVER['DOCUMENT_ROOT']
        . '/momo.flower-processing/download'
    );

$full_path =
    realpath($base_dir . '/' . $file);

// ===実ファイル確認======================
if (
    $base_dir === false ||
    $full_path === false ||
    !is_file($full_path)
) {

    http_response_code(404);

    exit('ファイルが見つかりません。');

}

// ===指定フォルダー外へのアクセスを禁止=====
$base_dir_with_slash =
    rtrim($base_dir, DIRECTORY_SEPARATOR)
    . DIRECTORY_SEPARATOR;


if (
    strncmp(
        $full_path,
        $base_dir_with_slash,
        strlen($base_dir_with_slash)
    ) !== 0
) {

    http_response_code(403);

    exit('アクセスが拒否されました。');

}

// ======MIMEタイプ========================
$mime_types = [

    'css'  => 'text/css',
    'js'   => 'application/javascript',

    'jpg'  => 'image/jpeg',
    'jpeg' => 'image/jpeg',
    'png'  => 'image/png',
    'gif'  => 'image/gif',
    'webp' => 'image/webp',
    'svg'  => 'image/svg+xml',

    'woff'  => 'font/woff',
    'woff2' => 'font/woff2',
    'ttf'   => 'font/ttf'

];

$mime = $mime_types[$extension] ?? 'application/octet-stream';

// ======ファイル送信======================
header('Content-Type: ' . $mime);

header('Content-Length: ' . filesize($full_path));

header('Cache-Control: private, max-age=3600');

readfile($full_path);

exit;

各メッセージ・CSS等は利用用途によって変更してください。

説明のため実際のアドレス(https://www.flower-processing.net/momo.flower-processing/download/test.zip)を書いていますが、アクセス制限が設定されています。ダウンロードするファイル等は必ず制限を設定してからファイル等を入れてください

参考コードについて
上記のコードの変更・修正・追加・削除・コピペ等の制限は一切ありません。ただし、全体コピーで公開使用する場合は、多少のデザイン(UI)やメッセージ等の変更をお願いします。
※コードのバグ、動作不具合等は一応確認済みですが、万が一不具合が発生した場合は開発者様で修正をお願いします。

動作確認できます。約3000×3000(5M)の花の透過画像(花の写真素材屋フリー画像)が1画像入っています

メール情報はDB(データベース)に保存されます。しかしあくまでテストダウンロードなので定期的に削除を行っております。一度ダウンロードしたアドレスは拒否される仕様ですが、再度登録できる場合があります。

ダウンロード test.zip