How to Create a One-Time Download Link in PHP with Database

by Vincy. Last modified on February 29th, 2024.

Creating a one-time download link is for providing downloads with limitations. The following scenarios are suitable for showing a download link with a single usage permit.

  • To share a one-time private key or any other sensitive data.
  • To provide a download for a particular user on receiving a request.
  • To enable free download for reputed customers.
  • To share sensitive files via application drop box.

This PHP example creates a code to show a one-time download link. It uses the MySQL database to store the download path with expiry.

Steps to create a one-time download link

  1. Send a request for a one-time download link.
  2. Create a downloadable file resource in the application directory.
  3. Store the file path to the database with expiry.
  4. Fetch the download link path from the database.
  5. Display the one-time download link if it has not expired.

one time download link

1. Send a request for a one-time download link

The below HTML form shows a button to request a one-time download link. On clicking this button, it posts the request to the PHP to create a link on the UI to download the file resource.

<form action="" method="post">
    <div class="row">
        <input type="submit" name="request_link" value="Request for a download link">
    </div>
</form>

2. Create a downloadable file resource in the application directory

When users submit a request for a one-time link, the PHP creates a file resource available for download.

This example contains a source document template in the /doc/ folder.

The copyFile() function makes a copy of the document to a specific directory created for the user. This function returns the file path for the particular user.

<?php
function copyFile($user_id)
{
	$user_directory = "user-data/" . $user_id;
	if (!is_dir($user_directory)) {
		mkdir($user_directory, 0644, true);
	}
	$file_path = $user_directory . "/example.pdf";

	if (!copy("doc/example.pdf", $file_path)) {
		return;
	}
	return $file_path;
}
?>

3. Store the file path to the database with expiry

This example uses the MySQL database to manage the link ownership and the expiry. If the data is not sensitive, we can also achieve this by using JavaScript localStorage by tracking the click attempts on client side.

The user who requests the one-time download link can only see the link on the view.

The below PHP code receives the form post request. It gets the downloadable $file_path from the copyLink() function.

If there is no download link requested by the user before, it inserts the downloadable file path to the database.

Otherwise, if there is a download link in the database for the user, then it updates only  is_expired = 0 to allow download.

<?php
if (!empty($_POST['request_link'])) {
	$file_path = copyFile($user_id);
	if ($file_path) {
		if (!empty($linkResult)) {
			$insertDownloadPathDetails = $linkModel->updateLinkByRecordId($linkResult[0]["id"], 0);
		} else {
			$insertDownloadPathDetails = $linkModel->insertDownloadLink($user_id, $file_path);
		}
		header("Location: index.php");
		exit();
	}
}
?>

The LinkModel PHP class contains the functions to perform the database operations.

The insertDownloadLink and updateLinkByRecordId functions perform those query executions.

It shows how the one-time download link is saved for a user with an expiration flag.

<?php
class LinkModel
{
    ...
    ...

    function insertDownloadLink($user_id, $file_path)
    {
        $sql = "INSERT INTO tbl_download_link(user_id,file_download_path,is_expired) VALUES(?, ?, ?)";
        $paramType = 'isi';
        $paramValue = array(
            $user_id,
            $file_path,
            0
        );
        $insertId = $this->conn->insert($sql, $paramType, $paramValue);
        return $insertId;
    }

    function updateLinkByRecordId($recordId, $is_expired)
    {
        $sql = "UPDATE tbl_download_link SET is_expired=? WHERE id=?";
        $paramType = 'ii';
        $paramValue = array(
            $is_expired,
            $recordId
        );
        $updateId = $this->conn->execute($sql, $paramType, $paramValue);
        return $updateId;
    }
}
?>

4. Fetch the download link path from the database

On the view page, it fetches the database result from the tbl_download_link table.

The getLinkByUser reads the download link to display to the browser for one-time usage.

When the user downloads the link, the getLinkByRecordId is used to get the results by the link record ID. It is used in the download.php to check the expiration and permit downloading.

<?php
require_once __DIR__ . '/lib/LinkModel.php';
$linkModel = new LinkModel();

// User id is hardcoded here/
// Plugin your authentication module.
$user_id = 3;

$linkResult = $linkModel->getLinkByUser($user_id);
?>
<?php
class LinkModel
{

    private $conn;

    function __construct()
    {
        require_once __DIR__ . "/DataSource.php";
        $this->conn = new DataSource();
    }

    function getLinkByUser($user_id)
    {
        $sql = "SELECT * FROM tbl_download_link WHERE user_id=?";
        $paramType = "i";
        $paramValue = array($user_id);
        $result = $this->conn->select($sql, $paramType, $paramValue);
        return $result;
    }

    ...
    ...

    function getLinkByRecordId($recordId)
    {
        $sql = "SELECT * FROM tbl_download_link WHERE id=?";
        $paramType = "i";
        $paramValue = array($recordId);
        $result = $this->conn->select($sql, $paramType, $paramValue);
        return $result;
    }
}
?>

Note: This code initiates a static user id for example. You can plugin the login authentication module which returns the logged-in user ID dynamically.

5. Display the one-time download link if not expired

Once the one-time download link is created, the below HTML code will display it to the UI.

This code checks the link expiry before showing the link to the user. If the link is expired, then it will show an error message.

<div class="phppot-container">
    <h1>How to Create a One-Time Download Link</h1>

    <?php if (!empty($linkResult) && $linkResult[0]['is_expired'] == 1) { ?>
        <div class="row">
            <div class='phppot-message error'>The download link is expired.</div>
        </div>
    <?php } ?>
    <?php if (!empty($linkResult) && $linkResult[0]['is_expired'] == 0) {
        echo "<a href='download.php?id=" . $linkResult[0]['id'] . "'>Download</a>";
    } ?>
</div>

The PHP header function is used to set the content properties to allow downloading to the browser.

Once downloaded the file, then the database table expiration flag will be turned to 1.

download.php

<?php
require_once __DIR__ . '/lib/LinkModel.php';
$linkModel = new LinkModel();

$linkResult = $linkModel->getLinkByRecordId($_GET['id']);

if (!empty($linkResult) && $linkResult[0]['is_expired'] == 0) {
  // Download the file
  $file_path = $linkResult[0]['file_download_path'];
  header('Content-Type: application/octet-stream');
  header('Content-Disposition: attachment; filename="' . basename($file_path) . '"');
  readfile($file_path);

  $updateLinkById = $linkModel->updateLinkByRecordId($_GET['id'], 1);
} else {
  header("Location: index.php");
  exit();
}
?>

Download

Vincy
Written by Vincy, a web developer with 15+ years of experience and a Masters degree in Computer Science. She specializes in building modern, lightweight websites using PHP, JavaScript, React, and related technologies. Phppot helps you in mastering web development through over a decade of publishing quality tutorials.

Leave a Reply

Your email address will not be published. Required fields are marked *

↑ Back to Top

Share this page